summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGreg Myers <gmyers@gitlab.com>2019-08-27 15:51:04 +0000
committerGreg Myers <gmyers@gitlab.com>2019-08-27 15:51:04 +0000
commit51a0166af2c532aa7a0d04bb503b43499be97033 (patch)
tree9ebd74e8518d573fe4860763ddb41146dab1f453 /spec
parentf706677357fcdf9cd870d098379788543ea71058 (diff)
parent27c222d16ccb666878c7ac558f484efa56f77f37 (diff)
downloadgitlab-ce-51a0166af2c532aa7a0d04bb503b43499be97033.tar.gz
Merge branch 'master' into 'api-curl-quote-url-docs'api-curl-quote-url-docs
# Conflicts: # doc/api/events.md
Diffstat (limited to 'spec')
-rw-r--r--spec/bin/changelog_spec.rb2
-rw-r--r--spec/config/smime_signature_settings_spec.rb56
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb2
-rw-r--r--spec/controllers/admin/clusters/applications_controller_spec.rb2
-rw-r--r--spec/controllers/admin/groups_controller_spec.rb8
-rw-r--r--spec/controllers/admin/requests_profiles_controller_spec.rb65
-rw-r--r--spec/controllers/admin/runners_controller_spec.rb5
-rw-r--r--spec/controllers/admin/users_controller_spec.rb6
-rw-r--r--spec/controllers/application_controller_spec.rb30
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb36
-rw-r--r--spec/controllers/boards/issues_controller_spec.rb219
-rw-r--r--spec/controllers/boards/lists_controller_spec.rb4
-rw-r--r--spec/controllers/chaos_controller_spec.rb127
-rw-r--r--spec/controllers/concerns/confirm_email_warning_spec.rb98
-rw-r--r--spec/controllers/concerns/group_tree_spec.rb4
-rw-r--r--spec/controllers/concerns/issuable_actions_spec.rb69
-rw-r--r--spec/controllers/concerns/issuable_collections_spec.rb72
-rw-r--r--spec/controllers/concerns/sorting_preference_spec.rb93
-rw-r--r--spec/controllers/dashboard/groups_controller_spec.rb2
-rw-r--r--spec/controllers/dashboard/milestones_controller_spec.rb15
-rw-r--r--spec/controllers/dashboard/projects_controller_spec.rb26
-rw-r--r--spec/controllers/explore/projects_controller_spec.rb95
-rw-r--r--spec/controllers/graphql_controller_spec.rb21
-rw-r--r--spec/controllers/groups/boards_controller_spec.rb4
-rw-r--r--spec/controllers/groups/children_controller_spec.rb4
-rw-r--r--spec/controllers/groups/clusters/applications_controller_spec.rb2
-rw-r--r--spec/controllers/groups/group_members_controller_spec.rb35
-rw-r--r--spec/controllers/groups/labels_controller_spec.rb4
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb4
-rw-r--r--spec/controllers/groups/uploads_controller_spec.rb5
-rw-r--r--spec/controllers/groups_controller_spec.rb13
-rw-r--r--spec/controllers/health_check_controller_spec.rb13
-rw-r--r--spec/controllers/health_controller_spec.rb1
-rw-r--r--spec/controllers/help_controller_spec.rb4
-rw-r--r--spec/controllers/ide_controller_spec.rb17
-rw-r--r--spec/controllers/import/bitbucket_controller_spec.rb6
-rw-r--r--spec/controllers/import/bitbucket_server_controller_spec.rb14
-rw-r--r--spec/controllers/import/github_controller_spec.rb10
-rw-r--r--spec/controllers/import/gitlab_controller_spec.rb6
-rw-r--r--spec/controllers/metrics_controller_spec.rb10
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb6
-rw-r--r--spec/controllers/projects/badges_controller_spec.rb124
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb4
-rw-r--r--spec/controllers/projects/boards_controller_spec.rb4
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb99
-rw-r--r--spec/controllers/projects/ci/lints_controller_spec.rb8
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb14
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb4
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb19
-rw-r--r--spec/controllers/projects/cycle_analytics/events_controller_spec.rb64
-rw-r--r--spec/controllers/projects/cycle_analytics_controller_spec.rb18
-rw-r--r--spec/controllers/projects/deploy_keys_controller_spec.rb10
-rw-r--r--spec/controllers/projects/deployments_controller_spec.rb74
-rw-r--r--spec/controllers/projects/discussions_controller_spec.rb4
-rw-r--r--spec/controllers/projects/environments/prometheus_api_controller_spec.rb2
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb200
-rw-r--r--spec/controllers/projects/error_tracking_controller_spec.rb2
-rw-r--r--spec/controllers/projects/find_file_controller_spec.rb5
-rw-r--r--spec/controllers/projects/git_http_controller_spec.rb11
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb140
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb17
-rw-r--r--spec/controllers/projects/merge_requests/content_controller_spec.rb54
-rw-r--r--spec/controllers/projects/merge_requests/creations_controller_spec.rb50
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb204
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb4
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb27
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb83
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb2
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb95
-rw-r--r--spec/controllers/projects/refs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/registry/tags_controller_spec.rb33
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb47
-rw-r--r--spec/controllers/projects/serverless/functions_controller_spec.rb19
-rw-r--r--spec/controllers/projects/services_controller_spec.rb2
-rw-r--r--spec/controllers/projects/settings/repository_controller_spec.rb20
-rw-r--r--spec/controllers/projects/starrers_controller_spec.rb196
-rw-r--r--spec/controllers/projects/templates_controller_spec.rb111
-rw-r--r--spec/controllers/projects/uploads_controller_spec.rb5
-rw-r--r--spec/controllers/projects/variables_controller_spec.rb65
-rw-r--r--spec/controllers/projects/wikis_controller_spec.rb120
-rw-r--r--spec/controllers/projects_controller_spec.rb172
-rw-r--r--spec/controllers/registrations_controller_spec.rb117
-rw-r--r--spec/controllers/search_controller_spec.rb202
-rw-r--r--spec/controllers/snippets/notes_controller_spec.rb125
-rw-r--r--spec/controllers/snippets_controller_spec.rb6
-rw-r--r--spec/controllers/uploads_controller_spec.rb177
-rw-r--r--spec/controllers/user_callouts_controller_spec.rb4
-rw-r--r--spec/controllers/users_controller_spec.rb8
-rw-r--r--spec/db/schema_spec.rb4
-rw-r--r--spec/factories/abuse_reports.rb2
-rw-r--r--spec/factories/appearances.rb2
-rw-r--r--spec/factories/application_settings.rb2
-rw-r--r--spec/factories/award_emoji.rb2
-rw-r--r--spec/factories/badge.rb2
-rw-r--r--spec/factories/boards.rb2
-rw-r--r--spec/factories/broadcast_messages.rb2
-rw-r--r--spec/factories/chat_names.rb2
-rw-r--r--spec/factories/chat_teams.rb2
-rw-r--r--spec/factories/ci/bridge.rb13
-rw-r--r--spec/factories/ci/build_need.rb8
-rw-r--r--spec/factories/ci/build_trace_chunks.rb2
-rw-r--r--spec/factories/ci/build_trace_section_names.rb2
-rw-r--r--spec/factories/ci/builds.rb2
-rw-r--r--spec/factories/ci/group_variables.rb2
-rw-r--r--spec/factories/ci/job_artifacts.rb6
-rw-r--r--spec/factories/ci/job_variables.rb10
-rw-r--r--spec/factories/ci/pipeline_schedule.rb2
-rw-r--r--spec/factories/ci/pipeline_schedule_variables.rb2
-rw-r--r--spec/factories/ci/pipeline_variables.rb2
-rw-r--r--spec/factories/ci/pipelines.rb2
-rw-r--r--spec/factories/ci/runner_projects.rb2
-rw-r--r--spec/factories/ci/runners.rb2
-rw-r--r--spec/factories/ci/stages.rb2
-rw-r--r--spec/factories/ci/trigger_requests.rb2
-rw-r--r--spec/factories/ci/triggers.rb2
-rw-r--r--spec/factories/ci/variables.rb2
-rw-r--r--spec/factories/clusters/applications/helm.rb18
-rw-r--r--spec/factories/clusters/clusters.rb11
-rw-r--r--spec/factories/clusters/kubernetes_namespaces.rb13
-rw-r--r--spec/factories/clusters/platforms/kubernetes.rb2
-rw-r--r--spec/factories/clusters/providers/gcp.rb2
-rw-r--r--spec/factories/commit_statuses.rb2
-rw-r--r--spec/factories/commits.rb2
-rw-r--r--spec/factories/container_repositories.rb4
-rw-r--r--spec/factories/conversational_development_index_metrics.rb2
-rw-r--r--spec/factories/deploy_keys_projects.rb2
-rw-r--r--spec/factories/deploy_tokens.rb5
-rw-r--r--spec/factories/deployments.rb6
-rw-r--r--spec/factories/emails.rb2
-rw-r--r--spec/factories/environments.rb2
-rw-r--r--spec/factories/events.rb2
-rw-r--r--spec/factories/file_uploaders.rb2
-rw-r--r--spec/factories/fork_network_members.rb2
-rw-r--r--spec/factories/fork_networks.rb2
-rw-r--r--spec/factories/gitaly/commit.rb2
-rw-r--r--spec/factories/gitaly/commit_author.rb2
-rw-r--r--spec/factories/gitaly/tag.rb2
-rw-r--r--spec/factories/gpg_key_subkeys.rb2
-rw-r--r--spec/factories/gpg_keys.rb2
-rw-r--r--spec/factories/gpg_signature.rb2
-rw-r--r--spec/factories/group_custom_attributes.rb2
-rw-r--r--spec/factories/group_members.rb10
-rw-r--r--spec/factories/groups.rb6
-rw-r--r--spec/factories/identities.rb2
-rw-r--r--spec/factories/import_export_uploads.rb2
-rw-r--r--spec/factories/import_states.rb2
-rw-r--r--spec/factories/instance_configuration.rb2
-rw-r--r--spec/factories/internal_ids.rb2
-rw-r--r--spec/factories/issues.rb2
-rw-r--r--spec/factories/keys.rb2
-rw-r--r--spec/factories/label_links.rb2
-rw-r--r--spec/factories/label_priorities.rb2
-rw-r--r--spec/factories/labels.rb2
-rw-r--r--spec/factories/lfs_file_locks.rb2
-rw-r--r--spec/factories/lfs_objects.rb3
-rw-r--r--spec/factories/lfs_objects_projects.rb2
-rw-r--r--spec/factories/lists.rb2
-rw-r--r--spec/factories/merge_requests.rb2
-rw-r--r--spec/factories/merge_requests_closing_issues.rb2
-rw-r--r--spec/factories/milestones.rb2
-rw-r--r--spec/factories/namespaces.rb2
-rw-r--r--spec/factories/notes.rb2
-rw-r--r--spec/factories/notification_settings.rb2
-rw-r--r--spec/factories/oauth_access_grants.rb2
-rw-r--r--spec/factories/oauth_access_tokens.rb2
-rw-r--r--spec/factories/oauth_applications.rb2
-rw-r--r--spec/factories/pages_domains.rb86
-rw-r--r--spec/factories/personal_access_tokens.rb2
-rw-r--r--spec/factories/pool_repositories.rb2
-rw-r--r--spec/factories/programming_languages.rb2
-rw-r--r--spec/factories/project_auto_devops.rb2
-rw-r--r--spec/factories/project_custom_attributes.rb2
-rw-r--r--spec/factories/project_deploy_tokens.rb2
-rw-r--r--spec/factories/project_group_links.rb2
-rw-r--r--spec/factories/project_hooks.rb2
-rw-r--r--spec/factories/project_members.rb2
-rw-r--r--spec/factories/project_statistics.rb17
-rw-r--r--spec/factories/project_wikis.rb2
-rw-r--r--spec/factories/projects.rb32
-rw-r--r--spec/factories/protected_branches.rb2
-rw-r--r--spec/factories/protected_tags.rb2
-rw-r--r--spec/factories/redirect_routes.rb2
-rw-r--r--spec/factories/releases.rb3
-rw-r--r--spec/factories/remote_mirrors.rb2
-rw-r--r--spec/factories/repository_languages.rb2
-rw-r--r--spec/factories/sent_notifications.rb2
-rw-r--r--spec/factories/sequences.rb4
-rw-r--r--spec/factories/service_hooks.rb2
-rw-r--r--spec/factories/services.rb65
-rw-r--r--spec/factories/services_data.rb8
-rw-r--r--spec/factories/shards.rb2
-rw-r--r--spec/factories/snippets.rb2
-rw-r--r--spec/factories/spam_logs.rb2
-rw-r--r--spec/factories/subscriptions.rb2
-rw-r--r--spec/factories/system_hooks.rb2
-rw-r--r--spec/factories/system_note_metadata.rb2
-rw-r--r--spec/factories/term_agreements.rb2
-rw-r--r--spec/factories/terms.rb2
-rw-r--r--spec/factories/timelogs.rb2
-rw-r--r--spec/factories/todos.rb2
-rw-r--r--spec/factories/trending_project.rb2
-rw-r--r--spec/factories/u2f_registrations.rb2
-rw-r--r--spec/factories/uploads.rb7
-rw-r--r--spec/factories/user_agent_details.rb2
-rw-r--r--spec/factories/user_callouts.rb2
-rw-r--r--spec/factories/user_custom_attributes.rb2
-rw-r--r--spec/factories/users.rb2
-rw-r--r--spec/factories/users_star_projects.rb2
-rw-r--r--spec/factories/web_hook_log.rb2
-rw-r--r--spec/factories/wiki_directories.rb2
-rw-r--r--spec/factories/wiki_pages.rb2
-rw-r--r--spec/fast_spec_helper.rb1
-rw-r--r--spec/features/abuse_report_spec.rb2
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb2
-rw-r--r--spec/features/admin/admin_broadcast_messages_spec.rb2
-rw-r--r--spec/features/admin/admin_browse_spam_logs_spec.rb2
-rw-r--r--spec/features/admin/admin_browses_logs_spec.rb4
-rw-r--r--spec/features/admin/admin_builds_spec.rb2
-rw-r--r--spec/features/admin/admin_deploy_keys_spec.rb2
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb2
-rw-r--r--spec/features/admin/admin_disables_two_factor_spec.rb2
-rw-r--r--spec/features/admin/admin_groups_spec.rb11
-rw-r--r--spec/features/admin/admin_health_check_spec.rb2
-rw-r--r--spec/features/admin/admin_hook_logs_spec.rb2
-rw-r--r--spec/features/admin/admin_hooks_spec.rb2
-rw-r--r--spec/features/admin/admin_labels_spec.rb2
-rw-r--r--spec/features/admin/admin_manage_applications_spec.rb2
-rw-r--r--spec/features/admin/admin_projects_spec.rb3
-rw-r--r--spec/features/admin/admin_requests_profiles_spec.rb117
-rw-r--r--spec/features/admin/admin_runners_spec.rb2
-rw-r--r--spec/features/admin/admin_sees_project_statistics_spec.rb2
-rw-r--r--spec/features/admin/admin_settings_spec.rb128
-rw-r--r--spec/features/admin/admin_system_info_spec.rb2
-rw-r--r--spec/features/admin/admin_users_impersonation_tokens_spec.rb2
-rw-r--r--spec/features/admin/admin_users_spec.rb2
-rw-r--r--spec/features/admin/admin_uses_repository_checks_spec.rb2
-rw-r--r--spec/features/admin/dashboard_spec.rb8
-rw-r--r--spec/features/admin/services/admin_activates_prometheus_spec.rb2
-rw-r--r--spec/features/atom/dashboard_spec.rb2
-rw-r--r--spec/features/atom/issues_spec.rb2
-rw-r--r--spec/features/atom/users_spec.rb2
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb2
-rw-r--r--spec/features/boards/boards_spec.rb16
-rw-r--r--spec/features/boards/issue_ordering_spec.rb2
-rw-r--r--spec/features/boards/keyboard_shortcut_spec.rb2
-rw-r--r--spec/features/boards/modal_filter_spec.rb2
-rw-r--r--spec/features/boards/multiple_boards_spec.rb15
-rw-r--r--spec/features/boards/new_issue_spec.rb2
-rw-r--r--spec/features/boards/reload_boards_on_browser_back_spec.rb2
-rw-r--r--spec/features/boards/sidebar_spec.rb2
-rw-r--r--spec/features/boards/sub_group_project_spec.rb2
-rw-r--r--spec/features/calendar_spec.rb2
-rw-r--r--spec/features/commits_spec.rb2
-rw-r--r--spec/features/container_registry_spec.rb14
-rw-r--r--spec/features/contextual_sidebar_spec.rb10
-rw-r--r--spec/features/cycle_analytics_spec.rb2
-rw-r--r--spec/features/dashboard/active_tab_spec.rb2
-rw-r--r--spec/features/dashboard/activity_spec.rb2
-rw-r--r--spec/features/dashboard/archived_projects_spec.rb2
-rw-r--r--spec/features/dashboard/datetime_on_tooltips_spec.rb2
-rw-r--r--spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb2
-rw-r--r--spec/features/dashboard/group_spec.rb2
-rw-r--r--spec/features/dashboard/groups_list_spec.rb8
-rw-r--r--spec/features/dashboard/help_spec.rb2
-rw-r--r--spec/features/dashboard/issuables_counter_spec.rb2
-rw-r--r--spec/features/dashboard/issues_filter_spec.rb2
-rw-r--r--spec/features/dashboard/issues_spec.rb2
-rw-r--r--spec/features/dashboard/label_filter_spec.rb2
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb2
-rw-r--r--spec/features/dashboard/milestone_tabs_spec.rb2
-rw-r--r--spec/features/dashboard/milestones_spec.rb16
-rw-r--r--spec/features/dashboard/project_member_activity_index_spec.rb2
-rw-r--r--spec/features/dashboard/projects_spec.rb28
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb6
-rw-r--r--spec/features/dashboard/snippets_spec.rb2
-rw-r--r--spec/features/dashboard/todos/target_state_spec.rb2
-rw-r--r--spec/features/dashboard/todos/todos_filtering_spec.rb44
-rw-r--r--spec/features/dashboard/todos/todos_sorting_spec.rb2
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb16
-rw-r--r--spec/features/dashboard/user_filters_projects_spec.rb2
-rw-r--r--spec/features/discussion_comments/commit_spec.rb6
-rw-r--r--spec/features/discussion_comments/issue_spec.rb6
-rw-r--r--spec/features/discussion_comments/merge_request_spec.rb6
-rw-r--r--spec/features/discussion_comments/snippets_spec.rb6
-rw-r--r--spec/features/display_system_header_and_footer_bar_spec.rb2
-rw-r--r--spec/features/error_pages_spec.rb2
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb2
-rw-r--r--spec/features/explore/groups_list_spec.rb2
-rw-r--r--spec/features/explore/groups_spec.rb2
-rw-r--r--spec/features/explore/user_explores_projects_spec.rb2
-rw-r--r--spec/features/global_search_spec.rb15
-rw-r--r--spec/features/group_variables_spec.rb2
-rw-r--r--spec/features/groups/activity_spec.rb2
-rw-r--r--spec/features/groups/board_spec.rb2
-rw-r--r--spec/features/groups/empty_states_spec.rb4
-rw-r--r--spec/features/groups/group_settings_spec.rb12
-rw-r--r--spec/features/groups/issues_spec.rb23
-rw-r--r--spec/features/groups/labels/edit_spec.rb2
-rw-r--r--spec/features/groups/labels/subscription_spec.rb2
-rw-r--r--spec/features/groups/labels/user_sees_links_to_issuables_spec.rb (renamed from spec/features/groups/labels/user_sees_links_to_issuables.rb)8
-rw-r--r--spec/features/groups/members/filter_members_spec.rb8
-rw-r--r--spec/features/groups/members/leave_group_spec.rb2
-rw-r--r--spec/features/groups/members/list_members_spec.rb6
-rw-r--r--spec/features/groups/members/manage_members_spec.rb6
-rw-r--r--spec/features/groups/members/master_manages_access_requests_spec.rb2
-rw-r--r--spec/features/groups/members/request_access_spec.rb2
-rw-r--r--spec/features/groups/members/search_members_spec.rb6
-rw-r--r--spec/features/groups/members/sort_members_spec.rb20
-rw-r--r--spec/features/groups/milestone_spec.rb2
-rw-r--r--spec/features/groups/milestones_sorting_spec.rb2
-rw-r--r--spec/features/groups/settings/group_badges_spec.rb2
-rw-r--r--spec/features/groups/share_lock_spec.rb4
-rw-r--r--spec/features/groups/show_spec.rb90
-rw-r--r--spec/features/groups/user_browse_projects_group_page_spec.rb2
-rw-r--r--spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb2
-rw-r--r--spec/features/groups_spec.rb6
-rw-r--r--spec/features/ics/dashboard_issues_spec.rb4
-rw-r--r--spec/features/ics/group_issues_spec.rb4
-rw-r--r--spec/features/ics/project_issues_spec.rb4
-rw-r--r--spec/features/ide/user_opens_merge_request_spec.rb2
-rw-r--r--spec/features/ide_spec.rb4
-rw-r--r--spec/features/import/manifest_import_spec.rb4
-rw-r--r--spec/features/instance_statistics/cohorts_spec.rb2
-rw-r--r--spec/features/instance_statistics/instance_statistics_spec.rb (renamed from spec/features/instance_statistics/instance_statistics.rb)2
-rw-r--r--spec/features/invites_spec.rb75
-rw-r--r--spec/features/issuables/close_reopen_report_toggle_spec.rb2
-rw-r--r--spec/features/issuables/discussion_lock_spec.rb2
-rw-r--r--spec/features/issuables/issuable_list_spec.rb2
-rw-r--r--spec/features/issuables/markdown_references/internal_references_spec.rb2
-rw-r--r--spec/features/issuables/markdown_references/jira_spec.rb2
-rw-r--r--spec/features/issuables/shortcuts_issuable_spec.rb2
-rw-r--r--spec/features/issuables/sorting_list_spec.rb4
-rw-r--r--spec/features/issuables/user_sees_sidebar_spec.rb2
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb4
-rw-r--r--spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb34
-rw-r--r--spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb26
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_emoji_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_hint_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_label_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/recent_searches_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb2
-rw-r--r--spec/features/issues/form_spec.rb2
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb2
-rw-r--r--spec/features/issues/group_label_sidebar_spec.rb2
-rw-r--r--spec/features/issues/issue_detail_spec.rb2
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb2
-rw-r--r--spec/features/issues/keyboard_shortcut_spec.rb2
-rw-r--r--spec/features/issues/markdown_toolbar_spec.rb2
-rw-r--r--spec/features/issues/move_spec.rb2
-rw-r--r--spec/features/issues/note_polling_spec.rb2
-rw-r--r--spec/features/issues/notes_on_issues_spec.rb2
-rw-r--r--spec/features/issues/rss_spec.rb2
-rw-r--r--spec/features/issues/spam_issues_spec.rb2
-rw-r--r--spec/features/issues/todo_spec.rb10
-rw-r--r--spec/features/issues/update_issues_spec.rb2
-rw-r--r--spec/features/issues/user_comments_on_issue_spec.rb7
-rw-r--r--spec/features/issues/user_creates_branch_and_merge_request_spec.rb2
-rw-r--r--spec/features/issues/user_creates_confidential_merge_request_spec.rb56
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb2
-rw-r--r--spec/features/issues/user_edits_issue_spec.rb2
-rw-r--r--spec/features/issues/user_interacts_with_awards_spec.rb2
-rw-r--r--spec/features/issues/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/issues/user_sorts_issues_spec.rb2
-rw-r--r--spec/features/issues/user_toggles_subscription_spec.rb12
-rw-r--r--spec/features/issues/user_views_issue_spec.rb2
-rw-r--r--spec/features/issues/user_views_issues_spec.rb2
-rw-r--r--spec/features/labels_hierarchy_spec.rb4
-rw-r--r--spec/features/markdown/copy_as_gfm_spec.rb10
-rw-r--r--spec/features/markdown/gitlab_flavored_markdown_spec.rb2
-rw-r--r--spec/features/markdown/markdown_spec.rb2
-rw-r--r--spec/features/markdown/math_spec.rb2
-rw-r--r--spec/features/markdown/mermaid_spec.rb20
-rw-r--r--spec/features/markdown/metrics_spec.rb78
-rw-r--r--spec/features/merge_request/maintainer_edits_fork_spec.rb2
-rw-r--r--spec/features/merge_request/user_accepts_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb2
-rw-r--r--spec/features/merge_request/user_assigns_themselves_spec.rb2
-rw-r--r--spec/features/merge_request/user_awards_emoji_spec.rb2
-rw-r--r--spec/features/merge_request/user_closes_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_comments_on_commit_spec.rb2
-rw-r--r--spec/features/merge_request/user_comments_on_diff_spec.rb2
-rw-r--r--spec/features/merge_request/user_comments_on_merge_request_spec.rb4
-rw-r--r--spec/features/merge_request/user_creates_image_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_creates_merge_request_spec.rb4
-rw-r--r--spec/features/merge_request/user_creates_mr_spec.rb2
-rw-r--r--spec/features/merge_request/user_customizes_merge_commit_message_spec.rb2
-rw-r--r--spec/features/merge_request/user_edits_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_edits_mr_spec.rb2
-rw-r--r--spec/features/merge_request/user_expands_diff_spec.rb2
-rw-r--r--spec/features/merge_request/user_locks_discussion_spec.rb2
-rw-r--r--spec/features/merge_request/user_manages_subscription_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_immediately_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb2
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb14
-rw-r--r--spec/features/merge_request/user_posts_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_rebases_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_reopens_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_resolves_conflicts_spec.rb2
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb170
-rw-r--r--spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb2
-rw-r--r--spec/features/merge_request/user_reverts_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_closing_issues_message_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_deleted_target_branch_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_diff_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_discussions_spec.rb10
-rw-r--r--spec/features/merge_request/user_sees_empty_state_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb14
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb64
-rw-r--r--spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_pipelines_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_system_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_versions_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_wip_help_message_spec.rb2
-rw-r--r--spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb2
-rw-r--r--spec/features/merge_request/user_suggests_changes_on_diff_spec.rb4
-rw-r--r--spec/features/merge_request/user_toggles_whitespace_changes_spec.rb2
-rw-r--r--spec/features/merge_request/user_tries_to_access_private_project_info_through_new_mr_spec.rb (renamed from spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb)21
-rw-r--r--spec/features/merge_request/user_views_diffs_spec.rb20
-rw-r--r--spec/features/merge_request/user_views_open_merge_request_spec.rb2
-rw-r--r--spec/features/merge_requests/filters_generic_behavior_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_assignees_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_labels_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_milestones_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_target_branch_spec.rb2
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_mass_updates_spec.rb2
-rw-r--r--spec/features/merge_requests/user_sorts_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_squashes_merge_request_spec.rb2
-rw-r--r--spec/features/merge_requests/user_views_all_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_views_closed_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_views_merged_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_views_open_merge_requests_spec.rb2
-rw-r--r--spec/features/milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_creates_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_deletes_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_edits_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_promotes_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/milestones/user_views_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_views_milestones_spec.rb2
-rw-r--r--spec/features/oauth_login_spec.rb27
-rw-r--r--spec/features/participants_autocomplete_spec.rb2
-rw-r--r--spec/features/password_reset_spec.rb2
-rw-r--r--spec/features/profile_spec.rb2
-rw-r--r--spec/features/profiles/account_spec.rb2
-rw-r--r--spec/features/profiles/active_sessions_spec.rb2
-rw-r--r--spec/features/profiles/chat_names_spec.rb2
-rw-r--r--spec/features/profiles/emails_spec.rb2
-rw-r--r--spec/features/profiles/gpg_keys_spec.rb2
-rw-r--r--spec/features/profiles/keys_spec.rb2
-rw-r--r--spec/features/profiles/oauth_applications_spec.rb2
-rw-r--r--spec/features/profiles/password_spec.rb2
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb2
-rw-r--r--spec/features/profiles/user_changes_notified_of_own_activity_spec.rb2
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb4
-rw-r--r--spec/features/profiles/user_manages_applications_spec.rb2
-rw-r--r--spec/features/profiles/user_manages_emails_spec.rb2
-rw-r--r--spec/features/profiles/user_visits_notifications_tab_spec.rb10
-rw-r--r--spec/features/profiles/user_visits_profile_account_page_spec.rb2
-rw-r--r--spec/features/profiles/user_visits_profile_authentication_log_spec.rb2
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb6
-rw-r--r--spec/features/profiles/user_visits_profile_spec.rb2
-rw-r--r--spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb2
-rw-r--r--spec/features/project_variables_spec.rb23
-rw-r--r--spec/features/projects/activity/rss_spec.rb2
-rw-r--r--spec/features/projects/activity/user_sees_activity_spec.rb2
-rw-r--r--spec/features/projects/activity/user_sees_private_activity_spec.rb2
-rw-r--r--spec/features/projects/actve_tabs_spec.rb2
-rw-r--r--spec/features/projects/artifacts/file_spec.rb2
-rw-r--r--spec/features/projects/artifacts/raw_spec.rb2
-rw-r--r--spec/features/projects/artifacts/user_browses_artifacts_spec.rb2
-rw-r--r--spec/features/projects/artifacts/user_downloads_artifacts_spec.rb6
-rw-r--r--spec/features/projects/badges/coverage_spec.rb2
-rw-r--r--spec/features/projects/badges/list_spec.rb2
-rw-r--r--spec/features/projects/badges/pipeline_badge_spec.rb2
-rw-r--r--spec/features/projects/blobs/blob_line_permalink_updater_spec.rb2
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb2
-rw-r--r--spec/features/projects/blobs/edit_spec.rb2
-rw-r--r--spec/features/projects/blobs/shortcuts_blob_spec.rb2
-rw-r--r--spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb2
-rw-r--r--spec/features/projects/branches/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/branches/new_branch_ref_dropdown_spec.rb2
-rw-r--r--spec/features/projects/branches/user_creates_branch_spec.rb2
-rw-r--r--spec/features/projects/branches/user_deletes_branch_spec.rb2
-rw-r--r--spec/features/projects/branches/user_views_branches_spec.rb2
-rw-r--r--spec/features/projects/branches_spec.rb2
-rw-r--r--spec/features/projects/ci/lint_spec.rb2
-rw-r--r--spec/features/projects/clusters/applications_spec.rb33
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb2
-rw-r--r--spec/features/projects/clusters/user_spec.rb2
-rw-r--r--spec/features/projects/commit/builds_spec.rb2
-rw-r--r--spec/features/projects/commit/cherry_pick_spec.rb2
-rw-r--r--spec/features/projects/commit/comments/user_adds_comment_spec.rb2
-rw-r--r--spec/features/projects/commit/comments/user_deletes_comments_spec.rb2
-rw-r--r--spec/features/projects/commit/comments/user_edits_comments_spec.rb2
-rw-r--r--spec/features/projects/commit/diff_notes_spec.rb2
-rw-r--r--spec/features/projects/commit/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/projects/commit/user_comments_on_commit_spec.rb2
-rw-r--r--spec/features/projects/commit/user_reverts_commit_spec.rb2
-rw-r--r--spec/features/projects/commits/rss_spec.rb2
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb4
-rw-r--r--spec/features/projects/compare_spec.rb2
-rw-r--r--spec/features/projects/deploy_keys_spec.rb2
-rw-r--r--spec/features/projects/diffs/diff_show_spec.rb2
-rw-r--r--spec/features/projects/environments/environment_metrics_spec.rb2
-rw-r--r--spec/features/projects/environments/environment_spec.rb2
-rw-r--r--spec/features/projects/environments/environments_spec.rb2
-rw-r--r--spec/features/projects/features_visibility_spec.rb2
-rw-r--r--spec/features/projects/files/dockerfile_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/files/edit_file_soft_wrap_spec.rb2
-rw-r--r--spec/features/projects/files/editing_a_file_spec.rb2
-rw-r--r--spec/features/projects/files/files_sort_submodules_with_folders_spec.rb2
-rw-r--r--spec/features/projects/files/find_file_keyboard_spec.rb2
-rw-r--r--spec/features/projects/files/gitignore_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb2
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb2
-rw-r--r--spec/features/projects/files/template_selector_menu_spec.rb2
-rw-r--r--spec/features/projects/files/template_type_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/undo_template_spec.rb2
-rw-r--r--spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb (renamed from spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb)8
-rw-r--r--spec/features/projects/files/user_browses_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_browses_lfs_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_creates_directory_spec.rb2
-rw-r--r--spec/features/projects/files/user_creates_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_deletes_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_edits_files_spec.rb3
-rw-r--r--spec/features/projects/files/user_find_file_spec.rb2
-rw-r--r--spec/features/projects/files/user_reads_pipeline_status_spec.rb2
-rw-r--r--spec/features/projects/files/user_replaces_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_searches_for_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_uploads_files_spec.rb2
-rw-r--r--spec/features/projects/fork_spec.rb4
-rw-r--r--spec/features/projects/forks/fork_list_spec.rb2
-rw-r--r--spec/features/projects/gfm_autocomplete_load_spec.rb2
-rw-r--r--spec/features/projects/graph_spec.rb2
-rw-r--r--spec/features/projects/hook_logs/user_reads_log_spec.rb2
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb2
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb2
-rw-r--r--spec/features/projects/issuable_templates_spec.rb2
-rw-r--r--spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb2
-rw-r--r--spec/features/projects/jobs/permissions_spec.rb2
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb16
-rw-r--r--spec/features/projects/jobs/user_browses_jobs_spec.rb2
-rw-r--r--spec/features/projects/jobs_spec.rb10
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb2
-rw-r--r--spec/features/projects/labels/subscription_spec.rb2
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb2
-rw-r--r--spec/features/projects/labels/user_creates_labels_spec.rb2
-rw-r--r--spec/features/projects/labels/user_edits_labels_spec.rb2
-rw-r--r--spec/features/projects/labels/user_removes_labels_spec.rb2
-rw-r--r--spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/projects/labels/user_sees_links_to_issuables_spec.rb (renamed from spec/features/projects/labels/user_sees_links_to_issuables.rb)38
-rw-r--r--spec/features/projects/members/anonymous_user_sees_members_spec.rb2
-rw-r--r--spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb2
-rw-r--r--spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb2
-rw-r--r--spec/features/projects/members/group_members_spec.rb2
-rw-r--r--spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb2
-rw-r--r--spec/features/projects/members/groups_with_access_list_spec.rb10
-rw-r--r--spec/features/projects/members/invite_group_spec.rb6
-rw-r--r--spec/features/projects/members/list_spec.rb2
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb2
-rw-r--r--spec/features/projects/members/master_manages_access_requests_spec.rb2
-rw-r--r--spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb2
-rw-r--r--spec/features/projects/members/member_leaves_project_spec.rb2
-rw-r--r--spec/features/projects/members/owner_cannot_leave_project_spec.rb2
-rw-r--r--spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb2
-rw-r--r--spec/features/projects/members/sorting_spec.rb20
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb2
-rw-r--r--spec/features/projects/merge_request_button_spec.rb2
-rw-r--r--spec/features/projects/milestones/milestone_spec.rb2
-rw-r--r--spec/features/projects/milestones/milestones_sorting_spec.rb2
-rw-r--r--spec/features/projects/milestones/new_spec.rb2
-rw-r--r--spec/features/projects/milestones/user_interacts_with_labels_spec.rb2
-rw-r--r--spec/features/projects/network_graph_spec.rb2
-rw-r--r--spec/features/projects/new_project_spec.rb434
-rw-r--r--spec/features/projects/pages_lets_encrypt_spec.rb8
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb2
-rw-r--r--spec/features/projects/raw/user_interacts_with_raw_endpoint_spec.rb39
-rw-r--r--spec/features/projects/releases/user_views_releases_spec.rb14
-rw-r--r--spec/features/projects/remote_mirror_spec.rb2
-rw-r--r--spec/features/projects/serverless/functions_spec.rb12
-rw-r--r--spec/features/projects/services/disable_triggers_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_asana_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_assembla_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_emails_on_push_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_flowdock_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_irker_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_issue_tracker_spec.rb4
-rw-r--r--spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_jira_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_packagist_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_pivotaltracker_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_prometheus_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_pushover_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_slack_notifications_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_slack_slash_command_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_youtrack_spec.rb4
-rw-r--r--spec/features/projects/services/user_views_services_spec.rb2
-rw-r--r--spec/features/projects/settings/forked_project_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/integration_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/lfs_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb4
-rw-r--r--spec/features/projects/settings/project_badges_spec.rb2
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb16
-rw-r--r--spec/features/projects/settings/user_archives_project_spec.rb2
-rw-r--r--spec/features/projects/settings/user_changes_avatar_spec.rb2
-rw-r--r--spec/features/projects/settings/user_changes_default_branch_spec.rb2
-rw-r--r--spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb2
-rw-r--r--spec/features/projects/settings/user_manages_group_links_spec.rb2
-rw-r--r--spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/user_manages_project_members_spec.rb2
-rw-r--r--spec/features/projects/settings/user_renames_a_project_spec.rb2
-rw-r--r--spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb2
-rw-r--r--spec/features/projects/settings/user_tags_project_spec.rb2
-rw-r--r--spec/features/projects/settings/user_transfers_a_project_spec.rb4
-rw-r--r--spec/features/projects/settings/visibility_settings_spec.rb14
-rw-r--r--spec/features/projects/show/developer_views_empty_project_instructions_spec.rb2
-rw-r--r--spec/features/projects/show/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/show/no_password_spec.rb2
-rw-r--r--spec/features/projects/show/redirects_spec.rb2
-rw-r--r--spec/features/projects/show/rss_spec.rb2
-rw-r--r--spec/features/projects/show/user_interacts_with_stars_spec.rb2
-rw-r--r--spec/features/projects/show/user_manages_notifications_spec.rb10
-rw-r--r--spec/features/projects/show/user_sees_collaboration_links_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_deletion_failure_message_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_git_instructions_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb4
-rw-r--r--spec/features/projects/show/user_sees_readme_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb2
-rw-r--r--spec/features/projects/snippets/create_snippet_spec.rb2
-rw-r--r--spec/features/projects/snippets/show_spec.rb2
-rw-r--r--spec/features/projects/snippets/user_comments_on_snippet_spec.rb2
-rw-r--r--spec/features/projects/snippets/user_deletes_snippet_spec.rb2
-rw-r--r--spec/features/projects/snippets/user_updates_snippet_spec.rb2
-rw-r--r--spec/features/projects/snippets/user_views_snippets_spec.rb2
-rw-r--r--spec/features/projects/sub_group_issuables_spec.rb4
-rw-r--r--spec/features/projects/tags/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/tree/create_directory_spec.rb19
-rw-r--r--spec/features/projects/tree/create_file_spec.rb13
-rw-r--r--spec/features/projects/tree/rss_spec.rb2
-rw-r--r--spec/features/projects/tree/tree_show_spec.rb2
-rw-r--r--spec/features/projects/tree/upload_file_spec.rb2
-rw-r--r--spec/features/projects/user_creates_project_spec.rb4
-rw-r--r--spec/features/projects/user_sees_sidebar_spec.rb2
-rw-r--r--spec/features/projects/user_uses_shortcuts_spec.rb2
-rw-r--r--spec/features/projects/user_views_empty_project_spec.rb2
-rw-r--r--spec/features/projects/view_on_env_spec.rb2
-rw-r--r--spec/features/projects/wiki/markdown_preview_spec.rb95
-rw-r--r--spec/features/projects/wiki/shortcuts_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb70
-rw-r--r--spec/features/projects/wiki/user_deletes_wiki_page_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_git_access_wiki_page_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb18
-rw-r--r--spec/features/projects/wiki/user_views_wiki_empty_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_views_wiki_page_spec.rb9
-rw-r--r--spec/features/projects/wiki/user_views_wiki_pages_spec.rb4
-rw-r--r--spec/features/projects_spec.rb4
-rw-r--r--spec/features/protected_branches_spec.rb2
-rw-r--r--spec/features/protected_tags_spec.rb8
-rw-r--r--spec/features/raven_js_spec.rb2
-rw-r--r--spec/features/read_only_spec.rb2
-rw-r--r--spec/features/reportable_note/commit_spec.rb2
-rw-r--r--spec/features/reportable_note/issue_spec.rb2
-rw-r--r--spec/features/reportable_note/merge_request_spec.rb2
-rw-r--r--spec/features/reportable_note/snippets_spec.rb2
-rw-r--r--spec/features/runners_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_code_spec.rb101
-rw-r--r--spec/features/search/user_searches_for_comments_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_commits_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_issues_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_merge_requests_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_milestones_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_projects_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_users_spec.rb6
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb2
-rw-r--r--spec/features/search/user_uses_header_search_field_spec.rb145
-rw-r--r--spec/features/search/user_uses_search_filters_spec.rb4
-rw-r--r--spec/features/security/admin_access_spec.rb2
-rw-r--r--spec/features/security/dashboard_access_spec.rb2
-rw-r--r--spec/features/security/group/internal_access_spec.rb2
-rw-r--r--spec/features/security/group/private_access_spec.rb2
-rw-r--r--spec/features/security/group/public_access_spec.rb2
-rw-r--r--spec/features/security/project/internal_access_spec.rb2
-rw-r--r--spec/features/security/project/private_access_spec.rb4
-rw-r--r--spec/features/security/project/public_access_spec.rb2
-rw-r--r--spec/features/security/project/snippet/internal_access_spec.rb2
-rw-r--r--spec/features/security/project/snippet/private_access_spec.rb2
-rw-r--r--spec/features/security/project/snippet/public_access_spec.rb2
-rw-r--r--spec/features/signed_commits_spec.rb18
-rw-r--r--spec/features/snippets/embedded_snippet_spec.rb2
-rw-r--r--spec/features/snippets/explore_spec.rb2
-rw-r--r--spec/features/snippets/internal_snippet_spec.rb2
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb2
-rw-r--r--spec/features/snippets/public_snippets_spec.rb2
-rw-r--r--spec/features/snippets/search_snippets_spec.rb2
-rw-r--r--spec/features/snippets/show_spec.rb2
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb4
-rw-r--r--spec/features/snippets/user_deletes_snippet_spec.rb2
-rw-r--r--spec/features/snippets/user_edits_snippet_spec.rb4
-rw-r--r--spec/features/snippets/user_sees_breadcrumb_links.rb17
-rw-r--r--spec/features/snippets/user_snippets_spec.rb2
-rw-r--r--spec/features/snippets_spec.rb2
-rw-r--r--spec/features/tags/developer_creates_tag_spec.rb2
-rw-r--r--spec/features/tags/developer_deletes_tag_spec.rb2
-rw-r--r--spec/features/tags/developer_updates_tag_spec.rb2
-rw-r--r--spec/features/tags/developer_views_tags_spec.rb2
-rw-r--r--spec/features/task_lists_spec.rb2
-rw-r--r--spec/features/triggers_spec.rb103
-rw-r--r--spec/features/u2f_spec.rb2
-rw-r--r--spec/features/unsubscribe_links_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_group_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_profile_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_file_to_note_spec.rb2
-rw-r--r--spec/features/usage_stats_consent_spec.rb10
-rw-r--r--spec/features/user_can_display_performance_bar_spec.rb2
-rw-r--r--spec/features/user_opens_link_to_comment_spec.rb (renamed from spec/features/user_opens_link_to_comment.rb)5
-rw-r--r--spec/features/user_sees_revert_modal_spec.rb2
-rw-r--r--spec/features/user_sorts_things_spec.rb2
-rw-r--r--spec/features/users/active_sessions_spec.rb2
-rw-r--r--spec/features/users/add_email_to_existing_account_spec.rb (renamed from spec/features/users/add_email_to_existing_account.rb)2
-rw-r--r--spec/features/users/login_spec.rb111
-rw-r--r--spec/features/users/logout_spec.rb2
-rw-r--r--spec/features/users/overview_spec.rb2
-rw-r--r--spec/features/users/rss_spec.rb2
-rw-r--r--spec/features/users/show_spec.rb2
-rw-r--r--spec/features/users/signup_spec.rb55
-rw-r--r--spec/features/users/snippets_spec.rb2
-rw-r--r--spec/features/users/terms_spec.rb17
-rw-r--r--spec/features/users/user_browses_projects_on_user_page_spec.rb15
-rw-r--r--spec/finders/autocomplete/move_to_project_finder_spec.rb40
-rw-r--r--spec/finders/autocomplete/users_finder_spec.rb2
-rw-r--r--spec/finders/award_emojis_finder_spec.rb49
-rw-r--r--spec/finders/branches_finder_spec.rb9
-rw-r--r--spec/finders/cluster_ancestors_finder_spec.rb4
-rw-r--r--spec/finders/clusters/knative_services_finder_spec.rb22
-rw-r--r--spec/finders/clusters/kubernetes_namespace_finder_spec.rb110
-rw-r--r--spec/finders/container_repositories_finder_spec.rb44
-rw-r--r--spec/finders/group_descendants_finder_spec.rb6
-rw-r--r--spec/finders/group_members_finder_spec.rb4
-rw-r--r--spec/finders/group_projects_finder_spec.rb12
-rw-r--r--spec/finders/groups_finder_spec.rb2
-rw-r--r--spec/finders/issues_finder_spec.rb15
-rw-r--r--spec/finders/labels_finder_spec.rb6
-rw-r--r--spec/finders/members_finder_spec.rb8
-rw-r--r--spec/finders/merge_requests_finder_spec.rb2
-rw-r--r--spec/finders/notes_finder_spec.rb123
-rw-r--r--spec/finders/projects/serverless/functions_finder_spec.rb25
-rw-r--r--spec/finders/releases_finder_spec.rb6
-rw-r--r--spec/finders/runner_jobs_finder_spec.rb22
-rw-r--r--spec/finders/starred_projects_finder_spec.rb41
-rw-r--r--spec/finders/todos_finder_spec.rb2
-rw-r--r--spec/finders/users_star_projects_finder_spec.rb42
-rw-r--r--spec/fixtures/api/schemas/current-board.json16
-rw-r--r--spec/fixtures/api/schemas/deployment.json2
-rw-r--r--spec/fixtures/api/schemas/entities/discussion.json67
-rw-r--r--spec/fixtures/api/schemas/entities/discussions.json4
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_poll_cached_widget.json46
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_poll_widget.json55
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_sidebar.json1
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json159
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/group_labels.json19
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/labels/label.json11
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json16
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/labels/project_label.json15
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json9
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json4
-rw-r--r--spec/fixtures/api/schemas/registry/repository.json6
-rw-r--r--spec/fixtures/clusters/sample_key.key9
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json6
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json3
-rw-r--r--spec/fixtures/phabricator_responses/user.search.json62
-rw-r--r--spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json422
-rw-r--r--spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json178
-rw-r--r--spec/fixtures/security-reports/deprecated/gl-sast-report.json944
-rw-r--r--spec/fixtures/security-reports/feature-branch.zipbin7140 -> 0 bytes
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json16
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-dast-report.json40
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json181
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-license-management-report.json42
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-sast-report.json947
-rw-r--r--spec/fixtures/security-reports/master.zipbin9413 -> 0 bytes
-rw-r--r--spec/fixtures/security-reports/master/gl-container-scanning-report.json105
-rw-r--r--spec/fixtures/security-reports/master/gl-dast-report.json42
-rw-r--r--spec/fixtures/security-reports/master/gl-dependency-scanning-report.json181
-rw-r--r--spec/fixtures/security-reports/master/gl-license-management-report.json817
-rw-r--r--spec/fixtures/security-reports/master/gl-sast-report.json947
-rw-r--r--spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json104
-rw-r--r--spec/fixtures/security-reports/remediations/remediation.patch180
-rw-r--r--spec/fixtures/security-reports/remediations/yarn.lock104
-rw-r--r--spec/frontend/api_spec.js38
-rw-r--r--spec/frontend/autosave_spec.js11
-rw-r--r--spec/frontend/behaviors/markdown/render_metrics_spec.js37
-rw-r--r--spec/frontend/boards/services/board_service_spec.js163
-rw-r--r--spec/frontend/branches/divergence_graph_spec.js40
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js51
-rw-r--r--spec/frontend/commons/nav/user_merge_requests_spec.js113
-rw-r--r--spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap101
-rw-r--r--spec/frontend/confidential_merge_request/components/dropdown_spec.js56
-rw-r--r--spec/frontend/confidential_merge_request/components/project_form_group_spec.js77
-rw-r--r--spec/frontend/create_merge_request_dropdown_spec.js (renamed from spec/javascripts/create_merge_request_dropdown_spec.js)43
-rw-r--r--spec/frontend/cycle_analytics/stage_nav_item_spec.js177
-rw-r--r--spec/frontend/diffs/components/diff_discussion_reply_spec.js90
-rw-r--r--spec/frontend/diffs/components/diff_gutter_avatars_spec.js113
-rw-r--r--spec/frontend/diffs/mock_data/diff_discussions.js529
-rw-r--r--spec/frontend/environment.js8
-rw-r--r--spec/frontend/environments/confirm_rollback_modal_spec.js (renamed from spec/javascripts/environments/confirm_rollback_modal_spec.js)2
-rw-r--r--spec/frontend/environments/environment_rollback_spec.js (renamed from spec/javascripts/environments/environment_rollback_spec.js)30
-rw-r--r--spec/frontend/error_tracking_settings/components/app_spec.js (renamed from spec/javascripts/error_tracking_settings/components/app_spec.js)2
-rw-r--r--spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js (renamed from spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js)0
-rw-r--r--spec/frontend/error_tracking_settings/components/project_dropdown_spec.js (renamed from spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js)0
-rw-r--r--spec/frontend/error_tracking_settings/mock.js (renamed from spec/javascripts/error_tracking_settings/mock.js)2
-rw-r--r--spec/frontend/error_tracking_settings/store/actions_spec.js (renamed from spec/javascripts/error_tracking_settings/store/actions_spec.js)11
-rw-r--r--spec/frontend/error_tracking_settings/store/getters_spec.js (renamed from spec/javascripts/error_tracking_settings/store/getters_spec.js)0
-rw-r--r--spec/frontend/error_tracking_settings/store/mutation_spec.js (renamed from spec/javascripts/error_tracking_settings/store/mutation_spec.js)2
-rw-r--r--spec/frontend/error_tracking_settings/utils_spec.js (renamed from spec/javascripts/error_tracking_settings/utils_spec.js)0
-rw-r--r--spec/frontend/filterable_list_spec.js53
-rw-r--r--spec/frontend/fixtures/abuse_reports.rb (renamed from spec/javascripts/fixtures/abuse_reports.rb)2
-rw-r--r--spec/frontend/fixtures/admin_users.rb (renamed from spec/javascripts/fixtures/admin_users.rb)2
-rw-r--r--spec/frontend/fixtures/application_settings.rb (renamed from spec/javascripts/fixtures/application_settings.rb)2
-rw-r--r--spec/frontend/fixtures/autocomplete_sources.rb (renamed from spec/javascripts/fixtures/autocomplete_sources.rb)2
-rw-r--r--spec/frontend/fixtures/blob.rb (renamed from spec/javascripts/fixtures/blob.rb)2
-rw-r--r--spec/frontend/fixtures/boards.rb (renamed from spec/javascripts/fixtures/boards.rb)2
-rw-r--r--spec/frontend/fixtures/branches.rb (renamed from spec/javascripts/fixtures/branches.rb)2
-rw-r--r--spec/frontend/fixtures/clusters.rb (renamed from spec/javascripts/fixtures/clusters.rb)2
-rw-r--r--spec/frontend/fixtures/commit.rb (renamed from spec/javascripts/fixtures/commit.rb)2
-rw-r--r--spec/frontend/fixtures/deploy_keys.rb (renamed from spec/javascripts/fixtures/deploy_keys.rb)2
-rw-r--r--spec/frontend/fixtures/groups.rb (renamed from spec/javascripts/fixtures/groups.rb)4
-rw-r--r--spec/frontend/fixtures/issues.rb (renamed from spec/javascripts/fixtures/issues.rb)6
-rw-r--r--spec/frontend/fixtures/jobs.rb (renamed from spec/javascripts/fixtures/jobs.rb)4
-rw-r--r--spec/frontend/fixtures/labels.rb (renamed from spec/javascripts/fixtures/labels.rb)4
-rw-r--r--spec/frontend/fixtures/merge_requests.rb (renamed from spec/javascripts/fixtures/merge_requests.rb)2
-rw-r--r--spec/frontend/fixtures/merge_requests_diffs.rb (renamed from spec/javascripts/fixtures/merge_requests_diffs.rb)3
-rw-r--r--spec/frontend/fixtures/pipeline_schedules.rb (renamed from spec/javascripts/fixtures/pipeline_schedules.rb)4
-rw-r--r--spec/frontend/fixtures/pipelines.rb (renamed from spec/javascripts/fixtures/pipelines.rb)2
-rw-r--r--spec/frontend/fixtures/projects.rb (renamed from spec/javascripts/fixtures/projects.rb)10
-rw-r--r--spec/frontend/fixtures/prometheus_service.rb (renamed from spec/javascripts/fixtures/prometheus_service.rb)2
-rw-r--r--spec/frontend/fixtures/raw.rb (renamed from spec/javascripts/fixtures/raw.rb)0
-rw-r--r--spec/frontend/fixtures/search.rb (renamed from spec/javascripts/fixtures/search.rb)2
-rw-r--r--spec/frontend/fixtures/services.rb (renamed from spec/javascripts/fixtures/services.rb)2
-rw-r--r--spec/frontend/fixtures/sessions.rb (renamed from spec/javascripts/fixtures/sessions.rb)2
-rw-r--r--spec/frontend/fixtures/snippet.rb (renamed from spec/javascripts/fixtures/snippet.rb)2
-rw-r--r--spec/frontend/fixtures/static/README.md3
-rw-r--r--spec/frontend/fixtures/static/ajax_loading_spinner.html (renamed from spec/javascripts/fixtures/static/ajax_loading_spinner.html)0
-rw-r--r--spec/frontend/fixtures/static/balsamiq_viewer.html (renamed from spec/javascripts/fixtures/static/balsamiq_viewer.html)0
-rw-r--r--spec/frontend/fixtures/static/create_item_dropdown.html (renamed from spec/javascripts/fixtures/static/create_item_dropdown.html)0
-rw-r--r--spec/frontend/fixtures/static/environments/table.html (renamed from spec/javascripts/fixtures/static/environments/table.html)0
-rw-r--r--spec/frontend/fixtures/static/environments_logs.html29
-rw-r--r--spec/frontend/fixtures/static/event_filter.html (renamed from spec/javascripts/fixtures/static/event_filter.html)0
-rw-r--r--spec/frontend/fixtures/static/gl_dropdown.html (renamed from spec/javascripts/fixtures/static/gl_dropdown.html)0
-rw-r--r--spec/frontend/fixtures/static/gl_field_errors.html (renamed from spec/javascripts/fixtures/static/gl_field_errors.html)0
-rw-r--r--spec/frontend/fixtures/static/images/green_box.png (renamed from spec/javascripts/fixtures/static/images/green_box.png)bin1306 -> 1306 bytes
-rw-r--r--spec/frontend/fixtures/static/images/one_white_pixel.png (renamed from spec/javascripts/fixtures/static/images/one_white_pixel.png)bin68 -> 68 bytes
-rw-r--r--spec/frontend/fixtures/static/images/red_box.png (renamed from spec/javascripts/fixtures/static/images/red_box.png)bin1305 -> 1305 bytes
-rw-r--r--spec/frontend/fixtures/static/issuable_filter.html (renamed from spec/javascripts/fixtures/static/issuable_filter.html)0
-rw-r--r--spec/frontend/fixtures/static/issue_sidebar_label.html (renamed from spec/javascripts/fixtures/static/issue_sidebar_label.html)0
-rw-r--r--spec/frontend/fixtures/static/line_highlighter.html (renamed from spec/javascripts/fixtures/static/line_highlighter.html)0
-rw-r--r--spec/frontend/fixtures/static/linked_tabs.html (renamed from spec/javascripts/fixtures/static/linked_tabs.html)0
-rw-r--r--spec/frontend/fixtures/static/merge_requests_show.html (renamed from spec/javascripts/fixtures/static/merge_requests_show.html)0
-rw-r--r--spec/frontend/fixtures/static/mini_dropdown_graph.html (renamed from spec/javascripts/fixtures/static/mini_dropdown_graph.html)0
-rw-r--r--spec/frontend/fixtures/static/notebook_viewer.html (renamed from spec/javascripts/fixtures/static/notebook_viewer.html)0
-rw-r--r--spec/frontend/fixtures/static/oauth_remember_me.html (renamed from spec/javascripts/fixtures/static/oauth_remember_me.html)0
-rw-r--r--spec/frontend/fixtures/static/pdf_viewer.html (renamed from spec/javascripts/fixtures/static/pdf_viewer.html)0
-rw-r--r--spec/frontend/fixtures/static/pipeline_graph.html (renamed from spec/javascripts/fixtures/static/pipeline_graph.html)0
-rw-r--r--spec/frontend/fixtures/static/pipelines.html (renamed from spec/javascripts/fixtures/static/pipelines.html)0
-rw-r--r--spec/frontend/fixtures/static/project_select_combo_button.html (renamed from spec/javascripts/fixtures/static/project_select_combo_button.html)0
-rw-r--r--spec/frontend/fixtures/static/projects.json (renamed from spec/javascripts/fixtures/static/projects.json)0
-rw-r--r--spec/frontend/fixtures/static/search_autocomplete.html (renamed from spec/javascripts/fixtures/static/search_autocomplete.html)0
-rw-r--r--spec/frontend/fixtures/static/signin_tabs.html (renamed from spec/javascripts/fixtures/static/signin_tabs.html)0
-rw-r--r--spec/frontend/fixtures/static/sketch_viewer.html (renamed from spec/javascripts/fixtures/static/sketch_viewer.html)0
-rw-r--r--spec/frontend/fixtures/todos.rb (renamed from spec/javascripts/fixtures/todos.rb)4
-rw-r--r--spec/frontend/fixtures/u2f.rb (renamed from spec/javascripts/fixtures/u2f.rb)4
-rw-r--r--spec/frontend/helpers/fixtures.js7
-rw-r--r--spec/frontend/helpers/vue_test_utils_helper.js18
-rw-r--r--spec/frontend/helpers/vue_test_utils_helper_spec.js (renamed from spec/javascripts/helpers/vue_test_utils_helper_spec.js)0
-rw-r--r--spec/frontend/ide/lib/files_spec.js4
-rw-r--r--spec/frontend/ide/services/index_spec.js32
-rw-r--r--spec/frontend/ide/stores/modules/commit/mutations_spec.js8
-rw-r--r--spec/frontend/issue_show/components/pinned_links_spec.js52
-rw-r--r--spec/frontend/lib/utils/color_utils_spec.js35
-rw-r--r--spec/frontend/lib/utils/forms_spec.js74
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js33
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js41
-rw-r--r--spec/frontend/matchers.js38
-rw-r--r--spec/frontend/mocks/ce/lib/utils/axios_utils.js15
-rw-r--r--spec/frontend/mocks/mocks_helper.js60
-rw-r--r--spec/frontend/mocks/mocks_helper_spec.js149
-rw-r--r--spec/frontend/mocks/node/jquery.js13
-rw-r--r--spec/frontend/mocks_spec.js13
-rw-r--r--spec/frontend/monitoring/__snapshots__/dashboard_state_spec.js.snap37
-rw-r--r--spec/frontend/monitoring/dashboard_state_spec.js43
-rw-r--r--spec/frontend/monitoring/embed/embed_spec.js78
-rw-r--r--spec/frontend/monitoring/embed/mock_data.js87
-rw-r--r--spec/frontend/notes/components/discussion_jump_to_next_button_spec.js5
-rw-r--r--spec/frontend/notes/components/discussion_keyboard_navigator_spec.js104
-rw-r--r--spec/frontend/notes/components/discussion_notes_replies_wrapper_spec.js51
-rw-r--r--spec/frontend/notes/components/discussion_notes_spec.js2
-rw-r--r--spec/frontend/notes/components/discussion_reply_placeholder_spec.js14
-rw-r--r--spec/frontend/operation_settings/components/external_dashboard_spec.js5
-rw-r--r--spec/frontend/pages/search/show/__snapshots__/refresh_counts_spec.js.snap7
-rw-r--r--spec/frontend/pages/search/show/refresh_counts_spec.js35
-rw-r--r--spec/frontend/projects/gke_cluster_namespace/gke_cluster_namespace_spec.js61
-rw-r--r--spec/frontend/projects/projects_filterable_list_spec.js31
-rw-r--r--spec/frontend/repository/components/breadcrumbs_spec.js20
-rw-r--r--spec/frontend/repository/components/table/row_spec.js16
-rw-r--r--spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js85
-rw-r--r--spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js78
-rw-r--r--spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js189
-rw-r--r--spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js49
-rw-r--r--spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js103
-rw-r--r--spec/frontend/sidebar/user_data_mock.js9
-rw-r--r--spec/frontend/test_setup.js59
-rw-r--r--spec/frontend/tracking_spec.js143
-rw-r--r--spec/frontend/vue_shared/components/changed_file_icon_spec.js123
-rw-r--r--spec/frontend/vue_shared/components/markdown/header_spec.js (renamed from spec/javascripts/vue_shared/components/markdown/header_spec.js)16
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js9
-rw-r--r--spec/frontend/wikis_spec.js74
-rw-r--r--spec/graphql/gitlab_schema_spec.rb24
-rw-r--r--spec/graphql/resolvers/echo_resolver_spec.rb24
-rw-r--r--spec/graphql/resolvers/namespace_projects_resolver_spec.rb2
-rw-r--r--spec/graphql/types/base_field_spec.rb59
-rw-r--r--spec/graphql/types/diff_refs_type_spec.rb9
-rw-r--r--spec/graphql/types/label_type_spec.rb2
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb2
-rw-r--r--spec/graphql/types/metadata_type_spec.rb1
-rw-r--r--spec/graphql/types/namespace_type_spec.rb4
-rw-r--r--spec/graphql/types/notes/diff_position_type_spec.rb2
-rw-r--r--spec/graphql/types/notes/discussion_type_spec.rb2
-rw-r--r--spec/graphql/types/query_type_spec.rb4
-rw-r--r--spec/graphql/types/root_storage_statistics_type_spec.rb14
-rw-r--r--spec/graphql/types/tree/submodule_type_spec.rb2
-rw-r--r--spec/helpers/auto_devops_helper_spec.rb6
-rw-r--r--spec/helpers/avatars_helper_spec.rb2
-rw-r--r--spec/helpers/blob_helper_spec.rb38
-rw-r--r--spec/helpers/boards_helper_spec.rb16
-rw-r--r--spec/helpers/ci_status_helper_spec.rb76
-rw-r--r--spec/helpers/dashboard_helper_spec.rb39
-rw-r--r--spec/helpers/emails_helper_spec.rb2
-rw-r--r--spec/helpers/groups_helper_spec.rb46
-rw-r--r--spec/helpers/icons_helper_spec.rb20
-rw-r--r--spec/helpers/issuables_helper_spec.rb41
-rw-r--r--spec/helpers/labels_helper_spec.rb24
-rw-r--r--spec/helpers/markup_helper_spec.rb110
-rw-r--r--spec/helpers/namespaces_helper_spec.rb4
-rw-r--r--spec/helpers/notifications_helper_spec.rb11
-rw-r--r--spec/helpers/onboarding_experiment_helper_spec.rb38
-rw-r--r--spec/helpers/preferences_helper_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb25
-rw-r--r--spec/helpers/search_helper_spec.rb44
-rw-r--r--spec/helpers/sessions_helper_spec.rb17
-rw-r--r--spec/helpers/sorting_helper_spec.rb148
-rw-r--r--spec/helpers/storage_helper_spec.rb2
-rw-r--r--spec/helpers/submodule_helper_spec.rb94
-rw-r--r--spec/helpers/tracking_helper_spec.rb28
-rw-r--r--spec/helpers/users_helper_spec.rb2
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb72
-rw-r--r--spec/helpers/wiki_helper_spec.rb2
-rw-r--r--spec/initializers/6_validations_spec.rb20
-rw-r--r--spec/initializers/action_mailer_hooks_spec.rb46
-rw-r--r--spec/javascripts/badges/components/badge_list_spec.js2
-rw-r--r--spec/javascripts/badges/components/badge_spec.js2
-rw-r--r--spec/javascripts/boards/board_blank_state_spec.js19
-rw-r--r--spec/javascripts/boards/board_list_spec.js2
-rw-r--r--spec/javascripts/boards/boards_store_spec.js13
-rw-r--r--spec/javascripts/boards/components/board_form_spec.js56
-rw-r--r--spec/javascripts/boards/components/board_spec.js61
-rw-r--r--spec/javascripts/boards/components/boards_selector_spec.js206
-rw-r--r--spec/javascripts/boards/mock_data.js5
-rw-r--r--spec/javascripts/ci_variable_list/ajax_variable_list_spec.js2
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js2
-rw-r--r--spec/javascripts/collapsed_sidebar_todo_spec.js16
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js)6
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js)6
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js)6
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/helpers.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/helpers.js)0
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/mock_data.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/mock_data.js)0
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js)4
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/stores/getters_spec.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/stores/getters_spec.js)2
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/stores/mutations_spec.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/stores/mutations_spec.js)4
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_expansion_cell_spec.js64
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js8
-rw-r--r--spec/javascripts/diffs/components/diff_file_spec.js20
-rw-r--r--spec/javascripts/diffs/components/diff_gutter_avatars_spec.js146
-rw-r--r--spec/javascripts/diffs/components/diff_line_gutter_content_spec.js15
-rw-r--r--spec/javascripts/diffs/components/diff_line_note_form_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_table_cell_spec.js4
-rw-r--r--spec/javascripts/diffs/components/inline_diff_expansion_row_spec.js31
-rw-r--r--spec/javascripts/diffs/components/inline_diff_table_row_spec.js4
-rw-r--r--spec/javascripts/diffs/components/inline_diff_view_spec.js12
-rw-r--r--spec/javascripts/diffs/components/parallel_diff_expansion_row_spec.js31
-rw-r--r--spec/javascripts/diffs/components/parallel_diff_view_spec.js4
-rw-r--r--spec/javascripts/diffs/mock_data/diff_file.js2
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js7
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js5
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js11
-rw-r--r--spec/javascripts/environments/environment_terminal_button_spec.js40
-rw-r--r--spec/javascripts/fixtures/.gitignore7
-rw-r--r--spec/javascripts/fixtures/static/README.md3
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper.js24
-rw-r--r--spec/javascripts/helpers/vuex_action_helper.js4
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/actions_spec.js188
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js160
-rw-r--r--spec/javascripts/ide/components/repo_editor_spec.js105
-rw-r--r--spec/javascripts/ide/mock_data.js34
-rw-r--r--spec/javascripts/ide/stores/actions_spec.js43
-rw-r--r--spec/javascripts/ide/stores/getters_spec.js32
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js181
-rw-r--r--spec/javascripts/ide/stores/modules/commit/getters_spec.js162
-rw-r--r--spec/javascripts/ide/stores/mutations/file_spec.js75
-rw-r--r--spec/javascripts/ide/stores/mutations_spec.js68
-rw-r--r--spec/javascripts/ide/stores/utils_spec.js61
-rw-r--r--spec/javascripts/issuable_spec.js12
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js2
-rw-r--r--spec/javascripts/issue_show/components/form_spec.js111
-rw-r--r--spec/javascripts/jobs/components/empty_state_spec.js (renamed from spec/frontend/jobs/components/empty_state_spec.js)48
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js684
-rw-r--r--spec/javascripts/jobs/components/job_log_spec.js24
-rw-r--r--spec/javascripts/jobs/components/manual_variables_form_spec.js88
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js39
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js15
-rw-r--r--spec/javascripts/monitoring/charts/empty_chart_spec.js29
-rw-r--r--spec/javascripts/monitoring/charts/single_stat_spec.js9
-rw-r--r--spec/javascripts/monitoring/charts/time_series_spec.js335
-rw-r--r--spec/javascripts/monitoring/components/dashboard_spec.js (renamed from spec/javascripts/monitoring/dashboard_spec.js)127
-rw-r--r--spec/javascripts/monitoring/dashboard_state_spec.js101
-rw-r--r--spec/javascripts/monitoring/mock_data.js76
-rw-r--r--spec/javascripts/monitoring/panel_type_spec.js79
-rw-r--r--spec/javascripts/monitoring/store/actions_spec.js4
-rw-r--r--spec/javascripts/monitoring/store/mutations_spec.js2
-rw-r--r--spec/javascripts/monitoring/store/utils_spec.js37
-rw-r--r--spec/javascripts/monitoring/utils_spec.js55
-rw-r--r--spec/javascripts/notes/components/comment_form_spec.js15
-rw-r--r--spec/javascripts/notes/components/note_actions/reply_button_spec.js5
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js52
-rw-r--r--spec/javascripts/notes/mock_data.js2
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js116
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js114
-rw-r--r--spec/javascripts/pdf/page_spec.js5
-rw-r--r--spec/javascripts/performance_bar/components/detailed_metric_spec.js27
-rw-r--r--spec/javascripts/performance_bar/components/simple_metric_spec.js47
-rw-r--r--spec/javascripts/persistent_user_callout_spec.js87
-rw-r--r--spec/javascripts/pipelines/pipelines_actions_spec.js42
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js8
-rw-r--r--spec/javascripts/registry/components/app_spec.js44
-rw-r--r--spec/javascripts/registry/components/collapsible_container_spec.js18
-rw-r--r--spec/javascripts/registry/components/table_registry_spec.js180
-rw-r--r--spec/javascripts/registry/mock_data.js11
-rw-r--r--spec/javascripts/releases/components/release_block_spec.js12
-rw-r--r--spec/javascripts/reports/components/grouped_test_reports_app_spec.js12
-rw-r--r--spec/javascripts/sidebar/assignee_title_spec.js14
-rw-r--r--spec/javascripts/sidebar/assignees_spec.js206
-rw-r--r--spec/javascripts/sidebar/confidential_issue_sidebar_spec.js8
-rw-r--r--spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js8
-rw-r--r--spec/javascripts/sidebar/subscriptions_spec.js9
-rw-r--r--spec/javascripts/sidebar/todo_spec.js8
-rw-r--r--spec/javascripts/test_bundle.js7
-rw-r--r--spec/javascripts/test_constants.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js18
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js27
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js8
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js2
-rw-r--r--spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js42
-rw-r--r--spec/javascripts/vue_shared/components/changed_file_icon_spec.js63
-rw-r--r--spec/javascripts/vue_shared/components/file_icon_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/file_row_spec.js13
-rw-r--r--spec/javascripts/vue_shared/components/header_ci_component_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js2
-rw-r--r--spec/javascripts/vue_shared/directives/autofocusonshow_spec.js38
-rw-r--r--spec/lib/after_commit_queue_spec.rb2
-rw-r--r--spec/lib/api/api_spec.rb2
-rw-r--r--spec/lib/api/helpers/custom_validators_spec.rb2
-rw-r--r--spec/lib/api/helpers/label_helpers_spec.rb33
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb12
-rw-r--r--spec/lib/api/helpers/related_resources_helpers_spec.rb2
-rw-r--r--spec/lib/api/helpers/version_spec.rb2
-rw-r--r--spec/lib/api/helpers_spec.rb2
-rw-r--r--spec/lib/backup/files_spec.rb2
-rw-r--r--spec/lib/backup/manager_spec.rb2
-rw-r--r--spec/lib/backup/repository_spec.rb2
-rw-r--r--spec/lib/backup/uploads_spec.rb2
-rw-r--r--spec/lib/banzai/color_parser_spec.rb2
-rw-r--r--spec/lib/banzai/commit_renderer_spec.rb2
-rw-r--r--spec/lib/banzai/cross_project_reference_spec.rb2
-rw-r--r--spec/lib/banzai/filter/absolute_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/abstract_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/autolink_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/blockquote_fence_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/color_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb13
-rw-r--r--spec/lib/banzai/filter/commit_trailers_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/emoji_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/front_matter_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/gollum_tags_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/html_entity_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/image_lazy_load_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_diff_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_metrics_filter_spec.rb70
-rw-r--r--spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb48
-rw-r--r--spec/lib/banzai/filter/issuable_state_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/markdown_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/math_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/mermaid_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/plantuml_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/reference_redactor_filter_spec.rb (renamed from spec/lib/banzai/filter/redactor_filter_spec.rb)4
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb7
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/snippet_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/spaced_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/table_of_contents_filter_spec.rb12
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/video_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb44
-rw-r--r--spec/lib/banzai/filter_array_spec.rb2
-rw-r--r--spec/lib/banzai/issuable_extractor_spec.rb2
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb10
-rw-r--r--spec/lib/banzai/pipeline/description_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/email_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/full_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb8
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb81
-rw-r--r--spec/lib/banzai/querying_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/commit_range_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/external_issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/label_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/merge_request_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/milestone_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/snippet_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_redactor_spec.rb (renamed from spec/lib/banzai/redactor_spec.rb)4
-rw-r--r--spec/lib/banzai/renderer_spec.rb20
-rw-r--r--spec/lib/bitbucket/collection_spec.rb2
-rw-r--r--spec/lib/bitbucket/connection_spec.rb2
-rw-r--r--spec/lib/bitbucket/page_spec.rb2
-rw-r--r--spec/lib/bitbucket/paginator_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/comment_spec.rb4
-rw-r--r--spec/lib/bitbucket/representation/issue_spec.rb4
-rw-r--r--spec/lib/bitbucket/representation/pull_request_comment_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/pull_request_spec.rb4
-rw-r--r--spec/lib/bitbucket/representation/repo_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/user_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/client_spec.rb13
-rw-r--r--spec/lib/bitbucket_server/connection_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/page_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/paginator_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/activity_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/comment_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/pull_request_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/repo_spec.rb2
-rw-r--r--spec/lib/constraints/feature_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/group_url_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/project_url_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/user_url_constrainer_spec.rb2
-rw-r--r--spec/lib/container_registry/blob_spec.rb21
-rw-r--r--spec/lib/container_registry/client_spec.rb38
-rw-r--r--spec/lib/container_registry/path_spec.rb2
-rw-r--r--spec/lib/container_registry/registry_spec.rb2
-rw-r--r--spec/lib/container_registry/tag_spec.rb4
-rw-r--r--spec/lib/event_filter_spec.rb2
-rw-r--r--spec/lib/expand_variables_spec.rb177
-rw-r--r--spec/lib/extracts_path_spec.rb2
-rw-r--r--spec/lib/feature/gitaly_spec.rb2
-rw-r--r--spec/lib/feature_spec.rb82
-rw-r--r--spec/lib/file_size_validator_spec.rb2
-rw-r--r--spec/lib/forever_spec.rb18
-rw-r--r--spec/lib/gitaly/server_spec.rb2
-rw-r--r--spec/lib/gitlab/action_rate_limiter_spec.rb103
-rw-r--r--spec/lib/gitlab/allowable_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb10
-rw-r--r--spec/lib/gitlab/app_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb288
-rw-r--r--spec/lib/gitlab/auth/activity_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/blocked_user_tracker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/access_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/adapter_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/authentication_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/config_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/dn_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb21
-rw-r--r--spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/provider_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb24
-rw-r--r--spec/lib/gitlab/auth/request_authenticator_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/auth_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/identity_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/unique_ips_limiter_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/user_access_denied_reason_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/user_auth_finders_spec.rb16
-rw-r--r--spec/lib/gitlab/auth_spec.rb79
-rw-r--r--spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb125
-rw-r--r--spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb35
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_columns_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb104
-rw-r--r--spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb98
-rw-r--r--spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb296
-rw-r--r--spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb63
-rw-r--r--spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb433
-rw-r--r--spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb23
-rw-r--r--spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb92
-rw-r--r--spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb21
-rw-r--r--spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb74
-rw-r--r--spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb36
-rw-r--r--spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb97
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb26
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id_spec.rb62
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb4
-rw-r--r--spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/report_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/template_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/status_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/template_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/shared/metadata.rb2
-rw-r--r--spec/lib/gitlab/bare_repository_import/importer_spec.rb18
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/batch_pop_queueing_spec.rb147
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb43
-rw-r--r--spec/lib/gitlab/bitbucket_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_server_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/blame_spec.rb2
-rw-r--r--spec/lib/gitlab/build_access_spec.rb2
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb2
-rw-r--r--spec/lib/gitlab/cache/request_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/changes_list_spec.rb2
-rw-r--r--spec/lib/gitlab/chat_name_token_spec.rb2
-rw-r--r--spec/lib/gitlab/chat_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/change_access_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/force_push_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/lfs_integrity_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/project_created_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/project_moved_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2html_spec.rb51
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/path_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/credentials/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/credentials/registry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/image_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/changes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/refs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/variables_spec.rb49
-rw-r--r--spec/lib/gitlab/ci/build/policy_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb83
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule_spec.rb50
-rw-r--r--spec/lib/gitlab/ci/build/rules_spec.rb168
-rw-r--r--spec/lib/gitlab/ci/build/step_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/charts_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/commands_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/coverage_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/default_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/environment_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/hidden_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb198
-rw-r--r--spec/lib/gitlab/ci/config/entry/jobs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/key_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/paths_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/retry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb208
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules_spec.rb135
-rw-r--r--spec/lib/gitlab/ci/config/entry/script_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/services_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/stage_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/stages_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/extendable/entry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/extendable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/normalizer_spec.rb59
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/cron_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/mask_secret_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/test/junit_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/create_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb46
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/duration_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb38
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/token_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb632
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/reports/test_case_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/reports/test_reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/action_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/cancelable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/canceled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/created_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/erased_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/pending_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/play_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/retried_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/retryable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/scheduled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/skipped_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/stop_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/unschedule_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/canceled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/created_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/extended_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/external/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/external/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/failed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/group/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/group/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pending_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/running_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/scheduled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/skipped_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/stage/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/stage/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/success_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/success_warning_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/chunked_io_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/trace/section_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb220
-rw-r--r--spec/lib/gitlab/ci_access_spec.rb2
-rw-r--r--spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb66
-rw-r--r--spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb68
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb4
-rw-r--r--spec/lib/gitlab/color_schemes_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/attributable_spec.rb5
-rw-r--r--spec/lib/gitlab/config/entry/boolean_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/configurable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/simplifiable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/undefined_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/unspecified_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/validatable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/validator_spec.rb2
-rw-r--r--spec/lib/gitlab/config/loader/yaml_spec.rb74
-rw-r--r--spec/lib/gitlab/conflict/file_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb2
-rw-r--r--spec/lib/gitlab/content_security_policy/config_loader_spec.rb61
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/check_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/check_info_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/class_methods_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access_spec.rb2
-rw-r--r--spec/lib/gitlab/crypto_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb16
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb8
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_stage_spec.rb100
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb4
-rw-r--r--spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb114
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb98
-rw-r--r--spec/lib/gitlab/cycle_analytics/permissions_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb80
-rw-r--r--spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/production_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_stage_spec.rb70
-rw-r--r--spec/lib/gitlab/cycle_analytics/shared_event_spec.rb4
-rw-r--r--spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb6
-rw-r--r--spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb72
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_stage_spec.rb36
-rw-r--r--spec/lib/gitlab/cycle_analytics/updater_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/usage_data_spec.rb82
-rw-r--r--spec/lib/gitlab/daemon_spec.rb32
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb59
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb10
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/note_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb36
-rw-r--r--spec/lib/gitlab/data_builder/wiki_page_spec.rb2
-rw-r--r--spec/lib/gitlab/database/count/exact_count_strategy_spec.rb16
-rw-r--r--spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb18
-rw-r--r--spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb22
-rw-r--r--spec/lib/gitlab/database/count_spec.rb17
-rw-r--r--spec/lib/gitlab/database/grant_spec.rb4
-rw-r--r--spec/lib/gitlab/database/median_spec.rb17
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb605
-rw-r--r--spec/lib/gitlab/database/multi_threaded_migration_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb2
-rw-r--r--spec/lib/gitlab/database/sha_attribute_spec.rb8
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb (renamed from spec/db/importers/common_metrics_importer_spec.rb)25
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb16
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb278
-rw-r--r--spec/lib/gitlab/database_spec.rb214
-rw-r--r--spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/diff_refs_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/formatters/image_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/formatters/text_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/line_mapper_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/line_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/lines_unfolder_spec.rb33
-rw-r--r--spec/lib/gitlab/diff/parallel_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb15
-rw-r--r--spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb238
-rw-r--r--spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb1805
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb1920
-rw-r--r--spec/lib/gitlab/downtime_check/message_spec.rb2
-rw-r--r--spec/lib/gitlab/downtime_check_spec.rb2
-rw-r--r--spec/lib/gitlab/email/attachment_uploader_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb1
-rw-r--r--spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb2
-rw-r--r--spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb2
-rw-r--r--spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb5
-rw-r--r--spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb52
-rw-r--r--spec/lib/gitlab/email/message/repository_push_spec.rb2
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb2
-rw-r--r--spec/lib/gitlab/email/reply_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/email/smime/certificate_spec.rb77
-rw-r--r--spec/lib/gitlab/email/smime/signer_spec.rb26
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb14
-rw-r--r--spec/lib/gitlab/etag_caching/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/etag_caching/router_spec.rb2
-rw-r--r--spec/lib/gitlab/exclusive_lease_helpers_spec.rb17
-rw-r--r--spec/lib/gitlab/fogbugz_import/project_creator_spec.rb29
-rw-r--r--spec/lib/gitlab/gfm/uploads_rewriter_spec.rb11
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb29
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb121
-rw-r--r--spec/lib/gitlab/git_post_receive_spec.rb138
-rw-r--r--spec/lib/gitlab/git_spec.rb20
-rw-r--r--spec/lib/gitlab/gitaly_client/notification_service_spec.rb17
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb4
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb44
-rw-r--r--spec/lib/gitlab/github_import/importer/releases_importer_spec.rb7
-rw-r--r--spec/lib/gitlab/global_id_spec.rb37
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb89
-rw-r--r--spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb23
-rw-r--r--spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb44
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb18
-rw-r--r--spec/lib/gitlab/graphql/markdown_field_spec.rb7
-rw-r--r--spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb30
-rw-r--r--spec/lib/gitlab/group_search_results_spec.rb6
-rw-r--r--spec/lib/gitlab/http_connection_adapter_spec.rb6
-rw-r--r--spec/lib/gitlab/http_spec.rb6
-rw-r--r--spec/lib/gitlab/import/database_helpers_spec.rb31
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml17
-rw-r--r--spec/lib/gitlab/import_export/attribute_cleaner_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/lfs_restorer_spec.rb98
-rw-r--r--spec/lib/gitlab/import_export/lfs_saver_spec.rb49
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/project.group.json6
-rw-r--r--spec/lib/gitlab/import_export/project.json87
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb54
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb18
-rw-r--r--spec/lib/gitlab/import_export/relation_rename_service_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml27
-rw-r--r--spec/lib/gitlab/issuable_metadata_spec.rb8
-rw-r--r--spec/lib/gitlab/kubernetes/default_namespace_spec.rb85
-rw-r--r--spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb16
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb12
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb65
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb5
-rw-r--r--spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb48
-rw-r--r--spec/lib/gitlab/kubernetes/role_binding_spec.rb4
-rw-r--r--spec/lib/gitlab/kubernetes/role_spec.rb30
-rw-r--r--spec/lib/gitlab/legacy_github_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb6
-rw-r--r--spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/lets_encrypt_spec.rb31
-rw-r--r--spec/lib/gitlab/loop_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/manifest_import/manifest_spec.rb4
-rw-r--r--spec/lib/gitlab/manifest_import/project_creator_spec.rb4
-rw-r--r--spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb7
-rw-r--r--spec/lib/gitlab/markup_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/background_transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/dashboard/defaults_spec.rb8
-rw-r--r--spec/lib/gitlab/metrics/dashboard/finder_spec.rb76
-rw-r--r--spec/lib/gitlab/metrics/dashboard/processor_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb80
-rw-r--r--spec/lib/gitlab/metrics/dashboard/url_spec.rb64
-rw-r--r--spec/lib/gitlab/metrics/delta_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/method_call_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/methods_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/metric_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/prometheus_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb22
-rw-r--r--spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb38
-rw-r--r--spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb35
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/subscribers/action_view_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb12
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb18
-rw-r--r--spec/lib/gitlab/metrics/web_transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/basic_health_check_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/multipart_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/rails_queue_duration_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/release_env_spec.rb2
-rw-r--r--spec/lib/gitlab/multi_collection_paginator_spec.rb2
-rw-r--r--spec/lib/gitlab/namespaced_session_store_spec.rb30
-rw-r--r--spec/lib/gitlab/object_hierarchy_spec.rb4
-rw-r--r--spec/lib/gitlab/octokit/middleware_spec.rb70
-rw-r--r--spec/lib/gitlab/omniauth_initializer_spec.rb30
-rw-r--r--spec/lib/gitlab/optimistic_locking_spec.rb2
-rw-r--r--spec/lib/gitlab/other_markup_spec.rb2
-rw-r--r--spec/lib/gitlab/otp_key_rotator_spec.rb2
-rw-r--r--spec/lib/gitlab/pages_client_spec.rb2
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb2
-rw-r--r--spec/lib/gitlab/performance_bar_spec.rb33
-rw-r--r--spec/lib/gitlab/phabricator_import/cache/map_spec.rb17
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/user_spec.rb49
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb21
-rw-r--r--spec/lib/gitlab/phabricator_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/importer_spec.rb32
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb36
-rw-r--r--spec/lib/gitlab/phabricator_import/representation/task_spec.rb16
-rw-r--r--spec/lib/gitlab/phabricator_import/representation/user_spec.rb28
-rw-r--r--spec/lib/gitlab/phabricator_import/user_finder_spec.rb91
-rw-r--r--spec/lib/gitlab/phabricator_import/worker_state_spec.rb2
-rw-r--r--spec/lib/gitlab/plugin_spec.rb2
-rw-r--r--spec/lib/gitlab/polling_interval_spec.rb2
-rw-r--r--spec/lib/gitlab/popen/runner_spec.rb2
-rw-r--r--spec/lib/gitlab/popen_spec.rb2
-rw-r--r--spec/lib/gitlab/profiler_spec.rb2
-rw-r--r--spec/lib/gitlab/project_authorizations_spec.rb40
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb24
-rw-r--r--spec/lib/gitlab/project_template_spec.rb20
-rw-r--r--spec/lib/gitlab/project_transfer_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/metric_group_spec.rb4
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/query_variables_spec.rb6
-rw-r--r--spec/lib/gitlab/prometheus_client_spec.rb49
-rw-r--r--spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting/transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/command_definition_spec.rb48
-rw-r--r--spec/lib/gitlab/quick_actions/dsl_spec.rb16
-rw-r--r--spec/lib/gitlab/quick_actions/extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/substitution_definition_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/queues_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/shared_state_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/wrapper_spec.rb2
-rw-r--r--spec/lib/gitlab/reference_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/regex_spec.rb10
-rw-r--r--spec/lib/gitlab/repo_path_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb9
-rw-r--r--spec/lib/gitlab/repository_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb75
-rw-r--r--spec/lib/gitlab/request_context_spec.rb2
-rw-r--r--spec/lib/gitlab/request_forgery_protection_spec.rb2
-rw-r--r--spec/lib/gitlab/request_profiler/profile_spec.rb61
-rw-r--r--spec/lib/gitlab/request_profiler_spec.rb43
-rw-r--r--spec/lib/gitlab/route_map_spec.rb2
-rw-r--r--spec/lib/gitlab/routing_spec.rb2
-rw-r--r--spec/lib/gitlab/rugged_instrumentation_spec.rb27
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb2
-rw-r--r--spec/lib/gitlab/sanitizers/svg_spec.rb2
-rw-r--r--spec/lib/gitlab/search/found_blob_spec.rb3
-rw-r--r--spec/lib/gitlab/search/query_spec.rb2
-rw-r--r--spec/lib/gitlab/search_results_spec.rb39
-rw-r--r--spec/lib/gitlab/sentry_spec.rb31
-rw-r--r--spec/lib/gitlab/serializer/ci/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/serializer/pagination_spec.rb2
-rw-r--r--spec/lib/gitlab/shard_health_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/shell_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/collection_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/file_sample_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/line_profiler_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/line_sample_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/location_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/query_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_config_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb107
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb10
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb57
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb49
-rw-r--r--spec/lib/gitlab/sidekiq_monitor_spec.rb261
-rw-r--r--spec/lib/gitlab/sidekiq_signals_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_versioning/manager_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_versioning_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/command_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/deploy_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_move_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_new_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_search_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_show_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/access_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb2
-rw-r--r--spec/lib/gitlab/slug/environment_spec.rb38
-rw-r--r--spec/lib/gitlab/snippet_search_results_spec.rb20
-rw-r--r--spec/lib/gitlab/snowplow_tracker_spec.rb45
-rw-r--r--spec/lib/gitlab/sql/cte_spec.rb4
-rw-r--r--spec/lib/gitlab/sql/glob_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/pattern_spec.rb14
-rw-r--r--spec/lib/gitlab/sql/recursive_cte_spec.rb4
-rw-r--r--spec/lib/gitlab/sql/union_spec.rb2
-rw-r--r--spec/lib/gitlab/ssh_public_key_spec.rb2
-rw-r--r--spec/lib/gitlab/string_placeholder_replacer_spec.rb2
-rw-r--r--spec/lib/gitlab/string_range_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/string_regex_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/submodule_links_spec.rb47
-rw-r--r--spec/lib/gitlab/tcp_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/template/finders/global_template_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/template/finders/repo_template_finders_spec.rb2
-rw-r--r--spec/lib/gitlab/template/gitignore_template_spec.rb2
-rw-r--r--spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb2
-rw-r--r--spec/lib/gitlab/template/issue_template_spec.rb2
-rw-r--r--spec/lib/gitlab/template/merge_request_template_spec.rb2
-rw-r--r--spec/lib/gitlab/themes_spec.rb2
-rw-r--r--spec/lib/gitlab/tree_summary_spec.rb2
-rw-r--r--spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb2
-rw-r--r--spec/lib/gitlab/untrusted_regexp_spec.rb2
-rw-r--r--spec/lib/gitlab/uploads_transfer_spec.rb2
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb260
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb9
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb9
-rw-r--r--spec/lib/gitlab/usage_data_counters/note_counter_spec.rb88
-rw-r--r--spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb33
-rw-r--r--spec/lib/gitlab/usage_data_counters/search_counter_spec.rb13
-rw-r--r--spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb12
-rw-r--r--spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb9
-rw-r--r--spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb56
-rw-r--r--spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb14
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb65
-rw-r--r--spec/lib/gitlab/user_access_spec.rb2
-rw-r--r--spec/lib/gitlab/user_extractor_spec.rb78
-rw-r--r--spec/lib/gitlab/utils/deep_size_spec.rb45
-rw-r--r--spec/lib/gitlab/utils/merge_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/override_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/sanitize_node_link_spec.rb74
-rw-r--r--spec/lib/gitlab/utils/strong_memoize_spec.rb2
-rw-r--r--spec/lib/gitlab/utils_spec.rb21
-rw-r--r--spec/lib/gitlab/verify/job_artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/verify/lfs_objects_spec.rb2
-rw-r--r--spec/lib/gitlab/verify/uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/version_info_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/base_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/delegated_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/simple_spec.rb2
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb2
-rw-r--r--spec/lib/gitlab/web_ide_commits_counter_spec.rb19
-rw-r--r--spec/lib/gitlab/wiki_file_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb3
-rw-r--r--spec/lib/gitlab/zoom_link_extractor_spec.rb34
-rw-r--r--spec/lib/gitlab_spec.rb8
-rw-r--r--spec/lib/google_api/auth_spec.rb2
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb2
-rw-r--r--spec/lib/json_web_token/rsa_token_spec.rb2
-rw-r--r--spec/lib/json_web_token/token_spec.rb2
-rw-r--r--spec/lib/mattermost/client_spec.rb2
-rw-r--r--spec/lib/mattermost/command_spec.rb2
-rw-r--r--spec/lib/mattermost/session_spec.rb2
-rw-r--r--spec/lib/mattermost/team_spec.rb2
-rw-r--r--spec/lib/microsoft_teams/activity_spec.rb2
-rw-r--r--spec/lib/microsoft_teams/notifier_spec.rb2
-rw-r--r--spec/lib/milestone_array_spec.rb2
-rw-r--r--spec/lib/object_storage/direct_upload_spec.rb2
-rw-r--r--spec/lib/omni_auth/strategies/jwt_spec.rb2
-rw-r--r--spec/lib/peek/views/redis_detailed_spec.rb39
-rw-r--r--spec/lib/peek/views/rugged_spec.rb42
-rw-r--r--spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb51
-rw-r--r--spec/lib/prometheus/pid_provider_spec.rb117
-rw-r--r--spec/lib/quality/test_level_spec.rb4
-rw-r--r--spec/lib/rspec_flaky/config_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/example_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/flaky_example_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/flaky_examples_collection_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/listener_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/report_spec.rb2
-rw-r--r--spec/lib/safe_zip/entry_spec.rb2
-rw-r--r--spec/lib/safe_zip/extract_params_spec.rb2
-rw-r--r--spec/lib/safe_zip/extract_spec.rb2
-rw-r--r--spec/lib/sentry/client_spec.rb2
-rw-r--r--spec/lib/serializers/json_spec.rb86
-rw-r--r--spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb2
-rw-r--r--spec/lib/system_check/base_check_spec.rb2
-rw-r--r--spec/lib/system_check/orphans/namespace_check_spec.rb2
-rw-r--r--spec/lib/system_check/orphans/repository_check_spec.rb2
-rw-r--r--spec/lib/system_check/simple_executor_spec.rb2
-rw-r--r--spec/lib/system_check_spec.rb2
-rw-r--r--spec/lib/uploaded_file_spec.rb2
-rw-r--r--spec/mailers/emails/pages_domains_spec.rb6
-rw-r--r--spec/mailers/notify_spec.rb102
-rw-r--r--spec/migrations/active_record/schema_spec.rb2
-rw-r--r--spec/migrations/add_foreign_key_to_merge_requests_spec.rb39
-rw-r--r--spec/migrations/add_gitlab_instance_administration_project_spec.rb252
-rw-r--r--spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb32
-rw-r--r--spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb28
-rw-r--r--spec/migrations/backfill_store_project_full_path_in_repo_spec.rb2
-rw-r--r--spec/migrations/calculate_conv_dev_index_percentages_spec.rb59
-rw-r--r--spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb21
-rw-r--r--spec/migrations/clean_appearance_symlinks_spec.rb46
-rw-r--r--spec/migrations/clean_stage_id_reference_migration_spec.rb34
-rw-r--r--spec/migrations/clean_stages_statuses_migration_spec.rb51
-rw-r--r--spec/migrations/clean_upload_symlinks_spec.rb46
-rw-r--r--spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb35
-rw-r--r--spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb29
-rw-r--r--spec/migrations/convert_custom_notification_settings_to_columns_spec.rb120
-rw-r--r--spec/migrations/delete_conflicting_redirect_routes_spec.rb42
-rw-r--r--spec/migrations/encrypt_deploy_tokens_tokens_spec.rb47
-rw-r--r--spec/migrations/fix_wrong_pages_access_level_spec.rb97
-rw-r--r--spec/migrations/fix_wrongly_renamed_routes_spec.rb86
-rw-r--r--spec/migrations/issues_moved_to_id_foreign_key_spec.rb24
-rw-r--r--spec/migrations/migrate_build_stage_reference_again_spec.rb62
-rw-r--r--spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb181
-rw-r--r--spec/migrations/migrate_issues_to_ghost_user_spec.rb51
-rw-r--r--spec/migrations/migrate_kubernetes_service_to_new_clusters_architectures_spec.rb312
-rw-r--r--spec/migrations/migrate_old_artifacts_spec.rb140
-rw-r--r--spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb49
-rw-r--r--spec/migrations/migrate_pipeline_stages_spec.rb56
-rw-r--r--spec/migrations/migrate_process_commit_worker_jobs_spec.rb197
-rw-r--r--spec/migrations/migrate_stage_id_reference_in_background_spec.rb55
-rw-r--r--spec/migrations/migrate_stages_statuses_spec.rb68
-rw-r--r--spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb49
-rw-r--r--spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb25
-rw-r--r--spec/migrations/migrate_user_project_view_spec.rb17
-rw-r--r--spec/migrations/move_personal_snippets_files_spec.rb197
-rw-r--r--spec/migrations/move_system_upload_folder_spec.rb80
-rw-r--r--spec/migrations/move_uploads_to_system_dir_spec.rb68
-rw-r--r--spec/migrations/normalize_ldap_extern_uids_spec.rb56
-rw-r--r--spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb43
-rw-r--r--spec/migrations/remove_assignee_id_from_issue_spec.rb37
-rw-r--r--spec/migrations/remove_dot_git_from_usernames_spec.rb58
-rw-r--r--spec/migrations/remove_duplicate_mr_events_spec.rb26
-rw-r--r--spec/migrations/remove_empty_fork_networks_spec.rb35
-rw-r--r--spec/migrations/remove_project_labels_group_id_spec.rb21
-rw-r--r--spec/migrations/rename_duplicated_variable_key_spec.rb34
-rw-r--r--spec/migrations/rename_more_reserved_project_names_spec.rb57
-rw-r--r--spec/migrations/rename_reserved_project_names_spec.rb61
-rw-r--r--spec/migrations/rename_users_with_renamed_namespace_spec.rb22
-rw-r--r--spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb64
-rw-r--r--spec/migrations/set_issue_id_for_all_versions_spec.rb38
-rw-r--r--spec/migrations/track_untracked_uploads_spec.rb15
-rw-r--r--spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb70
-rw-r--r--spec/migrations/update_legacy_diff_notes_type_for_import_spec.rb22
-rw-r--r--spec/migrations/update_notes_type_for_import_spec.rb22
-rw-r--r--spec/migrations/update_retried_for_ci_build_spec.rb17
-rw-r--r--spec/migrations/update_upload_paths_to_system_spec.rb59
-rw-r--r--spec/models/active_session_spec.rb15
-rw-r--r--spec/models/analytics/cycle_analytics/project_stage_spec.rb23
-rw-r--r--spec/models/application_setting_spec.rb11
-rw-r--r--spec/models/award_emoji_spec.rb23
-rw-r--r--spec/models/ci/bridge_spec.rb2
-rw-r--r--spec/models/ci/build_need_spec.rb13
-rw-r--r--spec/models/ci/build_spec.rb217
-rw-r--r--spec/models/ci/job_artifact_spec.rb25
-rw-r--r--spec/models/ci/job_variable_spec.rb12
-rw-r--r--spec/models/ci/pipeline_spec.rb53
-rw-r--r--spec/models/ci/runner_spec.rb13
-rw-r--r--spec/models/ci/trigger_spec.rb20
-rw-r--r--spec/models/ci/variable_spec.rb1
-rw-r--r--spec/models/clusters/applications/cert_manager_spec.rb52
-rw-r--r--spec/models/clusters/applications/helm_spec.rb67
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb16
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb2
-rw-r--r--spec/models/clusters/applications/knative_spec.rb46
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb17
-rw-r--r--spec/models/clusters/applications/runner_spec.rb33
-rw-r--r--spec/models/clusters/cluster_spec.rb125
-rw-r--r--spec/models/clusters/clusters_hierarchy_spec.rb73
-rw-r--r--spec/models/clusters/kubernetes_namespace_spec.rb84
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb213
-rw-r--r--spec/models/commit_range_spec.rb12
-rw-r--r--spec/models/commit_spec.rb1
-rw-r--r--spec/models/concerns/awardable_spec.rb10
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb30
-rw-r--r--spec/models/concerns/cacheable_attributes_spec.rb54
-rw-r--r--spec/models/concerns/case_sensitivity_spec.rb27
-rw-r--r--spec/models/concerns/deployable_spec.rb14
-rw-r--r--spec/models/concerns/deployment_platform_spec.rb12
-rw-r--r--spec/models/concerns/group_descendant_spec.rb6
-rw-r--r--spec/models/concerns/has_environment_scope_spec.rb66
-rw-r--r--spec/models/concerns/issuable_spec.rb33
-rw-r--r--spec/models/concerns/project_api_compatibility_spec.rb59
-rw-r--r--spec/models/concerns/project_features_compatibility_spec.rb18
-rw-r--r--spec/models/concerns/prometheus_adapter_spec.rb4
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb61
-rw-r--r--spec/models/concerns/relative_positioning_spec.rb242
-rw-r--r--spec/models/concerns/routable_spec.rb47
-rw-r--r--spec/models/concerns/stepable_spec.rb111
-rw-r--r--spec/models/container_repository_spec.rb2
-rw-r--r--spec/models/cycle_analytics/code_spec.rb7
-rw-r--r--spec/models/cycle_analytics/group_level_spec.rb46
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb7
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb7
-rw-r--r--spec/models/cycle_analytics/production_spec.rb7
-rw-r--r--spec/models/cycle_analytics/project_level_spec.rb (renamed from spec/models/cycle_analytics_spec.rb)10
-rw-r--r--spec/models/cycle_analytics/review_spec.rb5
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb6
-rw-r--r--spec/models/cycle_analytics/test_spec.rb11
-rw-r--r--spec/models/deploy_token_spec.rb35
-rw-r--r--spec/models/deployment_metrics_spec.rb114
-rw-r--r--spec/models/deployment_spec.rb99
-rw-r--r--spec/models/diff_note_spec.rb4
-rw-r--r--spec/models/discussion_spec.rb14
-rw-r--r--spec/models/environment_spec.rb114
-rw-r--r--spec/models/environment_status_spec.rb3
-rw-r--r--spec/models/group_spec.rb73
-rw-r--r--spec/models/issue/metrics_spec.rb6
-rw-r--r--spec/models/issue_spec.rb8
-rw-r--r--spec/models/label_spec.rb13
-rw-r--r--spec/models/lfs_download_object_spec.rb2
-rw-r--r--spec/models/member_spec.rb13
-rw-r--r--spec/models/members/group_member_spec.rb38
-rw-r--r--spec/models/members/project_member_spec.rb2
-rw-r--r--spec/models/merge_request_diff_spec.rb8
-rw-r--r--spec/models/merge_request_spec.rb139
-rw-r--r--spec/models/namespace/aggregation_schedule_spec.rb68
-rw-r--r--spec/models/namespace/root_storage_statistics_spec.rb78
-rw-r--r--spec/models/namespace_spec.rb96
-rw-r--r--spec/models/note_spec.rb18
-rw-r--r--spec/models/notification_recipient_spec.rb34
-rw-r--r--spec/models/pages_domain_spec.rb53
-rw-r--r--spec/models/postgresql/replication_slot_spec.rb2
-rw-r--r--spec/models/project_auto_devops_spec.rb2
-rw-r--r--spec/models/project_feature_spec.rb34
-rw-r--r--spec/models/project_group_link_spec.rb2
-rw-r--r--spec/models/project_services/bugzilla_service_spec.rb47
-rw-r--r--spec/models/project_services/chat_message/pipeline_message_spec.rb484
-rw-r--r--spec/models/project_services/custom_issue_tracker_service_spec.rb45
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb9
-rw-r--r--spec/models/project_services/emails_on_push_service_spec.rb20
-rw-r--r--spec/models/project_services/gitlab_issue_tracker_service_spec.rb45
-rw-r--r--spec/models/project_services/jira_service_spec.rb164
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb396
-rw-r--r--spec/models/project_services/microsoft_teams_service_spec.rb3
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb4
-rw-r--r--spec/models/project_services/redmine_service_spec.rb45
-rw-r--r--spec/models/project_services/youtrack_service_spec.rb45
-rw-r--r--spec/models/project_spec.rb467
-rw-r--r--spec/models/project_statistics_spec.rb32
-rw-r--r--spec/models/prometheus_metric_spec.rb13
-rw-r--r--spec/models/release_spec.rb10
-rw-r--r--spec/models/remote_mirror_spec.rb19
-rw-r--r--spec/models/repository_spec.rb151
-rw-r--r--spec/models/service_spec.rb9
-rw-r--r--spec/models/todo_spec.rb6
-rw-r--r--spec/models/user_spec.rb145
-rw-r--r--spec/models/wiki_page_spec.rb3
-rw-r--r--spec/policies/ci/trigger_policy_spec.rb94
-rw-r--r--spec/policies/clusters/instance_policy_spec.rb4
-rw-r--r--spec/policies/global_policy_spec.rb28
-rw-r--r--spec/policies/group_member_policy_spec.rb2
-rw-r--r--spec/policies/group_policy_spec.rb142
-rw-r--r--spec/policies/namespace/root_storage_statistics_policy_spec.rb80
-rw-r--r--spec/policies/namespace_policy_spec.rb2
-rw-r--r--spec/policies/project_policy_spec.rb133
-rw-r--r--spec/presenters/blobs/unfold_presenter_spec.rb95
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb2
-rw-r--r--spec/presenters/projects/settings/deploy_keys_presenter_spec.rb8
-rw-r--r--spec/requests/api/award_emoji_spec.rb16
-rw-r--r--spec/requests/api/boards_spec.rb2
-rw-r--r--spec/requests/api/commit_statuses_spec.rb24
-rw-r--r--spec/requests/api/commits_spec.rb211
-rw-r--r--spec/requests/api/discussions_spec.rb7
-rw-r--r--spec/requests/api/files_spec.rb162
-rw-r--r--spec/requests/api/graphql/multiplexed_queries_spec.rb8
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/add_spec.rb17
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb17
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb64
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb70
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/note_spec.rb64
-rw-r--r--spec/requests/api/graphql/mutations/notes/destroy_spec.rb52
-rw-r--r--spec/requests/api/graphql/mutations/notes/update_spec.rb72
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb6
-rw-r--r--spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb55
-rw-r--r--spec/requests/api/graphql/project/project_statistics_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/repository_spec.rb24
-rw-r--r--spec/requests/api/graphql_spec.rb41
-rw-r--r--spec/requests/api/group_clusters_spec.rb452
-rw-r--r--spec/requests/api/group_container_repositories_spec.rb57
-rw-r--r--spec/requests/api/group_labels_spec.rb19
-rw-r--r--spec/requests/api/groups_spec.rb14
-rw-r--r--spec/requests/api/internal_spec.rb12
-rw-r--r--spec/requests/api/issues/get_group_issues_spec.rb40
-rw-r--r--spec/requests/api/issues/get_project_issues_spec.rb67
-rw-r--r--spec/requests/api/issues/issues_spec.rb51
-rw-r--r--spec/requests/api/labels_spec.rb474
-rw-r--r--spec/requests/api/members_spec.rb6
-rw-r--r--spec/requests/api/merge_requests_spec.rb42
-rw-r--r--spec/requests/api/notes_spec.rb7
-rw-r--r--spec/requests/api/pipelines_spec.rb11
-rw-r--r--spec/requests/api/project_clusters_spec.rb78
-rw-r--r--spec/requests/api/project_container_repositories_spec.rb (renamed from spec/requests/api/container_registry_spec.rb)75
-rw-r--r--spec/requests/api/projects_spec.rb219
-rw-r--r--spec/requests/api/releases_spec.rb81
-rw-r--r--spec/requests/api/resource_label_events_spec.rb45
-rw-r--r--spec/requests/api/runners_spec.rb44
-rw-r--r--spec/requests/api/search_spec.rb4
-rw-r--r--spec/requests/api/services_spec.rb23
-rw-r--r--spec/requests/api/settings_spec.rb68
-rw-r--r--spec/requests/api/triggers_spec.rb28
-rw-r--r--spec/requests/api/user_counts_spec.rb40
-rw-r--r--spec/requests/api/users_spec.rb16
-rw-r--r--spec/requests/api/variables_spec.rb24
-rw-r--r--spec/requests/jwt_controller_spec.rb8
-rw-r--r--spec/requests/lfs_http_spec.rb4
-rw-r--r--spec/requests/lfs_locks_api_spec.rb4
-rw-r--r--spec/requests/openid_connect_spec.rb2
-rw-r--r--spec/requests/rack_attack_global_spec.rb43
-rw-r--r--spec/requests/request_profiler_spec.rb20
-rw-r--r--spec/routing/api_routing_spec.rb23
-rw-r--r--spec/routing/environments_spec.rb9
-rw-r--r--spec/routing/git_http_routing_spec.rb24
-rw-r--r--spec/routing/project_routing_spec.rb26
-rw-r--r--spec/routing/uploads_routing_spec.rb13
-rw-r--r--spec/rubocop/cop/gitlab/rails_logger_spec.rb42
-rw-r--r--spec/rubocop/cop/gitlab/union_spec.rb6
-rw-r--r--spec/rubocop/cop/graphql/authorize_types_spec.rb66
-rw-r--r--spec/rubocop/cop/inject_enterprise_edition_module_spec.rb144
-rw-r--r--spec/rubocop/cop/migration/add_limit_to_string_columns_spec.rb268
-rw-r--r--spec/rubocop/cop/qa/element_with_pattern_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/env_assignment_spec.rb26
-rw-r--r--spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb22
-rw-r--r--spec/rubocop/cop/rspec/top_level_describe_path_spec.rb67
-rw-r--r--spec/serializers/analytics_issue_entity_spec.rb8
-rw-r--r--spec/serializers/analytics_issue_serializer_spec.rb8
-rw-r--r--spec/serializers/analytics_merge_request_serializer_spec.rb8
-rw-r--r--spec/serializers/analytics_stage_serializer_spec.rb10
-rw-r--r--spec/serializers/cluster_application_entity_spec.rb2
-rw-r--r--spec/serializers/deployment_entity_spec.rb4
-rw-r--r--spec/serializers/diff_file_base_entity_spec.rb26
-rw-r--r--spec/serializers/environment_status_entity_spec.rb12
-rw-r--r--spec/serializers/group_child_entity_spec.rb2
-rw-r--r--spec/serializers/group_child_serializer_spec.rb4
-rw-r--r--spec/serializers/issue_entity_spec.rb33
-rw-r--r--spec/serializers/merge_request_sidebar_basic_entity_spec.rb22
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb2
-rw-r--r--spec/serializers/test_case_entity_spec.rb2
-rw-r--r--spec/serializers/test_reports_comparer_entity_spec.rb8
-rw-r--r--spec/serializers/test_reports_comparer_serializer_spec.rb8
-rw-r--r--spec/serializers/test_suite_comparer_entity_spec.rb92
-rw-r--r--spec/serializers/user_serializer_spec.rb30
-rw-r--r--spec/serializers/variable_entity_spec.rb2
-rw-r--r--spec/services/application_settings/update_service_spec.rb82
-rw-r--r--spec/services/audit_event_service_spec.rb32
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb13
-rw-r--r--spec/services/auto_merge/base_service_spec.rb45
-rw-r--r--spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb16
-rw-r--r--spec/services/auto_merge_service_spec.rb25
-rw-r--r--spec/services/award_emojis/add_service_spec.rb103
-rw-r--r--spec/services/award_emojis/collect_user_emoji_service_spec.rb (renamed from spec/finders/awarded_emoji_finder_spec.rb)2
-rw-r--r--spec/services/award_emojis/destroy_service_spec.rb89
-rw-r--r--spec/services/award_emojis/toggle_service_spec.rb72
-rw-r--r--spec/services/boards/issues/list_service_spec.rb2
-rw-r--r--spec/services/boards/issues/move_service_spec.rb86
-rw-r--r--spec/services/branches/diverging_commit_counts_service_spec.rb31
-rw-r--r--spec/services/ci/archive_trace_service_spec.rb38
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb57
-rw-r--r--spec/services/ci/play_build_service_spec.rb13
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb136
-rw-r--r--spec/services/ci/retry_build_service_spec.rb6
-rw-r--r--spec/services/ci/update_build_queue_service_spec.rb110
-rw-r--r--spec/services/clusters/applications/check_uninstall_progress_service_spec.rb8
-rw-r--r--spec/services/clusters/build_kubernetes_namespace_service_spec.rb57
-rw-r--r--spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb16
-rw-r--r--spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb20
-rw-r--r--spec/services/clusters/refresh_service_spec.rb113
-rw-r--r--spec/services/create_snippet_service_spec.rb16
-rw-r--r--spec/services/deploy_tokens/create_service_spec.rb16
-rw-r--r--spec/services/git/base_hooks_service_spec.rb72
-rw-r--r--spec/services/git/branch_hooks_service_spec.rb84
-rw-r--r--spec/services/git/branch_push_service_spec.rb25
-rw-r--r--spec/services/git/tag_hooks_service_spec.rb6
-rw-r--r--spec/services/git/tag_push_service_spec.rb4
-rw-r--r--spec/services/groups/auto_devops_service_spec.rb2
-rw-r--r--spec/services/groups/create_service_spec.rb38
-rw-r--r--spec/services/groups/nested_create_service_spec.rb30
-rw-r--r--spec/services/groups/transfer_service_spec.rb17
-rw-r--r--spec/services/groups/update_service_spec.rb17
-rw-r--r--spec/services/issuable/bulk_update_service_spec.rb395
-rw-r--r--spec/services/issuable/clone/content_rewriter_spec.rb13
-rw-r--r--spec/services/issues/update_service_spec.rb31
-rw-r--r--spec/services/members/destroy_service_spec.rb6
-rw-r--r--spec/services/merge_requests/build_service_spec.rb37
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb205
-rw-r--r--spec/services/merge_requests/create_service_spec.rb8
-rw-r--r--spec/services/merge_requests/get_urls_service_spec.rb6
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb13
-rw-r--r--spec/services/merge_requests/merge_to_ref_service_spec.rb77
-rw-r--r--spec/services/merge_requests/mergeability_check_service_spec.rb85
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb230
-rw-r--r--spec/services/merge_requests/rebase_service_spec.rb28
-rw-r--r--spec/services/merge_requests/update_service_spec.rb7
-rw-r--r--spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb145
-rw-r--r--spec/services/metrics/dashboard/default_embed_service_spec.rb (renamed from spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb)8
-rw-r--r--spec/services/metrics/dashboard/dynamic_embed_service_spec.rb151
-rw-r--r--spec/services/metrics/dashboard/project_dashboard_service_spec.rb (renamed from spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb)2
-rw-r--r--spec/services/metrics/dashboard/system_dashboard_service_spec.rb (renamed from spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb)2
-rw-r--r--spec/services/namespaces/statistics_refresher_service_spec.rb58
-rw-r--r--spec/services/notes/create_service_spec.rb38
-rw-r--r--spec/services/notification_service_spec.rb469
-rw-r--r--spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb6
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb2
-rw-r--r--spec/services/projects/create_service_spec.rb1
-rw-r--r--spec/services/projects/destroy_service_spec.rb29
-rw-r--r--spec/services/projects/download_service_spec.rb9
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb4
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_service_spec.rb2
-rw-r--r--spec/services/projects/update_remote_mirror_service_spec.rb66
-rw-r--r--spec/services/projects/update_service_spec.rb31
-rw-r--r--spec/services/prometheus/proxy_service_spec.rb2
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb398
-rw-r--r--spec/services/submodules/update_service_spec.rb2
-rw-r--r--spec/services/system_note_service_spec.rb100
-rw-r--r--spec/services/task_list_toggle_service_spec.rb17
-rw-r--r--spec/services/todos/destroy/entity_leave_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/group_private_service_spec.rb2
-rw-r--r--spec/services/update_snippet_service_spec.rb17
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb4
-rw-r--r--spec/services/web_hook_service_spec.rb67
-rw-r--r--spec/services/wiki_pages/base_service_spec.rb27
-rw-r--r--spec/services/wiki_pages/create_service_spec.rb25
-rw-r--r--spec/services/wiki_pages/destroy_service_spec.rb12
-rw-r--r--spec/services/wiki_pages/update_service_spec.rb25
-rw-r--r--spec/services/zoom_notes_service_spec.rb81
-rw-r--r--spec/spec_helper.rb38
-rw-r--r--spec/support/api/boards_shared_examples.rb2
-rw-r--r--spec/support/api/issues_resolving_discussions_shared_examples.rb2
-rw-r--r--spec/support/api/members_shared_examples.rb2
-rw-r--r--spec/support/api/milestones_shared_examples.rb2
-rw-r--r--spec/support/api/repositories_shared_context.rb2
-rw-r--r--spec/support/api/schema_matcher.rb2
-rw-r--r--spec/support/api/scopes/read_user_shared_examples.rb2
-rw-r--r--spec/support/api/time_tracking_shared_examples.rb2
-rw-r--r--spec/support/banzai/reference_filter_shared_examples.rb2
-rw-r--r--spec/support/batch_loader.rb2
-rw-r--r--spec/support/capybara.rb6
-rw-r--r--spec/support/carrierwave.rb2
-rw-r--r--spec/support/chunked_io/chunked_io_helpers.rb2
-rw-r--r--spec/support/commit_trailers_spec_helper.rb2
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_context.rb2
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_examples.rb8
-rw-r--r--spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb2
-rw-r--r--spec/support/controllers/sessionless_auth_controller_shared_examples.rb2
-rw-r--r--spec/support/cycle_analytics_helpers/test_generation.rb14
-rw-r--r--spec/support/database_cleaner.rb27
-rw-r--r--spec/support/db_cleaner.rb6
-rw-r--r--spec/support/external_authorization_service_helpers.rb2
-rw-r--r--spec/support/features/discussion_comments_shared_example.rb42
-rw-r--r--spec/support/features/reportable_note_shared_examples.rb2
-rw-r--r--spec/support/features/resolving_discussions_in_issues_shared_examples.rb4
-rw-r--r--spec/support/features/rss_shared_examples.rb2
-rw-r--r--spec/support/features/variable_list_shared_examples.rb2
-rw-r--r--spec/support/forgery_protection.rb2
-rw-r--r--spec/support/google_api/cloud_platform_helpers.rb2
-rw-r--r--spec/support/helpers/api_helpers.rb11
-rw-r--r--spec/support/helpers/assets_helpers.rb2
-rw-r--r--spec/support/helpers/bare_repo_operations.rb2
-rw-r--r--spec/support/helpers/board_helpers.rb2
-rw-r--r--spec/support/helpers/capybara_helpers.rb2
-rw-r--r--spec/support/helpers/ci_artifact_metadata_generator.rb2
-rw-r--r--spec/support/helpers/cookie_helper.rb2
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb2
-rw-r--r--spec/support/helpers/database_connection_helpers.rb2
-rw-r--r--spec/support/helpers/devise_helpers.rb14
-rw-r--r--spec/support/helpers/drag_to_helper.rb21
-rw-r--r--spec/support/helpers/dropzone_helper.rb2
-rw-r--r--spec/support/helpers/email_helpers.rb21
-rw-r--r--spec/support/helpers/exclusive_lease_helpers.rb2
-rw-r--r--spec/support/helpers/expect_next_instance_of.rb2
-rw-r--r--spec/support/helpers/expect_offense.rb2
-rw-r--r--spec/support/helpers/expect_request_with_status.rb11
-rw-r--r--spec/support/helpers/fake_blob_helpers.rb2
-rw-r--r--spec/support/helpers/fake_migration_classes.rb2
-rw-r--r--spec/support/helpers/fake_u2f_device.rb6
-rw-r--r--spec/support/helpers/features/branches_helpers.rb2
-rw-r--r--spec/support/helpers/features/notes_helpers.rb10
-rw-r--r--spec/support/helpers/features/sorting_helpers.rb2
-rw-r--r--spec/support/helpers/filter_spec_helper.rb2
-rw-r--r--spec/support/helpers/filtered_search_helpers.rb2
-rw-r--r--spec/support/helpers/fixture_helpers.rb2
-rw-r--r--spec/support/helpers/git_http_helpers.rb6
-rw-r--r--spec/support/helpers/gitlab_verify_helpers.rb2
-rw-r--r--spec/support/helpers/graphql_helpers.rb25
-rw-r--r--spec/support/helpers/import_spec_helper.rb2
-rw-r--r--spec/support/helpers/input_helper.rb2
-rw-r--r--spec/support/helpers/inspect_requests.rb2
-rw-r--r--spec/support/helpers/issue_helpers.rb2
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb4
-rw-r--r--spec/support/helpers/jira_service_helper.rb2
-rw-r--r--spec/support/helpers/key_generator_helper.rb4
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb7
-rw-r--r--spec/support/helpers/ldap_helpers.rb2
-rw-r--r--spec/support/helpers/live_debugger.rb2
-rw-r--r--spec/support/helpers/login_helpers.rb14
-rw-r--r--spec/support/helpers/markdown_feature.rb2
-rw-r--r--spec/support/helpers/memory_usage_helper.rb37
-rw-r--r--spec/support/helpers/merge_request_diff_helpers.rb2
-rw-r--r--spec/support/helpers/merge_request_helpers.rb2
-rw-r--r--spec/support/helpers/metrics_dashboard_helpers.rb8
-rw-r--r--spec/support/helpers/migrations_helpers.rb15
-rw-r--r--spec/support/helpers/mobile_helpers.rb2
-rw-r--r--spec/support/helpers/note_interaction_helpers.rb2
-rw-r--r--spec/support/helpers/notification_helpers.rb2
-rw-r--r--spec/support/helpers/position_tracer_helpers.rb93
-rw-r--r--spec/support/helpers/project_forks_helper.rb2
-rw-r--r--spec/support/helpers/prometheus_helpers.rb10
-rw-r--r--spec/support/helpers/query_recorder.rb2
-rw-r--r--spec/support/helpers/quick_actions_helpers.rb2
-rw-r--r--spec/support/helpers/rake_helpers.rb2
-rw-r--r--spec/support/helpers/reactive_caching_helpers.rb4
-rw-r--r--spec/support/helpers/redis_without_keys.rb2
-rw-r--r--spec/support/helpers/reference_parser_helpers.rb2
-rw-r--r--spec/support/helpers/repo_helpers.rb2
-rw-r--r--spec/support/helpers/routes_helpers.rb2
-rw-r--r--spec/support/helpers/search_helpers.rb2
-rw-r--r--spec/support/helpers/seed_repo.rb2
-rw-r--r--spec/support/helpers/select2_helper.rb2
-rw-r--r--spec/support/helpers/selection_helper.rb2
-rw-r--r--spec/support/helpers/smime_helper.rb55
-rw-r--r--spec/support/helpers/sorting_helper.rb2
-rw-r--r--spec/support/helpers/stub_configuration.rb10
-rw-r--r--spec/support/helpers/stub_env.rb2
-rw-r--r--spec/support/helpers/stub_feature_flags.rb2
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb6
-rw-r--r--spec/support/helpers/stub_gitlab_data.rb2
-rw-r--r--spec/support/helpers/stub_metrics.rb2
-rw-r--r--spec/support/helpers/stub_object_storage.rb2
-rw-r--r--spec/support/helpers/stub_requests.rb15
-rw-r--r--spec/support/helpers/stub_worker.rb2
-rw-r--r--spec/support/helpers/terms_helper.rb2
-rw-r--r--spec/support/helpers/test_env.rb40
-rw-r--r--spec/support/helpers/upload_helpers.rb2
-rw-r--r--spec/support/helpers/wait_for_requests.rb2
-rw-r--r--spec/support/helpers/wait_helpers.rb2
-rw-r--r--spec/support/helpers/wiki_helpers.rb2
-rw-r--r--spec/support/helpers/workhorse_helpers.rb2
-rw-r--r--spec/support/http_io/http_io_helpers.rb2
-rw-r--r--spec/support/import_export/common_util.rb2
-rw-r--r--spec/support/import_export/configuration_helper.rb2
-rw-r--r--spec/support/import_export/export_file_helper.rb2
-rw-r--r--spec/support/inspect_squelch.rb2
-rw-r--r--spec/support/issuables_requiring_filter_shared_examples.rb2
-rw-r--r--spec/support/json_response.rb4
-rw-r--r--spec/support/matchers/abort_matcher.rb48
-rw-r--r--spec/support/matchers/access_matchers.rb2
-rw-r--r--spec/support/matchers/access_matchers_for_controller.rb2
-rw-r--r--spec/support/matchers/background_migrations_matchers.rb2
-rw-r--r--spec/support/matchers/be_a_binary_string.rb2
-rw-r--r--spec/support/matchers/be_like_time.rb2
-rw-r--r--spec/support/matchers/be_url.rb6
-rw-r--r--spec/support/matchers/be_utf8.rb2
-rw-r--r--spec/support/matchers/be_valid_commit.rb2
-rw-r--r--spec/support/matchers/disallow_request_matchers.rb2
-rw-r--r--spec/support/matchers/exceed_query_limit.rb2
-rw-r--r--spec/support/matchers/execute_check.rb2
-rw-r--r--spec/support/matchers/gitaly_matchers.rb4
-rw-r--r--spec/support/matchers/gitlab_git_matchers.rb2
-rw-r--r--spec/support/matchers/graphql_matchers.rb2
-rw-r--r--spec/support/matchers/have_emoji.rb2
-rw-r--r--spec/support/matchers/have_gitlab_http_status.rb2
-rw-r--r--spec/support/matchers/have_issuable_counts.rb2
-rw-r--r--spec/support/matchers/include_module.rb2
-rw-r--r--spec/support/matchers/issuable_matchers.rb2
-rw-r--r--spec/support/matchers/markdown_matchers.rb2
-rw-r--r--spec/support/matchers/match_file.rb2
-rw-r--r--spec/support/matchers/match_ids.rb2
-rw-r--r--spec/support/matchers/metric_counter_matcher.rb2
-rw-r--r--spec/support/matchers/navigation_matcher.rb2
-rw-r--r--spec/support/matchers/pagination_matcher.rb2
-rw-r--r--spec/support/matchers/query_matcher.rb2
-rw-r--r--spec/support/matchers/satisfy_matchers.rb2
-rw-r--r--spec/support/matchers/security_header_matcher.rb2
-rw-r--r--spec/support/migrations_helpers/track_untracked_uploads_helpers.rb2
-rw-r--r--spec/support/omni_auth.rb2
-rw-r--r--spec/support/prometheus/additional_metrics_shared_examples.rb4
-rw-r--r--spec/support/prometheus/metric_builders.rb2
-rw-r--r--spec/support/protected_tags/access_control_ce_shared_examples.rb2
-rw-r--r--spec/support/redis/redis_helpers.rb2
-rw-r--r--spec/support/redis/redis_shared_examples.rb2
-rw-r--r--spec/support/rspec.rb2
-rw-r--r--spec/support/seed.rb2
-rw-r--r--spec/support/services/clusters/create_service_shared.rb69
-rw-r--r--spec/support/services/issuable_create_service_slash_commands_shared_examples.rb2
-rw-r--r--spec/support/services/issuable_update_service_shared_examples.rb2
-rw-r--r--spec/support/services/migrate_to_ghost_user_service_shared_examples.rb2
-rw-r--r--spec/support/setup_builds_storage.rb2
-rw-r--r--spec/support/shared_contexts/email_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/finders/users_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/json_response_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/merge_requests_allowing_collaboration.rb2
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb7
-rw-r--r--spec/support/shared_contexts/services_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/url_shared_context.rb2
-rw-r--r--spec/support/shared_examples/application_setting_examples.rb164
-rw-r--r--spec/support/shared_examples/award_emoji_todo_shared_examples.rb59
-rw-r--r--spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb144
-rw-r--r--spec/support/shared_examples/chat_slash_commands_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb58
-rw-r--r--spec/support/shared_examples/common_system_notes_examples.rb2
-rw-r--r--spec/support/shared_examples/container_repositories_shared_examples.rb58
-rw-r--r--spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/controllers/todos_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/controllers/variables_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/cycle_analytics_stage_examples.rb74
-rw-r--r--spec/support/shared_examples/dirty_submit_form_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/discussions_provider_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/email_format_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/fast_destroy_all.rb2
-rw-r--r--spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb2
-rw-r--r--spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/protected_branches_access_control_ce.rb14
-rw-r--r--spec/support/shared_examples/features/search_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/file_finder.rb2
-rw-r--r--spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb2
-rw-r--r--spec/support/shared_examples/gitlab_verify.rb2
-rw-r--r--spec/support/shared_examples/graphql/issuable_state_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/graphql/notes_creation_shared_examples.rb61
-rw-r--r--spec/support/shared_examples/group_members_shared_example.rb2
-rw-r--r--spec/support/shared_examples/helm_generated_script.rb2
-rw-r--r--spec/support/shared_examples/issuable_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/issuables_list_metadata_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/issue_tracker_service_shared_example.rb2
-rw-r--r--spec/support/shared_examples/ldap_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/legacy_path_redirect_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb57
-rw-r--r--spec/support/shared_examples/malicious_regexp_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/mentionable_shared_examples.rb53
-rw-r--r--spec/support/shared_examples/milestone_tabs_examples.rb2
-rw-r--r--spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/chat_service_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/models/cluster_application_core_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb52
-rw-r--r--spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/members_notifications_shared_example.rb2
-rw-r--r--spec/support/shared_examples/models/project_hook_data_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/services_fields_shared_examples.rb31
-rw-r--r--spec/support/shared_examples/models/update_project_statistics_shared_examples.rb50
-rw-r--r--spec/support/shared_examples/models/with_uploads_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/notify_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/policies/clusterable_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/position_formatters.rb2
-rw-r--r--spec/support/shared_examples/project_latest_successful_build_for_examples.rb63
-rw-r--r--spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb57
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/reference_parser_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/relative_positioning_shared_examples.rb283
-rw-r--r--spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/diff_discussions.rb2
-rw-r--r--spec/support/shared_examples/requests/api/discussions.rb56
-rw-r--r--spec/support/shared_examples/requests/api/issuable_participants_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb39
-rw-r--r--spec/support/shared_examples/requests/api/notes.rb2
-rw-r--r--spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb235
-rw-r--r--spec/support/shared_examples/requests/api/resolvable_discussions.rb2
-rw-r--r--spec/support/shared_examples/requests/api/status_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/graphql_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/resource_label_events_api.rb44
-rw-r--r--spec/support/shared_examples/serializers/note_entity_examples.rb2
-rw-r--r--spec/support/shared_examples/services/boards/boards_create_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/boards_list_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/issues_list_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/issues_move_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/lists_destroy_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/lists_list_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/lists_move_service.rb2
-rw-r--r--spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/notification_service_shared_examples.rb54
-rw-r--r--spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/snippet_visibility_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/snippets_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/taskable_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/throttled_touch.rb2
-rw-r--r--spec/support/shared_examples/unique_ip_check_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/update_invalid_issuable.rb4
-rw-r--r--spec/support/shared_examples/updating_mentions_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/uploaders/object_storage_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/url_validator_examples.rb59
-rw-r--r--spec/support/sidekiq.rb2
-rw-r--r--spec/support/stored_repositories.rb2
-rw-r--r--spec/support/test_reports/test_reports_helper.rb36
-rw-r--r--spec/support/trace/trace_helpers.rb2
-rw-r--r--spec/support/webmock.rb2
-rw-r--r--spec/tasks/gitlab/cleanup_rake_spec.rb59
-rw-r--r--spec/tasks/gitlab/mail_google_schema_whitelisting.rb27
-rw-r--r--spec/tasks/gitlab/storage_rake_spec.rb34
-rw-r--r--spec/tasks/gitlab/update_templates_rake_spec.rb34
-rw-r--r--spec/tasks/migrate/schema_check_rake_spec.rb48
-rw-r--r--spec/uploaders/file_mover_spec.rb132
-rw-r--r--spec/uploaders/file_uploader_spec.rb62
-rw-r--r--spec/uploaders/records_uploads_spec.rb21
-rw-r--r--spec/validators/color_validator_spec.rb43
-rw-r--r--spec/validators/public_url_validator_spec.rb24
-rw-r--r--spec/validators/qualified_domain_array_validator_spec.rb103
-rw-r--r--spec/validators/system_hook_url_validator_spec.rb8
-rw-r--r--spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb (renamed from spec/views/dashboard/projects/_blank_state_admin_welcome.haml.rb)0
-rw-r--r--spec/views/dashboard/projects/_nav.html.haml_spec.rb (renamed from spec/views/dashboard/projects/_nav.html.haml.rb)4
-rw-r--r--spec/views/groups/edit.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/_head.html.haml_spec.rb37
-rw-r--r--spec/views/layouts/header/_new_dropdown.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb13
-rw-r--r--spec/views/notify/pipeline_failed_email.html.haml_spec.rb4
-rw-r--r--spec/views/notify/pipeline_failed_email.text.erb_spec.rb2
-rw-r--r--spec/views/notify/pipeline_success_email.html.haml_spec.rb4
-rw-r--r--spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb (renamed from spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb)0
-rw-r--r--spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb1
-rw-r--r--spec/views/projects/merge_requests/edit.html.haml_spec.rb1
-rw-r--r--spec/views/projects/pages_domains/show.html.haml_spec.rb66
-rw-r--r--spec/views/search/_filter.html.haml_spec.rb17
-rw-r--r--spec/views/search/_form.html.haml_spec.rb14
-rw-r--r--spec/views/search/show.html.haml_spec.rb37
-rw-r--r--spec/views/shared/_label_row.html.haml_spec.rb (renamed from spec/views/shared/_label_row.html.haml.rb)13
-rw-r--r--spec/views/shared/milestones/_issuable.html.haml_spec.rb (renamed from spec/views/shared/milestones/_issuable.html.haml.rb)0
-rw-r--r--spec/views/shared/milestones/_issuables.html.haml_spec.rb (renamed from spec/views/shared/milestones/_issuables.html.haml.rb)2
-rw-r--r--spec/views/shared/milestones/_top.html.haml_spec.rb (renamed from spec/views/shared/milestones/_top.html.haml.rb)1
-rw-r--r--spec/workers/archive_trace_worker_spec.rb2
-rw-r--r--spec/workers/background_migration_worker_spec.rb20
-rw-r--r--spec/workers/ci/archive_traces_cron_worker_spec.rb23
-rw-r--r--spec/workers/cluster_configure_worker_spec.rb71
-rw-r--r--spec/workers/cluster_project_configure_worker_spec.rb14
-rw-r--r--spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb35
-rw-r--r--spec/workers/namespaces/root_statistics_worker_spec.rb77
-rw-r--r--spec/workers/namespaces/schedule_aggregation_worker_spec.rb67
-rw-r--r--spec/workers/pipeline_process_worker_spec.rb11
-rw-r--r--spec/workers/post_receive_spec.rb152
-rw-r--r--spec/workers/process_commit_worker_spec.rb40
-rw-r--r--spec/workers/project_cache_worker_spec.rb24
-rw-r--r--spec/workers/repository_update_remote_mirror_worker_spec.rb111
-rw-r--r--spec/workers/stuck_ci_jobs_worker_spec.rb4
2486 files changed, 43918 insertions, 20999 deletions
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb
index 84bbe0525e5..c3a5abf2b7a 100644
--- a/spec/bin/changelog_spec.rb
+++ b/spec/bin/changelog_spec.rb
@@ -15,7 +15,7 @@ describe 'bin/changelog' do
allow(entry).to receive(:branch_name).and_return('long-branch-' * 100)
file_path = entry.send(:file_path)
- expect(file_path.length).to eq(140)
+ expect(file_path.length).to eq(99)
end
end
diff --git a/spec/config/smime_signature_settings_spec.rb b/spec/config/smime_signature_settings_spec.rb
new file mode 100644
index 00000000000..4f0c227d866
--- /dev/null
+++ b/spec/config/smime_signature_settings_spec.rb
@@ -0,0 +1,56 @@
+require 'fast_spec_helper'
+
+describe SmimeSignatureSettings do
+ describe '.parse' do
+ let(:default_smime_key) { Rails.root.join('.gitlab_smime_key') }
+ let(:default_smime_cert) { Rails.root.join('.gitlab_smime_cert') }
+
+ it 'sets correct default values to disabled' do
+ parsed_settings = described_class.parse(nil)
+
+ expect(parsed_settings['enabled']).to be(false)
+ expect(parsed_settings['key_file']).to eq(default_smime_key)
+ expect(parsed_settings['cert_file']).to eq(default_smime_cert)
+ end
+
+ context 'when providing custom values' do
+ it 'sets correct default values to disabled' do
+ custom_settings = Settingslogic.new({})
+
+ parsed_settings = described_class.parse(custom_settings)
+
+ expect(parsed_settings['enabled']).to be(false)
+ expect(parsed_settings['key_file']).to eq(default_smime_key)
+ expect(parsed_settings['cert_file']).to eq(default_smime_cert)
+ end
+
+ it 'enables smime with default key and cert' do
+ custom_settings = Settingslogic.new({
+ 'enabled' => true
+ })
+
+ parsed_settings = described_class.parse(custom_settings)
+
+ expect(parsed_settings['enabled']).to be(true)
+ expect(parsed_settings['key_file']).to eq(default_smime_key)
+ expect(parsed_settings['cert_file']).to eq(default_smime_cert)
+ end
+
+ it 'enables smime with custom key and cert' do
+ custom_key = '/custom/key'
+ custom_cert = '/custom/cert'
+ custom_settings = Settingslogic.new({
+ 'enabled' => true,
+ 'key_file' => custom_key,
+ 'cert_file' => custom_cert
+ })
+
+ parsed_settings = described_class.parse(custom_settings)
+
+ expect(parsed_settings['enabled']).to be(true)
+ expect(parsed_settings['key_file']).to eq(custom_key)
+ expect(parsed_settings['cert_file']).to eq(custom_cert)
+ 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 5ad5f9cdeea..4eb0545eb6c 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -41,7 +41,7 @@ describe Admin::ApplicationSettingsController do
it 'returns JSON data' do
get :usage_data, format: :json
- body = JSON.parse(response.body)
+ body = json_response
expect(body["version"]).to eq(Gitlab::VERSION)
expect(body).to include('counts')
expect(response.status).to eq(200)
diff --git a/spec/controllers/admin/clusters/applications_controller_spec.rb b/spec/controllers/admin/clusters/applications_controller_spec.rb
index cf202d88acc..9d6edcd80c0 100644
--- a/spec/controllers/admin/clusters/applications_controller_spec.rb
+++ b/spec/controllers/admin/clusters/applications_controller_spec.rb
@@ -84,7 +84,7 @@ describe Admin::Clusters::ApplicationsController do
patch :update, params: params
end
- let!(:application) { create(:clusters_applications_cert_managers, :installed, cluster: cluster) }
+ let!(:application) { create(:clusters_applications_cert_manager, :installed, cluster: cluster) }
let(:application_name) { application.name }
let(:params) { { application: application_name, id: cluster.id, email: "new-email@example.com" } }
diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb
index 509d8944e3a..1123563c1e3 100644
--- a/spec/controllers/admin/groups_controller_spec.rb
+++ b/spec/controllers/admin/groups_controller_spec.rb
@@ -68,5 +68,13 @@ describe Admin::GroupsController do
post :update, params: { id: group.to_param, group: { project_creation_level: ::Gitlab::Access::NO_ONE_PROJECT_ACCESS } }
end.to change { group.reload.project_creation_level }.to(::Gitlab::Access::NO_ONE_PROJECT_ACCESS)
end
+
+ it 'updates the subgroup_creation_level successfully' do
+ expect do
+ post :update,
+ params: { id: group.to_param,
+ group: { subgroup_creation_level: ::Gitlab::Access::OWNER_SUBGROUP_ACCESS } }
+ end.to change { group.reload.subgroup_creation_level }.to(::Gitlab::Access::OWNER_SUBGROUP_ACCESS)
+ end
end
end
diff --git a/spec/controllers/admin/requests_profiles_controller_spec.rb b/spec/controllers/admin/requests_profiles_controller_spec.rb
index 10850cb4603..345f7720c25 100644
--- a/spec/controllers/admin/requests_profiles_controller_spec.rb
+++ b/spec/controllers/admin/requests_profiles_controller_spec.rb
@@ -10,38 +10,63 @@ describe Admin::RequestsProfilesController do
end
describe '#show' do
- let(:basename) { "profile_#{Time.now.to_i}.html" }
let(:tmpdir) { Dir.mktmpdir('profiler-test') }
let(:test_file) { File.join(tmpdir, basename) }
- let(:profile) { Gitlab::RequestProfiler::Profile.new(basename) }
- let(:sample_data) do
- <<~HTML
- <!DOCTYPE html>
- <html>
- <body>
- <h1>My First Heading</h1>
- <p>My first paragraph.</p>
- </body>
- </html>
- HTML
+
+ subject do
+ get :show, params: { name: basename }
end
before do
stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir)
- output = File.open(test_file, 'w')
- output.write(sample_data)
- output.close
+ File.write(test_file, sample_data)
end
after do
- File.unlink(test_file)
+ FileUtils.rm_rf(tmpdir)
end
- it 'loads an HTML profile' do
- get :show, params: { name: basename }
+ context 'when loading HTML profile' do
+ let(:basename) { "profile_#{Time.now.to_i}_execution.html" }
+
+ let(:sample_data) do
+ '<html> <body> <h1>Heading</h1> <p>paragraph.</p> </body> </html>'
+ end
+
+ it 'renders the data' do
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to eq(sample_data)
+ end
+ end
+
+ context 'when loading TXT profile' do
+ let(:basename) { "profile_#{Time.now.to_i}_memory.txt" }
+
+ let(:sample_data) do
+ <<~TXT
+ Total allocated: 112096396 bytes (1080431 objects)
+ Total retained: 10312598 bytes (53567 objects)
+ TXT
+ end
+
+ it 'renders the data' do
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to eq(sample_data)
+ end
+ end
+
+ context 'when loading PDF profile' do
+ let(:basename) { "profile_#{Time.now.to_i}_anything.pdf" }
+
+ let(:sample_data) { 'mocked pdf content' }
- expect(response).to have_gitlab_http_status(200)
- expect(response.body).to eq(sample_data)
+ it 'fails to render the data' do
+ expect { subject }.to raise_error(ActionController::UrlGenerationError, /No route matches.*unmatched constraints:/)
+ end
end
end
end
diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb
index 78c5e2a2656..bbeda7dae0f 100644
--- a/spec/controllers/admin/runners_controller_spec.rb
+++ b/spec/controllers/admin/runners_controller_spec.rb
@@ -23,10 +23,11 @@ describe Admin::RunnersController do
control_count = ActiveRecord::QueryRecorder.new { get :index }.count
- create(:ci_runner, :tagged_only)
+ create_list(:ci_runner, 5, :tagged_only)
# There is still an N+1 query for `runner.builds.count`
- expect { get :index }.not_to exceed_query_limit(control_count + 1)
+ # We also need to add 1 because it takes 2 queries to preload tags
+ expect { get :index }.not_to exceed_query_limit(control_count + 6)
expect(response).to have_gitlab_http_status(200)
expect(response.body).to have_content('tag1')
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 89a0eba66f7..d7428f8b52c 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -279,6 +279,12 @@ describe Admin::UsersController do
expect(warden.user).to eq(user)
end
+ it 'logs the beginning of the impersonation event' do
+ expect(Gitlab::AppLogger).to receive(:info).with("User #{admin.username} has started impersonating #{user.username}").and_call_original
+
+ post :impersonate, params: { id: user.username }
+ end
+
it "redirects to root" do
post :impersonate, params: { id: user.username }
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 447a12b2fac..0b3833e6515 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -63,8 +63,6 @@ describe ApplicationController do
sign_in user
end
- let(:json_response) { JSON.parse(response.body) }
-
controller(described_class) do
def index
render json: Gon.all_variables
@@ -643,24 +641,32 @@ describe ApplicationController do
end
end
- it 'does not set a custom header' do
+ it 'sets a custom header' do
get :index, format: :json
- expect(response.headers['X-GitLab-Custom-Error']).to be_nil
+ expect(response.headers['X-GitLab-Custom-Error']).to eq '1'
end
- end
- context 'given a json response for an html request' do
- controller do
- def index
- render json: {}, status: :unprocessable_entity
+ context 'for html request' do
+ it 'sets a custom header' do
+ get :index
+
+ expect(response.headers['X-GitLab-Custom-Error']).to eq '1'
end
end
- it 'does not set a custom header' do
- get :index
+ context 'for 200 response' do
+ controller do
+ def index
+ render json: {}, status: :ok
+ end
+ end
- expect(response.headers['X-GitLab-Custom-Error']).to be_nil
+ it 'does not set a custom header' do
+ get :index, format: :json
+
+ expect(response.headers['X-GitLab-Custom-Error']).to be_nil
+ end
end
end
end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 3f1c0ae8ac4..6cdd61e7abd 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -222,6 +222,20 @@ describe AutocompleteController do
expect(response_user_ids).to contain_exactly(non_member.id)
end
end
+
+ context 'merge_request_iid parameter included' do
+ before do
+ sign_in(user)
+ end
+
+ it 'includes can_merge option to users' do
+ merge_request = create(:merge_request, source_project: project)
+
+ get(:users, params: { merge_request_iid: merge_request.iid, project_id: project.id })
+
+ expect(json_response.first).to have_key('can_merge')
+ end
+ end
end
context 'GET projects' do
@@ -295,28 +309,6 @@ describe AutocompleteController do
end
end
- context 'authorized projects with offset' do
- before do
- authorized_project2 = create(:project)
- authorized_project3 = create(:project)
-
- authorized_project.add_maintainer(user)
- authorized_project2.add_maintainer(user)
- authorized_project3.add_maintainer(user)
- end
-
- describe 'GET #projects with project ID and offset_id' do
- before do
- get(:projects, params: { project_id: project.id, offset_id: authorized_project.id })
- end
-
- it 'returns projects' do
- expect(json_response).to be_kind_of(Array)
- expect(json_response.size).to eq 2 # Of a total of 3
- end
- end
- end
-
context 'authorized projects without admin_issue ability' do
before do
authorized_project.add_guest(user)
diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb
index c84bb913cad..d54f7ad33cf 100644
--- a/spec/controllers/boards/issues_controller_spec.rb
+++ b/spec/controllers/boards/issues_controller_spec.rb
@@ -52,10 +52,8 @@ describe Boards::IssuesController do
list_issues user: user, board: board, list: list2
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('entities/issue_boards')
- expect(parsed_response['issues'].length).to eq 2
+ expect(json_response['issues'].length).to eq 2
expect(development.issues.map(&:relative_position)).not_to include(nil)
end
@@ -87,7 +85,7 @@ describe Boards::IssuesController do
expect { list_issues(user: user, board: group_board, list: list3) }.not_to exceed_query_limit(control_count + (2 * 8 - 1))
end
- it 'avoids N+1 database queries when adding a subgroup, project, and issue', :nested_groups do
+ it 'avoids N+1 database queries when adding a subgroup, project, and issue' do
create(:project, group: sub_group_1)
create(:labeled_issue, project: project, labels: [development])
control_count = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: group_board, list: list3) }.count
@@ -123,10 +121,8 @@ describe Boards::IssuesController do
list_issues user: user, board: board
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('entities/issue_boards')
- expect(parsed_response['issues'].length).to eq 2
+ expect(json_response['issues'].length).to eq 2
end
end
@@ -164,6 +160,215 @@ describe Boards::IssuesController do
end
end
+ describe 'PUT bulk_move' do
+ let(:todo) { create(:group_label, group: group, name: 'Todo') }
+ let(:development) { create(:group_label, group: group, name: 'Development') }
+ let(:user) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
+ let(:guest) { create(:group_member, :guest, user: create(:user), group: group ).user }
+ let(:project) { create(:project, group: group) }
+ let(:group) { create(:group) }
+ let(:board) { create(:board, project: project) }
+ let(:list1) { create(:list, board: board, label: todo, position: 0) }
+ let(:list2) { create(:list, board: board, label: development, position: 1) }
+ let(:issue1) { create(:labeled_issue, project: project, labels: [todo], author: user, relative_position: 10) }
+ let(:issue2) { create(:labeled_issue, project: project, labels: [todo], author: user, relative_position: 20) }
+ let(:issue3) { create(:labeled_issue, project: project, labels: [todo], author: user, relative_position: 30) }
+ let(:issue4) { create(:labeled_issue, project: project, labels: [development], author: user, relative_position: 100) }
+
+ let(:move_params) do
+ {
+ board_id: board.id,
+ ids: [issue1.id, issue2.id, issue3.id],
+ from_list_id: list1.id,
+ to_list_id: list2.id,
+ move_before_id: issue4.id,
+ move_after_id: nil
+ }
+ end
+
+ before do
+ project.add_maintainer(user)
+ project.add_guest(guest)
+ end
+
+ shared_examples 'move issues endpoint provider' do
+ before do
+ sign_in(signed_in_user)
+ end
+
+ it 'responds as expected' do
+ put :bulk_move, params: move_issues_params
+ expect(response).to have_gitlab_http_status(expected_status)
+
+ if expected_status == 200
+ expect(json_response).to include(
+ 'count' => move_issues_params[:ids].size,
+ 'success' => true
+ )
+
+ expect(json_response['issues'].pluck('id')).to match_array(move_issues_params[:ids])
+ end
+ end
+
+ it 'moves issues as expected' do
+ put :bulk_move, params: move_issues_params
+ expect(response).to have_gitlab_http_status(expected_status)
+
+ list_issues user: requesting_user, board: board, list: list2
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(response).to match_response_schema('entities/issue_boards')
+
+ responded_issues = json_response['issues']
+ expect(responded_issues.length).to eq expected_issue_count
+
+ ids_in_order = responded_issues.pluck('id')
+ expect(ids_in_order).to eq(expected_issue_ids_in_order)
+ end
+ end
+
+ context 'when items are moved to another list' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) { move_params }
+ let(:requesting_user) { user }
+ let(:expected_status) { 200 }
+ let(:expected_issue_count) { 4 }
+ let(:expected_issue_ids_in_order) { [issue4.id, issue1.id, issue2.id, issue3.id] }
+ end
+ end
+
+ context 'when moving just one issue' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:ids] = [issue2.id]
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 200 }
+ let(:expected_issue_count) { 2 }
+ let(:expected_issue_ids_in_order) { [issue4.id, issue2.id] }
+ end
+ end
+
+ context 'when user is not allowed to move issue' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { guest }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:ids] = [issue2.id]
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 403 }
+ let(:expected_issue_count) { 1 }
+ let(:expected_issue_ids_in_order) { [issue4.id] }
+ end
+ end
+
+ context 'when issues should be moved visually above existing issue in list' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:move_after_id] = issue4.id
+ hash[:move_before_id] = nil
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 200 }
+ let(:expected_issue_count) { 4 }
+ let(:expected_issue_ids_in_order) { [issue1.id, issue2.id, issue3.id, issue4.id] }
+ end
+ end
+
+ context 'when destination list is empty' do
+ before do
+ # Remove issue from list
+ issue4.labels -= [development]
+ issue4.save!
+ end
+
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:move_before_id] = nil
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 200 }
+ let(:expected_issue_count) { 3 }
+ let(:expected_issue_ids_in_order) { [issue1.id, issue2.id, issue3.id] }
+ end
+ end
+
+ context 'when no position arguments are given' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:move_before_id] = nil
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 200 }
+ let(:expected_issue_count) { 4 }
+ let(:expected_issue_ids_in_order) { [issue1.id, issue2.id, issue3.id, issue4.id] }
+ end
+ end
+
+ context 'when move_before_id and move_after_id are given' do
+ let(:issue5) { create(:labeled_issue, project: project, labels: [development], author: user, relative_position: 90) }
+
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:move_before_id] = issue5.id
+ hash[:move_after_id] = issue4.id
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 200 }
+ let(:expected_issue_count) { 5 }
+ let(:expected_issue_ids_in_order) { [issue5.id, issue1.id, issue2.id, issue3.id, issue4.id] }
+ end
+ end
+
+ context 'when request contains too many issues' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:ids] = (0..51).to_a
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 422 }
+ let(:expected_issue_count) { 1 }
+ let(:expected_issue_ids_in_order) { [issue4.id] }
+ end
+ end
+
+ context 'when request is malformed' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:ids] = 'foobar'
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 400 }
+ let(:expected_issue_count) { 1 }
+ let(:expected_issue_ids_in_order) { [issue4.id] }
+ end
+ end
+ end
+
def list_issues(user:, board:, list: nil)
sign_in(user)
diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb
index e1f75fa3395..418ca6f3210 100644
--- a/spec/controllers/boards/lists_controller_spec.rb
+++ b/spec/controllers/boards/lists_controller_spec.rb
@@ -26,10 +26,8 @@ describe Boards::ListsController do
read_board_list user: user, board: board
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('lists')
- expect(parsed_response.length).to eq 3
+ expect(json_response.length).to eq 3
end
context 'with unauthorized user' do
diff --git a/spec/controllers/chaos_controller_spec.rb b/spec/controllers/chaos_controller_spec.rb
new file mode 100644
index 00000000000..bafd4a70862
--- /dev/null
+++ b/spec/controllers/chaos_controller_spec.rb
@@ -0,0 +1,127 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ChaosController do
+ describe '#leakmem' do
+ it 'calls synchronously' do
+ expect(Gitlab::Chaos).to receive(:leak_mem).with(100, 30.seconds)
+
+ get :leakmem
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'call synchronously with params' do
+ expect(Gitlab::Chaos).to receive(:leak_mem).with(1, 2.seconds)
+
+ get :leakmem, params: { memory_mb: 1, duration_s: 2 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls asynchronously' do
+ expect(Chaos::LeakMemWorker).to receive(:perform_async).with(100, 30.seconds)
+
+ get :leakmem, params: { async: 1 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe '#cpu_spin' do
+ it 'calls synchronously' do
+ expect(Gitlab::Chaos).to receive(:cpu_spin).with(30.seconds)
+
+ get :cpu_spin
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls synchronously with params' do
+ expect(Gitlab::Chaos).to receive(:cpu_spin).with(3.seconds)
+
+ get :cpu_spin, params: { duration_s: 3 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls asynchronously' do
+ expect(Chaos::CpuSpinWorker).to receive(:perform_async).with(30.seconds)
+
+ get :cpu_spin, params: { async: 1 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe '#db_spin' do
+ it 'calls synchronously' do
+ expect(Gitlab::Chaos).to receive(:db_spin).with(30.seconds, 1.second)
+
+ get :db_spin
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls synchronously with params' do
+ expect(Gitlab::Chaos).to receive(:db_spin).with(4.seconds, 5.seconds)
+
+ get :db_spin, params: { duration_s: 4, interval_s: 5 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls asynchronously' do
+ expect(Chaos::DbSpinWorker).to receive(:perform_async).with(30.seconds, 1.second)
+
+ get :db_spin, params: { async: 1 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe '#sleep' do
+ it 'calls synchronously' do
+ expect(Gitlab::Chaos).to receive(:sleep).with(30.seconds)
+
+ get :sleep
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls synchronously with params' do
+ expect(Gitlab::Chaos).to receive(:sleep).with(5.seconds)
+
+ get :sleep, params: { duration_s: 5 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls asynchronously' do
+ expect(Chaos::SleepWorker).to receive(:perform_async).with(30.seconds)
+
+ get :sleep, params: { async: 1 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe '#kill' do
+ it 'calls synchronously' do
+ expect(Gitlab::Chaos).to receive(:kill).with(no_args)
+
+ get :kill
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls asynchronously' do
+ expect(Chaos::KillWorker).to receive(:perform_async).with(no_args)
+
+ get :kill, params: { async: 1 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+end
diff --git a/spec/controllers/concerns/confirm_email_warning_spec.rb b/spec/controllers/concerns/confirm_email_warning_spec.rb
new file mode 100644
index 00000000000..0c598a360af
--- /dev/null
+++ b/spec/controllers/concerns/confirm_email_warning_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ConfirmEmailWarning do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ controller(ApplicationController) do
+ # `described_class` is not available in this context
+ include ConfirmEmailWarning # rubocop:disable RSpec/DescribedClass
+
+ def index
+ head :ok
+ end
+ end
+
+ RSpec::Matchers.define :set_confirm_warning_for do |email|
+ match do |response|
+ expect(response).to set_flash.now[:warning].to include("Please check your email (#{email}) to verify that you own this address.")
+ end
+ end
+
+ describe 'confirm email flash warning' do
+ context 'when not signed in' do
+ let(:user) { create(:user, confirmed_at: nil) }
+
+ before do
+ get :index
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ context 'with a confirmed user' do
+ let(:user) { create(:user) }
+
+ before do
+ get :index
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'with an unconfirmed user' do
+ let(:user) { create(:user, confirmed_at: nil) }
+
+ context 'when executing a peek request' do
+ before do
+ request.path = '/-/peek'
+ get :index
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'when executing a json request' do
+ before do
+ get :index, format: :json
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'when executing a post request' do
+ before do
+ post :index
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'when executing a get request' do
+ before do
+ get :index
+ end
+
+ context 'with an unconfirmed email address present' do
+ let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: 'unconfirmed@gitlab.com') }
+
+ it { is_expected.to set_confirm_warning_for(user.unconfirmed_email) }
+ end
+
+ context 'without an unconfirmed email address present' do
+ it { is_expected.to set_confirm_warning_for(user.email) }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/concerns/group_tree_spec.rb b/spec/controllers/concerns/group_tree_spec.rb
index aa3cd690e3f..835c3d9b3af 100644
--- a/spec/controllers/concerns/group_tree_spec.rb
+++ b/spec/controllers/concerns/group_tree_spec.rb
@@ -30,7 +30,7 @@ describe GroupTree do
expect(assigns(:groups)).to contain_exactly(other_group)
end
- context 'for subgroups', :nested_groups do
+ context 'for subgroups' do
it 'only renders root groups when no parent was given' do
create(:group, :public, parent: group)
@@ -85,7 +85,7 @@ describe GroupTree do
expect(json_response.first['id']).to eq(group.id)
end
- context 'nested groups', :nested_groups do
+ context 'nested groups' do
it 'expands the tree when filtering' do
subgroup = create(:group, :public, parent: group, name: 'filter')
diff --git a/spec/controllers/concerns/issuable_actions_spec.rb b/spec/controllers/concerns/issuable_actions_spec.rb
new file mode 100644
index 00000000000..7b0b4497f3f
--- /dev/null
+++ b/spec/controllers/concerns/issuable_actions_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe IssuableActions do
+ let(:project) { double('project') }
+ let(:user) { double('user') }
+ let(:issuable) { double('issuable') }
+ let(:finder_params_for_issuable) { {} }
+ let(:notes_result) { double('notes_result') }
+ let(:discussion_serializer) { double('discussion_serializer') }
+
+ let(:controller) do
+ klass = Class.new do
+ attr_reader :current_user, :project, :issuable
+
+ def self.before_action(action, params = nil)
+ end
+
+ include IssuableActions
+
+ def initialize(issuable, project, user, finder_params)
+ @issuable = issuable
+ @project = project
+ @current_user = user
+ @finder_params = finder_params
+ end
+
+ def finder_params_for_issuable
+ @finder_params
+ end
+
+ def params
+ {
+ notes_filter: 1
+ }
+ end
+
+ def prepare_notes_for_rendering(notes)
+ []
+ end
+
+ def render(options)
+ end
+ end
+
+ klass.new(issuable, project, user, finder_params_for_issuable)
+ end
+
+ describe '#discussions' do
+ before do
+ allow(user).to receive(:set_notes_filter)
+ allow(user).to receive(:user_preference)
+ allow(discussion_serializer).to receive(:represent)
+ end
+
+ it 'instantiates and calls NotesFinder as expected' do
+ expect(Discussion).to receive(:build_collection).and_return([])
+ expect(DiscussionSerializer).to receive(:new).and_return(discussion_serializer)
+ expect(NotesFinder).to receive(:new).with(user, finder_params_for_issuable).and_call_original
+
+ expect_any_instance_of(NotesFinder).to receive(:execute).and_return(notes_result)
+
+ expect(notes_result).to receive_messages(inc_relations_for_view: notes_result, includes: notes_result, fresh: notes_result)
+
+ controller.discussions
+ end
+ end
+end
diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb
index f210537aad5..7bdf5c49425 100644
--- a/spec/controllers/concerns/issuable_collections_spec.rb
+++ b/spec/controllers/concerns/issuable_collections_spec.rb
@@ -24,78 +24,6 @@ describe IssuableCollections do
controller
end
- describe '#set_sort_order_from_user_preference' do
- describe 'when sort param given' do
- let(:params) { { sort: 'updated_desc' } }
-
- context 'when issuable_sorting_field is defined' do
- before do
- controller.class.define_method(:issuable_sorting_field) { :issues_sort}
- end
-
- it 'sets user_preference with the right value' do
- controller.send(:set_sort_order_from_user_preference)
-
- expect(user.user_preference.reload.issues_sort).to eq('updated_desc')
- end
- end
-
- context 'when no issuable_sorting_field is defined on the controller' do
- it 'does not touch user_preference' do
- allow(user).to receive(:user_preference)
-
- controller.send(:set_sort_order_from_user_preference)
-
- expect(user).not_to have_received(:user_preference)
- end
- end
- end
-
- context 'when a user sorting preference exists' do
- let(:params) { {} }
-
- before do
- controller.class.define_method(:issuable_sorting_field) { :issues_sort }
- end
-
- it 'returns the set preference' do
- user.user_preference.update(issues_sort: 'updated_asc')
-
- sort_preference = controller.send(:set_sort_order_from_user_preference)
-
- expect(sort_preference).to eq('updated_asc')
- end
- end
- end
-
- describe '#set_set_order_from_cookie' do
- describe 'when sort param given' do
- let(:cookies) { {} }
- let(:params) { { sort: 'downvotes_asc' } }
-
- it 'sets the cookie with the right values and flags' do
- allow(controller).to receive(:cookies).and_return(cookies)
-
- controller.send(:set_sort_order_from_cookie)
-
- expect(cookies['issue_sort']).to eq({ value: 'popularity', secure: false, httponly: false })
- end
- end
-
- describe 'when cookie exists' do
- let(:cookies) { { 'issue_sort' => 'id_asc' } }
- let(:params) { {} }
-
- it 'sets the cookie with the right values and flags' do
- allow(controller).to receive(:cookies).and_return(cookies)
-
- controller.send(:set_sort_order_from_cookie)
-
- expect(cookies['issue_sort']).to eq({ value: 'created_asc', secure: false, httponly: false })
- end
- end
- end
-
describe '#page_count_for_relation' do
let(:params) { { state: 'opened' } }
diff --git a/spec/controllers/concerns/sorting_preference_spec.rb b/spec/controllers/concerns/sorting_preference_spec.rb
new file mode 100644
index 00000000000..a36124c6776
--- /dev/null
+++ b/spec/controllers/concerns/sorting_preference_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SortingPreference do
+ let(:user) { create(:user) }
+
+ let(:controller_class) do
+ Class.new do
+ def self.helper_method(name); end
+
+ include SortingPreference
+ include SortingHelper
+ end
+ end
+
+ let(:controller) { controller_class.new }
+
+ before do
+ allow(controller).to receive(:params).and_return(ActionController::Parameters.new(params))
+ allow(controller).to receive(:current_user).and_return(user)
+ allow(controller).to receive(:legacy_sort_cookie_name).and_return('issuable_sort')
+ allow(controller).to receive(:sorting_field).and_return(:issues_sort)
+ end
+
+ describe '#set_sort_order_from_user_preference' do
+ subject { controller.send(:set_sort_order_from_user_preference) }
+
+ context 'when sort param given' do
+ let(:params) { { sort: 'updated_desc' } }
+
+ context 'when sorting_field is defined' do
+ it 'sets user_preference with the right value' do
+ is_expected.to eq('updated_desc')
+ end
+ end
+
+ context 'when no sorting_field is defined on the controller' do
+ before do
+ allow(controller).to receive(:sorting_field).and_return(nil)
+ end
+
+ it 'does not touch user_preference' do
+ expect(user).not_to receive(:user_preference)
+
+ subject
+ end
+ end
+ end
+
+ context 'when a user sorting preference exists' do
+ let(:params) { {} }
+
+ before do
+ user.user_preference.update!(issues_sort: 'updated_asc')
+ end
+
+ it 'returns the set preference' do
+ is_expected.to eq('updated_asc')
+ end
+ end
+ end
+
+ describe '#set_set_order_from_cookie' do
+ subject { controller.send(:set_sort_order_from_cookie) }
+
+ before do
+ allow(controller).to receive(:cookies).and_return(cookies)
+ end
+
+ context 'when sort param given' do
+ let(:cookies) { {} }
+ let(:params) { { sort: 'downvotes_asc' } }
+
+ it 'sets the cookie with the right values and flags' do
+ subject
+
+ expect(cookies['issue_sort']).to eq(value: 'popularity', secure: false, httponly: false)
+ end
+ end
+
+ context 'when cookie exists' do
+ let(:cookies) { { 'issue_sort' => 'id_asc' } }
+ let(:params) { {} }
+
+ it 'sets the cookie with the right values and flags' do
+ subject
+
+ expect(cookies['issue_sort']).to eq(value: 'created_asc', secure: false, httponly: false)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/dashboard/groups_controller_spec.rb b/spec/controllers/dashboard/groups_controller_spec.rb
index 48373d29412..20a0951423b 100644
--- a/spec/controllers/dashboard/groups_controller_spec.rb
+++ b/spec/controllers/dashboard/groups_controller_spec.rb
@@ -26,7 +26,7 @@ describe Dashboard::GroupsController do
expect(assigns(:groups)).to contain_exactly(member_of_group)
end
- context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do
+ context 'when rendering an expanded hierarchy with public groups you are not a member of' do
let!(:top_level_result) { create(:group, name: 'chef-top') }
let!(:top_level_a) { create(:group, name: 'top-a') }
let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) }
diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb
index 4de537ae6f8..67939aa4e6a 100644
--- a/spec/controllers/dashboard/milestones_controller_spec.rb
+++ b/spec/controllers/dashboard/milestones_controller_spec.rb
@@ -47,6 +47,8 @@ describe Dashboard::MilestonesController do
describe "#index" do
let(:public_group) { create(:group, :public) }
let!(:public_milestone) { create(:milestone, group: public_group) }
+ let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') }
+ let!(:closed_project_milestone) { create(:milestone, project: project, state: 'closed') }
render_views
@@ -59,6 +61,15 @@ describe Dashboard::MilestonesController do
expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name)
end
+ it 'returns closed group and project milestones to which the user belongs' do
+ get :index, params: { state: 'closed' }, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |i| i["name"] }).to match_array([closed_group_milestone.name, closed_project_milestone.name])
+ expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name)
+ end
+
it 'searches legacy project milestones by title when search_title is given' do
project_milestone = create(:milestone, title: 'Project milestone title', project: project)
@@ -77,11 +88,11 @@ describe Dashboard::MilestonesController do
expect(response.body).not_to include(project_milestone.title)
end
- it 'shows counts of group and project milestones to which the user belongs to' do
+ it 'shows counts of open and closed group and project milestones to which the user belongs to' do
get :index
expect(response.body).to include("Open\n<span class=\"badge badge-pill\">2</span>")
- expect(response.body).to include("Closed\n<span class=\"badge badge-pill\">0</span>")
+ expect(response.body).to include("Closed\n<span class=\"badge badge-pill\">2</span>")
end
context 'external authorization' do
diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb
index ea68eae12ed..8b95c9f2496 100644
--- a/spec/controllers/dashboard/projects_controller_spec.rb
+++ b/spec/controllers/dashboard/projects_controller_spec.rb
@@ -11,8 +11,10 @@ describe Dashboard::ProjectsController do
end
context 'user logged in' do
+ let(:user) { create(:user) }
+
before do
- sign_in create(:user)
+ sign_in(user)
end
context 'external authorization' do
@@ -24,6 +26,28 @@ describe Dashboard::ProjectsController do
expect(response).to have_gitlab_http_status(200)
end
end
+
+ it 'orders the projects by last activity by default' do
+ project = create(:project)
+ project.add_developer(user)
+ project.update!(last_repository_updated_at: 3.days.ago, last_activity_at: 3.days.ago)
+
+ project2 = create(:project)
+ project2.add_developer(user)
+ project2.update!(last_repository_updated_at: 10.days.ago, last_activity_at: 10.days.ago)
+
+ get :index
+
+ expect(assigns(:projects)).to eq([project, project2])
+ end
+
+ context 'project sorting' do
+ let(:project) { create(:project) }
+
+ it_behaves_like 'set sort order from user preference' do
+ let(:sorting_param) { 'created_asc' }
+ end
+ end
end
end
diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb
index 463586ee422..6752d2b8ebd 100644
--- a/spec/controllers/explore/projects_controller_spec.rb
+++ b/spec/controllers/explore/projects_controller_spec.rb
@@ -3,56 +3,91 @@
require 'spec_helper'
describe Explore::ProjectsController do
- describe 'GET #index.json' do
- render_views
+ shared_examples 'explore projects' do
+ describe 'GET #index.json' do
+ render_views
- before do
- get :index, format: :json
+ before do
+ get :index, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
end
- it { is_expected.to respond_with(:success) }
- end
+ describe 'GET #trending.json' do
+ render_views
- describe 'GET #trending.json' do
- render_views
+ before do
+ get :trending, format: :json
+ end
- before do
- get :trending, format: :json
+ it { is_expected.to respond_with(:success) }
+ end
+
+ describe 'GET #starred.json' do
+ render_views
+
+ before do
+ get :starred, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
end
- it { is_expected.to respond_with(:success) }
+ describe 'GET #trending' do
+ context 'sorting by update date' do
+ let(:project1) { create(:project, :public, updated_at: 3.days.ago) }
+ let(:project2) { create(:project, :public, updated_at: 1.day.ago) }
+
+ before do
+ create(:trending_project, project: project1)
+ create(:trending_project, project: project2)
+ end
+
+ it 'sorts by last updated' do
+ get :trending, params: { sort: 'updated_desc' }
+
+ expect(assigns(:projects)).to eq [project2, project1]
+ end
+
+ it 'sorts by oldest updated' do
+ get :trending, params: { sort: 'updated_asc' }
+
+ expect(assigns(:projects)).to eq [project1, project2]
+ end
+ end
+ end
end
- describe 'GET #starred.json' do
- render_views
+ context 'when user is signed in' do
+ let(:user) { create(:user) }
before do
- get :starred, format: :json
+ sign_in(user)
end
- it { is_expected.to respond_with(:success) }
- end
+ include_examples 'explore projects'
- describe 'GET #trending' do
- context 'sorting by update date' do
- let(:project1) { create(:project, :public, updated_at: 3.days.ago) }
- let(:project2) { create(:project, :public, updated_at: 1.day.ago) }
+ context 'user preference sorting' do
+ let(:project) { create(:project) }
- before do
- create(:trending_project, project: project1)
- create(:trending_project, project: project2)
+ it_behaves_like 'set sort order from user preference' do
+ let(:sorting_param) { 'created_asc' }
end
+ end
+ end
- it 'sorts by last updated' do
- get :trending, params: { sort: 'updated_desc' }
+ context 'when user is not signed in' do
+ include_examples 'explore projects'
- expect(assigns(:projects)).to eq [project2, project1]
- end
+ context 'user preference sorting' do
+ let(:project) { create(:project) }
+ let(:sorting_param) { 'created_asc' }
- it 'sorts by oldest updated' do
- get :trending, params: { sort: 'updated_asc' }
+ it 'does not set sort order from user preference' do
+ expect_any_instance_of(UserPreference).not_to receive(:update)
- expect(assigns(:projects)).to eq [project1, project2]
+ get :index, params: { sort: sorting_param }
end
end
end
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
index c19a752b07b..9937bdf4061 100644
--- a/spec/controllers/graphql_controller_spec.rb
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -7,6 +7,27 @@ describe GraphqlController do
stub_feature_flags(graphql: true)
end
+ describe 'ArgumentError' do
+ let(:user) { create(:user) }
+ let(:message) { 'green ideas sleep furiously' }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'handles argument errors' do
+ allow(subject).to receive(:execute) do
+ raise Gitlab::Graphql::Errors::ArgumentError, message
+ end
+
+ post :execute
+
+ expect(json_response).to include(
+ 'errors' => include(a_hash_including('message' => message))
+ )
+ end
+ end
+
describe 'POST #execute' do
context 'when user is logged in' do
let(:user) { create(:user) }
diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb
index 5e0f64ccca4..e4232c2c1ab 100644
--- a/spec/controllers/groups/boards_controller_spec.rb
+++ b/spec/controllers/groups/boards_controller_spec.rb
@@ -63,10 +63,8 @@ describe Groups::BoardsController do
list_boards format: :json
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('boards')
- expect(parsed_response.length).to eq 1
+ expect(json_response.length).to eq 1
end
context 'with unauthorized user' do
diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb
index 02fb971bd9a..bced300a24c 100644
--- a/spec/controllers/groups/children_controller_spec.rb
+++ b/spec/controllers/groups/children_controller_spec.rb
@@ -46,7 +46,7 @@ describe Groups::ChildrenController do
end
end
- context 'for subgroups', :nested_groups do
+ context 'for subgroups' do
let!(:public_subgroup) { create(:group, :public, parent: group) }
let!(:private_subgroup) { create(:group, :private, parent: group) }
let!(:public_project) { create(:project, :public, namespace: group) }
@@ -292,7 +292,7 @@ describe Groups::ChildrenController do
end
end
- context 'with subgroups and projects', :nested_groups do
+ context 'with subgroups and projects' do
let!(:first_page_subgroups) { create_list(:group, per_page, :public, parent: group) }
let!(:other_subgroup) { create(:group, :public, parent: group) }
let!(:next_page_projects) { create_list(:project, per_page, :public, namespace: group) }
diff --git a/spec/controllers/groups/clusters/applications_controller_spec.rb b/spec/controllers/groups/clusters/applications_controller_spec.rb
index 16a63536ea6..21533d1c89a 100644
--- a/spec/controllers/groups/clusters/applications_controller_spec.rb
+++ b/spec/controllers/groups/clusters/applications_controller_spec.rb
@@ -91,7 +91,7 @@ describe Groups::Clusters::ApplicationsController do
patch :update, params: params.merge(group_id: group)
end
- let!(:application) { create(:clusters_applications_cert_managers, :installed, cluster: cluster) }
+ let!(:application) { create(:clusters_applications_cert_manager, :installed, cluster: cluster) }
let(:application_name) { application.name }
let(:params) { { application: application_name, id: cluster.id, email: "new-email@example.com" } }
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index 413598ddde0..0c3dd971582 100644
--- a/spec/controllers/groups/group_members_controller_spec.rb
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -16,6 +16,39 @@ describe Groups::GroupMembersController do
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template(:index)
end
+
+ context 'user with owner access' do
+ let!(:invited) { create_list(:group_member, 3, :invited, group: group) }
+
+ before do
+ group.add_owner(user)
+ sign_in(user)
+ end
+
+ it 'assigns invited members' do
+ get :index, params: { group_id: group }
+
+ expect(assigns(:invited_members).map(&:invite_email)).to match_array(invited.map(&:invite_email))
+ end
+
+ it 'restricts search to one email' do
+ get :index, params: { group_id: group, search_invited: invited.first.invite_email }
+
+ expect(assigns(:invited_members).map(&:invite_email)).to match_array(invited.first.invite_email)
+ end
+
+ it 'paginates invited list' do
+ stub_const('Groups::GroupMembersController::MEMBER_PER_PAGE_LIMIT', 2)
+
+ get :index, params: { group_id: group, invited_members_page: 1 }
+
+ expect(assigns(:invited_members).count).to eq(2)
+
+ get :index, params: { group_id: group, invited_members_page: 2 }
+
+ expect(assigns(:invited_members).count).to eq(1)
+ end
+ end
end
describe 'POST create' do
@@ -139,7 +172,7 @@ describe Groups::GroupMembersController do
it '[JS] removes user from members' do
delete :destroy, params: { group_id: group, id: member }, xhr: true
- expect(response).to be_success
+ expect(response).to be_successful
expect(group.members).not_to include member
end
end
diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb
index 3cc6fc6f066..98a4c50fc49 100644
--- a/spec/controllers/groups/labels_controller_spec.rb
+++ b/spec/controllers/groups/labels_controller_spec.rb
@@ -24,7 +24,7 @@ describe Groups::LabelsController do
expect(label_ids).to match_array([label_1.title, group_label_1.title])
end
- context 'with ancestor group', :nested_groups do
+ context 'with ancestor group' do
set(:subgroup) { create(:group, parent: group) }
set(:subgroup_label_1) { create(:group_label, group: subgroup, title: 'subgroup_label_1') }
@@ -32,7 +32,7 @@ describe Groups::LabelsController do
subgroup.add_owner(user)
end
- it 'returns ancestor group labels', :nested_groups do
+ it 'returns ancestor group labels' do
get :index, params: { group_id: subgroup, include_ancestor_groups: true, only_group_labels: true }, format: :json
label_ids = json_response.map {|label| label['title']}
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index 19b18091aef..41927907fd1 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -73,7 +73,7 @@ describe Groups::MilestonesController do
it 'lists legacy group milestones and group milestones' do
get :index, params: { group_id: group.to_param }, format: :json
- milestones = JSON.parse(response.body)
+ milestones = json_response
expect(milestones.count).to eq(2)
expect(milestones.first["title"]).to eq("group milestone")
@@ -186,7 +186,7 @@ describe Groups::MilestonesController do
it "removes milestone" do
delete :destroy, params: { group_id: group.to_param, id: milestone.iid }, format: :js
- expect(response).to be_success
+ expect(response).to be_successful
expect { Milestone.find(milestone.id) }.to raise_exception(ActiveRecord::RecordNotFound)
end
end
diff --git a/spec/controllers/groups/uploads_controller_spec.rb b/spec/controllers/groups/uploads_controller_spec.rb
index 0f99a957581..60342bf8e3d 100644
--- a/spec/controllers/groups/uploads_controller_spec.rb
+++ b/spec/controllers/groups/uploads_controller_spec.rb
@@ -10,6 +10,11 @@ describe Groups::UploadsController do
{ group_id: model }
end
+ let(:other_model) { create(:group, :public) }
+ let(:other_params) do
+ { group_id: other_model }
+ end
+
it_behaves_like 'handle uploads' do
let(:uploader_class) { NamespaceFileUploader }
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 47d7e278183..404e61c5271 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -89,7 +89,7 @@ describe GroupsController do
end
describe 'GET #new' do
- context 'when creating subgroups', :nested_groups do
+ context 'when creating subgroups' do
[true, false].each do |can_create_group_status|
context "and can_create_group is #{can_create_group_status}" do
before do
@@ -125,11 +125,14 @@ describe GroupsController do
end
context 'as json' do
- it 'includes all projects in event feed' do
- 3.times do
+ it 'includes all projects from groups and subgroups in event feed' do
+ 2.times do
project = create(:project, group: group)
create(:event, project: project)
end
+ subgroup = create(:group, parent: group)
+ project = create(:project, group: subgroup)
+ create(:event, project: project)
get :activity, params: { id: group.to_param }, format: :json
@@ -163,7 +166,7 @@ describe GroupsController do
end
end
- context 'when creating subgroups', :nested_groups do
+ context 'when creating subgroups' do
[true, false].each do |can_create_group_status|
context "and can_create_group is #{can_create_group_status}" do
context 'and logged in as Owner' do
@@ -581,7 +584,7 @@ describe GroupsController do
end
end
- describe 'PUT transfer', :postgresql do
+ describe 'PUT transfer' do
before do
sign_in(user)
end
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
index 19d739fcf4f..b48b7dc86e0 100644
--- a/spec/controllers/health_check_controller_spec.rb
+++ b/spec/controllers/health_check_controller_spec.rb
@@ -5,7 +5,6 @@ require 'spec_helper'
describe HealthCheckController do
include StubENV
- let(:json_response) { JSON.parse(response.body) }
let(:xml_response) { Hash.from_xml(response.body)['hash'] }
let(:token) { Gitlab::CurrentSettings.health_check_access_token }
let(:whitelisted_ip) { '127.0.0.1' }
@@ -34,14 +33,14 @@ describe HealthCheckController do
get :index
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'text/plain'
end
it 'supports passing the token in query params' do
get :index, params: { token: token }
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'text/plain'
end
end
@@ -55,14 +54,14 @@ describe HealthCheckController do
it 'supports successful plaintext response' do
get :index
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'text/plain'
end
it 'supports successful json response' do
get :index, format: :json
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be true
end
@@ -70,7 +69,7 @@ describe HealthCheckController do
it 'supports successful xml response' do
get :index, format: :xml
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'application/xml'
expect(xml_response['healthy']).to be true
end
@@ -78,7 +77,7 @@ describe HealthCheckController do
it 'supports successful responses for specific checks' do
get :index, params: { checks: 'email' }, format: :json
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be true
end
diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb
index fc62a8310aa..e82dcfcdb64 100644
--- a/spec/controllers/health_controller_spec.rb
+++ b/spec/controllers/health_controller_spec.rb
@@ -5,7 +5,6 @@ require 'spec_helper'
describe HealthController do
include StubENV
- let(:json_response) { JSON.parse(response.body) }
let(:token) { Gitlab::CurrentSettings.health_check_access_token }
let(:whitelisted_ip) { '127.0.0.1' }
let(:not_whitelisted_ip) { '127.0.0.2' }
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index dbfacf4e42e..03b6e85b653 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -111,10 +111,10 @@ describe HelpController do
it 'renders the raw file' do
get :show,
params: {
- path: 'user/project/img/labels_default'
+ path: 'user/project/img/labels_default_v12_1'
},
format: :png
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'image/png'
expect(response.headers['Content-Disposition']).to match(/^inline;/)
end
diff --git a/spec/controllers/ide_controller_spec.rb b/spec/controllers/ide_controller_spec.rb
new file mode 100644
index 00000000000..0462f9520d5
--- /dev/null
+++ b/spec/controllers/ide_controller_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe IdeController do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'increases the views counter' do
+ expect(Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_views_count)
+
+ get :index
+ end
+end
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index 64a66502732..38388c21749 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -231,7 +231,7 @@ describe Import::BitbucketController do
end
end
- context 'user has chosen an existing nested namespace and name for the project', :postgresql do
+ context 'user has chosen an existing nested namespace and name for the project' do
let(:parent_namespace) { create(:group, name: 'foo') }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' }
@@ -250,7 +250,7 @@ describe Import::BitbucketController do
end
end
- context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen a non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do
@@ -281,7 +281,7 @@ describe Import::BitbucketController do
end
end
- context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo') }
diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb
index b89d7317b9c..e1aeab46fca 100644
--- a/spec/controllers/import/bitbucket_server_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_server_controller_spec.rb
@@ -134,6 +134,8 @@ describe Import::BitbucketServerController do
describe 'GET status' do
render_views
+ let(:repos) { instance_double(BitbucketServer::Collection) }
+
before do
allow(controller).to receive(:bitbucket_client).and_return(client)
@@ -145,7 +147,6 @@ describe Import::BitbucketServerController do
it 'assigns repository categories' do
created_project = create(:project, :import_finished, import_type: 'bitbucket_server', creator_id: user.id, import_source: @created_repo.browse_url)
- repos = instance_double(BitbucketServer::Collection)
expect(repos).to receive(:partition).and_return([[@repo, @created_repo], [@invalid_repo]])
expect(repos).to receive(:current_page).and_return(1)
@@ -159,6 +160,17 @@ describe Import::BitbucketServerController do
expect(assigns(:repos)).to eq([@repo])
expect(assigns(:incompatible_repos)).to eq([@invalid_repo])
end
+
+ context 'when filtering' do
+ let(:filter) { 'test' }
+
+ it 'passes filter param to bitbucket client' do
+ expect(repos).to receive(:partition).and_return([[@repo, @created_repo], [@invalid_repo]])
+ expect(client).to receive(:repos).with(filter: filter, limit: 25, page_offset: 0).and_return(repos)
+
+ get :status, params: { filter: filter }, as: :json
+ end
+ end
end
describe 'GET jobs' do
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index 059354870b5..5675798ac33 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -33,6 +33,16 @@ describe Import::GithubController do
expect(response).to have_http_status(200)
end
+
+ context 'when importing a CI/CD project' do
+ it 'always prompts for an access token' do
+ allow(controller).to receive(:github_import_configured?).and_return(true)
+
+ get :new, params: { ci_cd_only: true }
+
+ expect(response).to render_template(:new)
+ end
+ end
end
describe "GET callback" do
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index 5af7572e74e..e465eca6c71 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -197,7 +197,7 @@ describe Import::GitlabController do
end
end
- context 'user has chosen an existing nested namespace for the project', :postgresql do
+ context 'user has chosen an existing nested namespace for the project' do
let(:parent_namespace) { create(:group, name: 'foo') }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
@@ -215,7 +215,7 @@ describe Import::GitlabController do
end
end
- context 'user has chosen a non-existent nested namespaces for the project', :postgresql do
+ context 'user has chosen a non-existent nested namespaces for the project' do
let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do
@@ -246,7 +246,7 @@ describe Import::GitlabController do
end
end
- context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo') }
diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb
index ee454a7818c..7fb3578cd0a 100644
--- a/spec/controllers/metrics_controller_spec.rb
+++ b/spec/controllers/metrics_controller_spec.rb
@@ -5,13 +5,19 @@ require 'spec_helper'
describe MetricsController do
include StubENV
- let(:json_response) { JSON.parse(response.body) }
- let(:metrics_multiproc_dir) { Dir.mktmpdir }
+ let(:metrics_multiproc_dir) { @metrics_multiproc_dir }
let(:whitelisted_ip) { '127.0.0.1' }
let(:whitelisted_ip_range) { '10.0.0.0/24' }
let(:ip_in_whitelisted_range) { '10.0.0.1' }
let(:not_whitelisted_ip) { '10.0.1.1' }
+ around do |example|
+ Dir.mktmpdir do |path|
+ @metrics_multiproc_dir = path
+ example.run
+ end
+ end
+
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(metrics_multiproc_dir)
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index 228c97d591d..df836c2c3e3 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -17,7 +17,7 @@ describe Oauth::ApplicationsController do
expect(response).to have_gitlab_http_status(200)
end
- it 'shows list of applications' do
+ it 'redirects back to profile page if OAuth applications are disabled' do
disable_user_oauth
get :index
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 753eb432c5e..3bed117deb0 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -10,7 +10,7 @@ describe Profiles::KeysController do
it "does not generally work" do
get :get_keys, params: { username: 'not-existent' }
- expect(response).not_to be_success
+ expect(response).not_to be_successful
end
end
@@ -18,7 +18,7 @@ describe Profiles::KeysController do
it "does generally work" do
get :get_keys, params: { username: user.username }
- expect(response).to be_success
+ expect(response).to be_successful
end
it "renders all keys separated with a new line" do
@@ -41,7 +41,7 @@ describe Profiles::KeysController do
it "does generally work" do
get :get_keys, params: { username: user.username }
- expect(response).to be_success
+ expect(response).to be_successful
end
it "renders all non deploy keys separated with a new line" do
diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb
index 5ec8d8d41d7..4ae29ba7f54 100644
--- a/spec/controllers/projects/badges_controller_spec.rb
+++ b/spec/controllers/projects/badges_controller_spec.rb
@@ -7,51 +7,115 @@ describe Projects::BadgesController do
let!(:pipeline) { create(:ci_empty_pipeline) }
let(:user) { create(:user) }
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ shared_examples 'a badge resource' do |badge_type|
+ context 'when pipelines are public' do
+ before do
+ project.update!(public_builds: true)
+ end
+
+ context 'when project is public' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ it "returns the #{badge_type} badge to unauthenticated users" do
+ get_badge(badge_type)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when project is restricted' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ project.add_guest(user)
+ sign_in(user)
+ end
+
+ it "returns the #{badge_type} badge to guest users" do
+ get_badge(badge_type)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
- it 'requests the pipeline badge successfully' do
- get_badge(:pipeline)
+ context 'format' do
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
- expect(response).to have_gitlab_http_status(:ok)
- end
+ it 'renders the `flat` badge layout by default' do
+ get_badge(badge_type)
- it 'requests the coverage badge successfully' do
- get_badge(:coverage)
+ expect(response).to render_template('projects/badges/badge')
+ end
- expect(response).to have_gitlab_http_status(:ok)
- end
+ context 'when style param is set to `flat`' do
+ it 'renders the `flat` badge layout' do
+ get_badge(badge_type, 'flat')
- it 'renders the `flat` badge layout by default' do
- get_badge(:coverage)
+ expect(response).to render_template('projects/badges/badge')
+ end
+ end
- expect(response).to render_template('projects/badges/badge')
- end
+ context 'when style param is set to an invalid type' do
+ it 'renders the `flat` (default) badge layout' do
+ get_badge(badge_type, 'xxx')
+
+ expect(response).to render_template('projects/badges/badge')
+ end
+ end
- context 'when style param is set to `flat`' do
- it 'renders the `flat` badge layout' do
- get_badge(:coverage, 'flat')
+ context 'when style param is set to `flat-square`' do
+ it 'renders the `flat-square` badge layout' do
+ get_badge(badge_type, 'flat-square')
- expect(response).to render_template('projects/badges/badge')
+ expect(response).to render_template('projects/badges/badge_flat-square')
+ end
+ end
end
- end
- context 'when style param is set to an invalid type' do
- it 'renders the `flat` (default) badge layout' do
- get_badge(:coverage, 'xxx')
+ context 'when pipelines are not public' do
+ before do
+ project.update!(public_builds: false)
+ end
- expect(response).to render_template('projects/badges/badge')
+ context 'when project is public' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ it 'returns 404 to unauthenticated users' do
+ get_badge(badge_type)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when project is restricted to the user' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ project.add_guest(user)
+ sign_in(user)
+ end
+
+ it 'defaults to project permissions' do
+ get_badge(:coverage)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
end
end
- context 'when style param is set to `flat-square`' do
- it 'renders the `flat-square` badge layout' do
- get_badge(:coverage, 'flat-square')
+ describe '#pipeline' do
+ it_behaves_like 'a badge resource', :pipeline
+ end
- expect(response).to render_template('projects/badges/badge_flat-square')
- end
+ describe '#coverage' do
+ it_behaves_like 'a badge resource', :coverage
end
def get_badge(badge, style = nil)
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 44500d3cde3..45aebd1554c 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -160,7 +160,7 @@ describe Projects::BlobController do
it 'renders diff context lines Gitlab::Diff::Line array' do
do_get(since: 1, to: 2, offset: 0, from_merge_request: true)
- lines = JSON.parse(response.body)
+ lines = json_response
expect(lines.size).to eq(diff_lines.size)
lines.each do |line|
@@ -173,7 +173,7 @@ describe Projects::BlobController do
it 'handles full being true' do
do_get(full: true, from_merge_request: true)
- lines = JSON.parse(response.body)
+ lines = json_response
expect(lines.size).to eq(diff_lines.size)
end
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index c07afc57aea..543479d8dd5 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -69,10 +69,8 @@ describe Projects::BoardsController do
list_boards format: :json
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('boards')
- expect(parsed_response.length).to eq 2
+ expect(json_response.length).to eq 2
end
context 'with unauthorized user' do
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index cf201c9f735..f5bcea4a097 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -92,7 +92,7 @@ describe Projects::BranchesController do
end
it 'posts a system note' do
- expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch")
+ expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch", branch_project: project)
post :create,
params: {
@@ -103,6 +103,75 @@ describe Projects::BranchesController do
}
end
+ context 'confidential_issue_project_id is present' do
+ let(:confidential_issue_project) { create(:project) }
+
+ def create_branch_with_confidential_issue_project
+ post(
+ :create,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ branch_name: branch,
+ confidential_issue_project_id: confidential_issue_project.id,
+ issue_iid: issue.iid
+ }
+ )
+ end
+
+ context 'create_confidential_merge_request feature is enabled' do
+ before do
+ stub_feature_flags(create_confidential_merge_request: true)
+ end
+
+ context 'user cannot update issue' do
+ let(:issue) { create(:issue, project: confidential_issue_project) }
+
+ it 'does not post a system note' do
+ expect(SystemNoteService).not_to receive(:new_issue_branch)
+
+ create_branch_with_confidential_issue_project
+ end
+ end
+
+ context 'user can update issue' do
+ before do
+ confidential_issue_project.add_reporter(user)
+ end
+
+ context 'issue is under the specified project' do
+ let(:issue) { create(:issue, project: confidential_issue_project) }
+
+ it 'posts a system note' do
+ expect(SystemNoteService).to receive(:new_issue_branch).with(issue, confidential_issue_project, user, "1-feature-branch", branch_project: project)
+
+ create_branch_with_confidential_issue_project
+ end
+ end
+
+ context 'issue is not under the specified project' do
+ it 'does not post a system note' do
+ expect(SystemNoteService).not_to receive(:new_issue_branch)
+
+ create_branch_with_confidential_issue_project
+ end
+ end
+ end
+ end
+
+ context 'create_confidential_merge_request feature is disabled' do
+ before do
+ stub_feature_flags(create_confidential_merge_request: false)
+ end
+
+ it 'posts a system note on project' do
+ expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch", branch_project: project)
+
+ create_branch_with_confidential_issue_project
+ end
+ end
+ end
+
context 'repository-less project' do
let(:project) { create :project }
@@ -426,10 +495,8 @@ describe Projects::BranchesController do
search: 'master'
}
- parsed_response = JSON.parse(response.body)
-
- expect(parsed_response.length).to eq 1
- expect(parsed_response.first).to eq 'master'
+ expect(json_response.length).to eq 1
+ expect(json_response.first).to eq 'master'
end
end
@@ -507,4 +574,26 @@ describe Projects::BranchesController do
end
end
end
+
+ describe 'GET diverging_commit_counts' do
+ before do
+ sign_in(user)
+
+ get :diverging_commit_counts,
+ format: :json,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ names: ['fix', 'add-pdf-file', 'branch-merged']
+ }
+ end
+
+ it 'returns the commit counts behind and ahead of default branch' do
+ expect(json_response).to eq(
+ "fix" => { "behind" => 29, "ahead" => 2 },
+ "branch-merged" => { "behind" => 1, "ahead" => 0 },
+ "add-pdf-file" => { "behind" => 0, "ahead" => 3 }
+ )
+ end
+ end
end
diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb
index 96e82b7086c..14128fb5b0e 100644
--- a/spec/controllers/projects/ci/lints_controller_spec.rb
+++ b/spec/controllers/projects/ci/lints_controller_spec.rb
@@ -20,9 +20,7 @@ describe Projects::Ci::LintsController do
get :show, params: { namespace_id: project.namespace, project_id: project }
end
- it 'is success' do
- expect(response).to be_success
- end
+ it { expect(response).to be_successful }
it 'renders show page' do
expect(response).to render_template :show
@@ -78,9 +76,7 @@ describe Projects::Ci::LintsController do
post :create, params: { namespace_id: project.namespace, project_id: project, content: content }
end
- it 'is success' do
- expect(response).to be_success
- end
+ it { expect(response).to be_successful }
it 'render show page' do
expect(response).to render_template :show
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index b5c6382a26d..afd5cb15e0f 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -45,14 +45,14 @@ describe Projects::CommitController do
it 'handles binary files' do
go(id: TestEnv::BRANCH_SHA['binary-encoding'], format: 'html')
- expect(response).to be_success
+ expect(response).to be_successful
end
shared_examples "export as" do |format|
it "does generally work" do
go(id: commit.id, format: format)
- expect(response).to be_success
+ expect(response).to be_successful
end
it "generates it" do
@@ -110,7 +110,7 @@ describe Projects::CommitController do
id: commit.id
})
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -177,7 +177,7 @@ describe Projects::CommitController do
id: commit.id
})
- expect(response).not_to be_success
+ expect(response).not_to be_successful
expect(response).to have_gitlab_http_status(404)
end
end
@@ -234,7 +234,7 @@ describe Projects::CommitController do
id: master_pickable_commit.id
})
- expect(response).not_to be_success
+ expect(response).not_to be_successful
expect(response).to have_gitlab_http_status(404)
end
end
@@ -378,8 +378,8 @@ describe Projects::CommitController do
get_pipelines(id: commit.id, format: :json)
expect(response).to be_ok
- expect(JSON.parse(response.body)['pipelines']).not_to be_empty
- expect(JSON.parse(response.body)['count']['all']).to eq 1
+ expect(json_response['pipelines']).not_to be_empty
+ expect(json_response['count']['all']).to eq 1
expect(response).to include_pagination_headers
end
end
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 9db1ac2a46c..9c4d6fdcb2a 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -79,7 +79,7 @@ describe Projects::CommitsController do
end
it "renders as atom" do
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq('application/atom+xml')
end
@@ -104,7 +104,7 @@ describe Projects::CommitsController do
end
it "renders as HTML" do
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq('text/html')
end
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 92380a2bf09..9afc46c4be9 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -19,7 +19,7 @@ describe Projects::CompareController do
end
it 'returns successfully' do
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -49,7 +49,7 @@ describe Projects::CompareController do
it 'shows some diffs with ignore whitespace change option' do
show_request
- expect(response).to be_success
+ expect(response).to be_successful
diff_file = assigns(:diffs).diff_files.first
expect(diff_file).not_to be_nil
expect(assigns(:commits).length).to be >= 1
@@ -67,7 +67,7 @@ describe Projects::CompareController do
it 'sets the diffs and commits ivars' do
show_request
- expect(response).to be_success
+ expect(response).to be_successful
expect(assigns(:diffs).diff_files.first).not_to be_nil
expect(assigns(:commits).length).to be >= 1
end
@@ -81,7 +81,7 @@ describe Projects::CompareController do
it 'sets empty diff and commit ivars' do
show_request
- expect(response).to be_success
+ expect(response).to be_successful
expect(assigns(:diffs)).to eq([])
expect(assigns(:commits)).to eq([])
end
@@ -94,7 +94,7 @@ describe Projects::CompareController do
it 'sets empty diff and commit ivars' do
show_request
- expect(response).to be_success
+ expect(response).to be_successful
expect(assigns(:diffs)).to eq([])
expect(assigns(:commits)).to eq([])
end
@@ -302,8 +302,7 @@ describe Projects::CompareController do
signatures_request
expect(response).to have_gitlab_http_status(200)
- parsed_body = JSON.parse(response.body)
- signatures = parsed_body['signatures']
+ signatures = json_response['signatures']
expect(signatures.size).to eq(1)
expect(signatures.first['commit_sha']).to eq(signature_commit.sha)
@@ -332,8 +331,7 @@ describe Projects::CompareController do
signatures_request
expect(response).to have_gitlab_http_status(200)
- parsed_body = JSON.parse(response.body)
- expect(parsed_body['signatures']).to be_empty
+ expect(json_response['signatures']).to be_empty
end
end
@@ -345,8 +343,7 @@ describe Projects::CompareController do
signatures_request
expect(response).to have_gitlab_http_status(200)
- parsed_body = JSON.parse(response.body)
- expect(parsed_body['signatures']).to be_empty
+ expect(json_response['signatures']).to be_empty
end
end
end
diff --git a/spec/controllers/projects/cycle_analytics/events_controller_spec.rb b/spec/controllers/projects/cycle_analytics/events_controller_spec.rb
new file mode 100644
index 00000000000..b828c678d0c
--- /dev/null
+++ b/spec/controllers/projects/cycle_analytics/events_controller_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::CycleAnalytics::EventsController do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ project.add_maintainer(user)
+ end
+
+ describe 'cycle analytics not set up flag' do
+ context 'with no data' do
+ it 'is empty' do
+ get_issue
+
+ expect(response).to be_successful
+ expect(JSON.parse(response.body)['events']).to be_empty
+ end
+ end
+
+ context 'with data' do
+ let(:milestone) { create(:milestone, project: project, created_at: 10.days.ago) }
+ let(:issue) { create(:issue, project: project, created_at: 9.days.ago) }
+
+ before do
+ issue.update(milestone: milestone)
+ end
+
+ it 'is not empty' do
+ get_issue
+
+ expect(response).to be_successful
+ end
+
+ it 'contains event detais' do
+ get_issue
+
+ events = JSON.parse(response.body)['events']
+
+ expect(events).not_to be_empty
+ expect(events.first).to include('title', 'author', 'iid', 'total_time', 'created_at', 'url')
+ expect(events.first['title']).to eq(issue.title)
+ end
+
+ context 'with data older than start date' do
+ it 'is empty' do
+ get_issue(additional_params: { cycle_analytics: { start_date: 7 } })
+
+ expect(response).to be_successful
+
+ expect(JSON.parse(response.body)['events']).to be_empty
+ end
+ end
+ end
+ end
+
+ def get_issue(additional_params: {})
+ params = additional_params.merge(namespace_id: project.namespace, project_id: project)
+ get(:issue, params: params, format: :json)
+ end
+end
diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb
index 2dc97e18113..65eee7b8ead 100644
--- a/spec/controllers/projects/cycle_analytics_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb
@@ -11,6 +11,20 @@ describe Projects::CycleAnalyticsController do
project.add_maintainer(user)
end
+ context "counting page views for 'show'" do
+ it 'increases the counter' do
+ expect(Gitlab::UsageDataCounters::CycleAnalyticsCounter).to receive(:count).with(:views)
+
+ get(:show,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project
+ })
+
+ expect(response).to be_successful
+ end
+ end
+
describe 'cycle analytics not set up flag' do
context 'with no data' do
it 'is true' do
@@ -20,7 +34,7 @@ describe Projects::CycleAnalyticsController do
project_id: project
})
- expect(response).to be_success
+ expect(response).to be_successful
expect(assigns(:cycle_analytics_no_data)).to eq(true)
end
end
@@ -41,7 +55,7 @@ describe Projects::CycleAnalyticsController do
project_id: project
})
- expect(response).to be_success
+ expect(response).to be_successful
expect(assigns(:cycle_analytics_no_data)).to eq(false)
end
end
diff --git a/spec/controllers/projects/deploy_keys_controller_spec.rb b/spec/controllers/projects/deploy_keys_controller_spec.rb
index fcd14f13863..ccad76eaddd 100644
--- a/spec/controllers/projects/deploy_keys_controller_spec.rb
+++ b/spec/controllers/projects/deploy_keys_controller_spec.rb
@@ -52,12 +52,10 @@ describe Projects::DeployKeysController do
it 'returns json in a correct format' do
get :index, params: params.merge(format: :json)
- json = JSON.parse(response.body)
-
- expect(json.keys).to match_array(%w(enabled_keys available_project_keys public_keys))
- expect(json['enabled_keys'].count).to eq(1)
- expect(json['available_project_keys'].count).to eq(1)
- expect(json['public_keys'].count).to eq(1)
+ expect(json_response.keys).to match_array(%w(enabled_keys available_project_keys public_keys))
+ expect(json_response['enabled_keys'].count).to eq(1)
+ expect(json_response['available_project_keys'].count).to eq(1)
+ expect(json_response['public_keys'].count).to eq(1)
end
end
end
diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb
index 95417936df4..b9ee69a617b 100644
--- a/spec/controllers/projects/deployments_controller_spec.rb
+++ b/spec/controllers/projects/deployments_controller_spec.rb
@@ -41,34 +41,26 @@ describe Projects::DeploymentsController do
describe 'GET #metrics' do
let(:deployment) { create(:deployment, :success, project: project, environment: environment) }
- before do
- allow(controller).to receive(:deployment).and_return(deployment)
- end
-
context 'when metrics are disabled' do
- before do
- allow(deployment).to receive(:has_metrics?).and_return false
- end
-
it 'responds with not found' do
- get :metrics, params: deployment_params(id: deployment.id)
+ get :metrics, params: deployment_params(id: deployment.to_param)
expect(response).to be_not_found
end
end
context 'when metrics are enabled' do
- before do
- allow(deployment).to receive(:has_metrics?).and_return true
- end
-
context 'when environment has no metrics' do
before do
- expect(deployment).to receive(:metrics).and_return(nil)
+ expect_next_instance_of(DeploymentMetrics) do |deployment_metrics|
+ allow(deployment_metrics).to receive(:has_metrics?).and_return(true)
+
+ expect(deployment_metrics).to receive(:metrics).and_return(nil)
+ end
end
it 'returns a empty response 204 resposne' do
- get :metrics, params: deployment_params(id: deployment.id)
+ get :metrics, params: deployment_params(id: deployment.to_param)
expect(response).to have_gitlab_http_status(204)
expect(response.body).to eq('')
end
@@ -84,11 +76,15 @@ describe Projects::DeploymentsController do
end
before do
- expect(deployment).to receive(:metrics).and_return(empty_metrics)
+ expect_next_instance_of(DeploymentMetrics) do |deployment_metrics|
+ allow(deployment_metrics).to receive(:has_metrics?).and_return(true)
+
+ expect(deployment_metrics).to receive(:metrics).and_return(empty_metrics)
+ end
end
it 'returns a metrics JSON document' do
- get :metrics, params: deployment_params(id: deployment.id)
+ get :metrics, params: deployment_params(id: deployment.to_param)
expect(response).to be_ok
expect(json_response['success']).to be(true)
@@ -96,54 +92,32 @@ describe Projects::DeploymentsController do
expect(json_response['last_update']).to eq(42)
end
end
-
- context 'when metrics service does not implement deployment metrics' do
- before do
- allow(deployment).to receive(:metrics).and_raise(NotImplementedError)
- end
-
- it 'responds with not found' do
- get :metrics, params: deployment_params(id: deployment.id)
-
- expect(response).to be_not_found
- end
- end
end
end
describe 'GET #additional_metrics' do
let(:deployment) { create(:deployment, :success, project: project, environment: environment) }
- before do
- allow(controller).to receive(:deployment).and_return(deployment)
- end
-
context 'when metrics are disabled' do
- before do
- allow(deployment).to receive(:has_metrics?).and_return false
- end
-
it 'responds with not found' do
- get :metrics, params: deployment_params(id: deployment.id)
+ get :metrics, params: deployment_params(id: deployment.to_param)
expect(response).to be_not_found
end
end
context 'when metrics are enabled' do
- let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) }
-
- before do
- allow(deployment).to receive(:prometheus_adapter).and_return(prometheus_adapter)
- end
-
context 'when environment has no metrics' do
before do
- expect(deployment).to receive(:additional_metrics).and_return({})
+ expect_next_instance_of(DeploymentMetrics) do |deployment_metrics|
+ allow(deployment_metrics).to receive(:has_metrics?).and_return(true)
+
+ expect(deployment_metrics).to receive(:additional_metrics).and_return({})
+ end
end
it 'returns a empty response 204 response' do
- get :additional_metrics, params: deployment_params(id: deployment.id, format: :json)
+ get :additional_metrics, params: deployment_params(id: deployment.to_param, format: :json)
expect(response).to have_gitlab_http_status(204)
expect(response.body).to eq('')
end
@@ -159,11 +133,15 @@ describe Projects::DeploymentsController do
end
before do
- expect(deployment).to receive(:additional_metrics).and_return(empty_metrics)
+ expect_next_instance_of(DeploymentMetrics) do |deployment_metrics|
+ allow(deployment_metrics).to receive(:has_metrics?).and_return(true)
+
+ expect(deployment_metrics).to receive(:additional_metrics).and_return(empty_metrics)
+ end
end
it 'returns a metrics JSON document' do
- get :additional_metrics, params: deployment_params(id: deployment.id, format: :json)
+ get :additional_metrics, params: deployment_params(id: deployment.to_param, format: :json)
expect(response).to be_ok
expect(json_response['success']).to be(true)
diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb
index 4c29162cd0f..e30b28a4bd5 100644
--- a/spec/controllers/projects/discussions_controller_spec.rb
+++ b/spec/controllers/projects/discussions_controller_spec.rb
@@ -112,7 +112,7 @@ describe Projects::DiscussionsController do
it "returns the name of the resolving user" do
post :resolve, params: request_params
- expect(JSON.parse(response.body)['resolved_by']['name']).to eq(user.name)
+ expect(json_response['resolved_by']['name']).to eq(user.name)
end
it "returns status 200" do
@@ -135,7 +135,7 @@ describe Projects::DiscussionsController do
it "returns truncated diff lines" do
post :resolve, params: request_params
- expect(JSON.parse(response.body)['truncated_diff_lines']).to be_present
+ expect(json_response['truncated_diff_lines']).to be_present
end
end
end
diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
index fdef9bc5638..45328482ad7 100644
--- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
+++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
@@ -176,7 +176,7 @@ describe Projects::Environments::PrometheusApiController do
def environment_params(params = {})
{
id: environment.id.to_s,
- namespace_id: project.namespace.name,
+ namespace_id: project.namespace.full_path,
project_id: project.name,
proxy_path: 'query',
query: '1'
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index 4c2c6160c62..71ee1fd03bf 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Projects::EnvironmentsController do
+ include MetricsDashboardHelpers
+
set(:user) { create(:user) }
set(:project) { create(:project) }
@@ -445,131 +447,187 @@ describe Projects::EnvironmentsController do
end
end
- describe 'metrics_dashboard' do
- context 'when prometheus endpoint is disabled' do
- before do
- stub_feature_flags(environment_metrics_use_prometheus_endpoint: false)
- end
+ describe 'GET #metrics_dashboard' do
+ shared_examples_for 'correctly formatted response' do |status_code|
+ it 'returns a json object with the correct keys' do
+ get :metrics_dashboard, params: environment_params(dashboard_params)
- it 'responds with status code 403' do
- get :metrics_dashboard, params: environment_params(format: :json)
+ # Exlcude `all_dashboards` to handle separately.
+ found_keys = json_response.keys - ['all_dashboards']
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(status_code)
+ expect(found_keys).to contain_exactly(*expected_keys)
end
end
- shared_examples_for '200 response' do |contains_all_dashboards: false|
+ shared_examples_for '200 response' do
let(:expected_keys) { %w(dashboard status) }
- before do
- expected_keys << 'all_dashboards' if contains_all_dashboards
- end
-
- it 'returns a json representation of the environment dashboard' do
- get :metrics_dashboard, params: environment_params(dashboard_params)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.keys).to contain_exactly(*expected_keys)
- expect(json_response['dashboard']).to be_an_instance_of(Hash)
- end
+ it_behaves_like 'correctly formatted response', :ok
end
- shared_examples_for 'error response' do |status_code, contains_all_dashboards: false|
+ shared_examples_for 'error response' do |status_code|
let(:expected_keys) { %w(message status) }
- before do
- expected_keys << 'all_dashboards' if contains_all_dashboards
- end
+ it_behaves_like 'correctly formatted response', status_code
+ end
- it 'returns an error response' do
+ shared_examples_for 'includes all dashboards' do
+ it 'includes info for all findable dashboard' do
get :metrics_dashboard, params: environment_params(dashboard_params)
- expect(response).to have_gitlab_http_status(status_code)
- expect(json_response.keys).to contain_exactly(*expected_keys)
+ expect(json_response).to have_key('all_dashboards')
+ expect(json_response['all_dashboards']).to be_an_instance_of(Array)
+ expect(json_response['all_dashboards']).to all( include('path', 'default', 'display_name') )
end
end
- shared_examples_for 'has all dashboards' do
- it 'includes an index of all available dashboards' do
+ shared_examples_for 'the default dashboard' do
+ all_dashboards = Feature.enabled?(:environment_metrics_show_multiple_dashboards)
+
+ it_behaves_like '200 response'
+ it_behaves_like 'includes all dashboards' if all_dashboards
+
+ it 'is the default dashboard' do
get :metrics_dashboard, params: environment_params(dashboard_params)
- expect(json_response.keys).to include('all_dashboards')
- expect(json_response['all_dashboards']).to be_an_instance_of(Array)
- expect(json_response['all_dashboards']).to all( include('path', 'default') )
+ expect(json_response['dashboard']['dashboard']).to eq('Environment metrics')
end
end
- context 'when multiple dashboards is disabled' do
- before do
- stub_feature_flags(environment_metrics_show_multiple_dashboards: false)
- end
+ shared_examples_for 'the specified dashboard' do |expected_dashboard|
+ it_behaves_like '200 response'
+ it_behaves_like 'includes all dashboards'
- let(:dashboard_params) { { format: :json } }
+ it 'has the correct name' do
+ get :metrics_dashboard, params: environment_params(dashboard_params)
- it_behaves_like '200 response'
+ dashboard_name = json_response['dashboard']['dashboard']
- context 'when the dashboard could not be provided' do
+ # 'Environment metrics' is the default dashboard.
+ expect(dashboard_name).not_to eq('Environment metrics')
+ expect(dashboard_name).to eq(expected_dashboard)
+ end
+
+ context 'when the dashboard cannot not be processed' do
before do
allow(YAML).to receive(:safe_load).and_return({})
end
it_behaves_like 'error response', :unprocessable_entity
end
-
- context 'when a dashboard param is specified' do
- let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/not_there_dashboard.yml' } }
-
- it_behaves_like '200 response'
- end
end
- context 'when multiple dashboards is enabled' do
- let(:dashboard_params) { { format: :json } }
+ shared_examples_for 'specified dashboard embed' do |expected_titles|
+ it_behaves_like '200 response'
- it_behaves_like '200 response', contains_all_dashboards: true
- it_behaves_like 'has all dashboards'
+ it 'contains only the specified charts' do
+ get :metrics_dashboard, params: environment_params(dashboard_params)
- context 'when a dashboard could not be provided' do
- before do
- allow(YAML).to receive(:safe_load).and_return({})
- end
+ dashboard = json_response['dashboard']
+ panel_group = dashboard['panel_groups'].first
+ titles = panel_group['panels'].map { |panel| panel['title'] }
- it_behaves_like 'error response', :unprocessable_entity, contains_all_dashboards: true
- it_behaves_like 'has all dashboards'
+ expect(dashboard['dashboard']).to be_nil
+ expect(dashboard['panel_groups'].length).to eq 1
+ expect(panel_group['group']).to be_nil
+ expect(titles).to eq expected_titles
end
+ end
- context 'when a dashboard param is specified' do
- let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } }
+ shared_examples_for 'the default dynamic dashboard' do
+ it_behaves_like 'specified dashboard embed', ['Memory Usage (Total)', 'Core Usage (Total)']
+ end
+
+ shared_examples_for 'dashboard can be specified' do
+ context 'when dashboard is specified' do
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+ let(:dashboard_params) { { format: :json, dashboard: dashboard_path } }
- context 'when the dashboard is available' do
+ it_behaves_like 'error response', :not_found
+
+ context 'when the project dashboard is available' do
let(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
- let(:dashboard_file) { { '.gitlab/dashboards/test.yml' => dashboard_yml } }
- let(:project) { create(:project, :custom_repo, files: dashboard_file) }
+ let(:project) { project_with_dashboard(dashboard_path, dashboard_yml) }
let(:environment) { create(:environment, name: 'production', project: project) }
- it_behaves_like '200 response', contains_all_dashboards: true
- it_behaves_like 'has all dashboards'
+ it_behaves_like 'the specified dashboard', 'Test Dashboard'
end
- context 'when the dashboard does not exist' do
- it_behaves_like 'error response', :not_found, contains_all_dashboards: true
- it_behaves_like 'has all dashboards'
+ context 'when the specified dashboard is the default dashboard' do
+ let(:dashboard_path) { system_dashboard_path }
+
+ it_behaves_like 'the default dashboard'
end
end
+ end
- context 'when the dashboard is intended for embedding' do
+ shared_examples_for 'dashboard can be embedded' do
+ context 'when the embedded flag is included' do
let(:dashboard_params) { { format: :json, embedded: true } }
- it_behaves_like '200 response'
+ it_behaves_like 'the default dynamic dashboard'
+
+ context 'when incomplete dashboard params are provided' do
+ let(:dashboard_params) { { format: :json, embedded: true, title: 'Title' } }
+
+ # The title param should be ignored.
+ it_behaves_like 'the default dynamic dashboard'
+ end
+
+ context 'when invalid params are provided' do
+ let(:dashboard_params) { { format: :json, embedded: true, metric_id: 16 } }
- context 'when a dashboard path is provided' do
- let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml', embedded: true } }
+ # The superfluous param should be ignored.
+ it_behaves_like 'the default dynamic dashboard'
+ end
- # The dashboard path should simple be ignored.
- it_behaves_like '200 response'
+ context 'when the dashboard is correctly specified' do
+ let(:dashboard_params) do
+ {
+ format: :json,
+ embedded: true,
+ dashboard: system_dashboard_path,
+ group: business_metric_title,
+ title: 'title',
+ y_label: 'y_label'
+ }
+ end
+
+ it_behaves_like 'error response', :not_found
+
+ context 'and exists' do
+ let!(:metric) { create(:prometheus_metric, project: project) }
+
+ it_behaves_like 'specified dashboard embed', ['title']
+ end
end
end
end
+
+ shared_examples_for 'dashboard cannot be specified' do
+ context 'when dashboard is specified' do
+ let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } }
+
+ it_behaves_like 'the default dashboard'
+ end
+ end
+
+ let(:dashboard_params) { { format: :json } }
+
+ it_behaves_like 'the default dashboard'
+ it_behaves_like 'dashboard can be specified'
+ it_behaves_like 'dashboard can be embedded'
+
+ context 'when multiple dashboards is disabled' do
+ before do
+ stub_feature_flags(environment_metrics_show_multiple_dashboards: false)
+ end
+
+ it_behaves_like 'the default dashboard'
+ it_behaves_like 'dashboard cannot be specified'
+ it_behaves_like 'dashboard can be embedded'
+ end
end
describe 'GET #search' do
diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb
index 844c61f1ace..d11ef24ef96 100644
--- a/spec/controllers/projects/error_tracking_controller_spec.rb
+++ b/spec/controllers/projects/error_tracking_controller_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Projects::ErrorTrackingController do
set(:project) { create(:project) }
diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb
index 538dbb5ad0b..a493985f8a0 100644
--- a/spec/controllers/projects/find_file_controller_spec.rb
+++ b/spec/controllers/projects/find_file_controller_spec.rb
@@ -53,10 +53,9 @@ describe Projects::FindFileController do
it 'returns an array of file path list' do
go
- json = JSON.parse(response.body)
is_expected.to respond_with(:success)
- expect(json).not_to eq(nil)
- expect(json.length).to be >= 0
+ expect(json_response).not_to eq(nil)
+ expect(json_response.length).to be >= 0
end
end
diff --git a/spec/controllers/projects/git_http_controller_spec.rb b/spec/controllers/projects/git_http_controller_spec.rb
index bf099e8deeb..88fa2236e33 100644
--- a/spec/controllers/projects/git_http_controller_spec.rb
+++ b/spec/controllers/projects/git_http_controller_spec.rb
@@ -12,4 +12,15 @@ describe Projects::GitHttpController do
expect(response.status).to eq(403)
end
end
+
+ describe 'GET #info_refs' do
+ it 'returns 401 for unauthenticated requests to public repositories when http protocol is disabled' do
+ stub_application_setting(enabled_git_access_protocol: 'ssh')
+ project = create(:project, :public, :repository)
+
+ get :info_refs, params: { service: 'git-upload-pack', namespace_id: project.namespace.to_param, project_id: project.path + '.git' }
+
+ expect(response.status).to eq(401)
+ end
+ end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index f82e3c8c7dc..187c7864ad7 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Projects::IssuesController do
+ include ProjectForksHelper
+
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
@@ -249,15 +251,13 @@ describe Projects::IssuesController do
end
end
- describe 'Redirect after sign in' do
+ # This spec runs as a request-style spec in order to invoke the
+ # Rails router. A controller-style spec matches the wrong route, and
+ # session['user_return_to'] becomes incorrect.
+ describe 'Redirect after sign in', type: :request do
context 'with an AJAX request' do
it 'does not store the visited URL' do
- get :show, params: {
- format: :json,
- namespace_id: project.namespace,
- project_id: project,
- id: issue.iid
- }, xhr: true
+ get project_issue_path(project, issue), xhr: true
expect(session['user_return_to']).to be_blank
end
@@ -265,14 +265,9 @@ describe Projects::IssuesController do
context 'without an AJAX request' do
it 'stores the visited URL' do
- get :show,
- params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: issue.iid
- }
+ get project_issue_path(project, issue)
- expect(session['user_return_to']).to eq("/#{project.namespace.to_param}/#{project.to_param}/issues/#{issue.iid}")
+ expect(session['user_return_to']).to eq(project_issue_path(project, issue))
end
end
end
@@ -442,7 +437,7 @@ describe Projects::IssuesController do
it 'renders json with recaptcha_html' do
subject
- expect(JSON.parse(response.body)).to have_key('recaptcha_html')
+ expect(json_response).to have_key('recaptcha_html')
end
end
end
@@ -482,10 +477,8 @@ describe Projects::IssuesController do
it 'returns last edited time' do
go(id: issue.iid)
- data = JSON.parse(response.body)
-
- expect(data).to include('updated_at')
- expect(data['updated_at']).to eq(issue.last_edited_at.to_time.iso8601)
+ expect(json_response).to include('updated_at')
+ expect(json_response['updated_at']).to eq(issue.last_edited_at.to_time.iso8601)
end
end
@@ -518,10 +511,8 @@ describe Projects::IssuesController do
it 'returns the necessary data' do
go(id: issue.iid)
- data = JSON.parse(response.body)
-
- expect(data).to include('title_text', 'description', 'description_text')
- expect(data).to include('task_status', 'lock_version')
+ expect(json_response).to include('title_text', 'description', 'description_text')
+ expect(json_response).to include('task_status', 'lock_version')
end
end
end
@@ -690,9 +681,7 @@ describe Projects::IssuesController do
update_issue(issue_params: { assignee_ids: [assignee.id] })
- body = JSON.parse(response.body)
-
- expect(body['assignees'].first.keys)
+ expect(json_response['assignees'].first.keys)
.to match_array(%w(id name username avatar_url state web_url))
end
end
@@ -1115,21 +1104,43 @@ describe Projects::IssuesController do
project.add_developer(user)
end
+ subject do
+ post(:toggle_award_emoji, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: issue.iid,
+ name: emoji_name
+ })
+ end
+ let(:emoji_name) { 'thumbsup' }
+
it "toggles the award emoji" do
expect do
- post(:toggle_award_emoji, params: {
- namespace_id: project.namespace,
- project_id: project,
- id: issue.iid,
- name: "thumbsup"
- })
+ subject
end.to change { issue.award_emoji.count }.by(1)
expect(response).to have_gitlab_http_status(200)
end
+
+ it "removes the already awarded emoji" do
+ create(:award_emoji, awardable: issue, name: emoji_name, user: user)
+
+ expect { subject }.to change { AwardEmoji.count }.by(-1)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'marks Todos on the Issue as done' do
+ todo = create(:todo, target: issue, project: project, user: user)
+
+ subject
+
+ expect(todo.reload).to be_done
+ end
end
describe 'POST create_merge_request' do
+ let(:target_project_id) { nil }
let(:project) { create(:project, :repository, :public) }
before do
@@ -1163,13 +1174,42 @@ describe Projects::IssuesController do
expect(response).to have_gitlab_http_status(404)
end
+ context 'target_project_id is set' do
+ let(:target_project) { fork_project(project, user, repository: true) }
+ let(:target_project_id) { target_project.id }
+
+ context 'create_confidential_merge_request feature is enabled' do
+ before do
+ stub_feature_flags(create_confidential_merge_request: true)
+ end
+
+ it 'creates a new merge request' do
+ expect { create_merge_request }.to change(target_project.merge_requests, :count).by(1)
+ end
+ end
+
+ context 'create_confidential_merge_request feature is disabled' do
+ before do
+ stub_feature_flags(create_confidential_merge_request: false)
+ end
+
+ it 'creates a new merge request' do
+ expect { create_merge_request }.to change(project.merge_requests, :count).by(1)
+ end
+ end
+ end
+
def create_merge_request
- post :create_merge_request, params: {
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: issue.to_param
- },
- format: :json
+ post(
+ :create_merge_request,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: issue.to_param,
+ target_project_id: target_project_id
+ },
+ format: :json
+ )
end
end
@@ -1234,6 +1274,28 @@ describe Projects::IssuesController do
sign_in(user)
end
+ context do
+ it_behaves_like 'discussions provider' do
+ let!(:author) { create(:user) }
+ let!(:project) { create(:project) }
+
+ let!(:issue) { create(:issue, project: project, author: user) }
+
+ let!(:note_on_issue1) { create(:discussion_note_on_issue, noteable: issue, project: issue.project, author: create(:user)) }
+ let!(:note_on_issue2) { create(:discussion_note_on_issue, noteable: issue, project: issue.project, author: create(:user)) }
+
+ let(:requested_iid) { issue.iid }
+ let(:expected_discussion_count) { 3 }
+ let(:expected_discussion_ids) do
+ [
+ issue.notes.first.discussion_id,
+ note_on_issue1.discussion_id,
+ note_on_issue2.discussion_id
+ ]
+ end
+ end
+ end
+
it 'returns discussion json' do
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
@@ -1282,7 +1344,7 @@ describe Projects::IssuesController do
it 'filters notes that the user should not see' do
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
- expect(JSON.parse(response.body).count).to eq(1)
+ expect(json_response.count).to eq(1)
end
it 'does not result in N+1 queries' do
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 901402aa5fd..f076a5e769f 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -158,7 +158,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
get_show_json
json_response.dig('pipeline', 'details', 'stages').tap do |stages|
- expect(stages.map(&:keys).flatten)
+ expect(stages.flat_map(&:keys))
.to eq %w[name title status path dropdown_path]
end
end
@@ -546,7 +546,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq job.id
expect(json_response['status']).to eq job.status
- expect(json_response['html']).to eq('<span class="">BUILD TRACE</span>')
+ expect(json_response['html']).to eq('<span>BUILD TRACE</span>')
end
end
@@ -676,6 +676,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
describe 'POST play' do
+ let(:variable_attributes) { [] }
+
before do
project.add_developer(user)
@@ -698,6 +700,14 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
it 'transits to pending' do
expect(job.reload).to be_pending
end
+
+ context 'when job variables are specified' do
+ let(:variable_attributes) { [{ key: 'first', secret_value: 'first' }] }
+
+ it 'assigns the job variables' do
+ expect(job.reload.job_variables.map(&:key)).to contain_exactly('first')
+ end
+ end
end
context 'when job is not playable' do
@@ -712,7 +722,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
post :play, params: {
namespace_id: project.namespace,
project_id: project,
- id: job.id
+ id: job.id,
+ job_variables_attributes: variable_attributes
}
end
end
diff --git a/spec/controllers/projects/merge_requests/content_controller_spec.rb b/spec/controllers/projects/merge_requests/content_controller_spec.rb
index 2879e06aee4..818cf794ec6 100644
--- a/spec/controllers/projects/merge_requests/content_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/content_controller_spec.rb
@@ -11,8 +11,8 @@ describe Projects::MergeRequests::ContentController do
sign_in(user)
end
- def do_request
- get :widget, params: {
+ def do_request(action = :cached_widget)
+ get action, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.iid,
@@ -20,41 +20,65 @@ describe Projects::MergeRequests::ContentController do
}
end
- describe 'GET widget' do
- context 'user has access to the project' do
- before do
- expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+ context 'user has access to the project' do
+ before do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
- project.add_maintainer(user)
- end
+ project.add_maintainer(user)
+ end
+ describe 'GET cached_widget' do
it 'renders widget MR entity as json' do
do_request
- expect(response).to match_response_schema('entities/merge_request_widget')
+ expect(response).to match_response_schema('entities/merge_request_poll_cached_widget')
end
+ it 'closes an MR with moved source project' do
+ merge_request.update_column(:source_project_id, nil)
+
+ expect { do_request }.to change { merge_request.reload.open? }.from(true).to(false)
+ end
+ end
+
+ describe 'GET widget' do
it 'checks whether the MR can be merged' do
controller.instance_variable_set(:@merge_request, merge_request)
expect(merge_request).to receive(:check_mergeability)
- do_request
+ do_request(:widget)
end
- it 'closes an MR with moved source project' do
- merge_request.update_column(:source_project_id, nil)
+ context 'merged merge request' do
+ let(:merge_request) do
+ create(:merged_merge_request, :with_test_reports, target_project: project, source_project: project)
+ end
- expect { do_request }.to change { merge_request.reload.open? }.from(true).to(false)
+ it 'renders widget MR entity as json' do
+ do_request(:widget)
+
+ expect(response).to match_response_schema('entities/merge_request_poll_widget')
+ end
end
end
+ end
- context 'user does not have access to the project' do
- it 'renders widget MR entity as json' do
+ context 'user does not have access to the project' do
+ describe 'GET cached_widget' do
+ it 'returns 404' do
do_request
expect(response).to have_http_status(:not_found)
end
end
+
+ describe 'GET widget' do
+ it 'returns 404' do
+ do_request(:widget)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
end
end
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
index 5fefad86ef3..ce977f26ec6 100644
--- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
@@ -28,7 +28,7 @@ describe Projects::MergeRequests::CreationsController do
it 'renders new merge request widget template' do
get :new, params: get_diff_params
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -56,7 +56,7 @@ describe Projects::MergeRequests::CreationsController do
it 'limits total commits' do
get :new, params: large_diff_params
- expect(response).to be_success
+ expect(response).to be_successful
total = assigns(:total_commit_count)
expect(assigns(:commits)).to be_an Array
@@ -70,7 +70,7 @@ describe Projects::MergeRequests::CreationsController do
it 'shows total commits' do
get :new, params: large_diff_params
- expect(response).to be_success
+ expect(response).to be_successful
total = assigns(:total_commit_count)
expect(assigns(:commits)).to be_an CommitCollection
@@ -89,7 +89,7 @@ describe Projects::MergeRequests::CreationsController do
get :diffs, params: get_diff_params.merge(format: 'json')
- expect(response).to be_success
+ expect(response).to be_successful
expect(assigns[:diffs]).to be_nil
end
end
@@ -212,4 +212,46 @@ describe Projects::MergeRequests::CreationsController do
expect(response).to have_gitlab_http_status(200)
end
end
+
+ describe 'POST create' do
+ let(:params) do
+ {
+ namespace_id: fork_project.namespace.to_param,
+ project_id: fork_project,
+ merge_request: {
+ title: 'Test merge request',
+ source_branch: 'remove-submodule',
+ target_branch: 'master'
+ }
+ }
+ end
+
+ it 'creates merge request' do
+ expect do
+ post_request(params)
+ end.to change { MergeRequest.count }.by(1)
+ end
+
+ context 'when the merge request is not created from the web ide' do
+ it 'counter is not increased' do
+ expect(Gitlab::UsageDataCounters::WebIdeCounter).not_to receive(:increment_merge_requests_count)
+
+ post_request(params)
+ end
+ end
+
+ context 'when the merge request is created from the web ide' do
+ let(:nav_source) { { nav_source: 'webide' } }
+
+ it 'counter is increased' do
+ expect(Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_merge_requests_count)
+
+ post_request(params.merge(nav_source))
+ end
+ end
+
+ def post_request(merge_request_params)
+ post :create, params: merge_request_params
+ end
+ end
end
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index 13a28b738ca..ac3e9901123 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -66,7 +66,7 @@ describe Projects::MergeRequests::DiffsController do
end
it 'renders' do
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.body).to have_content('Subproject commit')
end
end
@@ -112,7 +112,7 @@ describe Projects::MergeRequests::DiffsController do
it 'only renders the diffs for the path given' do
diff_for_path(old_path: existing_path, new_path: existing_path)
- paths = JSON.parse(response.body)["diff_files"].map { |file| file['new_path'] }
+ paths = json_response["diff_files"].map { |file| file['new_path'] }
expect(paths).to include(existing_path)
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 0eca663a683..11b1eaf11b7 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -57,7 +57,7 @@ describe Projects::MergeRequestsController do
go(format: :html)
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -66,7 +66,7 @@ describe Projects::MergeRequestsController do
go(format: :html)
- expect(response).to be_success
+ expect(response).to be_successful
end
context "that is invalid" do
@@ -75,7 +75,7 @@ describe Projects::MergeRequestsController do
it "renders merge request page" do
go(format: :html)
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
@@ -124,7 +124,7 @@ describe Projects::MergeRequestsController do
it "renders merge request page" do
go(format: :json)
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
@@ -242,9 +242,7 @@ describe Projects::MergeRequestsController do
update_merge_request({ assignee_ids: [assignee.id] }, format: :json)
- body = JSON.parse(response.body)
-
- expect(body['assignees']).to all(include(*%w(name username avatar_url id state web_url)))
+ expect(json_response['assignees']).to all(include(*%w(name username avatar_url id state web_url)))
end
end
@@ -623,10 +621,100 @@ describe Projects::MergeRequestsController do
format: :json
end
- it 'responds with serialized pipelines' do
- expect(json_response['pipelines']).not_to be_empty
- expect(json_response['count']['all']).to eq 1
- expect(response).to include_pagination_headers
+ context 'with "enabled" builds on a public project' do
+ let(:project) { create(:project, :repository, :public) }
+
+ context 'for a project owner' do
+ it 'responds with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'for an unassociated user' do
+ let(:user) { create :user }
+
+ it 'responds with no pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+ end
+
+ context 'with private builds on a public project' do
+ let(:project) { create(:project, :repository, :public, :builds_private) }
+
+ context 'for a project owner' do
+ it 'responds with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'for an unassociated user' do
+ let(:user) { create :user }
+
+ it 'responds with no pipelines' do
+ expect(json_response['pipelines']).to be_empty
+ expect(json_response['count']['all']).to eq(0)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'from a project fork' do
+ let(:fork_user) { create :user }
+ let(:forked_project) { fork_project(project, fork_user, repository: true) } # Forked project carries over :builds_private
+ let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: forked_project) }
+
+ context 'with private builds' do
+ context 'for the target project member' do
+ it 'does not respond with serialized pipelines' do
+ expect(json_response['pipelines']).to be_empty
+ expect(json_response['count']['all']).to eq(0)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'for the source project member' do
+ let(:user) { fork_user }
+
+ it 'responds with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+ end
+
+ context 'with public builds' do
+ let(:forked_project) do
+ fork_project(project, fork_user, repository: true).tap do |new_project|
+ new_project.project_feature.update(builds_access_level: ProjectFeature::ENABLED)
+ end
+ end
+
+ context 'for the target project member' do
+ it 'does not respond with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'for the source project member' do
+ let(:user) { fork_user }
+
+ it 'responds with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+ end
+ end
end
end
@@ -878,17 +966,79 @@ describe Projects::MergeRequestsController do
expect(control_count).to be <= 137
end
- def get_ci_environments_status(extra_params = {})
- params = {
- namespace_id: merge_request.project.namespace.to_param,
- project_id: merge_request.project,
- id: merge_request.iid,
- format: 'json'
- }
+ it 'has no N+1 SQL issues for environments', :request_store, retry: 0 do
+ # First run to insert test data from lets, which does take up some 30 queries
+ get_ci_environments_status
+
+ control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { get_ci_environments_status }.count
- get :ci_environments_status, params: params.merge(extra_params)
+ environment2 = create(:environment, project: forked)
+ create(:deployment, :succeed, environment: environment2, sha: sha, ref: 'master', deployable: build)
+
+ # TODO address the last 5 queries
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63952 (5 queries)
+ leeway = 5
+ expect { get_ci_environments_status }.not_to exceed_all_query_limit(control_count + leeway)
end
end
+
+ context 'when a merge request has multiple environments with deployments' do
+ let(:sha) { merge_request.diff_head_sha }
+ let(:ref) { merge_request.source_branch }
+
+ let!(:build) { create(:ci_build, pipeline: pipeline) }
+ let!(:pipeline) { create(:ci_pipeline, sha: sha, project: project) }
+ let!(:environment) { create(:environment, name: 'env_a', project: project) }
+ let!(:another_environment) { create(:environment, name: 'env_b', project: project) }
+
+ before do
+ merge_request.update_head_pipeline
+
+ create(:deployment, :succeed, environment: environment, sha: sha, ref: ref, deployable: build)
+ create(:deployment, :succeed, environment: another_environment, sha: sha, ref: ref, deployable: build)
+ end
+
+ it 'exposes multiple environment statuses' do
+ get_ci_environments_status
+
+ expect(json_response.count).to eq 2
+ end
+
+ context 'when route map is not present in the project' do
+ it 'does not have N+1 Gitaly requests for environments', :request_store do
+ expect(merge_request).to be_present
+
+ expect { get_ci_environments_status }
+ .to change { Gitlab::GitalyClient.get_request_count }.by_at_most(1)
+ end
+ end
+
+ context 'when there is route map present in a project' do
+ before do
+ allow_any_instance_of(EnvironmentStatus)
+ .to receive(:has_route_map?)
+ .and_return(true)
+ end
+
+ it 'does not have N+1 Gitaly requests for diff files', :request_store do
+ expect(merge_request.merge_request_diff.merge_request_diff_files).to be_many
+
+ expect { get_ci_environments_status }
+ .to change { Gitlab::GitalyClient.get_request_count }.by_at_most(1)
+ end
+ end
+ end
+
+ def get_ci_environments_status(extra_params = {})
+ params = {
+ namespace_id: merge_request.project.namespace.to_param,
+ project_id: merge_request.project,
+ id: merge_request.iid,
+ format: 'json'
+ }
+
+ get :ci_environments_status, params: params.merge(extra_params)
+ end
end
describe 'GET pipeline_status.json' do
@@ -1060,6 +1210,22 @@ describe Projects::MergeRequestsController do
end
end
end
+
+ context do
+ it_behaves_like 'discussions provider' do
+ let!(:author) { create(:user) }
+ let!(:project) { create(:project) }
+
+ let!(:merge_request) { create(:merge_request, source_project: project) }
+
+ let!(:mr_note1) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) }
+ let!(:mr_note2) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) }
+
+ let(:requested_iid) { merge_request.iid }
+ let(:expected_discussion_count) { 2 }
+ let(:expected_discussion_ids) { [mr_note1.discussion_id, mr_note2.discussion_id] }
+ end
+ end
end
describe 'GET edit' do
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 767cee7d54a..cbf9d437909 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -115,7 +115,7 @@ describe Projects::MilestonesController do
end
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let!(:subgroup) { create(:group, :public, parent: group) }
let!(:subgroup_milestone) { create(:milestone, group: subgroup) }
@@ -139,7 +139,7 @@ describe Projects::MilestonesController do
expect(issue.milestone_id).to eq(milestone.id)
delete :destroy, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }, format: :js
- expect(response).to be_success
+ expect(response).to be_successful
expect(Event.recent.first.action).to eq(Event::DESTROYED)
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 6ec84f5c528..4500c412521 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -29,7 +29,7 @@ describe Projects::NotesController do
}
end
- let(:parsed_response) { JSON.parse(response.body).with_indifferent_access }
+ let(:parsed_response) { json_response.with_indifferent_access }
let(:note_json) { parsed_response[:notes].first }
before do
@@ -43,7 +43,7 @@ describe Projects::NotesController do
request.headers['X-Last-Fetched-At'] = last_fetched_at
expect(NotesFinder).to receive(:new)
- .with(anything, anything, hash_including(last_fetched_at: last_fetched_at))
+ .with(anything, hash_including(last_fetched_at: last_fetched_at))
.and_call_original
get :index, params: request_params
@@ -252,7 +252,7 @@ describe Projects::NotesController do
before do
service_params = ActionController::Parameters.new({
note: 'some note',
- noteable_id: merge_request.id.to_s,
+ noteable_id: merge_request.id,
noteable_type: 'MergeRequest',
commit_id: nil,
merge_request_diff_head_sha: 'sha'
@@ -543,23 +543,32 @@ describe Projects::NotesController do
project.add_developer(user)
end
+ subject { post(:toggle_award_emoji, params: request_params.merge(name: emoji_name)) }
+ let(:emoji_name) { 'thumbsup' }
+
it "toggles the award emoji" do
expect do
- post(:toggle_award_emoji, params: request_params.merge(name: "thumbsup"))
+ subject
end.to change { note.award_emoji.count }.by(1)
expect(response).to have_gitlab_http_status(200)
end
it "removes the already awarded emoji" do
- post(:toggle_award_emoji, params: request_params.merge(name: "thumbsup"))
+ create(:award_emoji, awardable: note, name: emoji_name, user: user)
- expect do
- post(:toggle_award_emoji, params: request_params.merge(name: "thumbsup"))
- end.to change { AwardEmoji.count }.by(-1)
+ expect { subject }.to change { AwardEmoji.count }.by(-1)
expect(response).to have_gitlab_http_status(200)
end
+
+ it 'marks Todos on the Noteable as done' do
+ todo = create(:todo, target: note.noteable, project: project, user: user)
+
+ subject
+
+ expect(todo.reload).to be_done
+ end
end
describe "resolving and unresolving" do
@@ -614,7 +623,7 @@ describe Projects::NotesController do
it "returns the name of the resolving user" do
post :resolve, params: request_params.merge(html: true)
- expect(JSON.parse(response.body)["resolved_by"]).to eq(user.name)
+ expect(json_response["resolved_by"]).to eq(user.name)
end
it "returns status 200" do
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 9a50ea79f5e..212d8b15252 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -177,18 +177,22 @@ describe Projects::PipelinesController do
end
it 'does not perform N + 1 queries' do
+ # Set up all required variables
+ get_pipeline_json
+
control_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count
- create_build('test', 1, 'rspec 1')
- create_build('test', 1, 'spinach 0')
- create_build('test', 1, 'spinach 1')
- create_build('test', 1, 'audit')
- create_build('post deploy', 3, 'pages 1')
- create_build('post deploy', 3, 'pages 2')
+ first_build = pipeline.builds.first
+ first_build.tag_list << [:hello, :world]
+ create(:deployment, deployable: first_build)
+
+ second_build = pipeline.builds.second
+ second_build.tag_list << [:docker, :ruby]
+ create(:deployment, deployable: second_build)
new_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count
- expect(new_count).to be_within(12).of(control_count)
+ expect(new_count).to be_within(1).of(control_count)
end
end
@@ -393,4 +397,69 @@ describe Projects::PipelinesController do
end
end
end
+
+ describe 'GET latest' do
+ let(:branch_main) { project.repository.branches[0] }
+ let(:branch_secondary) { project.repository.branches[1] }
+
+ let!(:pipeline_master) do
+ create(:ci_pipeline,
+ ref: branch_main.name,
+ sha: branch_main.target,
+ project: project)
+ end
+
+ let!(:pipeline_secondary) do
+ create(:ci_pipeline,
+ ref: branch_secondary.name,
+ sha: branch_secondary.target,
+ project: project)
+ end
+
+ before do
+ project.change_head(branch_main.name)
+ project.reload_default_branch
+ end
+
+ context 'no ref provided' do
+ it 'shows latest pipeline for the default project branch' do
+ get :show, params: { namespace_id: project.namespace, project_id: project, latest: true, ref: nil }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(assigns(:pipeline)).to have_attributes(id: pipeline_master.id)
+ end
+ end
+
+ context 'ref provided' do
+ before do
+ create(:ci_pipeline, ref: 'master', project: project)
+ end
+
+ it 'shows the latest pipeline for the provided ref' do
+ get :show, params: { namespace_id: project.namespace, project_id: project, latest: true, ref: branch_secondary.name }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(assigns(:pipeline)).to have_attributes(id: pipeline_secondary.id)
+ end
+
+ context 'newer pipeline exists for older sha' do
+ before do
+ create(:ci_pipeline, ref: branch_secondary.name, sha: project.commit(branch_secondary.name).parent, project: project)
+ end
+
+ it 'shows the provided ref with the last sha/pipeline combo' do
+ get :show, params: { namespace_id: project.namespace, project_id: project, latest: true, ref: branch_secondary.name }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(assigns(:pipeline)).to have_attributes(id: pipeline_secondary.id)
+ end
+ end
+ end
+
+ it 'renders a 404 if no pipeline is found for the ref' do
+ get :show, params: { namespace_id: project.namespace, project_id: project, ref: 'no-branch' }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index 4141e41c7a7..5130e26c928 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -158,7 +158,7 @@ describe Projects::ProjectMembersController do
id: member
}, xhr: true
- expect(response).to be_success
+ expect(response).to be_successful
expect(project.members).not_to include member
end
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 97acd47b4da..8b43d1264b2 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Projects::RawController do
+ include RepoHelpers
+
let(:project) { create(:project, :public, :repository) }
describe 'GET #show' do
@@ -46,5 +48,98 @@ describe Projects::RawController do
let(:filename) { 'lfs_object.iso' }
let(:filepath) { "be93687/files/lfs/#{filename}" }
end
+
+ context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_cache do
+ let(:file_path) { 'master/README.md' }
+
+ before do
+ stub_application_setting(raw_blob_request_limit: 5)
+ end
+
+ it 'prevents from accessing the raw file' do
+ execute_raw_requests(requests: 6, project: project, file_path: file_path)
+
+ expect(flash[:alert]).to eq('You cannot access the raw file. Please wait a minute.')
+ expect(response).to have_gitlab_http_status(429)
+ end
+
+ it 'logs the event on auth.log' do
+ attributes = {
+ message: 'Action_Rate_Limiter_Request',
+ env: :raw_blob_request_limit,
+ remote_ip: '0.0.0.0',
+ request_method: 'GET',
+ path: "/#{project.full_path}/raw/#{file_path}"
+ }
+
+ expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once
+
+ execute_raw_requests(requests: 6, project: project, file_path: file_path)
+ end
+
+ context 'when the request uses a different version of a commit' do
+ it 'prevents from accessing the raw file' do
+ # 3 times with the normal sha
+ commit_sha = project.repository.commit.sha
+ file_path = "#{commit_sha}/README.md"
+
+ execute_raw_requests(requests: 3, project: project, file_path: file_path)
+
+ # 3 times with the modified version
+ modified_sha = commit_sha.gsub(commit_sha[0..5], commit_sha[0..5].upcase)
+ modified_path = "#{modified_sha}/README.md"
+
+ execute_raw_requests(requests: 3, project: project, file_path: modified_path)
+
+ expect(flash[:alert]).to eq('You cannot access the raw file. Please wait a minute.')
+ expect(response).to have_gitlab_http_status(429)
+ end
+ end
+
+ context 'when the throttling has been disabled' do
+ before do
+ stub_application_setting(raw_blob_request_limit: 0)
+ end
+
+ it 'does not prevent from accessing the raw file' do
+ execute_raw_requests(requests: 10, project: project, file_path: file_path)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context 'with case-sensitive files' do
+ it 'prevents from accessing the specific file' do
+ create_file_in_repo(project, 'master', 'master', 'readme.md', 'Add readme.md')
+ create_file_in_repo(project, 'master', 'master', 'README.md', 'Add README.md')
+
+ commit_sha = project.repository.commit.sha
+ file_path = "#{commit_sha}/readme.md"
+
+ # Accessing downcase version of readme
+ execute_raw_requests(requests: 6, project: project, file_path: file_path)
+
+ expect(flash[:alert]).to eq('You cannot access the raw file. Please wait a minute.')
+ expect(response).to have_gitlab_http_status(429)
+
+ # Accessing upcase version of readme
+ file_path = "#{commit_sha}/README.md"
+
+ execute_raw_requests(requests: 1, project: project, file_path: file_path)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+ end
+
+ def execute_raw_requests(requests:, project:, file_path:)
+ requests.times do
+ get :show, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: file_path
+ }
+ end
end
end
diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb
index 6db98f2428b..646c7a7db7c 100644
--- a/spec/controllers/projects/refs_controller_spec.rb
+++ b/spec/controllers/projects/refs_controller_spec.rb
@@ -49,7 +49,7 @@ describe Projects::RefsController do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
xhr_get(:js)
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'renders JSON' do
@@ -57,7 +57,7 @@ describe Projects::RefsController do
xhr_get(:json)
- expect(response).to be_success
+ expect(response).to be_successful
expect(json_response).to be_kind_of(Array)
end
end
diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb
index ff35139ae2e..c6e063d8229 100644
--- a/spec/controllers/projects/registry/tags_controller_spec.rb
+++ b/spec/controllers/projects/registry/tags_controller_spec.rb
@@ -113,4 +113,37 @@ describe Projects::Registry::TagsController do
format: :json
end
end
+
+ describe 'POST bulk_destroy' do
+ context 'when user has access to registry' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when there is matching tag present' do
+ before do
+ stub_container_registry_tags(repository: repository.path, tags: %w[rc1 test.])
+ end
+
+ it 'makes it possible to delete tags in bulk' do
+ allow_any_instance_of(ContainerRegistry::Tag).to receive(:delete) { |*args| ContainerRegistry::Tag.delete(*args) }
+ expect(ContainerRegistry::Tag).to receive(:delete).exactly(2).times
+
+ bulk_destroy_tags(['rc1', 'test.'])
+ end
+ end
+ end
+
+ private
+
+ def bulk_destroy_tags(names)
+ post :bulk_destroy, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ repository_id: repository,
+ ids: names
+ },
+ format: :json
+ end
+ end
end
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 8fca9e680dd..fcab4d73dca 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -77,6 +77,53 @@ describe Projects::RepositoriesController do
expect(response).to have_gitlab_http_status(404)
end
end
+
+ describe 'caching' do
+ it 'sets appropriate caching headers' do
+ get_archive
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['ETag']).to be_present
+ expect(response.header['Cache-Control']).to include('max-age=60, private')
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it 'sets appropriate caching headers' do
+ get_archive
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['ETag']).to be_present
+ expect(response.header['Cache-Control']).to include('max-age=60, public')
+ end
+ end
+
+ context 'when ref is a commit SHA' do
+ it 'max-age is set to 3600 in Cache-Control header' do
+ get_archive('ddd0f15ae83993f5cb66a927a28673882e99100b')
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['Cache-Control']).to include('max-age=3600')
+ end
+ end
+
+ context 'when If-None-Modified header is set' do
+ it 'returns a 304 status' do
+ # Get the archive cached first
+ get_archive
+
+ request.headers['If-None-Match'] = response.headers['ETag']
+ get_archive
+
+ expect(response).to have_gitlab_http_status(304)
+ end
+ end
+
+ def get_archive(id = 'feature')
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: id }, format: 'zip'
+ end
+ end
end
end
end
diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb
index 18c594acae0..9f1ef3a4be8 100644
--- a/spec/controllers/projects/serverless/functions_controller_spec.rb
+++ b/spec/controllers/projects/serverless/functions_controller_spec.rb
@@ -10,12 +10,16 @@ describe Projects::Serverless::FunctionsController do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:service) { cluster.platform_kubernetes }
let(:project) { cluster.project }
+ let(:environment) { create(:environment, project: project) }
+ let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
+ let(:knative_services_finder) { environment.knative_services_finder }
let(:namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
cluster_project: cluster.cluster_project,
- project: cluster.cluster_project.project)
+ project: cluster.cluster_project.project,
+ environment: environment)
end
before do
@@ -47,12 +51,11 @@ describe Projects::Serverless::FunctionsController do
end
context 'when cache is ready' do
- let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
let(:knative_state) { true }
before do
- allow_any_instance_of(Clusters::Cluster)
- .to receive(:knative_services_finder)
+ allow(Clusters::KnativeServicesFinder)
+ .to receive(:new)
.and_return(knative_services_finder)
synchronous_reactive_cache(knative_services_finder)
stub_kubeclient_service_pods(
@@ -107,12 +110,12 @@ describe Projects::Serverless::FunctionsController do
context 'valid data', :use_clean_rails_memory_store_caching do
before do
stub_kubeclient_service_pods
- stub_reactive_cache(cluster.knative_services_finder(project),
+ stub_reactive_cache(knative_services_finder,
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
},
- *cluster.knative_services_finder(project).cache_args)
+ *knative_services_finder.cache_args)
end
it 'has a valid function name' do
@@ -140,12 +143,12 @@ describe Projects::Serverless::FunctionsController do
describe 'GET #index with data', :use_clean_rails_memory_store_caching do
before do
stub_kubeclient_service_pods
- stub_reactive_cache(cluster.knative_services_finder(project),
+ stub_reactive_cache(knative_services_finder,
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
},
- *cluster.knative_services_finder(project).cache_args)
+ *knative_services_finder.cache_args)
end
it 'has data' do
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 68eabce8513..563b61962cf 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -159,7 +159,7 @@ describe Projects::ServicesController do
context 'with approved services' do
it 'renders edit page' do
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/controllers/projects/settings/repository_controller_spec.rb b/spec/controllers/projects/settings/repository_controller_spec.rb
index b34053fc993..7f67f67e775 100644
--- a/spec/controllers/projects/settings/repository_controller_spec.rb
+++ b/spec/controllers/projects/settings/repository_controller_spec.rb
@@ -32,4 +32,24 @@ describe Projects::Settings::RepositoryController do
expect(RepositoryCleanupWorker).to have_received(:perform_async).once
end
end
+
+ describe 'POST create_deploy_token' do
+ let(:deploy_token_params) do
+ {
+ name: 'deployer_token',
+ expires_at: 1.month.from_now.to_date.to_s,
+ username: 'deployer',
+ read_repository: '1'
+ }
+ end
+
+ subject(:create_deploy_token) { post :create_deploy_token, params: { namespace_id: project.namespace, project_id: project, deploy_token: deploy_token_params } }
+
+ it 'creates deploy token' do
+ expect { create_deploy_token }.to change { DeployToken.active.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to render_template(:show)
+ end
+ end
end
diff --git a/spec/controllers/projects/starrers_controller_spec.rb b/spec/controllers/projects/starrers_controller_spec.rb
new file mode 100644
index 00000000000..5774ff7c576
--- /dev/null
+++ b/spec/controllers/projects/starrers_controller_spec.rb
@@ -0,0 +1,196 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::StarrersController do
+ let(:user_1) { create(:user, name: 'John') }
+ let(:user_2) { create(:user, name: 'Michael') }
+ let(:private_user) { create(:user, name: 'Michael Douglas', private_profile: true) }
+ let(:admin) { create(:user, admin: true) }
+ let(:project) { create(:project, :public) }
+
+ before do
+ user_1.toggle_star(project)
+ user_2.toggle_star(project)
+ private_user.toggle_star(project)
+ end
+
+ describe 'GET index' do
+ def get_starrers(search: nil)
+ get :index, params: { namespace_id: project.namespace, project_id: project, search: search }
+ end
+
+ def user_ids
+ assigns[:starrers].map { |s| s['user_id'] }
+ end
+
+ shared_examples 'starrers counts' do
+ it 'starrers counts are correct' do
+ expect(assigns[:total_count]).to eq(3)
+ expect(assigns[:public_count]).to eq(2)
+ expect(assigns[:private_count]).to eq(1)
+ end
+ end
+
+ context 'N+1 queries' do
+ render_views
+
+ it 'avoids N+1s loading users', :request_store do
+ get_starrers
+
+ control_count = ActiveRecord::QueryRecorder.new { get_starrers }.count
+
+ create_list(:user, 5).each { |user| user.toggle_star(project) }
+
+ expect { get_starrers }.not_to exceed_query_limit(control_count)
+ end
+ end
+
+ context 'when project is public' do
+ before do
+ project.update_attribute(:visibility_level, Project::PUBLIC)
+ end
+
+ context 'when no user is logged in' do
+ context 'with no searching' do
+ before do
+ get_starrers
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_1.id, user_2.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+
+ context 'when searching by user' do
+ before do
+ get_starrers(search: 'Michael')
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_2.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+ end
+
+ context 'when public user is logged in' do
+ before do
+ sign_in(user_1)
+ end
+
+ context 'with no searching' do
+ before do
+ get_starrers
+ end
+
+ it 'their star is also visible' do
+ expect(user_ids).to contain_exactly(user_1.id, user_2.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+
+ context 'when searching by user' do
+ before do
+ get_starrers(search: 'Michael')
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_2.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+ end
+
+ context 'when private user is logged in' do
+ before do
+ sign_in(private_user)
+ end
+
+ context 'with no searching' do
+ before do
+ get_starrers
+ end
+
+ it 'their star is also visible' do
+ expect(user_ids).to contain_exactly(user_1.id, user_2.id, private_user.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+
+ context 'when searching by user' do
+ before do
+ get_starrers(search: 'Michael')
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_2.id, private_user.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+ end
+
+ context 'when admin is logged in' do
+ before do
+ sign_in(admin)
+ end
+
+ context 'with no searching' do
+ before do
+ get_starrers
+ end
+
+ it 'all users are visible' do
+ expect(user_ids).to include(user_1.id, user_2.id, private_user.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+
+ context 'when searching by user' do
+ before do
+ get_starrers(search: 'Michael')
+ end
+
+ it 'public and private starrers are visible' do
+ expect(user_ids).to contain_exactly(user_2.id, private_user.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+ end
+ end
+
+ context 'when project is private' do
+ before do
+ project.update(visibility_level: Project::PRIVATE)
+ end
+
+ it 'starrers are not visible for non logged in users' do
+ get_starrers
+
+ expect(assigns[:starrers]).to be_blank
+ end
+
+ context 'when user is logged in' do
+ before do
+ sign_in(project.creator)
+ get_starrers
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_1.id, user_2.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb
index bebf17728c0..d5ef2b0e114 100644
--- a/spec/controllers/projects/templates_controller_spec.rb
+++ b/spec/controllers/projects/templates_controller_spec.rb
@@ -3,49 +3,100 @@
require 'spec_helper'
describe Projects::TemplatesController do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository, :private) }
let(:user) { create(:user) }
- let(:user2) { create(:user) }
- let(:file_path_1) { '.gitlab/issue_templates/bug.md' }
- let(:body) { JSON.parse(response.body) }
+ let(:file_path_1) { '.gitlab/issue_templates/issue_template.md' }
+ let(:file_path_2) { '.gitlab/merge_request_templates/merge_request_template.md' }
+ let!(:file_1) { project.repository.create_file(user, file_path_1, 'issue content', message: 'message', branch_name: 'master') }
+ let!(:file_2) { project.repository.create_file(user, file_path_2, 'merge request content', message: 'message', branch_name: 'master') }
- before do
- project.add_developer(user)
- sign_in(user)
- end
+ describe '#show' do
+ shared_examples 'renders issue templates as json' do
+ it do
+ get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :json)
- before do
- project.add_user(user, Gitlab::Access::MAINTAINER)
- project.repository.create_file(user, file_path_1, 'something valid',
- message: 'test 3', branch_name: 'master')
- end
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq('issue_template')
+ expect(json_response['content']).to eq('issue content')
+ end
+ end
- describe '#show' do
- it 'renders template name and content as json' do
- get(:show, params: { namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project }, format: :json)
+ shared_examples 'renders merge request templates as json' do
+ it do
+ get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template', project_id: project }, format: :json)
+
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq('merge_request_template')
+ expect(json_response['content']).to eq('merge request content')
+ end
+ end
+
+ shared_examples 'renders 404 when requesting an issue template' do
+ it do
+ get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :json)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ shared_examples 'renders 404 when requesting a merge request template' do
+ it do
+ get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template', project_id: project }, format: :json)
- expect(response.status).to eq(200)
- expect(body["name"]).to eq("bug")
- expect(body["content"]).to eq("something valid")
+ expect(response.status).to eq(404)
+ end
end
- it 'renders 404 when unauthorized' do
- sign_in(user2)
- get(:show, params: { namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project }, format: :json)
+ shared_examples 'renders 404 when params are invalid' do
+ it 'does not route when the template type is invalid' do
+ expect do
+ get(:show, params: { namespace_id: project.namespace, template_type: 'invalid_type', key: 'issue_template', project_id: project }, format: :json)
+ end.to raise_error(ActionController::UrlGenerationError)
+ end
+
+ it 'renders 404 when the format type is invalid' do
+ get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :html)
+
+ expect(response.status).to eq(404)
+ end
+
+ it 'renders 404 when the key is unknown' do
+ get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'unknown_template', project_id: project }, format: :json)
- expect(response.status).to eq(404)
+ expect(response.status).to eq(404)
+ end
end
- it 'renders 404 when template type is not found' do
- sign_in(user)
- get(:show, params: { namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project }, format: :json)
+ context 'when the user is not a member of the project' do
+ before do
+ sign_in(user)
+ end
- expect(response.status).to eq(404)
+ include_examples 'renders 404 when requesting an issue template'
+ include_examples 'renders 404 when requesting a merge request template'
+ include_examples 'renders 404 when params are invalid'
end
- it 'renders 404 without errors' do
- sign_in(user)
- expect { get(:show, params: { namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project }, format: :json) }.not_to raise_error
+ context 'when user is a member of the project' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ include_examples 'renders issue templates as json'
+ include_examples 'renders merge request templates as json'
+ include_examples 'renders 404 when params are invalid'
+ end
+
+ context 'when user is a guest of the project' do
+ before do
+ project.add_guest(user)
+ sign_in(user)
+ end
+
+ include_examples 'renders issue templates as json'
+ include_examples 'renders 404 when requesting a merge request template'
+ include_examples 'renders 404 when params are invalid'
end
end
end
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index 776c1270977..661ed9840b1 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -10,6 +10,11 @@ describe Projects::UploadsController do
{ namespace_id: model.namespace.to_param, project_id: model }
end
+ let(:other_model) { create(:project, :public) }
+ let(:other_params) do
+ { namespace_id: other_model.namespace.to_param, project_id: other_model }
+ end
+
it_behaves_like 'handle uploads'
context 'when the URL the old style, without /-/system' do
diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb
index a2a09e2580f..21e106660d0 100644
--- a/spec/controllers/projects/variables_controller_spec.rb
+++ b/spec/controllers/projects/variables_controller_spec.rb
@@ -36,5 +36,70 @@ describe Projects::VariablesController do
end
include_examples 'PATCH #update updates variables'
+
+ context 'with environment scope' do
+ let!(:variable) { create(:ci_variable, project: project, environment_scope: 'custom_scope') }
+
+ let(:variable_attributes) do
+ { id: variable.id,
+ key: variable.key,
+ secret_value: variable.value,
+ protected: variable.protected?.to_s,
+ environment_scope: variable.environment_scope }
+ end
+ let(:new_variable_attributes) do
+ { key: 'new_key',
+ secret_value: 'dummy_value',
+ protected: 'false',
+ environment_scope: 'new_scope' }
+ end
+
+ context 'with same key and different environment scope' do
+ let(:variables_attributes) do
+ [
+ variable_attributes,
+ new_variable_attributes.merge(key: variable.key)
+ ]
+ end
+
+ it 'does not update the existing variable' do
+ expect { subject }.not_to change { variable.reload.value }
+ end
+
+ it 'creates the new variable' do
+ expect { subject }.to change { owner.variables.count }.by(1)
+ end
+
+ it 'returns a successful response including all variables' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('variables')
+ end
+ end
+
+ context 'with same key and same environment scope' do
+ let(:variables_attributes) do
+ [
+ variable_attributes,
+ new_variable_attributes.merge(key: variable.key, environment_scope: variable.environment_scope)
+ ]
+ end
+
+ it 'does not update the existing variable' do
+ expect { subject }.not_to change { variable.reload.value }
+ end
+
+ it 'does not create the new variable' do
+ expect { subject }.not_to change { owner.variables.count }
+ end
+
+ it 'returns a bad request response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
end
end
diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb
index f2e0b5e5c1d..6fea6bca4f2 100644
--- a/spec/controllers/projects/wikis_controller_spec.rb
+++ b/spec/controllers/projects/wikis_controller_spec.rb
@@ -3,11 +3,11 @@
require 'spec_helper'
describe Projects::WikisController do
- let(:project) { create(:project, :public, :repository) }
- let(:user) { project.owner }
+ set(:project) { create(:project, :public, :repository) }
+ set(:user) { project.owner }
let(:project_wiki) { ProjectWiki.new(project, user) }
let(:wiki) { project_wiki.wiki }
- let(:wiki_title) { 'page-title-test' }
+ let(:wiki_title) { 'page title test' }
before do
create_page(wiki_title, 'hello world')
@@ -19,6 +19,21 @@ describe Projects::WikisController do
destroy_page(wiki_title)
end
+ describe 'GET #new' do
+ subject { get :new, params: { namespace_id: project.namespace, project_id: project } }
+
+ it 'redirects to #show and appends a `random_title` param' do
+ subject
+
+ expect(response).to have_http_status(302)
+ expect(Rails.application.routes.recognize_path(response.redirect_url)).to include(
+ controller: 'projects/wikis',
+ action: 'show'
+ )
+ expect(response.redirect_url).to match(/\?random_title=true\Z/)
+ end
+ end
+
describe 'GET #pages' do
subject { get :pages, params: { namespace_id: project.namespace, project_id: project, id: wiki_title } }
@@ -31,43 +46,106 @@ describe Projects::WikisController do
end
end
+ describe 'GET #history' do
+ before do
+ allow(controller)
+ .to receive(:can?)
+ .with(any_args)
+ .and_call_original
+
+ # The :create_wiki permission is irrelevant to reading history.
+ expect(controller)
+ .not_to receive(:can?)
+ .with(anything, :create_wiki, any_args)
+
+ allow(controller)
+ .to receive(:can?)
+ .with(anything, :read_wiki, any_args)
+ .and_return(allow_read_wiki)
+ end
+
+ shared_examples 'fetching history' do |expected_status|
+ before do
+ get :history, params: { namespace_id: project.namespace, project_id: project, id: wiki_title }
+ end
+
+ it "returns status #{expected_status}" do
+ expect(response).to have_http_status(expected_status)
+ end
+ end
+
+ it_behaves_like 'fetching history', :ok do
+ let(:allow_read_wiki) { true }
+
+ it 'assigns @page_versions' do
+ expect(assigns(:page_versions)).to be_present
+ end
+ end
+
+ it_behaves_like 'fetching history', :not_found do
+ let(:allow_read_wiki) { false }
+ end
+ end
+
describe 'GET #show' do
render_views
- subject { get :show, params: { namespace_id: project.namespace, project_id: project, id: wiki_title } }
+ let(:random_title) { nil }
- it 'limits the retrieved pages for the sidebar' do
- expect(controller).to receive(:load_wiki).and_return(project_wiki)
+ subject { get :show, params: { namespace_id: project.namespace, project_id: project, id: id, random_title: random_title } }
- # empty? call
- expect(project_wiki).to receive(:list_pages).with(limit: 1).and_call_original
- # Sidebar entries
- expect(project_wiki).to receive(:list_pages).with(limit: 15).and_call_original
+ context 'when page exists' do
+ let(:id) { wiki_title }
- subject
+ it 'limits the retrieved pages for the sidebar' do
+ expect(controller).to receive(:load_wiki).and_return(project_wiki)
+ expect(project_wiki).to receive(:list_pages).with(limit: 15).and_call_original
+
+ subject
- expect(response).to have_http_status(:ok)
- expect(response.body).to include(wiki_title)
+ expect(response).to have_http_status(:ok)
+ expect(assigns(:page).title).to eq(wiki_title)
+ end
+
+ context 'when page content encoding is invalid' do
+ it 'sets flash error' do
+ allow(controller).to receive(:valid_encoding?).and_return(false)
+
+ subject
+
+ expect(response).to have_http_status(:ok)
+ expect(flash[:notice]).to eq('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.')
+ end
+ end
end
- context 'when page content encoding is invalid' do
- it 'sets flash error' do
- allow(controller).to receive(:valid_encoding?).and_return(false)
+ context 'when the page does not exist' do
+ let(:id) { 'does not exist' }
+ before do
subject
+ end
- expect(response).to have_http_status(:ok)
- expect(flash[:notice]).to eq 'The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.'
+ it 'builds a new wiki page with the id as the title' do
+ expect(assigns(:page).title).to eq(id)
+ end
+
+ context 'when a random_title param is present' do
+ let(:random_title) { true }
+
+ it 'builds a new wiki page with no title' do
+ expect(assigns(:page).title).to be_empty
+ end
end
end
context 'when page is a file' do
include WikiHelpers
- let(:path) { upload_file_to_wiki(project, user, file_name) }
+ let(:id) { upload_file_to_wiki(project, user, file_name) }
before do
- get :show, params: { namespace_id: project.namespace, project_id: project, id: path }
+ subject
end
context 'when file is an image' do
@@ -103,7 +181,7 @@ describe Projects::WikisController do
it 'renders json in a correct format' do
post :preview_markdown, params: { namespace_id: project.namespace, project_id: project, id: 'page/path', text: '*Markdown* text' }
- expect(JSON.parse(response.body).keys).to match_array(%w(body references))
+ expect(json_response.keys).to match_array(%w(body references))
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 8d2412f97ef..c732caa6160 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -318,6 +318,149 @@ describe ProjectsController do
end
end
+ describe 'POST #archive' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'for a user with the ability to archive a project' do
+ before do
+ group.add_owner(user)
+
+ post :archive, params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
+ end
+
+ it 'archives the project' do
+ expect(project.reload.archived?).to be_truthy
+ end
+
+ it 'redirects to projects path' do
+ expect(response).to have_gitlab_http_status(302)
+ expect(response).to redirect_to(project_path(project))
+ end
+ end
+
+ context 'for a user that does not have the ability to archive a project' do
+ before do
+ project.add_maintainer(user)
+
+ post :archive, params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
+ end
+
+ it 'does not archive the project' do
+ expect(project.reload.archived?).to be_falsey
+ end
+
+ it 'returns 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ describe 'POST #unarchive' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :archived, group: group) }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'for a user with the ability to unarchive a project' do
+ before do
+ group.add_owner(user)
+
+ post :unarchive, params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
+ end
+
+ it 'unarchives the project' do
+ expect(project.reload.archived?).to be_falsey
+ end
+
+ it 'redirects to projects path' do
+ expect(response).to have_gitlab_http_status(302)
+ expect(response).to redirect_to(project_path(project))
+ end
+ end
+
+ context 'for a user that does not have the ability to unarchive a project' do
+ before do
+ project.add_maintainer(user)
+
+ post :unarchive, params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
+ end
+
+ it 'does not unarchive the project' do
+ expect(project.reload.archived?).to be_truthy
+ end
+
+ it 'returns 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ describe '#housekeeping' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:housekeeping) { Projects::HousekeepingService.new(project) }
+
+ context 'when authenticated as owner' do
+ before do
+ group.add_owner(user)
+ sign_in(user)
+
+ allow(Projects::HousekeepingService).to receive(:new).with(project, :gc).and_return(housekeeping)
+ end
+
+ it 'forces a full garbage collection' do
+ expect(housekeeping).to receive(:execute).once
+
+ post :housekeeping,
+ params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ context 'when authenticated as developer' do
+ let(:developer) { create(:user) }
+
+ before do
+ group.add_developer(developer)
+ end
+
+ it 'does not execute housekeeping' do
+ expect(housekeeping).not_to receive(:execute)
+
+ post :housekeeping,
+ params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+ end
+
describe "#update" do
render_views
@@ -693,20 +836,18 @@ describe ProjectsController do
it 'gets a list of branches and tags' do
get :refs, params: { namespace_id: project.namespace, id: project, sort: 'updated_desc' }
- parsed_body = JSON.parse(response.body)
- expect(parsed_body['Branches']).to include('master')
- expect(parsed_body['Tags'].first).to eq('v1.1.0')
- expect(parsed_body['Tags'].last).to eq('v1.0.0')
- expect(parsed_body['Commits']).to be_nil
+ expect(json_response['Branches']).to include('master')
+ expect(json_response['Tags'].first).to eq('v1.1.0')
+ expect(json_response['Tags'].last).to eq('v1.0.0')
+ expect(json_response['Commits']).to be_nil
end
it "gets a list of branches, tags and commits" do
get :refs, params: { namespace_id: project.namespace, id: project, ref: "123456" }
- parsed_body = JSON.parse(response.body)
- expect(parsed_body["Branches"]).to include("master")
- expect(parsed_body["Tags"]).to include("v1.0.0")
- expect(parsed_body["Commits"]).to include("123456")
+ expect(json_response["Branches"]).to include("master")
+ expect(json_response["Tags"]).to include("v1.0.0")
+ expect(json_response["Commits"]).to include("123456")
end
context "when preferred language is Japanese" do
@@ -718,10 +859,9 @@ describe ProjectsController do
it "gets a list of branches, tags and commits" do
get :refs, params: { namespace_id: project.namespace, id: project, ref: "123456" }
- parsed_body = JSON.parse(response.body)
- expect(parsed_body["Branches"]).to include("master")
- expect(parsed_body["Tags"]).to include("v1.0.0")
- expect(parsed_body["Commits"]).to include("123456")
+ expect(json_response["Branches"]).to include("master")
+ expect(json_response["Tags"]).to include("v1.0.0")
+ expect(json_response["Commits"]).to include("123456")
end
end
@@ -750,7 +890,7 @@ describe ProjectsController do
it 'renders json in a correct format' do
post :preview_markdown, params: { namespace_id: public_project.namespace, id: public_project, text: '*Markdown* text' }
- expect(JSON.parse(response.body).keys).to match_array(%w(body references))
+ expect(json_response.keys).to match_array(%w(body references))
end
context 'when not authorized' do
@@ -774,8 +914,6 @@ describe ProjectsController do
text: issue.to_reference
}
- json_response = JSON.parse(response.body)
-
expect(json_response['body']).to match(/\##{issue.iid} \(closed\)/)
end
@@ -786,8 +924,6 @@ describe ProjectsController do
text: merge_request.to_reference
}
- json_response = JSON.parse(response.body)
-
expect(json_response['body']).to match(/\!#{merge_request.iid} \(closed\)/)
end
end
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index faf3c990cb2..35487682462 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -5,6 +5,10 @@ require 'spec_helper'
describe RegistrationsController do
include TermsHelper
+ before do
+ stub_feature_flags(invisible_captcha: false)
+ end
+
describe '#create' do
let(:base_user_params) { { name: 'new_user', username: 'new_username', email: 'new@user.com', password: 'Any_password' } }
let(:user_params) { { user: base_user_params } }
@@ -26,13 +30,36 @@ describe RegistrationsController do
end
context 'when send_user_confirmation_email is true' do
- it 'does not authenticate user and sends confirmation email' do
+ before do
stub_application_setting(send_user_confirmation_email: true)
+ end
+
+ context 'when soft email confirmation is not enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: false)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
+ end
- post(:create, params: user_params)
+ it 'does not authenticate the user and sends a confirmation email' do
+ post(:create, params: user_params)
- expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
- expect(subject.current_user).to be_nil
+ expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
+ expect(subject.current_user).to be_nil
+ end
+ end
+
+ context 'when soft email confirmation is enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ it 'authenticates the user and sends a confirmation email' do
+ post(:create, params: user_params)
+
+ expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
+ expect(response).to redirect_to(dashboard_projects_path)
+ end
end
end
@@ -88,6 +115,88 @@ describe RegistrationsController do
end
end
+ context 'when invisible captcha is enabled' do
+ before do
+ stub_feature_flags(invisible_captcha: true)
+ InvisibleCaptcha.timestamp_threshold = treshold
+ end
+
+ let(:treshold) { 4 }
+ let(:session_params) { { invisible_captcha_timestamp: form_rendered_time.iso8601 } }
+ let(:form_rendered_time) { Time.current }
+ let(:submit_time) { form_rendered_time + treshold }
+ let(:auth_log_attributes) do
+ {
+ message: auth_log_message,
+ env: :invisible_captcha_signup_bot_detected,
+ remote_ip: '0.0.0.0',
+ request_method: 'POST',
+ path: '/users'
+ }
+ end
+
+ describe 'the honeypot has not been filled and the signup form has not been submitted too quickly' do
+ it 'creates an account' do
+ travel_to(submit_time) do
+ expect { post(:create, params: user_params, session: session_params) }.to change(User, :count).by(1)
+ end
+ end
+ end
+
+ describe 'honeypot spam detection' do
+ let(:user_params) { super().merge(firstname: 'Roy', lastname: 'Batty') }
+ let(:auth_log_message) { 'Invisible_Captcha_Honeypot_Request' }
+
+ it 'logs the request, refuses to create an account and renders an empty body' do
+ travel_to(submit_time) do
+ expect(Gitlab::Metrics).to receive(:counter)
+ .with(:bot_blocked_by_invisible_captcha_honeypot, 'Counter of blocked sign up attempts with filled honeypot')
+ .and_call_original
+ expect(Gitlab::AuthLogger).to receive(:error).with(auth_log_attributes).once
+ expect { post(:create, params: user_params, session: session_params) }.not_to change(User, :count)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to be_empty
+ end
+ end
+ end
+
+ describe 'timestamp spam detection' do
+ let(:auth_log_message) { 'Invisible_Captcha_Timestamp_Request' }
+
+ context 'the sign up form has been submitted without the invisible_captcha_timestamp parameter' do
+ let(:session_params) { nil }
+
+ it 'logs the request, refuses to create an account and displays a flash alert' do
+ travel_to(submit_time) do
+ expect(Gitlab::Metrics).to receive(:counter)
+ .with(:bot_blocked_by_invisible_captcha_timestamp, 'Counter of blocked sign up attempts with invalid timestamp')
+ .and_call_original
+ expect(Gitlab::AuthLogger).to receive(:error).with(auth_log_attributes).once
+ expect { post(:create, params: user_params, session: session_params) }.not_to change(User, :count)
+ expect(response).to redirect_to(new_user_session_path)
+ expect(flash[:alert]).to include 'That was a bit too quick! Please resubmit.'
+ end
+ end
+ end
+
+ context 'the sign up form has been submitted too quickly' do
+ let(:submit_time) { form_rendered_time }
+
+ it 'logs the request, refuses to create an account and displays a flash alert' do
+ travel_to(submit_time) do
+ expect(Gitlab::Metrics).to receive(:counter)
+ .with(:bot_blocked_by_invisible_captcha_timestamp, 'Counter of blocked sign up attempts with invalid timestamp')
+ .and_call_original
+ expect(Gitlab::AuthLogger).to receive(:error).with(auth_log_attributes).once
+ expect { post(:create, params: user_params, session: session_params) }.not_to change(User, :count)
+ expect(response).to redirect_to(new_user_session_path)
+ expect(flash[:alert]).to include 'That was a bit too quick! Please resubmit.'
+ end
+ end
+ end
+ end
+ end
+
context 'when terms are enforced' do
before do
enforce_terms
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index 5a5c0a1f6ac..3e0d53a6573 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -11,151 +11,173 @@ describe SearchController do
sign_in(user)
end
- context 'uses the right partials depending on scope' do
- using RSpec::Parameterized::TableSyntax
- render_views
-
- set(:project) { create(:project, :public, :repository, :wiki_repo) }
-
+ shared_examples_for 'when the user cannot read cross project' do |action, params|
before do
- expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?)
+ .with(user, :read_cross_project, :global) { false }
end
- subject { get(:show, params: { project_id: project.id, scope: scope, search: 'merge' }) }
+ it 'blocks access without a project_id' do
+ get action, params: params
- where(:partial, :scope) do
- '_blob' | :blobs
- '_wiki_blob' | :wiki_blobs
- '_commit' | :commits
+ expect(response).to have_gitlab_http_status(403)
end
- with_them do
- it do
- project_wiki = create(:project_wiki, project: project, user: user)
- create(:wiki_page, wiki: project_wiki, attrs: { title: 'merge', content: 'merge' })
+ it 'allows access with a project_id' do
+ get action, params: params.merge(project_id: create(:project, :public).id)
- expect(subject).to render_template("search/results/#{partial}")
- end
+ expect(response).to have_gitlab_http_status(200)
end
end
- context 'global search' do
- render_views
-
- it 'omits pipeline status from load' do
- project = create(:project, :public)
- expect(Gitlab::Cache::Ci::ProjectPipelineStatus).not_to receive(:load_in_batch_for_projects)
-
- get :show, params: { scope: 'projects', search: project.name }
+ shared_examples_for 'with external authorization service enabled' do |action, params|
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:note) { create(:note_on_issue, project: project) }
- expect(assigns[:search_objects].first).to eq project
+ before do
+ enable_external_authorization_service_check
end
- end
-
- it 'finds issue comments' do
- project = create(:project, :public)
- note = create(:note_on_issue, project: project)
- get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
+ it 'renders a 403 when no project is given' do
+ get action, params: params
- expect(assigns[:search_objects].first).to eq note
- end
-
- context 'when the user cannot read cross project' do
- before do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?)
- .with(user, :read_cross_project, :global) { false }
+ expect(response).to have_gitlab_http_status(403)
end
- it 'still allows accessing the search page' do
- get :show
+ it 'renders a 200 when a project was set' do
+ get action, params: params.merge(project_id: project.id)
expect(response).to have_gitlab_http_status(200)
end
+ end
- it 'still blocks searches without a project_id' do
- get :show, params: { search: 'hello' }
+ describe 'GET #show' do
+ it_behaves_like 'when the user cannot read cross project', :show, { search: 'hello' } do
+ it 'still allows accessing the search page' do
+ get :show
- expect(response).to have_gitlab_http_status(403)
+ expect(response).to have_gitlab_http_status(200)
+ end
end
- it 'allows searches with a project_id' do
- get :show, params: { search: 'hello', project_id: create(:project, :public).id }
+ it_behaves_like 'with external authorization service enabled', :show, { search: 'hello' }
- expect(response).to have_gitlab_http_status(200)
- end
- end
+ context 'uses the right partials depending on scope' do
+ using RSpec::Parameterized::TableSyntax
+ render_views
+
+ set(:project) { create(:project, :public, :repository, :wiki_repo) }
- context 'on restricted projects' do
- context 'when signed out' do
before do
- sign_out(user)
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
end
- it "doesn't expose comments on issues" do
- project = create(:project, :public, :issues_private)
- note = create(:note_on_issue, project: project)
+ subject { get(:show, params: { project_id: project.id, scope: scope, search: 'merge' }) }
- get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
+ where(:partial, :scope) do
+ '_blob' | :blobs
+ '_wiki_blob' | :wiki_blobs
+ '_commit' | :commits
+ end
- expect(assigns[:search_objects].count).to eq(0)
+ with_them do
+ it do
+ project_wiki = create(:project_wiki, project: project, user: user)
+ create(:wiki_page, wiki: project_wiki, attrs: { title: 'merge', content: 'merge' })
+
+ expect(subject).to render_template("search/results/#{partial}")
+ end
end
end
- it "doesn't expose comments on merge_requests" do
- project = create(:project, :public, :merge_requests_private)
- note = create(:note_on_merge_request, project: project)
+ context 'global search' do
+ render_views
- get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
+ it 'omits pipeline status from load' do
+ project = create(:project, :public)
+ expect(Gitlab::Cache::Ci::ProjectPipelineStatus).not_to receive(:load_in_batch_for_projects)
+
+ get :show, params: { scope: 'projects', search: project.name }
- expect(assigns[:search_objects].count).to eq(0)
+ expect(assigns[:search_objects].first).to eq project
+ end
end
- it "doesn't expose comments on snippets" do
- project = create(:project, :public, :snippets_private)
- note = create(:note_on_project_snippet, project: project)
+ it 'finds issue comments' do
+ project = create(:project, :public)
+ note = create(:note_on_issue, project: project)
get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
- expect(assigns[:search_objects].count).to eq(0)
+ expect(assigns[:search_objects].first).to eq note
end
- end
- context 'with external authorization service enabled' do
- let(:project) { create(:project, namespace: user.namespace) }
- let(:note) { create(:note_on_issue, project: project) }
+ context 'on restricted projects' do
+ context 'when signed out' do
+ before do
+ sign_out(user)
+ end
- before do
- enable_external_authorization_service_check
- end
+ it "doesn't expose comments on issues" do
+ project = create(:project, :public, :issues_private)
+ note = create(:note_on_issue, project: project)
- describe 'GET #show' do
- it 'renders a 403 when no project is given' do
- get :show, params: { scope: 'notes', search: note.note }
+ get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
- expect(response).to have_gitlab_http_status(403)
+ expect(assigns[:search_objects].count).to eq(0)
+ end
end
- it 'renders a 200 when a project was set' do
+ it "doesn't expose comments on merge_requests" do
+ project = create(:project, :public, :merge_requests_private)
+ note = create(:note_on_merge_request, project: project)
+
get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
- expect(response).to have_gitlab_http_status(200)
+ expect(assigns[:search_objects].count).to eq(0)
end
- end
- describe 'GET #autocomplete' do
- it 'renders a 403 when no project is given' do
- get :autocomplete, params: { term: 'hello' }
+ it "doesn't expose comments on snippets" do
+ project = create(:project, :public, :snippets_private)
+ note = create(:note_on_project_snippet, project: project)
- expect(response).to have_gitlab_http_status(403)
+ get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
+
+ expect(assigns[:search_objects].count).to eq(0)
end
+ end
+ end
- it 'renders a 200 when a project was set' do
- get :autocomplete, params: { project_id: project.id, term: 'hello' }
+ describe 'GET #count' do
+ it_behaves_like 'when the user cannot read cross project', :count, { search: 'hello', scope: 'projects' }
+ it_behaves_like 'with external authorization service enabled', :count, { search: 'hello', scope: 'projects' }
- expect(response).to have_gitlab_http_status(200)
- end
+ it 'returns the result count for the given term and scope' do
+ create(:project, :public, name: 'hello world')
+ create(:project, :public, name: 'foo bar')
+
+ get :count, params: { search: 'hello', scope: 'projects' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to eq({ 'count' => '1' })
+ end
+
+ it 'raises an error if search term is missing' do
+ expect do
+ get :count, params: { scope: 'projects' }
+ end.to raise_error(ActionController::ParameterMissing)
end
+
+ it 'raises an error if search scope is missing' do
+ expect do
+ get :count, params: { search: 'hello' }
+ end.to raise_error(ActionController::ParameterMissing)
+ end
+ end
+
+ describe 'GET #autocomplete' do
+ it_behaves_like 'when the user cannot read cross project', :autocomplete, { term: 'hello' }
+ it_behaves_like 'with external authorization service enabled', :autocomplete, { term: 'hello' }
end
end
diff --git a/spec/controllers/snippets/notes_controller_spec.rb b/spec/controllers/snippets/notes_controller_spec.rb
index 936d7c7dae4..fd4b95ce226 100644
--- a/spec/controllers/snippets/notes_controller_spec.rb
+++ b/spec/controllers/snippets/notes_controller_spec.rb
@@ -26,7 +26,7 @@ describe Snippets::NotesController do
end
it "returns not empty array of notes" do
- expect(JSON.parse(response.body)["notes"].empty?).to be_falsey
+ expect(json_response["notes"].empty?).to be_falsey
end
end
@@ -97,7 +97,7 @@ describe Snippets::NotesController do
it "returns 1 note" do
get :index, params: { snippet_id: private_snippet }
- expect(JSON.parse(response.body)['notes'].count).to eq(1)
+ expect(json_response['notes'].count).to eq(1)
end
end
end
@@ -114,7 +114,120 @@ describe Snippets::NotesController do
it "does not return any note" do
get :index, params: { snippet_id: public_snippet }
- expect(JSON.parse(response.body)['notes'].count).to eq(0)
+ expect(json_response['notes'].count).to eq(0)
+ end
+ end
+ end
+
+ describe 'POST create' do
+ context 'when a snippet is public' do
+ let(:request_params) do
+ {
+ note: attributes_for(:note_on_personal_snippet, noteable: public_snippet),
+ snippet_id: public_snippet.id
+ }
+ end
+
+ before do
+ sign_in user
+ end
+
+ it 'returns status 302' do
+ post :create, params: request_params
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+
+ it 'creates the note' do
+ expect { post :create, params: request_params }.to change { Note.count }.by(1)
+ end
+ end
+
+ context 'when a snippet is internal' do
+ let(:request_params) do
+ {
+ note: attributes_for(:note_on_personal_snippet, noteable: internal_snippet),
+ snippet_id: internal_snippet.id
+ }
+ end
+
+ before do
+ sign_in user
+ end
+
+ it 'returns status 302' do
+ post :create, params: request_params
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+
+ it 'creates the note' do
+ expect { post :create, params: request_params }.to change { Note.count }.by(1)
+ end
+ end
+
+ context 'when a snippet is private' do
+ let(:request_params) do
+ {
+ note: attributes_for(:note_on_personal_snippet, noteable: private_snippet),
+ snippet_id: private_snippet.id
+ }
+ end
+
+ before do
+ sign_in user
+ end
+
+ context 'when user is not the author' do
+ before do
+ sign_in(user)
+ end
+
+ it 'returns status 404' do
+ post :create, params: request_params
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'does not create the note' do
+ expect { post :create, params: request_params }.not_to change { Note.count }
+ end
+
+ context 'when user sends a snippet_id for a public snippet' do
+ let(:request_params) do
+ {
+ note: attributes_for(:note_on_personal_snippet, noteable: private_snippet),
+ snippet_id: public_snippet.id
+ }
+ end
+
+ it 'returns status 302' do
+ post :create, params: request_params
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+
+ it 'creates the note on the public snippet' do
+ expect { post :create, params: request_params }.to change { Note.count }.by(1)
+ expect(Note.last.noteable).to eq public_snippet
+ end
+ end
+ end
+
+ context 'when user is the author' do
+ before do
+ sign_in(private_snippet.author)
+ end
+
+ it 'returns status 302' do
+ post :create, params: request_params
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+
+ it 'creates the note' do
+ expect { post :create, params: request_params }.to change { Note.count }.by(1)
+ end
end
end
end
@@ -175,11 +288,13 @@ describe Snippets::NotesController do
describe 'POST toggle_award_emoji' do
let(:note) { create(:note_on_personal_snippet, noteable: public_snippet) }
+ let(:emoji_name) { 'thumbsup'}
+
before do
sign_in(user)
end
- subject { post(:toggle_award_emoji, params: { snippet_id: public_snippet, id: note.id, name: "thumbsup" }) }
+ subject { post(:toggle_award_emoji, params: { snippet_id: public_snippet, id: note.id, name: emoji_name }) }
it "toggles the award emoji" do
expect { subject }.to change { note.award_emoji.count }.by(1)
@@ -188,7 +303,7 @@ describe Snippets::NotesController do
end
it "removes the already awarded emoji when it exists" do
- note.toggle_award_emoji('thumbsup', user) # create award emoji before
+ create(:award_emoji, awardable: note, name: emoji_name, user: user)
expect { subject }.to change { AwardEmoji.count }.by(-1)
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index f8666a1986f..b0092bc8994 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -209,8 +209,8 @@ describe SnippetsController do
context 'when the snippet description contains a file' do
include FileMoverHelpers
- let(:picture_file) { '/-/system/temp/secret56/picture.jpg' }
- let(:text_file) { '/-/system/temp/secret78/text.txt' }
+ let(:picture_file) { "/-/system/user/#{user.id}/secret56/picture.jpg" }
+ let(:text_file) { "/-/system/user/#{user.id}/secret78/text.txt" }
let(:description) do
"Description with picture: ![picture](/uploads#{picture_file}) and "\
"text: [text.txt](/uploads#{text_file})"
@@ -622,7 +622,7 @@ describe SnippetsController do
post :preview_markdown, params: { id: snippet, text: '*Markdown* text' }
- expect(JSON.parse(response.body).keys).to match_array(%w(body references))
+ expect(json_response.keys).to match_array(%w(body references))
end
end
end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index d27658e02cb..0876502a899 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -24,121 +24,160 @@ describe UploadsController do
let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
describe 'POST create' do
- let(:model) { 'personal_snippet' }
- let(:snippet) { create(:personal_snippet, :public) }
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
- context 'when a user does not have permissions to upload a file' do
- it "returns 401 when the user is not logged in" do
- post :create, params: { model: model, id: snippet.id }, format: :json
+ context 'snippet uploads' do
+ let(:model) { 'personal_snippet' }
+ let(:snippet) { create(:personal_snippet, :public) }
- expect(response).to have_gitlab_http_status(401)
- end
+ context 'when a user does not have permissions to upload a file' do
+ it "returns 401 when the user is not logged in" do
+ post :create, params: { model: model, id: snippet.id }, format: :json
- it "returns 404 when user can't comment on a snippet" do
- private_snippet = create(:personal_snippet, :private)
+ expect(response).to have_gitlab_http_status(401)
+ end
- sign_in(user)
- post :create, params: { model: model, id: private_snippet.id }, format: :json
+ it "returns 404 when user can't comment on a snippet" do
+ private_snippet = create(:personal_snippet, :private)
- expect(response).to have_gitlab_http_status(404)
- end
- end
+ sign_in(user)
+ post :create, params: { model: model, id: private_snippet.id }, format: :json
- context 'when a user is logged in' do
- before do
- sign_in(user)
+ expect(response).to have_gitlab_http_status(404)
+ end
end
- it "returns an error without file" do
- post :create, params: { model: model, id: snippet.id }, format: :json
+ context 'when a user is logged in' do
+ before do
+ sign_in(user)
+ end
- expect(response).to have_gitlab_http_status(422)
- end
+ it "returns an error without file" do
+ post :create, params: { model: model, id: snippet.id }, format: :json
- it "returns an error with invalid model" do
- expect { post :create, params: { model: 'invalid', id: snippet.id }, format: :json }
- .to raise_error(ActionController::UrlGenerationError)
- end
+ expect(response).to have_gitlab_http_status(422)
+ end
- it "returns 404 status when object not found" do
- post :create, params: { model: model, id: 9999 }, format: :json
+ it "returns an error with invalid model" do
+ expect { post :create, params: { model: 'invalid', id: snippet.id }, format: :json }
+ .to raise_error(ActionController::UrlGenerationError)
+ end
- expect(response).to have_gitlab_http_status(404)
- end
+ it "returns 404 status when object not found" do
+ post :create, params: { model: model, id: 9999 }, format: :json
- context 'with valid image' do
- before do
- post :create, params: { model: 'personal_snippet', id: snippet.id, file: jpg }, format: :json
+ expect(response).to have_gitlab_http_status(404)
end
- it 'returns a content with original filename, new link, and correct type.' do
- expect(response.body).to match '\"alt\":\"rails_sample\"'
- expect(response.body).to match "\"url\":\"/uploads"
+ context 'with valid image' do
+ before do
+ post :create, params: { model: 'personal_snippet', id: snippet.id, file: jpg }, format: :json
+ end
+
+ it 'returns a content with original filename, new link, and correct type.' do
+ expect(response.body).to match '\"alt\":\"rails_sample\"'
+ expect(response.body).to match "\"url\":\"/uploads"
+ end
+
+ it 'creates a corresponding Upload record' do
+ upload = Upload.last
+
+ aggregate_failures do
+ expect(upload).to exist
+ expect(upload.model).to eq snippet
+ end
+ end
end
- it 'creates a corresponding Upload record' do
- upload = Upload.last
+ context 'with valid non-image file' do
+ before do
+ post :create, params: { model: 'personal_snippet', id: snippet.id, file: txt }, format: :json
+ end
- aggregate_failures do
- expect(upload).to exist
- expect(upload.model).to eq snippet
+ it 'returns a content with original filename, new link, and correct type.' do
+ expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
+ expect(response.body).to match "\"url\":\"/uploads"
+ end
+
+ it 'creates a corresponding Upload record' do
+ upload = Upload.last
+
+ aggregate_failures do
+ expect(upload).to exist
+ expect(upload.model).to eq snippet
+ end
end
end
end
+ end
+
+ context 'user uploads' do
+ let(:model) { 'user' }
+
+ it 'returns 401 when the user has no access' do
+ post :create, params: { model: 'user', id: user.id }, format: :json
- context 'with valid non-image file' do
+ expect(response).to have_gitlab_http_status(401)
+ end
+
+ context 'when user is logged in' do
before do
- post :create, params: { model: 'personal_snippet', id: snippet.id, file: txt }, format: :json
+ sign_in(user)
+ end
+
+ subject do
+ post :create, params: { model: model, id: user.id, file: jpg }, format: :json
end
it 'returns a content with original filename, new link, and correct type.' do
- expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
- expect(response.body).to match "\"url\":\"/uploads"
+ subject
+
+ expect(response.body).to match '\"alt\":\"rails_sample\"'
+ expect(response.body).to match "\"url\":\"/uploads/-/system/user/#{user.id}/"
end
it 'creates a corresponding Upload record' do
+ expect { subject }.to change { Upload.count }
+
upload = Upload.last
aggregate_failures do
expect(upload).to exist
- expect(upload.model).to eq snippet
+ expect(upload.model).to eq user
end
end
- end
- context 'temporal with valid image' do
- subject do
- post :create, params: { model: 'personal_snippet', file: jpg }, format: :json
- end
+ context 'with valid non-image file' do
+ subject do
+ post :create, params: { model: model, id: user.id, file: txt }, format: :json
+ end
- it 'returns a content with original filename, new link, and correct type.' do
- subject
+ it 'returns a content with original filename, new link, and correct type.' do
+ subject
- expect(response.body).to match '\"alt\":\"rails_sample\"'
- expect(response.body).to match "\"url\":\"/uploads/-/system/temp"
- end
+ expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
+ expect(response.body).to match "\"url\":\"/uploads/-/system/user/#{user.id}/"
+ end
- it 'does not create an Upload record' do
- expect { subject }.not_to change { Upload.count }
- end
- end
+ it 'creates a corresponding Upload record' do
+ expect { subject }.to change { Upload.count }
- context 'temporal with valid non-image file' do
- subject do
- post :create, params: { model: 'personal_snippet', file: txt }, format: :json
+ upload = Upload.last
+
+ aggregate_failures do
+ expect(upload).to exist
+ expect(upload.model).to eq user
+ end
+ end
end
- it 'returns a content with original filename, new link, and correct type.' do
- subject
+ it 'returns 404 when given user is not the logged in one' do
+ another_user = create(:user)
- expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
- expect(response.body).to match "\"url\":\"/uploads/-/system/temp"
- end
+ post :create, params: { model: model, id: another_user.id, file: txt }, format: :json
- it 'does not create an Upload record' do
- expect { subject }.not_to change { Upload.count }
+ expect(response).to have_gitlab_http_status(404)
end
end
end
diff --git a/spec/controllers/user_callouts_controller_spec.rb b/spec/controllers/user_callouts_controller_spec.rb
index babc93a83e5..07eaff2da09 100644
--- a/spec/controllers/user_callouts_controller_spec.rb
+++ b/spec/controllers/user_callouts_controller_spec.rb
@@ -13,7 +13,7 @@ describe UserCalloutsController do
subject { post :create, params: { feature_name: feature_name }, format: :json }
context 'with valid feature name' do
- let(:feature_name) { UserCallout.feature_names.keys.first }
+ let(:feature_name) { UserCallout.feature_names.first.first }
context 'when callout entry does not exist' do
it 'creates a callout entry with dismissed state' do
@@ -28,7 +28,7 @@ describe UserCalloutsController do
end
context 'when callout entry already exists' do
- let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.keys.first, user: user) }
+ let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.first.first, user: user) }
it 'returns success' do
subject
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index c3d6ea9cbcd..5566df0c216 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -19,7 +19,7 @@ describe UsersController do
it 'renders the show template' do
get :show, params: { username: user.username }
- expect(response).to be_success
+ expect(response).to be_successful
expect(response).to render_template('show')
end
end
@@ -291,7 +291,7 @@ describe UsersController do
it 'response with snippets json data' do
get :snippets, params: { username: user.username }, format: :json
expect(response).to have_gitlab_http_status(200)
- expect(JSON.parse(response.body)).to have_key('html')
+ expect(json_response).to have_key('html')
end
end
@@ -362,7 +362,7 @@ describe UsersController do
it 'responds with success' do
get :show, params: { username: user.username }
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -418,7 +418,7 @@ describe UsersController do
it 'responds with success' do
get :projects, params: { username: user.username }
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 6cfec5f4017..232890b1bba 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -1,12 +1,14 @@
# frozen_string_literal: true
require 'spec_helper'
+require Rails.root.join('ee', 'spec', 'db', 'schema_support') if Gitlab.ee?
describe 'Database schema' do
let(:connection) { ActiveRecord::Base.connection }
let(:tables) { connection.tables }
# Use if you are certain that this column should not have a foreign key
+ # EE: edit the ee/spec/db/schema_support.rb
IGNORED_FK_COLUMNS = {
abuse_reports: %w[reporter_id user_id],
application_settings: %w[performance_bar_allowed_group_id slack_app_id snowplow_site_id],
@@ -25,7 +27,7 @@ describe 'Database schema' do
cluster_providers_gcp: %w[gcp_project_id operation_id],
deploy_keys_projects: %w[deploy_key_id],
deployments: %w[deployable_id environment_id user_id],
- draft_notes: %w[discussion_id],
+ draft_notes: %w[discussion_id commit_id],
emails: %w[user_id],
events: %w[target_id],
epics: %w[updated_by_id last_edited_by_id start_date_sourcing_milestone_id due_date_sourcing_milestone_id],
diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb
index 021971ac421..578af9ed895 100644
--- a/spec/factories/abuse_reports.rb
+++ b/spec/factories/abuse_reports.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :abuse_report do
reporter factory: :user
diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb
index dd5129229d4..bdd5964fb93 100644
--- a/spec/factories/appearances.rb
+++ b/spec/factories/appearances.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Read about factories at https://github.com/thoughtbot/factory_bot
FactoryBot.define do
diff --git a/spec/factories/application_settings.rb b/spec/factories/application_settings.rb
index 00c063c49f8..90b6b9e648a 100644
--- a/spec/factories/application_settings.rb
+++ b/spec/factories/application_settings.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :application_setting do
default_projects_limit 42
diff --git a/spec/factories/award_emoji.rb b/spec/factories/award_emoji.rb
index 43753fa650c..a8bb806381e 100644
--- a/spec/factories/award_emoji.rb
+++ b/spec/factories/award_emoji.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :award_emoji do
name "thumbsup"
diff --git a/spec/factories/badge.rb b/spec/factories/badge.rb
index b87ece946cb..1d4e29014cc 100644
--- a/spec/factories/badge.rb
+++ b/spec/factories/badge.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
trait :base_badge do
link_url { generate(:url) }
diff --git a/spec/factories/boards.rb b/spec/factories/boards.rb
index 6524bb4830c..a5aff5c7504 100644
--- a/spec/factories/boards.rb
+++ b/spec/factories/boards.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :board do
transient do
diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb
index 1a2be5e9552..2a30e2034b1 100644
--- a/spec/factories/broadcast_messages.rb
+++ b/spec/factories/broadcast_messages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :broadcast_message do
message "MyText"
diff --git a/spec/factories/chat_names.rb b/spec/factories/chat_names.rb
index 56993e5da18..07bf990162f 100644
--- a/spec/factories/chat_names.rb
+++ b/spec/factories/chat_names.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :chat_name, class: ChatName do
user factory: :user
diff --git a/spec/factories/chat_teams.rb b/spec/factories/chat_teams.rb
index d048c46d6b6..52628e6d53d 100644
--- a/spec/factories/chat_teams.rb
+++ b/spec/factories/chat_teams.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :chat_team, class: ChatTeam do
sequence(:team_id) { |n| "abcdefghijklm#{n}" }
diff --git a/spec/factories/ci/bridge.rb b/spec/factories/ci/bridge.rb
index b1d82b98411..b1b714277e4 100644
--- a/spec/factories/ci/bridge.rb
+++ b/spec/factories/ci/bridge.rb
@@ -1,12 +1,14 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_bridge, class: Ci::Bridge do
- name ' bridge'
+ name 'bridge'
stage 'test'
stage_idx 0
ref 'master'
tag false
created_at 'Di 29. Okt 09:50:00 CET 2013'
- status :success
+ status :created
pipeline factory: :ci_pipeline
@@ -15,6 +17,7 @@ FactoryBot.define do
end
transient { downstream nil }
+ transient { upstream nil }
after(:build) do |bridge, evaluator|
bridge.project ||= bridge.pipeline.project
@@ -24,6 +27,12 @@ FactoryBot.define do
trigger: { project: evaluator.downstream.full_path }
)
end
+
+ if evaluator.upstream.present?
+ bridge.options = bridge.options.to_h.merge(
+ bridge_needs: { pipeline: evaluator.upstream.full_path }
+ )
+ end
end
end
end
diff --git a/spec/factories/ci/build_need.rb b/spec/factories/ci/build_need.rb
new file mode 100644
index 00000000000..568aff45a91
--- /dev/null
+++ b/spec/factories/ci/build_need.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :ci_build_need, class: Ci::BuildNeed do
+ build factory: :ci_build
+ sequence(:name) { |n| "build_#{n}" }
+ end
+end
diff --git a/spec/factories/ci/build_trace_chunks.rb b/spec/factories/ci/build_trace_chunks.rb
index 3e8e2736423..492dc47f083 100644
--- a/spec/factories/ci/build_trace_chunks.rb
+++ b/spec/factories/ci/build_trace_chunks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_build_trace_chunk, class: Ci::BuildTraceChunk do
build factory: :ci_build
diff --git a/spec/factories/ci/build_trace_section_names.rb b/spec/factories/ci/build_trace_section_names.rb
index ce07e716dde..e52694ef3dc 100644
--- a/spec/factories/ci/build_trace_section_names.rb
+++ b/spec/factories/ci/build_trace_section_names.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_build_trace_section_name, class: Ci::BuildTraceSectionName do
sequence(:name) { |n| "section_#{n}" }
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 5f7c75a3a92..e3b7c64176a 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
include ActionDispatch::TestProcess
FactoryBot.define do
diff --git a/spec/factories/ci/group_variables.rb b/spec/factories/ci/group_variables.rb
index 9bf520a2c0a..13c2b78e61b 100644
--- a/spec/factories/ci/group_variables.rb
+++ b/spec/factories/ci/group_variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_group_variable, class: Ci::GroupVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 542fa9775cd..6f553cadfa3 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
include ActionDispatch::TestProcess
FactoryBot.define do
@@ -6,6 +8,10 @@ FactoryBot.define do
file_type :archive
file_format :zip
+ trait :expired do
+ expire_at { Date.yesterday }
+ end
+
trait :remote_store do
file_store JobArtifactUploader::Store::REMOTE
end
diff --git a/spec/factories/ci/job_variables.rb b/spec/factories/ci/job_variables.rb
new file mode 100644
index 00000000000..d664b763abd
--- /dev/null
+++ b/spec/factories/ci/job_variables.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :ci_job_variable, class: Ci::JobVariable do
+ sequence(:key) { |n| "VARIABLE_#{n}" }
+ value 'VARIABLE_VALUE'
+
+ job factory: :ci_build
+ end
+end
diff --git a/spec/factories/ci/pipeline_schedule.rb b/spec/factories/ci/pipeline_schedule.rb
index 4b83ba2ac1b..8fae6986869 100644
--- a/spec/factories/ci/pipeline_schedule.rb
+++ b/spec/factories/ci/pipeline_schedule.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_pipeline_schedule, class: Ci::PipelineSchedule do
cron '0 1 * * *'
diff --git a/spec/factories/ci/pipeline_schedule_variables.rb b/spec/factories/ci/pipeline_schedule_variables.rb
index c85b97fbfc7..fd7cfada65b 100644
--- a/spec/factories/ci/pipeline_schedule_variables.rb
+++ b/spec/factories/ci/pipeline_schedule_variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_pipeline_schedule_variable, class: Ci::PipelineScheduleVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/ci/pipeline_variables.rb b/spec/factories/ci/pipeline_variables.rb
index b18055d7b3a..af0982124d7 100644
--- a/spec/factories/ci/pipeline_variables.rb
+++ b/spec/factories/ci/pipeline_variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_pipeline_variable, class: Ci::PipelineVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index aa5ccbda6cd..9652b0000a9 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_empty_pipeline, class: Ci::Pipeline do
source :push
diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb
index ec15972c423..bc28544a839 100644
--- a/spec/factories/ci/runner_projects.rb
+++ b/spec/factories/ci/runner_projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_runner_project, class: Ci::RunnerProject do
runner factory: [:ci_runner, :project]
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index 24e70913b87..1e4344b814d 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_runner, class: Ci::Runner do
sequence(:description) { |n| "My runner#{n}" }
diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb
index ce61e6bf759..88ff8d7dc53 100644
--- a/spec/factories/ci/stages.rb
+++ b/spec/factories/ci/stages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_stage, class: Ci::LegacyStage do
skip_create
diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb
index 0e9fc3d0014..d63bf9868c9 100644
--- a/spec/factories/ci/trigger_requests.rb
+++ b/spec/factories/ci/trigger_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_trigger_request, class: Ci::TriggerRequest do
trigger factory: :ci_trigger
diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb
index 742d9efba2d..6f628ed5435 100644
--- a/spec/factories/ci/triggers.rb
+++ b/spec/factories/ci/triggers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_trigger_without_token, class: Ci::Trigger do
owner
diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb
index 97a7c9ba252..55d11085040 100644
--- a/spec/factories/ci/variables.rb
+++ b/spec/factories/ci/variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_variable, class: Ci::Variable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb
index d78f01828d7..89f7bc15217 100644
--- a/spec/factories/clusters/applications/helm.rb
+++ b/spec/factories/clusters/applications/helm.rb
@@ -1,7 +1,23 @@
+# frozen_string_literal: true
+
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::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
+
+ after(:create) do
+ allow(Gitlab::Kubernetes::Helm::Certificate).to receive(:generate_root).and_call_original
+ end
+
trait :not_installable do
status(-2)
end
@@ -58,7 +74,7 @@ FactoryBot.define do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
- factory :clusters_applications_cert_managers, class: Clusters::Applications::CertManager 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)
end
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index 6eb0194b710..d294e6d055e 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -1,9 +1,14 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :cluster, class: Clusters::Cluster do
user
name 'test-cluster'
cluster_type :project_type
managed true
+ namespace_per_environment true
+
+ factory :cluster_for_group, traits: [:provided_by_gcp, :group]
trait :instance do
cluster_type { Clusters::Cluster.cluster_types[:instance_type] }
@@ -13,7 +18,7 @@ FactoryBot.define do
cluster_type { Clusters::Cluster.cluster_types[:project_type] }
before(:create) do |cluster, evaluator|
- cluster.projects << create(:project, :repository) unless cluster.projects.present?
+ cluster.projects << create(:project) unless cluster.projects.present?
end
end
@@ -25,6 +30,10 @@ FactoryBot.define do
end
end
+ trait :namespace_per_environment_disabled do
+ namespace_per_environment false
+ end
+
trait :provided_by_user do
provider_type :user
platform_type :kubernetes
diff --git a/spec/factories/clusters/kubernetes_namespaces.rb b/spec/factories/clusters/kubernetes_namespaces.rb
index 042be7b4c4a..8d6ad1b9f79 100644
--- a/spec/factories/clusters/kubernetes_namespaces.rb
+++ b/spec/factories/clusters/kubernetes_namespaces.rb
@@ -5,12 +5,21 @@ FactoryBot.define do
association :cluster, :project, :provided_by_gcp
after(:build) do |kubernetes_namespace|
- if kubernetes_namespace.cluster.project_type?
- cluster_project = kubernetes_namespace.cluster.cluster_project
+ cluster = kubernetes_namespace.cluster
+
+ if cluster.project_type?
+ cluster_project = cluster.cluster_project
kubernetes_namespace.project = cluster_project.project
kubernetes_namespace.cluster_project = cluster_project
end
+
+ kubernetes_namespace.namespace ||=
+ Gitlab::Kubernetes::DefaultNamespace.new(
+ cluster,
+ project: kubernetes_namespace.project
+ ).from_environment_slug(kubernetes_namespace.environment&.slug)
+ kubernetes_namespace.service_account_name ||= "#{kubernetes_namespace.namespace}-service-account"
end
trait :with_token do
diff --git a/spec/factories/clusters/platforms/kubernetes.rb b/spec/factories/clusters/platforms/kubernetes.rb
index bf30a9c3a61..d5dc288fddb 100644
--- a/spec/factories/clusters/platforms/kubernetes.rb
+++ b/spec/factories/clusters/platforms/kubernetes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :cluster_platform_kubernetes, class: Clusters::Platforms::Kubernetes do
cluster
diff --git a/spec/factories/clusters/providers/gcp.rb b/spec/factories/clusters/providers/gcp.rb
index 186c7c8027c..22462651b6a 100644
--- a/spec/factories/clusters/providers/gcp.rb
+++ b/spec/factories/clusters/providers/gcp.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :cluster_provider_gcp, class: Clusters::Providers::Gcp do
cluster
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 848a31e96c1..a76da30217e 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :commit_status, class: CommitStatus do
name 'default'
diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb
index 2bcc4b6cf52..d1554426a76 100644
--- a/spec/factories/commits.rb
+++ b/spec/factories/commits.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/repo_helpers'
FactoryBot.define do
diff --git a/spec/factories/container_repositories.rb b/spec/factories/container_repositories.rb
index 00fad7975c9..0b756220d68 100644
--- a/spec/factories/container_repositories.rb
+++ b/spec/factories/container_repositories.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :container_repository do
- name 'test_image'
+ sequence(:name) { |n| "test_image_#{n}" }
project
transient do
diff --git a/spec/factories/conversational_development_index_metrics.rb b/spec/factories/conversational_development_index_metrics.rb
index abf37fb861e..ea5816684c6 100644
--- a/spec/factories/conversational_development_index_metrics.rb
+++ b/spec/factories/conversational_development_index_metrics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :conversational_development_index_metric, class: ConversationalDevelopmentIndex::Metric do
leader_issues 9.256
diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb
index 4350652fb79..7f82902dee7 100644
--- a/spec/factories/deploy_keys_projects.rb
+++ b/spec/factories/deploy_keys_projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :deploy_keys_project do
deploy_key
diff --git a/spec/factories/deploy_tokens.rb b/spec/factories/deploy_tokens.rb
index 017e866e69c..99486acc2ab 100644
--- a/spec/factories/deploy_tokens.rb
+++ b/spec/factories/deploy_tokens.rb
@@ -1,6 +1,9 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :deploy_token do
- token { SecureRandom.hex(50) }
+ token nil
+ token_encrypted { Gitlab::CryptoHelper.aes256_gcm_encrypt( SecureRandom.hex(50) ) }
sequence(:name) { |n| "PDT #{n}" }
read_repository true
read_registry true
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index db438ad32d3..89ff1c527df 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :deployment, class: Deployment do
sha 'b83d6e391c22777fca1ed3012fce84f633d7fed0'
@@ -22,6 +24,10 @@ FactoryBot.define do
ref 'pages-deploy'
end
+ trait :on_cluster do
+ cluster factory: %i(cluster provided_by_gcp)
+ end
+
trait :running do
status :running
end
diff --git a/spec/factories/emails.rb b/spec/factories/emails.rb
index feacd5ccf15..284ba631c37 100644
--- a/spec/factories/emails.rb
+++ b/spec/factories/emails.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :email do
user
diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb
index 9d9e3d693b8..b5c8f0ca4f0 100644
--- a/spec/factories/environments.rb
+++ b/spec/factories/environments.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :environment, class: Environment do
sequence(:name) { |n| "environment#{n}" }
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
index bf8411b1894..b15eb1592fc 100644
--- a/spec/factories/events.rb
+++ b/spec/factories/events.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :event do
project
diff --git a/spec/factories/file_uploaders.rb b/spec/factories/file_uploaders.rb
index 8404985bfea..ec8f5c9af2d 100644
--- a/spec/factories/file_uploaders.rb
+++ b/spec/factories/file_uploaders.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :file_uploader do
skip_create
diff --git a/spec/factories/fork_network_members.rb b/spec/factories/fork_network_members.rb
index 850e3f158f1..d34de72ab60 100644
--- a/spec/factories/fork_network_members.rb
+++ b/spec/factories/fork_network_members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :fork_network_member do
association :project
diff --git a/spec/factories/fork_networks.rb b/spec/factories/fork_networks.rb
index 813b1943eb2..12ea688bbce 100644
--- a/spec/factories/fork_networks.rb
+++ b/spec/factories/fork_networks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :fork_network do
association :root_project, factory: :project
diff --git a/spec/factories/gitaly/commit.rb b/spec/factories/gitaly/commit.rb
index 5034c3d0e48..954b5338846 100644
--- a/spec/factories/gitaly/commit.rb
+++ b/spec/factories/gitaly/commit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
sequence(:gitaly_commit_id) { Digest::SHA1.hexdigest(Time.now.to_f.to_s) }
diff --git a/spec/factories/gitaly/commit_author.rb b/spec/factories/gitaly/commit_author.rb
index aaf634ce08b..51dcd8a623b 100644
--- a/spec/factories/gitaly/commit_author.rb
+++ b/spec/factories/gitaly/commit_author.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :gitaly_commit_author, class: Gitaly::CommitAuthor do
skip_create
diff --git a/spec/factories/gitaly/tag.rb b/spec/factories/gitaly/tag.rb
index e99776d524a..a7a84753090 100644
--- a/spec/factories/gitaly/tag.rb
+++ b/spec/factories/gitaly/tag.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :gitaly_tag, class: Gitaly::Tag do
skip_create
diff --git a/spec/factories/gpg_key_subkeys.rb b/spec/factories/gpg_key_subkeys.rb
index 6c7db5379a9..9daf26d930d 100644
--- a/spec/factories/gpg_key_subkeys.rb
+++ b/spec/factories/gpg_key_subkeys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :gpg_key_subkey do
gpg_key
diff --git a/spec/factories/gpg_keys.rb b/spec/factories/gpg_keys.rb
index 3c0f43cc1b6..9f321643174 100644
--- a/spec/factories/gpg_keys.rb
+++ b/spec/factories/gpg_keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/gpg_helpers'
FactoryBot.define do
diff --git a/spec/factories/gpg_signature.rb b/spec/factories/gpg_signature.rb
index b89e6ffc4b3..a0fc1740d77 100644
--- a/spec/factories/gpg_signature.rb
+++ b/spec/factories/gpg_signature.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :gpg_signature do
commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) }
diff --git a/spec/factories/group_custom_attributes.rb b/spec/factories/group_custom_attributes.rb
index d2f45d5d3ce..895bc42c84b 100644
--- a/spec/factories/group_custom_attributes.rb
+++ b/spec/factories/group_custom_attributes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :group_custom_attribute do
group
diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb
index 077c6ddc5ae..4c875935d82 100644
--- a/spec/factories/group_members.rb
+++ b/spec/factories/group_members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :group_member do
access_level { GroupMember::OWNER }
@@ -14,7 +16,13 @@ FactoryBot.define do
trait(:invited) do
user_id nil
invite_token 'xxx'
- invite_email 'email@email.com'
+ sequence :invite_email do |n|
+ "email#{n}@email.com"
+ end
+ end
+
+ trait(:ldap) do
+ ldap true
end
end
end
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index 18a0c2ec731..334c0f369cd 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :group, class: Group, parent: :namespace do
sequence(:name) { |n| "group#{n}" }
@@ -45,5 +47,9 @@ FactoryBot.define do
trait :auto_devops_disabled do
auto_devops_enabled false
end
+
+ trait :owner_subgroup_creation_only do
+ subgroup_creation_level ::Gitlab::Access::OWNER_SUBGROUP_ACCESS
+ end
end
end
diff --git a/spec/factories/identities.rb b/spec/factories/identities.rb
index 122d0d42938..21cfe7fe623 100644
--- a/spec/factories/identities.rb
+++ b/spec/factories/identities.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :identity do
provider 'ldapmain'
diff --git a/spec/factories/import_export_uploads.rb b/spec/factories/import_export_uploads.rb
index 7750d49b1d0..8521411e0e8 100644
--- a/spec/factories/import_export_uploads.rb
+++ b/spec/factories/import_export_uploads.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :import_export_upload do
project { create(:project) }
diff --git a/spec/factories/import_states.rb b/spec/factories/import_states.rb
index d6de26dccbc..8e778200389 100644
--- a/spec/factories/import_states.rb
+++ b/spec/factories/import_states.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :import_state, class: ProjectImportState do
status :none
diff --git a/spec/factories/instance_configuration.rb b/spec/factories/instance_configuration.rb
index 31866a9c221..daa0be3f404 100644
--- a/spec/factories/instance_configuration.rb
+++ b/spec/factories/instance_configuration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :instance_configuration do
skip_create
diff --git a/spec/factories/internal_ids.rb b/spec/factories/internal_ids.rb
index fbde07a391a..df5c5beeb42 100644
--- a/spec/factories/internal_ids.rb
+++ b/spec/factories/internal_ids.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :internal_id do
project
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index ab27fee33dc..434225f7022 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :issue do
title { generate(:title) }
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index 3f0c60f32b7..087d2521836 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/key_generator_helper'
FactoryBot.define do
diff --git a/spec/factories/label_links.rb b/spec/factories/label_links.rb
index 007847d9cf4..9b7c0a158bd 100644
--- a/spec/factories/label_links.rb
+++ b/spec/factories/label_links.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :label_link do
label
diff --git a/spec/factories/label_priorities.rb b/spec/factories/label_priorities.rb
index c4824faad53..2f9f2ddfa89 100644
--- a/spec/factories/label_priorities.rb
+++ b/spec/factories/label_priorities.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :label_priority do
project
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index f759b6d499d..3eed750be03 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
trait :base_label do
title { generate(:label_title) }
diff --git a/spec/factories/lfs_file_locks.rb b/spec/factories/lfs_file_locks.rb
index b9d24f82b65..73675d076ab 100644
--- a/spec/factories/lfs_file_locks.rb
+++ b/spec/factories/lfs_file_locks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :lfs_file_lock do
user
diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb
index c909471bb55..631d87cfb12 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
include ActionDispatch::TestProcess
FactoryBot.define do
@@ -14,6 +16,7 @@ FactoryBot.define do
# objects, so the test needs to decide which (if any) object gets it
trait :correct_oid do
oid 'b804383982bb89b00e828e3f44c038cc991d3d1768009fc39ba8e2c081b9fb75'
+ size 1062
end
trait :object_storage do
diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb
index 4804d0bb884..7b55cc57f75 100644
--- a/spec/factories/lfs_objects_projects.rb
+++ b/spec/factories/lfs_objects_projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :lfs_objects_project do
lfs_object
diff --git a/spec/factories/lists.rb b/spec/factories/lists.rb
index 210c58b21e9..e68611ec518 100644
--- a/spec/factories/lists.rb
+++ b/spec/factories/lists.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :list do
board
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 0b6a43b13a9..3d12ff98257 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :merge_request do
title { generate(:title) }
diff --git a/spec/factories/merge_requests_closing_issues.rb b/spec/factories/merge_requests_closing_issues.rb
index ee0606a72b6..94f8b27358c 100644
--- a/spec/factories/merge_requests_closing_issues.rb
+++ b/spec/factories/merge_requests_closing_issues.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :merge_requests_closing_issues do
issue
diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb
index 027349b2f8e..7d623000fc9 100644
--- a/spec/factories/milestones.rb
+++ b/spec/factories/milestones.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :milestone do
title
diff --git a/spec/factories/namespaces.rb b/spec/factories/namespaces.rb
index 0cfc6e3aa46..09dbe16ef9e 100644
--- a/spec/factories/namespaces.rb
+++ b/spec/factories/namespaces.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :namespace do
sequence(:name) { |n| "namespace#{n}" }
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index c51f2f958f9..5b9a7e6f864 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/repo_helpers'
include ActionDispatch::TestProcess
diff --git a/spec/factories/notification_settings.rb b/spec/factories/notification_settings.rb
index 5116ef33f5d..c16b0e456ba 100644
--- a/spec/factories/notification_settings.rb
+++ b/spec/factories/notification_settings.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :notification_setting do
source factory: :project
diff --git a/spec/factories/oauth_access_grants.rb b/spec/factories/oauth_access_grants.rb
index 02c51cd9899..7a46ae6d71d 100644
--- a/spec/factories/oauth_access_grants.rb
+++ b/spec/factories/oauth_access_grants.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :oauth_access_grant do
resource_owner_id { create(:user).id }
diff --git a/spec/factories/oauth_access_tokens.rb b/spec/factories/oauth_access_tokens.rb
index eabfd6cd830..8d1075dacbb 100644
--- a/spec/factories/oauth_access_tokens.rb
+++ b/spec/factories/oauth_access_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :oauth_access_token do
resource_owner
diff --git a/spec/factories/oauth_applications.rb b/spec/factories/oauth_applications.rb
index 4427da1d6c7..4748b320298 100644
--- a/spec/factories/oauth_applications.rb
+++ b/spec/factories/oauth_applications.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :oauth_application, class: 'Doorkeeper::Application', aliases: [:application] do
sequence(:name) { |n| "OAuth App #{n}" }
diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb
index 3e0baab04ce..ee5be82cd19 100644
--- a/spec/factories/pages_domains.rb
+++ b/spec/factories/pages_domains.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :pages_domain, class: 'PagesDomain' do
sequence(:domain) { |n| "my#{n}.domain.com" }
@@ -166,6 +168,90 @@ pu/xO28QOG8=
-----END CERTIFICATE-----'
end
+ trait :with_trusted_expired_chain do
+ # This contains
+ # Let's Encrypt Authority X3
+ # DST Root CA X3
+ certificate '-----BEGIN CERTIFICATE-----
+MIIFSjCCBDKgAwIBAgISAw24xGWrFotvTBa6AZI/pzq1MA0GCSqGSIb3DQEBCwUA
+MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
+ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTAzMDcxNzU5NTZaFw0x
+OTA2MDUxNzU5NTZaMBQxEjAQBgNVBAMTCXN5dHNlLmNvbTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALtIpQuqeZN6OgEE+y2UoGC/31Vt9NAeQWvTuWWO
+nMn/MvDJiw8731Dx4DDbMjhF50UBE20a9iAu2nhlxcsuuIITk2MXKMEgPtqSbwM7
+Mg0/WvgrBOWnF9CpdD3qcsjtstT6Djij06VfMfUrRZzMkGgbGzudR0cShKPmkBVU
+LgB6crFmSQ/qHt5PzBivdexCUpz5WzSKU5UWYFx2UnkSLykvEJuUr3Nn4/o9oyKw
+Qoiq354S262mFuMW+s6wQdMNNkwj41OqCwAGbqq7YUYLDc8OQiRC2LcqSO5yYnnA
+0lNfbEatZ1BzHiDjTH7wMUtwcLGHsZ1C5ZmORD2s2gtGiRkCAwEAAaOCAl4wggJa
+MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
+DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUAMn3t1s4zXdOQbJFOP1riSwjuGkwHwYD
+VR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo7KEwbwYIKwYBBQUHAQEEYzBhMC4G
+CCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2VuY3J5cHQub3JnMC8G
+CCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5cHQub3JnLzAU
+BgNVHREEDTALgglzeXRzZS5jb20wTAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYB
+BAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5v
+cmcwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAAdQB0ftqDMa0zEJEhnM4lT0Jwwr/9
+XkIgCMY3NXnmEHvMVgAAAWlZhr4pAAAEAwBGMEQCIBEA+3oiM1UJKY1kajBO5Aoz
+9AZMMlImaR1X5hFIPr95AiBXGIACuXUDLchB0kT8VIG/jM4f9iuXMoYCoKNJggNM
+/gB3ACk8UZZUyDlluqpQ/FgH1Ldvv1h6KXLcpMMM9OVFR/R4AAABaVmGv/AAAAQD
+AEgwRgIhANeTA7H51SZUmcT2ldtumFYX6/OkOr0fdvze72U0j9U9AiEAjSOSVQmi
+ZdYK6u3JYkDVOWsEzyKwjPWod8UN5K3ej0EwDQYJKoZIhvcNAQELBQADggEBAJev
+ArtxZVVTmLghV0O7471J1mN1fVC2p6b3AsK/TqrI7aiq8XuQq76KmUsB+U05MTXH
+3sYiHm+/RJ7+ljiKVIC8ZfbQsHo5I+F1CNMo6JB6z8Z+bOeRkoves5FNYmiJnUjO
+uoGzt//CyldbX1dEPVNuU7P0s2wZ6Bubump2LoapGIiGxQJfeb0vj0TQzfRacTIZ
+x9U5E/D0y0iewX4kPHK17QDBsSL9WlqsRzFAkQjJ9XWUVn3BO7JG3WU47iOuykby
+y2HmOWUxjv1Yf/H/OYRBiuSCR4LhrE5Ze4tTo2AByrXQ5h7ezjDJQqnKBP5NuwIq
+7NuX+D2esUNos/D6uJg=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
+SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
+GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
+q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
+SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
+Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
+a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
+/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
+AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
+CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
+bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
+c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
+VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
+ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
+MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
+Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
+AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
+uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
+wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
+X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
+PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
+KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
+PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
+Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
+rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
+OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
+xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
+7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
+aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
+SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
+ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
+AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
+R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
+JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
+Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
+-----END CERTIFICATE-----'
+ end
+
trait :with_expired_certificate do
certificate '-----BEGIN CERTIFICATE-----
MIIBsDCCARmgAwIBAgIBATANBgkqhkiG9w0BAQUFADAeMRwwGgYDVQQDExNleHBp
diff --git a/spec/factories/personal_access_tokens.rb b/spec/factories/personal_access_tokens.rb
index e7fd22a96b2..cc9b2328705 100644
--- a/spec/factories/personal_access_tokens.rb
+++ b/spec/factories/personal_access_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :personal_access_token do
user
diff --git a/spec/factories/pool_repositories.rb b/spec/factories/pool_repositories.rb
index 8cac666069c..7a8946d47b0 100644
--- a/spec/factories/pool_repositories.rb
+++ b/spec/factories/pool_repositories.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :pool_repository do
shard { Shard.by_name("default") }
diff --git a/spec/factories/programming_languages.rb b/spec/factories/programming_languages.rb
index d3511d42b6c..ee8e7765ec9 100644
--- a/spec/factories/programming_languages.rb
+++ b/spec/factories/programming_languages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :programming_language do
name 'Ruby'
diff --git a/spec/factories/project_auto_devops.rb b/spec/factories/project_auto_devops.rb
index 1de42512402..4cc59c7095c 100644
--- a/spec/factories/project_auto_devops.rb
+++ b/spec/factories/project_auto_devops.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_auto_devops do
project
diff --git a/spec/factories/project_custom_attributes.rb b/spec/factories/project_custom_attributes.rb
index 099d2d7ff19..c9ed86cc0ac 100644
--- a/spec/factories/project_custom_attributes.rb
+++ b/spec/factories/project_custom_attributes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_custom_attribute do
project
diff --git a/spec/factories/project_deploy_tokens.rb b/spec/factories/project_deploy_tokens.rb
index 4866cb58d88..fbf5454b0fe 100644
--- a/spec/factories/project_deploy_tokens.rb
+++ b/spec/factories/project_deploy_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_deploy_token do
project
diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb
index 59c77627ee5..b02d167a950 100644
--- a/spec/factories/project_group_links.rb
+++ b/spec/factories/project_group_links.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_group_link do
project
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index a448d565e4b..96c9742c7d0 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_hook do
url { generate(:url) }
diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb
index c72e0487895..6dcac0400ca 100644
--- a/spec/factories/project_members.rb
+++ b/spec/factories/project_members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_member do
user
diff --git a/spec/factories/project_statistics.rb b/spec/factories/project_statistics.rb
index 2d0f698475d..f084d9d5cbf 100644
--- a/spec/factories/project_statistics.rb
+++ b/spec/factories/project_statistics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_statistics do
project
@@ -6,5 +8,20 @@ FactoryBot.define do
# statistics are automatically created when a project is created
project&.statistics || new
end
+
+ transient do
+ with_data { false }
+ size_multiplier { 1 }
+ end
+
+ after(:build) do |project_statistics, evaluator|
+ if evaluator.with_data
+ project_statistics.repository_size = evaluator.size_multiplier
+ project_statistics.wiki_size = evaluator.size_multiplier * 2
+ project_statistics.lfs_objects_size = evaluator.size_multiplier * 3
+ project_statistics.build_artifacts_size = evaluator.size_multiplier * 4
+ project_statistics.packages_size = evaluator.size_multiplier * 5
+ end
+ end
end
end
diff --git a/spec/factories/project_wikis.rb b/spec/factories/project_wikis.rb
index 4d21ed47f39..401402614f4 100644
--- a/spec/factories/project_wikis.rb
+++ b/spec/factories/project_wikis.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_wiki do
skip_create
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 743ec322885..ea89555b0d5 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/test_env'
FactoryBot.define do
@@ -25,7 +27,9 @@ FactoryBot.define do
issues_access_level ProjectFeature::ENABLED
merge_requests_access_level ProjectFeature::ENABLED
repository_access_level ProjectFeature::ENABLED
- pages_access_level ProjectFeature::ENABLED
+ pages_access_level do
+ visibility_level == Gitlab::VisibilityLevel::PUBLIC ? ProjectFeature::ENABLED : ProjectFeature::PRIVATE
+ end
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
@@ -306,39 +310,19 @@ FactoryBot.define do
factory :redmine_project, parent: :project do
has_external_issue_tracker true
- after :create do |project|
- project.create_redmine_service(
- active: true,
- properties: {
- 'project_url' => 'http://redmine/projects/project_name_in_redmine',
- 'issues_url' => 'http://redmine/projects/project_name_in_redmine/issues/:id',
- 'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new'
- }
- )
- end
+ redmine_service
end
factory :youtrack_project, parent: :project do
has_external_issue_tracker true
- after :create do |project|
- project.create_youtrack_service(
- active: true,
- properties: {
- 'project_url' => 'http://youtrack/projects/project_guid_in_youtrack',
- 'issues_url' => 'http://youtrack/issues/:id'
- }
- )
- end
+ youtrack_service
end
factory :jira_project, parent: :project do
has_external_issue_tracker true
- jira_service
- end
- factory :kubernetes_project, parent: :project do
- kubernetes_service
+ jira_service
end
factory :mock_deployment_project, parent: :project do
diff --git a/spec/factories/protected_branches.rb b/spec/factories/protected_branches.rb
index 5457c0d2a8f..741615bc0d3 100644
--- a/spec/factories/protected_branches.rb
+++ b/spec/factories/protected_branches.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :protected_branch do
name
diff --git a/spec/factories/protected_tags.rb b/spec/factories/protected_tags.rb
index 2b81d089549..6ff2a245b58 100644
--- a/spec/factories/protected_tags.rb
+++ b/spec/factories/protected_tags.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :protected_tag do
name
diff --git a/spec/factories/redirect_routes.rb b/spec/factories/redirect_routes.rb
index 774232d0b34..8fa0aec007c 100644
--- a/spec/factories/redirect_routes.rb
+++ b/spec/factories/redirect_routes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :redirect_route do
sequence(:path) { |n| "redirect#{n}" }
diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb
index cab6b4a811f..34794f57284 100644
--- a/spec/factories/releases.rb
+++ b/spec/factories/releases.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :release do
tag "v1.1.0"
@@ -6,6 +8,7 @@ FactoryBot.define do
description "Awesome release"
project
author
+ released_at { Time.zone.parse('2018-10-20T18:00:00Z') }
trait :legacy do
sha nil
diff --git a/spec/factories/remote_mirrors.rb b/spec/factories/remote_mirrors.rb
index adc7da27522..ff1d751c86c 100644
--- a/spec/factories/remote_mirrors.rb
+++ b/spec/factories/remote_mirrors.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :remote_mirror, class: 'RemoteMirror' do
association :project, :repository
diff --git a/spec/factories/repository_languages.rb b/spec/factories/repository_languages.rb
index 1757ba6766c..b2b17ebbce2 100644
--- a/spec/factories/repository_languages.rb
+++ b/spec/factories/repository_languages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :repository_language do
project
diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb
index b0174dd06b7..11116af7dbb 100644
--- a/spec/factories/sent_notifications.rb
+++ b/spec/factories/sent_notifications.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :sent_notification do
project
diff --git a/spec/factories/sequences.rb b/spec/factories/sequences.rb
index f2b6e7a11f9..17b54d69372 100644
--- a/spec/factories/sequences.rb
+++ b/spec/factories/sequences.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
sequence(:username) { |n| "user#{n}" }
sequence(:name) { |n| "John Doe#{n}" }
@@ -5,7 +7,7 @@ FactoryBot.define do
sequence(:email_alias) { |n| "user.alias#{n}@example.org" }
sequence(:title) { |n| "My title #{n}" }
sequence(:filename) { |n| "filename-#{n}.rb" }
- sequence(:url) { |n| "http://example#{n}.org" }
+ sequence(:url) { |n| "http://example#{n}.test" }
sequence(:label_title) { |n| "label#{n}" }
sequence(:branch) { |n| "my-branch-#{n}" }
sequence(:past_time) { |n| 4.hours.ago + (2 * n).seconds }
diff --git a/spec/factories/service_hooks.rb b/spec/factories/service_hooks.rb
index c907862b4f6..ff819f4f8d0 100644
--- a/spec/factories/service_hooks.rb
+++ b/spec/factories/service_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :service_hook do
url { generate(:url) }
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index 763909f30bd..b2d6ada91fa 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :service do
project
@@ -6,8 +8,6 @@ FactoryBot.define do
factory :custom_issue_tracker_service, class: CustomIssueTrackerService do
project
- type 'CustomIssueTrackerService'
- category 'issue_tracker'
active true
properties(
project_url: 'https://project.url.com',
@@ -16,16 +16,17 @@ FactoryBot.define do
)
end
- factory :kubernetes_service do
+ factory :emails_on_push_service do
project
- type 'KubernetesService'
+ type 'EmailsOnPushService'
active true
- properties({
- api_url: 'https://kubernetes.example.com',
- token: 'a' * 40
- })
-
- skip_deprecation_validation true
+ push_events true
+ tag_push_events true
+ properties(
+ recipients: 'test@example.com',
+ disable_diffs: true,
+ send_from_committer_email: true
+ )
end
factory :mock_deployment_service do
@@ -54,9 +55,39 @@ FactoryBot.define do
)
end
- factory :jira_cloud_service, class: JiraService do
+ factory :bugzilla_service do
+ project
+ active true
+ issue_tracker
+ end
+
+ factory :redmine_service do
+ project
+ active true
+ issue_tracker
+ end
+
+ factory :youtrack_service do
project
active true
+ issue_tracker
+ end
+
+ factory :gitlab_issue_tracker_service do
+ project
+ active true
+ issue_tracker
+ end
+
+ trait :issue_tracker do
+ properties(
+ project_url: 'http://issue-tracker.example.com',
+ issues_url: 'http://issue-tracker.example.com/issues/:id',
+ new_issue_url: 'http://issue-tracker.example.com'
+ )
+ end
+
+ trait :jira_cloud_service do
properties(
url: 'https://mysite.atlassian.net',
username: 'jira_user',
@@ -70,4 +101,16 @@ FactoryBot.define do
type 'HipchatService'
token 'test_token'
end
+
+ trait :without_properties_callback do
+ after(:build) do |service|
+ allow(service).to receive(:handle_properties)
+ end
+
+ after(:create) do |service|
+ # we have to remove the stub because the behaviour of
+ # handle_properties method is tested after the creation
+ allow(service).to receive(:handle_properties).and_call_original
+ end
+ end
end
diff --git a/spec/factories/services_data.rb b/spec/factories/services_data.rb
index 387e130a743..5a3639895b6 100644
--- a/spec/factories/services_data.rb
+++ b/spec/factories/services_data.rb
@@ -1,18 +1,12 @@
# frozen_string_literal: true
+# these factories should never be called directly, they are used when creating services
FactoryBot.define do
factory :jira_tracker_data do
service
- url 'http://jira.example.com'
- api_url 'http://api-jira.example.com'
- username 'jira_username'
- password 'jira_password'
end
factory :issue_tracker_data do
service
- project_url 'http://issuetracker.example.com'
- issues_url 'http://issues.example.com'
- new_issue_url 'http://new-issue.example.com'
end
end
diff --git a/spec/factories/shards.rb b/spec/factories/shards.rb
index c095fa5f0a0..c30a38180e8 100644
--- a/spec/factories/shards.rb
+++ b/spec/factories/shards.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :shard do
name "default"
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index dc12b562108..9c3a0fbe9b3 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :snippet do
author
diff --git a/spec/factories/spam_logs.rb b/spec/factories/spam_logs.rb
index a467f850a80..42a856832e7 100644
--- a/spec/factories/spam_logs.rb
+++ b/spec/factories/spam_logs.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :spam_log do
user
diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb
index 8f7ab74ec70..2b652cd57bf 100644
--- a/spec/factories/subscriptions.rb
+++ b/spec/factories/subscriptions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :subscription do
project
diff --git a/spec/factories/system_hooks.rb b/spec/factories/system_hooks.rb
index 9e00eeb6ef1..a2d163a060f 100644
--- a/spec/factories/system_hooks.rb
+++ b/spec/factories/system_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :system_hook do
url { generate(:url) }
diff --git a/spec/factories/system_note_metadata.rb b/spec/factories/system_note_metadata.rb
index e913068da40..8cd4b77799c 100644
--- a/spec/factories/system_note_metadata.rb
+++ b/spec/factories/system_note_metadata.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :system_note_metadata do
note
diff --git a/spec/factories/term_agreements.rb b/spec/factories/term_agreements.rb
index 3c4eebd0196..b7e259bd44b 100644
--- a/spec/factories/term_agreements.rb
+++ b/spec/factories/term_agreements.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :term_agreement do
term
diff --git a/spec/factories/terms.rb b/spec/factories/terms.rb
index 5ffca365a5f..b890261d293 100644
--- a/spec/factories/terms.rb
+++ b/spec/factories/terms.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :term, class: ApplicationSetting::Term do
terms "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
diff --git a/spec/factories/timelogs.rb b/spec/factories/timelogs.rb
index b45f06b9a0a..056a8833c46 100644
--- a/spec/factories/timelogs.rb
+++ b/spec/factories/timelogs.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Read about factories at https://github.com/thoughtbot/factory_bot
FactoryBot.define do
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index ed3d87eb76b..2ff024112a4 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :todo do
project
diff --git a/spec/factories/trending_project.rb b/spec/factories/trending_project.rb
index f7c634fd21f..354b8c1e002 100644
--- a/spec/factories/trending_project.rb
+++ b/spec/factories/trending_project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
# TrendingProject
factory :trending_project, class: 'TrendingProject' do
diff --git a/spec/factories/u2f_registrations.rb b/spec/factories/u2f_registrations.rb
index 26090b08966..c968468834b 100644
--- a/spec/factories/u2f_registrations.rb
+++ b/spec/factories/u2f_registrations.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :u2f_registration do
certificate { FFaker::BaconIpsum.characters(728) }
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index 426abdc2a6c..3f6326114c9 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :upload do
model { build(:project) }
@@ -54,10 +56,7 @@ FactoryBot.define do
end
trait :attachment_upload do
- transient do
- mount_point :attachment
- end
-
+ mount_point :attachment
model { build(:note) }
uploader "AttachmentUploader"
end
diff --git a/spec/factories/user_agent_details.rb b/spec/factories/user_agent_details.rb
index 7183a8e1140..055aea50585 100644
--- a/spec/factories/user_agent_details.rb
+++ b/spec/factories/user_agent_details.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user_agent_detail do
ip_address '127.0.0.1'
diff --git a/spec/factories/user_callouts.rb b/spec/factories/user_callouts.rb
index 528e442c14b..c4a217fd357 100644
--- a/spec/factories/user_callouts.rb
+++ b/spec/factories/user_callouts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user_callout do
feature_name :gke_cluster_integration
diff --git a/spec/factories/user_custom_attributes.rb b/spec/factories/user_custom_attributes.rb
index a184a2e0f17..7bd5c06f4ef 100644
--- a/spec/factories/user_custom_attributes.rb
+++ b/spec/factories/user_custom_attributes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user_custom_attribute do
user
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 4f3392cdcbf..b2c8bdab013 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user, aliases: [:author, :assignee, :recipient, :owner, :resource_owner] do
email { generate(:email) }
diff --git a/spec/factories/users_star_projects.rb b/spec/factories/users_star_projects.rb
index 6afd08a2084..a1299024fe4 100644
--- a/spec/factories/users_star_projects.rb
+++ b/spec/factories/users_star_projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :users_star_project do
project
diff --git a/spec/factories/web_hook_log.rb b/spec/factories/web_hook_log.rb
index 17837260a4b..5750af85662 100644
--- a/spec/factories/web_hook_log.rb
+++ b/spec/factories/web_hook_log.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :web_hook_log do
web_hook factory: :project_hook
diff --git a/spec/factories/wiki_directories.rb b/spec/factories/wiki_directories.rb
index b105c82b19d..de23cf110b5 100644
--- a/spec/factories/wiki_directories.rb
+++ b/spec/factories/wiki_directories.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :wiki_directory do
skip_create
diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb
index ae257d769e8..761ba58edb2 100644
--- a/spec/factories/wiki_pages.rb
+++ b/spec/factories/wiki_pages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'ostruct'
FactoryBot.define do
diff --git a/spec/fast_spec_helper.rb b/spec/fast_spec_helper.rb
index 91ef7653822..f5a487b4d57 100644
--- a/spec/fast_spec_helper.rb
+++ b/spec/fast_spec_helper.rb
@@ -4,6 +4,7 @@ ENV['GITLAB_ENV'] = 'test'
ENV['IN_MEMORY_APPLICATION_SETTINGS'] = 'true'
require 'active_support/dependencies'
+require_relative '../config/initializers/0_inject_enterprise_edition_module'
require_relative '../config/settings'
require_relative 'support/rspec'
require 'active_support/all'
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb
index eb12eebe8e6..b1573bfb270 100644
--- a/spec/features/abuse_report_spec.rb
+++ b/spec/features/abuse_report_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Abuse reports' do
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
index 3ff1a66b0b2..48fff9e57d3 100644
--- a/spec/features/admin/admin_abuse_reports_spec.rb
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Admin::AbuseReports", :js do
diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb
index f6dc499df29..dfc7c89840a 100644
--- a/spec/features/admin/admin_broadcast_messages_spec.rb
+++ b/spec/features/admin/admin_broadcast_messages_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin Broadcast Messages' do
diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb
index 4645fde7522..c79524a7fb3 100644
--- a/spec/features/admin/admin_browse_spam_logs_spec.rb
+++ b/spec/features/admin/admin_browse_spam_logs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin browse spam logs' do
diff --git a/spec/features/admin/admin_browses_logs_spec.rb b/spec/features/admin/admin_browses_logs_spec.rb
index 1f83d04d9aa..2b97362c8e9 100644
--- a/spec/features/admin/admin_browses_logs_spec.rb
+++ b/spec/features/admin/admin_browses_logs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin browses logs' do
@@ -9,7 +11,7 @@ describe 'Admin browses logs' do
visit admin_logs_path
expect(page).to have_link 'application.log'
- expect(page).to have_link 'githost.log'
+ expect(page).to have_link 'git_json.log'
expect(page).to have_link 'test.log'
expect(page).to have_link 'sidekiq.log'
expect(page).to have_link 'repocheck.log'
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index 51b42d1b43b..afdf8eb0cca 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin Builds' do
diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb
index cb96830cb7c..2039a6ff1ee 100644
--- a/spec/features/admin/admin_deploy_keys_spec.rb
+++ b/spec/features/admin/admin_deploy_keys_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'admin deploy keys' do
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index f066b088800..ca8874a24f9 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Admin disables Git access protocol', :js do
diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb
index e41835b4f24..6e6a4964541 100644
--- a/spec/features/admin/admin_disables_two_factor_spec.rb
+++ b/spec/features/admin/admin_disables_two_factor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Admin disables 2FA for a user' do
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 735ca60f7da..34356a2ee90 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin Groups' do
@@ -90,6 +92,7 @@ describe 'Admin Groups' do
visit admin_group_path(group)
expect(page).to have_content("Group: #{group.name}")
+ expect(page).to have_content("ID: #{group.id}")
end
end
@@ -102,6 +105,14 @@ describe 'Admin Groups' do
expect_selected_visibility(group.visibility_level)
end
+ it 'shows the subgroup creation level dropdown populated with the group subgroup_creation_level value' do
+ group = create(:group, :private, :owner_subgroup_creation_only)
+
+ visit admin_group_edit_path(group)
+
+ expect(page).to have_content('Allowed to create subgroups')
+ end
+
it 'edit group path does not change group name', :js do
group = create(:group, :private)
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index 790051dd933..97c34d55d73 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Admin Health Check", :feature do
diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb
index 928f97b6d29..98900142353 100644
--- a/spec/features/admin/admin_hook_logs_spec.rb
+++ b/spec/features/admin/admin_hook_logs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin::HookLogs' do
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index ce780789f5a..0fd748af3ce 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin::Hooks' do
diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb
index 238ea2a25bd..35638e0829b 100644
--- a/spec/features/admin/admin_labels_spec.rb
+++ b/spec/features/admin/admin_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'admin issues labels' do
diff --git a/spec/features/admin/admin_manage_applications_spec.rb b/spec/features/admin/admin_manage_applications_spec.rb
index a4904272706..dd4d4b1a426 100644
--- a/spec/features/admin/admin_manage_applications_spec.rb
+++ b/spec/features/admin/admin_manage_applications_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'admin manage applications' do
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index 2b6bfa40beb..058e548208f 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Admin::Projects" do
@@ -60,6 +62,7 @@ describe "Admin::Projects" do
expect(page).to have_content(project.name)
expect(page).to have_content(project.full_name)
expect(page).to have_content(project.creator.name)
+ expect(page).to have_content(project.id)
end
end
diff --git a/spec/features/admin/admin_requests_profiles_spec.rb b/spec/features/admin/admin_requests_profiles_spec.rb
index 2503fc9067d..0400b89dbfd 100644
--- a/spec/features/admin/admin_requests_profiles_spec.rb
+++ b/spec/features/admin/admin_requests_profiles_spec.rb
@@ -1,13 +1,17 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin::RequestsProfilesController' do
+ let(:tmpdir) { Dir.mktmpdir('profiler-test') }
+
before do
- FileUtils.mkdir_p(Gitlab::RequestProfiler::PROFILES_DIR)
+ stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir)
sign_in(create(:admin))
end
after do
- Gitlab::RequestProfiler.remove_all_profiles
+ FileUtils.rm_rf(tmpdir)
end
describe 'GET /admin/requests_profiles' do
@@ -19,42 +23,103 @@ describe 'Admin::RequestsProfilesController' do
expect(page).to have_content("X-Profile-Token: #{Gitlab::RequestProfiler.profile_token}")
end
- it 'lists all available profiles' do
- time1 = 1.hour.ago
- time2 = 2.hours.ago
- time3 = 3.hours.ago
- profile1 = "|gitlab-org|gitlab-ce_#{time1.to_i}.html"
- profile2 = "|gitlab-org|gitlab-ce_#{time2.to_i}.html"
- profile3 = "|gitlab-com|infrastructure_#{time3.to_i}.html"
-
- FileUtils.touch("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile1}")
- FileUtils.touch("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile2}")
- FileUtils.touch("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile3}")
-
- visit admin_requests_profiles_path
+ context 'when having multiple profiles' do
+ let(:time1) { 1.hour.ago }
+ let(:time2) { 2.hours.ago }
+
+ let(:profiles) do
+ [
+ {
+ request_path: '/gitlab-org/gitlab-ce',
+ name: "|gitlab-org|gitlab-ce_#{time1.to_i}_execution.html",
+ created: time1,
+ profile_mode: 'Execution'
+ },
+ {
+ request_path: '/gitlab-org/gitlab-ce',
+ name: "|gitlab-org|gitlab-ce_#{time2.to_i}_execution.html",
+ created: time2,
+ profile_mode: 'Execution'
+ },
+ {
+ request_path: '/gitlab-org/gitlab-ce',
+ name: "|gitlab-org|gitlab-ce_#{time1.to_i}_memory.html",
+ created: time1,
+ profile_mode: 'Memory'
+ },
+ {
+ request_path: '/gitlab-org/gitlab-ce',
+ name: "|gitlab-org|gitlab-ce_#{time2.to_i}_memory.html",
+ created: time2,
+ profile_mode: 'Memory'
+ },
+ {
+ request_path: '/gitlab-org/infrastructure',
+ name: "|gitlab-org|infrastructure_#{time1.to_i}_execution.html",
+ created: time1,
+ profile_mode: 'Execution'
+ },
+ {
+ request_path: '/gitlab-org/infrastructure',
+ name: "|gitlab-org|infrastructure_#{time2.to_i}_memory.html",
+ created: time2,
+ profile_mode: 'Memory'
+ },
+ {
+ request_path: '/gitlab-org/infrastructure',
+ name: "|gitlab-org|infrastructure_#{time2.to_i}.html",
+ created: time2,
+ profile_mode: 'Unknown'
+ }
+ ]
+ end
- within('.card', text: '/gitlab-org/gitlab-ce') do
- expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile1)}']", text: time1.to_s(:long))
- expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile2)}']", text: time2.to_s(:long))
+ before do
+ profiles.each do |profile|
+ FileUtils.touch(File.join(Gitlab::RequestProfiler::PROFILES_DIR, profile[:name]))
+ end
end
- within('.card', text: '/gitlab-com/infrastructure') do
- expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile3)}']", text: time3.to_s(:long))
+ it 'lists all available profiles' do
+ visit admin_requests_profiles_path
+
+ profiles.each do |profile|
+ within('.card', text: profile[:request_path]) do
+ expect(page).to have_selector(
+ "a[href='#{admin_requests_profile_path(profile[:name])}']",
+ text: "#{profile[:created].to_s(:long)} #{profile[:profile_mode]}")
+ end
+ end
end
end
end
describe 'GET /admin/requests_profiles/:profile' do
context 'when a profile exists' do
- it 'displays the content of the profile' do
- content = 'This is a request profile'
- profile = "|gitlab-org|gitlab-ce_#{Time.now.to_i}.html"
-
+ before do
File.write("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile}", content)
+ end
+
+ context 'when is valid call stack profile' do
+ let(:content) { 'This is a call stack request profile' }
+ let(:profile) { "|gitlab-org|gitlab-ce_#{Time.now.to_i}_execution.html" }
+
+ it 'displays the content' do
+ visit admin_requests_profile_path(profile)
+
+ expect(page).to have_content(content)
+ end
+ end
+
+ context 'when is valid memory profile' do
+ let(:content) { 'This is a memory request profile' }
+ let(:profile) { "|gitlab-org|gitlab-ce_#{Time.now.to_i}_memory.txt" }
- visit admin_requests_profile_path(profile)
+ it 'displays the content' do
+ visit admin_requests_profile_path(profile)
- expect(page).to have_content(content)
+ expect(page).to have_content(content)
+ end
end
end
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 97b432a6751..4ad90c96558 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Admin Runners" do
diff --git a/spec/features/admin/admin_sees_project_statistics_spec.rb b/spec/features/admin/admin_sees_project_statistics_spec.rb
index b5323a1c76d..ecd0aab925b 100644
--- a/spec/features/admin/admin_sees_project_statistics_spec.rb
+++ b/spec/features/admin/admin_sees_project_statistics_spec.rb
@@ -15,7 +15,7 @@ describe "Admin > Admin sees project statistics" do
let(:project) { create(:project, :repository) }
it "shows project statistics" do
- expect(page).to have_content("Storage: 0 Bytes (0 Bytes repositories, 0 Bytes wikis, 0 Bytes build artifacts, 0 Bytes LFS)")
+ expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes)")
end
end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 45ef5d07ff0..ddd87404003 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin updates settings' do
@@ -40,7 +42,7 @@ describe 'Admin updates settings' do
end
it 'Modify import sources' do
- expect(Gitlab::CurrentSettings.import_sources).not_to be_empty
+ expect(current_settings.import_sources).not_to be_empty
page.within('.as-visibility-access') do
Gitlab::ImportSources.options.map do |name, _|
@@ -51,7 +53,7 @@ describe 'Admin updates settings' do
end
expect(page).to have_content "Application settings saved successfully"
- expect(Gitlab::CurrentSettings.import_sources).to be_empty
+ expect(current_settings.import_sources).to be_empty
page.within('.as-visibility-access') do
check "Repo by URL"
@@ -59,7 +61,7 @@ describe 'Admin updates settings' do
end
expect(page).to have_content "Application settings saved successfully"
- expect(Gitlab::CurrentSettings.import_sources).to eq(['git'])
+ expect(current_settings.import_sources).to eq(['git'])
end
it 'Change Visibility and Access Controls' do
@@ -68,7 +70,7 @@ describe 'Admin updates settings' do
click_button 'Save changes'
end
- expect(Gitlab::CurrentSettings.project_export_enabled).to be_falsey
+ expect(current_settings.project_export_enabled).to be_falsey
expect(page).to have_content "Application settings saved successfully"
end
@@ -96,7 +98,7 @@ describe 'Admin updates settings' do
click_button 'Save changes'
end
- expect(Gitlab::CurrentSettings.gravatar_enabled).to be_falsey
+ expect(current_settings.gravatar_enabled).to be_falsey
expect(page).to have_content "Application settings saved successfully"
end
@@ -118,7 +120,7 @@ describe 'Admin updates settings' do
click_button 'Save changes'
end
- expect(Gitlab::CurrentSettings.home_page_url).to eq "https://about.gitlab.com/"
+ expect(current_settings.home_page_url).to eq "https://about.gitlab.com/"
expect(page).to have_content "Application settings saved successfully"
end
@@ -133,13 +135,13 @@ describe 'Admin updates settings' do
click_button 'Save changes'
end
- expect(Gitlab::CurrentSettings.enforce_terms).to be(true)
- expect(Gitlab::CurrentSettings.terms).to eq 'Be nice!'
+ expect(current_settings.enforce_terms).to be(true)
+ expect(current_settings.terms).to eq 'Be nice!'
expect(page).to have_content 'Application settings saved successfully'
end
it 'Modify oauth providers' do
- expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to be_empty
+ expect(current_settings.disabled_oauth_sign_in_sources).to be_empty
page.within('.as-signin') do
uncheck 'Google'
@@ -147,7 +149,7 @@ describe 'Admin updates settings' do
end
expect(page).to have_content "Application settings saved successfully"
- expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to include('google_oauth2')
+ expect(current_settings.disabled_oauth_sign_in_sources).to include('google_oauth2')
page.within('.as-signin') do
check "Google"
@@ -155,11 +157,11 @@ describe 'Admin updates settings' do
end
expect(page).to have_content "Application settings saved successfully"
- expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).not_to include('google_oauth2')
+ expect(current_settings.disabled_oauth_sign_in_sources).not_to include('google_oauth2')
end
it 'Oauth providers do not raise validation errors when saving unrelated changes' do
- expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to be_empty
+ expect(current_settings.disabled_oauth_sign_in_sources).to be_empty
page.within('.as-signin') do
uncheck 'Google'
@@ -167,7 +169,7 @@ describe 'Admin updates settings' do
end
expect(page).to have_content "Application settings saved successfully"
- expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to include('google_oauth2')
+ expect(current_settings.disabled_oauth_sign_in_sources).to include('google_oauth2')
# Remove google_oauth2 from the Omniauth strategies
allow(Devise).to receive(:omniauth_providers).and_return([])
@@ -178,7 +180,7 @@ describe 'Admin updates settings' do
end
expect(page).to have_content "Application settings saved successfully"
- expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to include('google_oauth2')
+ expect(current_settings.disabled_oauth_sign_in_sources).to include('google_oauth2')
end
it 'Configure web terminal' do
@@ -188,7 +190,7 @@ describe 'Admin updates settings' do
end
expect(page).to have_content "Application settings saved successfully"
- expect(Gitlab::CurrentSettings.terminal_max_session_time).to eq(15)
+ expect(current_settings.terminal_max_session_time).to eq(15)
end
end
@@ -204,7 +206,7 @@ describe 'Admin updates settings' do
end
expect(page).to have_content "Application settings saved successfully"
- expect(Gitlab::CurrentSettings.hide_third_party_offers).to be true
+ expect(current_settings.hide_third_party_offers).to be true
end
it 'Change Slack Notifications Service template settings' do
@@ -249,8 +251,8 @@ describe 'Admin updates settings' do
click_button 'Save changes'
end
- expect(Gitlab::CurrentSettings.auto_devops_enabled?).to be true
- expect(Gitlab::CurrentSettings.auto_devops_domain).to eq('domain.com')
+ expect(current_settings.auto_devops_enabled?).to be true
+ expect(current_settings.auto_devops_domain).to eq('domain.com')
expect(page).to have_content "Application settings saved successfully"
end
end
@@ -268,8 +270,8 @@ describe 'Admin updates settings' do
end
expect(page).to have_content "Application settings saved successfully"
- expect(Gitlab::CurrentSettings.recaptcha_enabled).to be true
- expect(Gitlab::CurrentSettings.unique_ips_limit_per_user).to eq(15)
+ expect(current_settings.recaptcha_enabled).to be true
+ expect(current_settings.unique_ips_limit_per_user).to eq(15)
end
end
@@ -284,7 +286,7 @@ describe 'Admin updates settings' do
click_button 'Save changes'
end
- expect(Gitlab::CurrentSettings.metrics_enabled?).to be true
+ expect(current_settings.metrics_enabled?).to be true
expect(page).to have_content "Application settings saved successfully"
end
@@ -294,7 +296,7 @@ describe 'Admin updates settings' do
click_button 'Save changes'
end
- expect(Gitlab::CurrentSettings.prometheus_metrics_enabled?).to be true
+ expect(current_settings.prometheus_metrics_enabled?).to be true
expect(page).to have_content "Application settings saved successfully"
end
@@ -336,15 +338,18 @@ describe 'Admin updates settings' do
visit network_admin_application_settings_path
page.within('.as-outbound') do
- check 'Allow requests to the local network from hooks and services'
+ check 'Allow requests to the local network from web hooks and services'
+ # Enabled by default
+ uncheck 'Allow requests to the local network from system hooks'
# Enabled by default
uncheck 'Enforce DNS rebinding attack protection'
click_button 'Save changes'
end
expect(page).to have_content "Application settings saved successfully"
- expect(Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services).to be true
- expect(Gitlab::CurrentSettings.dns_rebinding_protection_enabled).to be false
+ expect(current_settings.allow_local_requests_from_web_hooks_and_services).to be true
+ expect(current_settings.allow_local_requests_from_system_hooks).to be false
+ expect(current_settings.dns_rebinding_protection_enabled).to be false
end
end
@@ -354,16 +359,18 @@ describe 'Admin updates settings' do
end
it 'Change Help page' do
+ new_support_url = 'http://example.com/help'
+
page.within('.as-help-page') do
fill_in 'Help page text', with: 'Example text'
check 'Hide marketing-related entries from help'
- fill_in 'Support page URL', with: 'http://example.com/help'
+ fill_in 'Support page URL', with: new_support_url
click_button 'Save changes'
end
- expect(Gitlab::CurrentSettings.help_page_text).to eq "Example text"
- expect(Gitlab::CurrentSettings.help_page_hide_commercial_content).to be_truthy
- expect(Gitlab::CurrentSettings.help_page_support_url).to eq "http://example.com/help"
+ expect(current_settings.help_page_text).to eq "Example text"
+ expect(current_settings.help_page_hide_commercial_content).to be_truthy
+ expect(current_settings.help_page_support_url).to eq new_support_url
expect(page).to have_content "Application settings saved successfully"
end
@@ -374,8 +381,8 @@ describe 'Admin updates settings' do
click_button 'Save changes'
end
- expect(Gitlab::CurrentSettings.max_pages_size).to eq 15
- expect(Gitlab::CurrentSettings.pages_domain_verification_enabled?).to be_truthy
+ expect(current_settings.max_pages_size).to eq 15
+ expect(current_settings.pages_domain_verification_enabled?).to be_truthy
expect(page).to have_content "Application settings saved successfully"
end
@@ -385,7 +392,7 @@ describe 'Admin updates settings' do
click_button 'Save changes'
end
- expect(Gitlab::CurrentSettings.polling_interval_multiplier).to eq 5.0
+ expect(current_settings.polling_interval_multiplier).to eq 5.0
expect(page).to have_content "Application settings saved successfully"
end
@@ -395,39 +402,48 @@ describe 'Admin updates settings' do
click_button 'Save changes'
end
- expect(Gitlab::CurrentSettings.polling_interval_multiplier).not_to eq(-1.0)
+ expect(current_settings.polling_interval_multiplier).not_to eq(-1.0)
expect(page)
.to have_content "The form contains the following error: Polling interval multiplier must be greater than or equal to 0"
end
- context 'When pages_auto_ssl is enabled' do
- before do
- stub_feature_flags(pages_auto_ssl: true)
- visit preferences_admin_application_settings_path
+ it "Change Pages Let's Encrypt settings" do
+ visit preferences_admin_application_settings_path
+ page.within('.as-pages') do
+ fill_in 'Email', with: 'my@test.example.com'
+ check "I have read and agree to the Let's Encrypt Terms of Service"
+ click_button 'Save changes'
end
- it "Change Pages Let's Encrypt settings" do
- page.within('.as-pages') do
- fill_in 'Email', with: 'my@test.example.com'
- check "I have read and agree to the Let's Encrypt Terms of Service"
- click_button 'Save changes'
- end
+ expect(current_settings.lets_encrypt_notification_email).to eq 'my@test.example.com'
+ expect(current_settings.lets_encrypt_terms_of_service_accepted).to eq true
+ end
+ end
- expect(Gitlab::CurrentSettings.lets_encrypt_notification_email).to eq 'my@test.example.com'
- expect(Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted).to eq true
+ context 'Nav bar' do
+ it 'Shows default help links in nav' do
+ default_support_url = 'https://about.gitlab.com/getting-help/'
+
+ visit root_dashboard_path
+
+ find('.header-help-dropdown-toggle').click
+
+ page.within '.header-help' do
+ expect(page).to have_link(text: 'Help', href: help_path)
+ expect(page).to have_link(text: 'Support', href: default_support_url)
end
end
- context 'When pages_auto_ssl is disabled' do
- before do
- stub_feature_flags(pages_auto_ssl: false)
- visit preferences_admin_application_settings_path
- end
+ it 'Shows custom support url in nav when set' do
+ new_support_url = 'http://example.com/help'
+ stub_application_setting(help_page_support_url: new_support_url)
- it "Doesn't show Let's Encrypt options" do
- page.within('.as-pages') do
- expect(page).not_to have_content('Email')
- end
+ visit root_dashboard_path
+
+ find('.header-help-dropdown-toggle').click
+
+ page.within '.header-help' do
+ expect(page).to have_link(text: 'Support', href: new_support_url)
end
end
end
@@ -445,4 +461,8 @@ describe 'Admin updates settings' do
page.check('Wiki page')
page.check('Deployment')
end
+
+ def current_settings
+ ApplicationSetting.current_without_cache
+ end
end
diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb
index 5a989319d5b..3dacf63e25a 100644
--- a/spec/features/admin/admin_system_info_spec.rb
+++ b/spec/features/admin/admin_system_info_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin System Info' do
diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
index c7860bebb06..27f2436108c 100644
--- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb
+++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin > Users > Impersonation Tokens', :js do
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index dafec29dfcc..ebf71d8c9da 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Admin::Users" do
diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb
index d04bb9acd9e..8c1ec183286 100644
--- a/spec/features/admin/admin_uses_repository_checks_spec.rb
+++ b/spec/features/admin/admin_uses_repository_checks_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Admin uses repository checks' do
diff --git a/spec/features/admin/dashboard_spec.rb b/spec/features/admin/dashboard_spec.rb
index a6ca0803469..e204e0a515d 100644
--- a/spec/features/admin/dashboard_spec.rb
+++ b/spec/features/admin/dashboard_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'admin visits dashboard' do
@@ -15,10 +17,8 @@ describe 'admin visits dashboard' do
# Make sure the fork_networks & fork_networks reltuples have been updated
# to get a correct count on postgresql
- if Gitlab::Database.postgresql?
- ActiveRecord::Base.connection.execute('ANALYZE fork_networks')
- ActiveRecord::Base.connection.execute('ANALYZE fork_network_members')
- end
+ ActiveRecord::Base.connection.execute('ANALYZE fork_networks')
+ ActiveRecord::Base.connection.execute('ANALYZE fork_network_members')
visit admin_root_path
diff --git a/spec/features/admin/services/admin_activates_prometheus_spec.rb b/spec/features/admin/services/admin_activates_prometheus_spec.rb
index 904fe5b406b..64c57cd425b 100644
--- a/spec/features/admin/services/admin_activates_prometheus_spec.rb
+++ b/spec/features/admin/services/admin_activates_prometheus_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin activates Prometheus' do
diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index 947587220a9..7df0e47cd14 100644
--- a/spec/features/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Dashboard Feed" do
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index 714a9885caa..c0413f0f352 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Issues Feed' do
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 7de8bea5049..6f013e13ae4 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "User Feed" do
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index 030993462b5..6e477a93293 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards add issue modal', :js do
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 4c6175f5590..902ecdcd3e8 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards', :js do
@@ -234,6 +236,15 @@ describe 'Issue Boards', :js do
expect(find('.board:nth-child(2)')).to have_content(planning.title)
end
+ it 'dragging does not duplicate list' do
+ selector = '.board:not(.is-ghost) .board-header'
+ expect(page).to have_selector(selector, text: development.title, count: 1)
+
+ drag(list_from_index: 2, list_to_index: 1, selector: '.board-header', perform_drop: false)
+
+ expect(page).to have_selector(selector, text: development.title, count: 1)
+ end
+
it 'issue moves between lists' do
drag(list_from_index: 1, from_index: 1, list_to_index: 2)
@@ -574,7 +585,7 @@ describe 'Issue Boards', :js do
end
end
- def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0)
+ 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)
@@ -583,7 +594,8 @@ describe 'Issue Boards', :js do
list_from_index: list_from_index,
from_index: from_index,
to_index: to_index,
- list_to_index: list_to_index)
+ list_to_index: list_to_index,
+ perform_drop: perform_drop)
end
def wait_for_board_cards(board_number, expected_cards)
diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb
index 21779336559..c8ea202169c 100644
--- a/spec/features/boards/issue_ordering_spec.rb
+++ b/spec/features/boards/issue_ordering_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards', :js do
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
index d820a59aa16..5f3a2c409ed 100644
--- a/spec/features/boards/keyboard_shortcut_spec.rb
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards shortcut', :js do
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index e42d18b457e..93de2750466 100644
--- a/spec/features/boards/modal_filter_spec.rb
+++ b/spec/features/boards/modal_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards add issue modal filtering', :js do
diff --git a/spec/features/boards/multiple_boards_spec.rb b/spec/features/boards/multiple_boards_spec.rb
new file mode 100644
index 00000000000..9a2b7a80498
--- /dev/null
+++ b/spec/features/boards/multiple_boards_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Multiple Issue Boards', :js do
+ set(:user) { create(:user) }
+ set(:project) { create(:project, :public) }
+ set(:planning) { create(:label, project: project, name: 'Planning') }
+ set(:board) { create(:board, name: 'board1', project: project) }
+ set(:board2) { create(:board, name: 'board2', project: project) }
+ let(:parent) { project }
+ let(:boards_path) { project_boards_path(project) }
+
+ it_behaves_like 'multiple issue boards'
+end
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb
index d0c4534e317..abbec0ea810 100644
--- a/spec/features/boards/new_issue_spec.rb
+++ b/spec/features/boards/new_issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards new issue', :js do
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 4b4bd705a77..752c8c1052d 100644
--- a/spec/features/boards/reload_boards_on_browser_back_spec.rb
+++ b/spec/features/boards/reload_boards_on_browser_back_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Ensure Boards do not show stale data on browser back', :js do
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 6c9ae343e01..7ee3a839293 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards', :js do
diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb
index de2cb4c335e..264e5e85505 100644
--- a/spec/features/boards/sub_group_project_spec.rb
+++ b/spec/features/boards/sub_group_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Sub-group project issue boards', :js do
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index 8cb9b57a049..235b6d0fd40 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Contributions Calendar', :js do
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index a2dd34e7f7c..96d8da845cb 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Commits' do
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index 21d97aba0c5..aefdc4d6d4f 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe "Container Registry", :js do
+describe 'Container Registry', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
@@ -19,7 +21,7 @@ describe "Container Registry", :js do
it 'user visits container registry main page' do
visit_container_registry
- expect(page).to have_content 'No container images'
+ expect(page).to have_content 'no container images'
end
end
@@ -38,8 +40,7 @@ describe "Container Registry", :js do
it 'user removes entire container repository' do
visit_container_registry
- expect_any_instance_of(ContainerRepository)
- .to receive(:delete_tags!).and_return(true)
+ expect_any_instance_of(ContainerRepository).to receive(:delete_tags!).and_return(true)
click_on(class: 'js-remove-repo')
expect(find('.modal .modal-title')).to have_content 'Remove repository'
@@ -52,10 +53,9 @@ describe "Container Registry", :js do
find('.js-toggle-repo').click
wait_for_requests
- expect_any_instance_of(ContainerRegistry::Tag)
- .to receive(:delete).and_return(true)
+ expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete).and_return(true)
- click_on(class: 'js-delete-registry')
+ click_on(class: 'js-delete-registry-row', visible: false)
expect(find('.modal .modal-title')).to have_content 'Remove image'
find('.modal .modal-footer .btn-danger').click
end
diff --git a/spec/features/contextual_sidebar_spec.rb b/spec/features/contextual_sidebar_spec.rb
index 88da1b7966b..e250e8cc90a 100644
--- a/spec/features/contextual_sidebar_spec.rb
+++ b/spec/features/contextual_sidebar_spec.rb
@@ -16,21 +16,21 @@ describe 'Contextual sidebar', :js do
it 'shows flyout navs when collapsed or expanded apart from on the active item when expanded' do
expect(page).not_to have_selector('.js-sidebar-collapsed')
- find('.qa-link-pipelines').hover
+ find('.rspec-link-pipelines').hover
expect(page).to have_selector('.is-showing-fly-out')
- find('.qa-link-project').hover
+ find('.rspec-project-link').hover
expect(page).not_to have_selector('.is-showing-fly-out')
- find('.qa-toggle-sidebar').click
+ find('.rspec-toggle-sidebar').click
- find('.qa-link-pipelines').hover
+ find('.rspec-link-pipelines').hover
expect(page).to have_selector('.is-showing-fly-out')
- find('.qa-link-project').hover
+ find('.rspec-project-link').hover
expect(page).to have_selector('.is-showing-fly-out')
end
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index 4108a0f370d..07f0864fb3b 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Cycle Analytics', :js do
diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb
index f4d0f82d248..92d0c0c9260 100644
--- a/spec/features/dashboard/active_tab_spec.rb
+++ b/spec/features/dashboard/active_tab_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'Dashboard Active Tab', :js do
diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb
index c55dc4523f7..0a3b550c0c4 100644
--- a/spec/features/dashboard/activity_spec.rb
+++ b/spec/features/dashboard/activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard > Activity' do
diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb
index d31df322d10..9965229a539 100644
--- a/spec/features/dashboard/archived_projects_spec.rb
+++ b/spec/features/dashboard/archived_projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'Dashboard Archived Project' do
diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb
index f44bd55ecf6..56b47b74626 100644
--- a/spec/features/dashboard/datetime_on_tooltips_spec.rb
+++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Tooltips on .timeago dates', :js do
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 4098dd02141..aebc2eb1916 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'The group dashboard' do
diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb
index 259f220c68b..653a16bbbcc 100644
--- a/spec/features/dashboard/group_spec.rb
+++ b/spec/features/dashboard/group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'Dashboard Group' do
diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb
index fb76e2b0014..76785534ec7 100644
--- a/spec/features/dashboard/groups_list_spec.rb
+++ b/spec/features/dashboard/groups_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard Groups page', :js do
@@ -27,7 +29,7 @@ describe 'Dashboard Groups page', :js do
expect(page).not_to have_content(another_group.name)
end
- it 'shows subgroups the user is member of', :nested_groups do
+ it 'shows subgroups the user is member of' do
group.add_owner(user)
nested_group.add_owner(user)
@@ -40,7 +42,7 @@ describe 'Dashboard Groups page', :js do
expect(page).to have_content(nested_group.name)
end
- context 'when filtering groups', :nested_groups do
+ context 'when filtering groups' do
before do
group.add_owner(user)
nested_group.add_owner(user)
@@ -79,7 +81,7 @@ describe 'Dashboard Groups page', :js do
end
end
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let!(:subgroup) { create(:group, :public, parent: group) }
before do
diff --git a/spec/features/dashboard/help_spec.rb b/spec/features/dashboard/help_spec.rb
index 467a503a62d..3f425001447 100644
--- a/spec/features/dashboard/help_spec.rb
+++ b/spec/features/dashboard/help_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'Dashboard Help' do
diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb
index 50b71368e13..eca78749171 100644
--- a/spec/features/dashboard/issuables_counter_spec.rb
+++ b/spec/features/dashboard/issuables_counter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Navigation bar counter', :use_clean_rails_memory_store_caching do
diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb
index c0434f767bb..1352e1bd8fc 100644
--- a/spec/features/dashboard/issues_filter_spec.rb
+++ b/spec/features/dashboard/issues_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard Issues filtering', :js do
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index 9957bec0f0b..cb055ff8416 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'Dashboard Issues' do
diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb
index 2d4659d380f..630b2b636b4 100644
--- a/spec/features/dashboard/label_filter_spec.rb
+++ b/spec/features/dashboard/label_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard > label filter', :js do
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 0c6713f623c..0c1e1d5910b 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard Merge Requests' do
diff --git a/spec/features/dashboard/milestone_tabs_spec.rb b/spec/features/dashboard/milestone_tabs_spec.rb
index 21de7c2f06f..a83e4c1f7c9 100644
--- a/spec/features/dashboard/milestone_tabs_spec.rb
+++ b/spec/features/dashboard/milestone_tabs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard milestone tabs', :js do
diff --git a/spec/features/dashboard/milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb
index 8fb2e37e269..c21bc922de7 100644
--- a/spec/features/dashboard/milestones_spec.rb
+++ b/spec/features/dashboard/milestones_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard > Milestones' do
@@ -29,5 +31,19 @@ describe 'Dashboard > Milestones' do
expect(page).to have_content(milestone.title)
expect(page).to have_content(group.name)
end
+
+ describe 'new milestones dropdown', :js do
+ it 'takes user to a new milestone page', :js do
+ find('.new-project-item-select-button').click
+
+ page.within('.select2-results') do
+ first('.select2-result-label').click
+ end
+
+ find('.new-project-item-link').click
+
+ expect(current_path).to eq(new_group_milestone_path(group))
+ end
+ end
end
end
diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb
index 16919fe63ad..8e7a0b2a611 100644
--- a/spec/features/dashboard/project_member_activity_index_spec.rb
+++ b/spec/features/dashboard/project_member_activity_index_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project member activity', :js do
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index d1ed64cce7f..973d5a2dcfc 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard Projects' do
@@ -167,7 +169,7 @@ describe 'Dashboard Projects' do
expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']")
expect(page).to have_css('.ci-status-link')
expect(page).to have_css('.ci-status-icon-success')
- expect(page).to have_link('Commit: passed')
+ expect(page).to have_link('Pipeline: passed')
end
end
@@ -187,7 +189,7 @@ describe 'Dashboard Projects' do
expect(page).not_to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']")
expect(page).not_to have_css('.ci-status-link')
expect(page).not_to have_css('.ci-status-icon-success')
- expect(page).not_to have_link('Commit: passed')
+ expect(page).not_to have_link('Pipeline: passed')
end
end
end
@@ -218,4 +220,26 @@ describe 'Dashboard Projects' do
expect(find('input#merge_request_target_branch', visible: false).value).to eq 'master'
end
end
+
+ it 'avoids an N+1 query in dashboard index' do
+ create(:ci_pipeline, :with_job, status: :success, project: project, ref: project.default_branch, sha: project.commit.sha)
+ visit dashboard_projects_path
+
+ control_count = ActiveRecord::QueryRecorder.new { visit dashboard_projects_path }.count
+
+ new_project = create(:project, :repository, name: 'new project')
+ create(:ci_pipeline, :with_job, status: :success, project: new_project, ref: new_project.commit.sha)
+ new_project.add_developer(user)
+
+ ActiveRecord::QueryRecorder.new { visit dashboard_projects_path }.count
+
+ # There are three known N+1 queries:
+ # 1. Project#open_issues_count
+ # 2. Project#open_merge_requests_count
+ # 3. Project#forks_count
+ #
+ # In addition, ProjectsHelper#load_pipeline_status also adds an
+ # additional query.
+ expect { visit dashboard_projects_path }.not_to exceed_query_limit(control_count + 4)
+ end
end
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index 55f5ff04d01..3a47475da2b 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard shortcuts', :js do
@@ -22,7 +24,7 @@ describe 'Dashboard shortcuts', :js do
find('body').send_keys([:shift, 'T'])
- check_page_title('Todos')
+ check_page_title('To-Do List')
find('body').send_keys([:shift, 'P'])
@@ -49,7 +51,7 @@ describe 'Dashboard shortcuts', :js do
find('body').send_keys([:shift, 'P'])
find('.nothing-here-block')
- expect(page).to have_content('This user doesn\'t have any personal projects')
+ expect(page).to have_content('Explore public groups to find projects to contribute to.')
end
end
diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb
index 0e248c8732d..4fb01995cb0 100644
--- a/spec/features/dashboard/snippets_spec.rb
+++ b/spec/features/dashboard/snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard snippets' do
diff --git a/spec/features/dashboard/todos/target_state_spec.rb b/spec/features/dashboard/todos/target_state_spec.rb
index d55c32b3082..931fc16a5eb 100644
--- a/spec/features/dashboard/todos/target_state_spec.rb
+++ b/spec/features/dashboard/todos/target_state_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Dashboard > Todo target states' do
diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb
index 85f865321cf..f273e416597 100644
--- a/spec/features/dashboard/todos/todos_filtering_spec.rb
+++ b/spec/features/dashboard/todos/todos_filtering_spec.rb
@@ -1,26 +1,41 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard > User filters todos', :js do
let(:user_1) { create(:user, username: 'user_1', name: 'user_1') }
let(:user_2) { create(:user, username: 'user_2', name: 'user_2') }
- let(:project_1) { create(:project, name: 'project_1') }
- let(:project_2) { create(:project, name: 'project_2') }
+ let(:group1) { create(:group) }
+ let(:group2) { create(:group) }
+
+ let(:project_1) { create(:project, name: 'project_1', namespace: group1) }
+ let(:project_2) { create(:project, name: 'project_2', namespace: group1) }
+ let(:project_3) { create(:project, name: 'project_3', namespace: group2) }
- let(:issue) { create(:issue, title: 'issue', project: project_1) }
+ let(:issue1) { create(:issue, title: 'issue', project: project_1) }
+ let(:issue2) { create(:issue, title: 'issue', project: project_3) }
let!(:merge_request) { create(:merge_request, source_project: project_2, title: 'merge_request') }
before do
- create(:todo, user: user_1, author: user_2, project: project_1, target: issue, action: 1)
+ create(:todo, user: user_1, author: user_2, project: project_1, target: issue1, action: 1)
+ create(:todo, user: user_1, author: user_2, project: project_3, target: issue2, action: 1)
create(:todo, user: user_1, author: user_1, project: project_2, target: merge_request, action: 2)
project_1.add_developer(user_1)
project_2.add_developer(user_1)
+ project_3.add_developer(user_1)
sign_in(user_1)
visit dashboard_todos_path
end
+ it 'displays all todos without a filter' do
+ expect(page).to have_content issue1.to_reference(full: true)
+ expect(page).to have_content merge_request.to_reference(full: true)
+ expect(page).to have_content issue2.to_reference(full: true)
+ end
+
it 'filters by project' do
click_button 'Project'
within '.dropdown-menu-project' do
@@ -34,6 +49,20 @@ describe 'Dashboard > User filters todos', :js do
expect(page).not_to have_content project_2.full_name
end
+ it 'filters by group' do
+ click_button 'Group'
+ within '.dropdown-menu-group' do
+ fill_in 'Search groups', with: group1.full_name
+ click_link group1.full_name
+ end
+
+ wait_for_requests
+
+ expect(page).to have_content issue1.to_reference(full: true)
+ expect(page).to have_content merge_request.to_reference(full: true)
+ expect(page).not_to have_content issue2.to_reference(full: true)
+ end
+
context 'Author filter' do
it 'filters by author' do
click_button 'Author'
@@ -63,7 +92,7 @@ describe 'Dashboard > User filters todos', :js do
it 'shows only authors of existing done todos' do
user_3 = create :user
user_4 = create :user
- create(:todo, user: user_1, author: user_3, project: project_1, target: issue, action: 1, state: :done)
+ create(:todo, user: user_1, author: user_3, project: project_1, target: issue1, action: 1, state: :done)
create(:todo, user: user_1, author: user_4, project: project_2, target: merge_request, action: 2, state: :done)
project_1.add_developer(user_3)
@@ -92,14 +121,15 @@ describe 'Dashboard > User filters todos', :js do
wait_for_requests
- expect(find('.todos-list')).to have_content issue.to_reference
+ expect(find('.todos-list')).to have_content issue1.to_reference
+ expect(find('.todos-list')).to have_content issue2.to_reference
expect(find('.todos-list')).not_to have_content merge_request.to_reference
end
describe 'filter by action' do
before do
create(:todo, :build_failed, user: user_1, author: user_2, project: project_1)
- create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue)
+ create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue1)
end
it 'filters by Assigned' do
diff --git a/spec/features/dashboard/todos/todos_sorting_spec.rb b/spec/features/dashboard/todos/todos_sorting_spec.rb
index b87caaa1c07..3870c661784 100644
--- a/spec/features/dashboard/todos/todos_sorting_spec.rb
+++ b/spec/features/dashboard/todos/todos_sorting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard > User sorts todos' do
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index d58e3b2841e..b98a04b0bda 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard Todos' do
@@ -13,7 +15,7 @@ describe 'Dashboard Todos' do
end
it 'shows "All done" message' do
- expect(page).to have_content 'Todos let you see what you should do next'
+ expect(page).to have_content 'Your To-Do List shows what to work on next'
end
end
@@ -72,7 +74,7 @@ describe 'Dashboard Todos' do
end
it 'updates todo count' do
- expect(page).to have_content 'Todos 0'
+ expect(page).to have_content 'To Do 0'
expect(page).to have_content 'Done 1'
end
@@ -101,7 +103,7 @@ describe 'Dashboard Todos' do
end
it 'updates todo count' do
- expect(page).to have_content 'Todos 1'
+ expect(page).to have_content 'To Do 1'
expect(page).to have_content 'Done 0'
end
end
@@ -211,7 +213,7 @@ describe 'Dashboard Todos' do
describe 'restoring the todo' do
before do
within first('.todo') do
- click_link 'Add todo'
+ click_link 'Add a To Do'
end
end
@@ -220,7 +222,7 @@ describe 'Dashboard Todos' do
end
it 'updates todo count' do
- expect(page).to have_content 'Todos 1'
+ expect(page).to have_content 'To Do 1'
expect(page).to have_content 'Done 0'
end
end
@@ -276,7 +278,7 @@ describe 'Dashboard Todos' do
end
it 'shows "All done" message!' do
- expect(page).to have_content 'Todos 0'
+ expect(page).to have_content 'To Do 0'
expect(page).to have_content "You're all done!"
expect(page).not_to have_selector('.gl-pagination')
end
@@ -303,7 +305,7 @@ describe 'Dashboard Todos' do
it 'updates todo count' do
mark_all_and_undo
- expect(page).to have_content 'Todos 2'
+ expect(page).to have_content 'To Do 2'
expect(page).to have_content 'Done 0'
end
diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb
index 4410c8f887f..c4e4eb8affe 100644
--- a/spec/features/dashboard/user_filters_projects_spec.rb
+++ b/spec/features/dashboard/user_filters_projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard > User filters projects' do
diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb
index ea720cee74e..0362ddbae82 100644
--- a/spec/features/discussion_comments/commit_spec.rb
+++ b/spec/features/discussion_comments/commit_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'Discussion Comments Commit', :js do
+describe 'Thread Comments Commit', :js do
include RepoHelpers
let(:user) { create(:user) }
@@ -16,7 +18,7 @@ describe 'Discussion Comments Commit', :js do
visit project_commit_path(project, sample_commit.id)
end
- it_behaves_like 'discussion comments', 'commit'
+ it_behaves_like 'thread comments', 'commit'
it 'has class .js-note-emoji' do
expect(page).to have_css('.js-note-emoji')
diff --git a/spec/features/discussion_comments/issue_spec.rb b/spec/features/discussion_comments/issue_spec.rb
index 5ec19460bbd..fbceb2a51ae 100644
--- a/spec/features/discussion_comments/issue_spec.rb
+++ b/spec/features/discussion_comments/issue_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'Discussion Comments Issue', :js do
+describe 'Thread Comments Issue', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
@@ -12,5 +14,5 @@ describe 'Discussion Comments Issue', :js do
visit project_issue_path(project, issue)
end
- it_behaves_like 'discussion comments', 'issue'
+ it_behaves_like 'thread comments', 'issue'
end
diff --git a/spec/features/discussion_comments/merge_request_spec.rb b/spec/features/discussion_comments/merge_request_spec.rb
index f940e973923..96184593655 100644
--- a/spec/features/discussion_comments/merge_request_spec.rb
+++ b/spec/features/discussion_comments/merge_request_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'Discussion Comments Merge Request', :js do
+describe 'Thread Comments Merge Request', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
@@ -12,5 +14,5 @@ describe 'Discussion Comments Merge Request', :js do
visit project_merge_request_path(project, merge_request)
end
- it_behaves_like 'discussion comments', 'merge request'
+ it_behaves_like 'thread comments', 'merge request'
end
diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb
index d330e89505e..082f35050c0 100644
--- a/spec/features/discussion_comments/snippets_spec.rb
+++ b/spec/features/discussion_comments/snippets_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'Discussion Comments Snippet', :js do
+describe 'Thread Comments Snippet', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:snippet) { create(:project_snippet, :private, project: project, author: user) }
@@ -12,5 +14,5 @@ describe 'Discussion Comments Snippet', :js do
visit project_snippet_path(project, snippet)
end
- it_behaves_like 'discussion comments', 'snippet'
+ it_behaves_like 'thread comments', 'snippet'
end
diff --git a/spec/features/display_system_header_and_footer_bar_spec.rb b/spec/features/display_system_header_and_footer_bar_spec.rb
index af9d9a5834f..e32da1a02bc 100644
--- a/spec/features/display_system_header_and_footer_bar_spec.rb
+++ b/spec/features/display_system_header_and_footer_bar_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Display system header and footer bar' do
let(:header_message) { "Foo" }
diff --git a/spec/features/error_pages_spec.rb b/spec/features/error_pages_spec.rb
index cd7bcf29cc9..562277388bb 100644
--- a/spec/features/error_pages_spec.rb
+++ b/spec/features/error_pages_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Error Pages' do
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index b2b3382666a..4bd2a305dfa 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Expand and collapse diffs', :js do
diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb
index 56f6b1f7eaf..9686eee18fc 100644
--- a/spec/features/explore/groups_list_spec.rb
+++ b/spec/features/explore/groups_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Explore Groups page', :js do
diff --git a/spec/features/explore/groups_spec.rb b/spec/features/explore/groups_spec.rb
index e4ef47d88dd..81c77a29ecd 100644
--- a/spec/features/explore/groups_spec.rb
+++ b/spec/features/explore/groups_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Explore Groups', :js do
diff --git a/spec/features/explore/user_explores_projects_spec.rb b/spec/features/explore/user_explores_projects_spec.rb
index c724c3d17f8..9c3686dba2d 100644
--- a/spec/features/explore/user_explores_projects_spec.rb
+++ b/spec/features/explore/user_explores_projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User explores projects' do
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
index f2ab5373d3d..a7ccc6f7d7b 100644
--- a/spec/features/global_search_spec.rb
+++ b/spec/features/global_search_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Global search' do
@@ -7,6 +9,15 @@ describe 'Global search' do
before do
project.add_maintainer(user)
sign_in(user)
+
+ visit dashboard_projects_path
+ end
+
+ it 'increases usage ping searches counter' do
+ expect(Gitlab::UsageDataCounters::SearchCounter).to receive(:increment_navbar_searches_count)
+
+ fill_in "search", with: "foobar"
+ click_button "Go"
end
describe 'I search through the issues and I see pagination' do
@@ -16,8 +27,6 @@ describe 'Global search' do
end
it "has a pagination" do
- visit dashboard_projects_path
-
fill_in "search", with: "initial"
click_button "Go"
@@ -27,8 +36,6 @@ describe 'Global search' do
end
it 'closes the dropdown on blur', :js do
- visit dashboard_projects_path
-
fill_in 'search', with: "a"
dropdown = find('.js-dashboard-search-options')
diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb
index fc5777e8c7c..2b8d37bd629 100644
--- a/spec/features/group_variables_spec.rb
+++ b/spec/features/group_variables_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group variables', :js do
diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb
index 88fc12ae1e4..c102e19d477 100644
--- a/spec/features/groups/activity_spec.rb
+++ b/spec/features/groups/activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group activity page' do
diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb
index 86a4a016f3d..ca33dbb7a33 100644
--- a/spec/features/groups/board_spec.rb
+++ b/spec/features/groups/board_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Group Boards' do
diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb
index e4eb0d355d1..bbc80b7eec4 100644
--- a/spec/features/groups/empty_states_spec.rb
+++ b/spec/features/groups/empty_states_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group empty states' do
@@ -110,7 +112,7 @@ describe 'Group empty states' do
end
context 'group without a project' do
- context 'group has a subgroup', :nested_groups do
+ context 'group has a subgroup' do
let(:subgroup) { create(:group, parent: group) }
let(:subgroup_project) { create(:project, namespace: subgroup) }
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index 5cef5f0521f..41ecd21a386 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Edit group settings' do
@@ -85,6 +87,14 @@ describe 'Edit group settings' do
end
end
+ describe 'subgroup creation level menu' do
+ it 'shows the selection menu' do
+ visit edit_group_path(group)
+
+ expect(page).to have_content('Allowed to create subgroups')
+ end
+ end
+
describe 'edit group avatar' do
before do
visit edit_group_path(group)
@@ -113,7 +123,7 @@ describe 'Edit group settings' do
expect(find(:css, '.group-root-path').text).to eq(root_url)
end
- it 'has a parent group URL label for a subgroup group', :postgresql do
+ it 'has a parent group URL label for a subgroup group' do
subgroup = create(:group, parent: group)
visit edit_group_path(subgroup)
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index c000165ccd9..0cb24ef856b 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group issues page' do
@@ -50,7 +52,7 @@ describe 'Group issues page' do
end
end
- context 'issues list', :nested_groups do
+ context 'issues list' do
let(:subgroup) { create(:group, parent: group) }
let(:subgroup_project) { create(:project, :public, group: subgroup)}
let(:user_in_group) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
@@ -150,6 +152,25 @@ describe 'Group issues page' do
check_issue_order
end
+ it 'issues should not be draggable when user is not logged in', :js do
+ sign_out(user_in_group)
+
+ visit issues_group_path(group, sort: 'relative_position')
+
+ drag_to(selector: '.manual-ordering',
+ from_index: 0,
+ to_index: 2)
+
+ wait_for_requests
+
+ # Issue order should remain the same
+ page.within('.manual-ordering') do
+ expect(find('.issue:nth-child(1) .title')).to have_content('Issue #1')
+ expect(find('.issue:nth-child(2) .title')).to have_content('Issue #2')
+ expect(find('.issue:nth-child(3) .title')).to have_content('Issue #3')
+ end
+ end
+
def check_issue_order
page.within('.manual-ordering') do
expect(find('.issue:nth-child(1) .title')).to have_content('Issue #2')
diff --git a/spec/features/groups/labels/edit_spec.rb b/spec/features/groups/labels/edit_spec.rb
index 7cfc27a8905..43f067b89d6 100644
--- a/spec/features/groups/labels/edit_spec.rb
+++ b/spec/features/groups/labels/edit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Edit group label' do
diff --git a/spec/features/groups/labels/subscription_spec.rb b/spec/features/groups/labels/subscription_spec.rb
index 22b51b297a6..cbccf4f3880 100644
--- a/spec/features/groups/labels/subscription_spec.rb
+++ b/spec/features/groups/labels/subscription_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Labels subscription' do
diff --git a/spec/features/groups/labels/user_sees_links_to_issuables.rb b/spec/features/groups/labels/user_sees_links_to_issuables_spec.rb
index 1fdba78fa6c..6199b566ebc 100644
--- a/spec/features/groups/labels/user_sees_links_to_issuables.rb
+++ b/spec/features/groups/labels/user_sees_links_to_issuables_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Labels > User sees links to issuables' do
@@ -9,7 +11,9 @@ describe 'Groups > Labels > User sees links to issuables' do
end
it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
end
end
diff --git a/spec/features/groups/members/filter_members_spec.rb b/spec/features/groups/members/filter_members_spec.rb
index 386d81546d7..fc62c92db4e 100644
--- a/spec/features/groups/members/filter_members_spec.rb
+++ b/spec/features/groups/members/filter_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > Filter members' do
@@ -17,7 +19,7 @@ describe 'Groups > Members > Filter members' do
expect(first_member).to include(user.name)
expect(second_member).to include(user_with_2fa.name)
- expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Everyone')
+ expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: 'Everyone')
end
it 'shows only 2FA members' do
@@ -25,7 +27,7 @@ describe 'Groups > Members > Filter members' do
expect(first_member).to include(user_with_2fa.name)
expect(members_list.size).to eq(1)
- expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Enabled')
+ expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: 'Enabled')
end
it 'shows only non 2FA members' do
@@ -33,7 +35,7 @@ describe 'Groups > Members > Filter members' do
expect(first_member).to include(user.name)
expect(members_list.size).to eq(1)
- expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Disabled')
+ expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: 'Disabled')
end
def visit_members_list(options = {})
diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb
index 439803f9255..df15f995be4 100644
--- a/spec/features/groups/members/leave_group_spec.rb
+++ b/spec/features/groups/members/leave_group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > Leave group' do
diff --git a/spec/features/groups/members/list_members_spec.rb b/spec/features/groups/members/list_members_spec.rb
index 4ba7161601e..8df807186be 100644
--- a/spec/features/groups/members/list_members_spec.rb
+++ b/spec/features/groups/members/list_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > List members' do
@@ -13,7 +15,7 @@ describe 'Groups > Members > List members' do
sign_in(user1)
end
- it 'show members from current group and parent', :nested_groups do
+ it 'show members from current group and parent' do
group.add_developer(user1)
nested_group.add_developer(user2)
@@ -23,7 +25,7 @@ describe 'Groups > Members > List members' do
expect(second_row.text).to include(user2.name)
end
- it 'show user once if member of both current group and parent', :nested_groups do
+ it 'show user once if member of both current group and parent' do
group.add_developer(user1)
nested_group.add_developer(user1)
diff --git a/spec/features/groups/members/manage_members_spec.rb b/spec/features/groups/members/manage_members_spec.rb
index e2b4a491a13..cdd16ae9441 100644
--- a/spec/features/groups/members/manage_members_spec.rb
+++ b/spec/features/groups/members/manage_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > Manage members' do
@@ -96,7 +98,9 @@ describe 'Groups > Members > Manage members' do
add_user('test@example.com', 'Reporter')
- page.within(second_row) do
+ click_link('Pending')
+
+ page.within('.content-list.members-list') do
expect(page).to have_content('test@example.com')
expect(page).to have_content('Invited')
expect(page).to have_button('Reporter')
diff --git a/spec/features/groups/members/master_manages_access_requests_spec.rb b/spec/features/groups/members/master_manages_access_requests_spec.rb
index bd615c99412..454da126c81 100644
--- a/spec/features/groups/members/master_manages_access_requests_spec.rb
+++ b/spec/features/groups/members/master_manages_access_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > Maintainer manages access requests' do
diff --git a/spec/features/groups/members/request_access_spec.rb b/spec/features/groups/members/request_access_spec.rb
index 94510f917a3..0d5321709ae 100644
--- a/spec/features/groups/members/request_access_spec.rb
+++ b/spec/features/groups/members/request_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > Request access' do
diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb
index e7efdf7dfef..9c17aac09e8 100644
--- a/spec/features/groups/members/search_members_spec.rb
+++ b/spec/features/groups/members/search_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Search group member' do
@@ -17,9 +19,9 @@ describe 'Search group member' do
end
it 'renders member users' do
- page.within '.member-search-form' do
+ page.within '.user-search-form' do
fill_in 'search', with: member.name
- find('.member-search-btn').click
+ find('.user-search-btn').click
end
group_members_list = find(".card .content-list")
diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb
index ee32f6d77fe..76709199942 100644
--- a/spec/features/groups/members/sort_members_spec.rb
+++ b/spec/features/groups/members/sort_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > Sort members' do
@@ -17,7 +19,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end
it 'sorts by access level ascending' do
@@ -25,7 +27,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.name)
expect(second_member).to include(owner.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
end
it 'sorts by access level descending' do
@@ -33,7 +35,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
end
it 'sorts by last joined' do
@@ -41,7 +43,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.name)
expect(second_member).to include(owner.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
end
it 'sorts by oldest joined' do
@@ -49,7 +51,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
end
it 'sorts by name ascending' do
@@ -57,7 +59,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end
it 'sorts by name descending' do
@@ -65,7 +67,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.name)
expect(second_member).to include(owner.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
end
it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
@@ -73,7 +75,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
end
it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
@@ -81,7 +83,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.name)
expect(second_member).to include(owner.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
end
def visit_members_list(sort:)
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index d57eb87ca77..7f0155b63e0 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Group milestones' do
diff --git a/spec/features/groups/milestones_sorting_spec.rb b/spec/features/groups/milestones_sorting_spec.rb
index 7bc015ea28f..d27511be0b0 100644
--- a/spec/features/groups/milestones_sorting_spec.rb
+++ b/spec/features/groups/milestones_sorting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Milestones sorting', :js do
diff --git a/spec/features/groups/settings/group_badges_spec.rb b/spec/features/groups/settings/group_badges_spec.rb
index a5c8dbf18d0..9236a50cce5 100644
--- a/spec/features/groups/settings/group_badges_spec.rb
+++ b/spec/features/groups/settings/group_badges_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group Badges' do
diff --git a/spec/features/groups/share_lock_spec.rb b/spec/features/groups/share_lock_spec.rb
index 704d9f12888..777f5d98720 100644
--- a/spec/features/groups/share_lock_spec.rb
+++ b/spec/features/groups/share_lock_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group share with group lock' do
@@ -9,7 +11,7 @@ describe 'Group share with group lock' do
sign_in(root_owner)
end
- context 'with a subgroup', :nested_groups do
+ context 'with a subgroup' do
let!(:subgroup) { create(:group, parent: root_group) }
context 'when enabling the parent group share with group lock' do
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index 9671a4d8c49..bcaed2a5f18 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group show page' do
@@ -56,32 +58,69 @@ describe 'Group show page' do
end
context 'subgroup support' do
- let(:user) { create(:user) }
+ let(:restricted_group) do
+ create(:group, subgroup_creation_level: ::Gitlab::Access::OWNER_SUBGROUP_ACCESS)
+ end
- before do
- group.add_owner(user)
- sign_in(user)
+ let(:relaxed_group) do
+ create(:group, subgroup_creation_level: ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS)
end
- context 'when subgroups are supported', :js, :nested_groups do
+ let(:owner) { create(:user) }
+ let(:maintainer) { create(:user) }
+
+ context 'for owners' do
+ let(:path) { group_path(restricted_group) }
+
before do
- allow(Group).to receive(:supports_nested_objects?) { true }
- visit path
+ restricted_group.add_owner(owner)
+ sign_in(owner)
end
- it 'allows creating subgroups' do
- expect(page).to have_css("li[data-text='New subgroup']", visible: false)
+ context 'when subgroups are supported' do
+ it 'allows creating subgroups' do
+ visit path
+
+ expect(page)
+ .to have_css("li[data-text='New subgroup']", visible: false)
+ end
end
end
- context 'when subgroups are not supported' do
+ context 'for maintainers' do
before do
- allow(Group).to receive(:supports_nested_objects?) { false }
- visit path
+ sign_in(maintainer)
end
- it 'allows creating subgroups' do
- expect(page).not_to have_selector("li[data-text='New subgroup']", visible: false)
+ context 'when subgroups are supported' do
+ context 'when subgroup_creation_level is set to maintainers' do
+ before do
+ relaxed_group.add_maintainer(maintainer)
+ end
+
+ it 'allows creating subgroups' do
+ path = group_path(relaxed_group)
+ visit path
+
+ expect(page)
+ .to have_css("li[data-text='New subgroup']", visible: false)
+ end
+ end
+
+ context 'when subgroup_creation_level is set to owners' do
+ before do
+ restricted_group.add_maintainer(maintainer)
+ end
+
+ it 'does not allow creating subgroups' do
+ path = group_path(restricted_group)
+ visit path
+
+ expect(page)
+ .not_to have_selector("li[data-text='New subgroup']",
+ visible: false)
+ end
+ end
end
end
end
@@ -122,4 +161,27 @@ describe 'Group show page' do
expect(find('.group-row:nth-child(3) .namespace-title > a')).to have_content(project3.title)
end
end
+
+ context 'notification button', :js do
+ let(:maintainer) { create(:user) }
+ let!(:project) { create(:project, namespace: group) }
+
+ before do
+ group.add_maintainer(maintainer)
+ sign_in(maintainer)
+ end
+
+ it 'is enabled by default' do
+ visit path
+
+ expect(page).to have_selector('.notifications-btn:not(.disabled)', visible: true)
+ end
+
+ it 'is disabled if emails are disabled' do
+ group.update_attribute(:emails_disabled, true)
+ visit path
+
+ expect(page).to have_selector('.notifications-btn.disabled', visible: true)
+ end
+ end
end
diff --git a/spec/features/groups/user_browse_projects_group_page_spec.rb b/spec/features/groups/user_browse_projects_group_page_spec.rb
index 916363c41dd..075bc1128ca 100644
--- a/spec/features/groups/user_browse_projects_group_page_spec.rb
+++ b/spec/features/groups/user_browse_projects_group_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User browse group projects page' do
diff --git a/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb b/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb
index 6d6f206d761..742021ae4a1 100644
--- a/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb
+++ b/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > User sees users dropdowns in issuables list' do
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 8e7f78cab81..ca994c95df8 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group' do
@@ -105,7 +107,7 @@ describe 'Group' do
end
end
- describe 'create a nested group', :nested_groups, :js do
+ describe 'create a nested group', :js do
let(:group) { create(:group, path: 'foo') }
context 'as admin' do
@@ -231,7 +233,7 @@ describe 'Group' do
end
end
- describe 'group page with nested groups', :nested_groups, :js do
+ describe 'group page with nested groups', :js do
let!(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) }
let!(:project) { create(:project, namespace: group) }
diff --git a/spec/features/ics/dashboard_issues_spec.rb b/spec/features/ics/dashboard_issues_spec.rb
index b74bbf848ac..bde5488f375 100644
--- a/spec/features/ics/dashboard_issues_spec.rb
+++ b/spec/features/ics/dashboard_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard Issues Calendar Feed' do
@@ -91,7 +93,7 @@ describe 'Dashboard Issues Calendar Feed' do
expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
# line length for ics is 75 chars
- expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, ' ')
+ expected_description = (+"DESCRIPTION:Find out more at #{issue_url(issue)}").insert(75, ' ')
expect(body).to have_text(expected_description)
expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
expect(body).to have_text("URL:#{issue_url(issue)}")
diff --git a/spec/features/ics/group_issues_spec.rb b/spec/features/ics/group_issues_spec.rb
index 86da720c8be..0b317095678 100644
--- a/spec/features/ics/group_issues_spec.rb
+++ b/spec/features/ics/group_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group Issues Calendar Feed' do
@@ -66,7 +68,7 @@ describe 'Group Issues Calendar Feed' do
expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
# line length for ics is 75 chars
- expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, ' ')
+ expected_description = (+"DESCRIPTION:Find out more at #{issue_url(issue)}").insert(75, ' ')
expect(body).to have_text(expected_description)
expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
expect(body).to have_text("URL:#{issue_url(issue)}")
diff --git a/spec/features/ics/project_issues_spec.rb b/spec/features/ics/project_issues_spec.rb
index 37b90c666bc..3c940149670 100644
--- a/spec/features/ics/project_issues_spec.rb
+++ b/spec/features/ics/project_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Issues Calendar Feed' do
@@ -65,7 +67,7 @@ describe 'Project Issues Calendar Feed' do
expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
# line length for ics is 75 chars
- expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, ' ')
+ expected_description = (+"DESCRIPTION:Find out more at #{issue_url(issue)}").insert(75, ' ')
expect(body).to have_text(expected_description)
expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
expect(body).to have_text("URL:#{issue_url(issue)}")
diff --git a/spec/features/ide/user_opens_merge_request_spec.rb b/spec/features/ide/user_opens_merge_request_spec.rb
index 185349219a7..03318287db9 100644
--- a/spec/features/ide/user_opens_merge_request_spec.rb
+++ b/spec/features/ide/user_opens_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'IDE merge request', :js do
diff --git a/spec/features/ide_spec.rb b/spec/features/ide_spec.rb
index 6eb59ef72c2..73f6180d944 100644
--- a/spec/features/ide_spec.rb
+++ b/spec/features/ide_spec.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'IDE', :js do
- describe 'sub-groups', :nested_groups do
+ describe 'sub-groups' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
diff --git a/spec/features/import/manifest_import_spec.rb b/spec/features/import/manifest_import_spec.rb
index a90cdd8d920..e9471257544 100644
--- a/spec/features/import/manifest_import_spec.rb
+++ b/spec/features/import/manifest_import_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'Import multiple repositories by uploading a manifest file', :js, :postgresql do
+describe 'Import multiple repositories by uploading a manifest file', :js do
include Select2Helper
let(:user) { create(:admin) }
diff --git a/spec/features/instance_statistics/cohorts_spec.rb b/spec/features/instance_statistics/cohorts_spec.rb
index 40e65515ceb..61294ec9af2 100644
--- a/spec/features/instance_statistics/cohorts_spec.rb
+++ b/spec/features/instance_statistics/cohorts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Cohorts page' do
diff --git a/spec/features/instance_statistics/instance_statistics.rb b/spec/features/instance_statistics/instance_statistics_spec.rb
index d03e6e68075..40d0f1db207 100644
--- a/spec/features/instance_statistics/instance_statistics.rb
+++ b/spec/features/instance_statistics/instance_statistics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Cohorts page', :js do
diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb
index 9e1a12a9c2a..1e054a7b358 100644
--- a/spec/features/invites_spec.rb
+++ b/spec/features/invites_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Invites' do
@@ -8,17 +10,17 @@ describe 'Invites' do
let(:group_invite) { group.group_members.invite.last }
before do
+ stub_feature_flags(invisible_captcha: false)
project.add_maintainer(owner)
group.add_user(owner, Gitlab::Access::OWNER)
group.add_developer('user@example.com', owner)
group_invite.generate_invite_token!
end
- def confirm_email_and_sign_in(new_user)
+ def confirm_email(new_user)
new_user_token = User.find_by_email(new_user.email).confirmation_token
visit user_confirmation_path(confirmation_token: new_user_token)
- fill_in_sign_in_form(new_user)
end
def fill_in_sign_up_form(new_user)
@@ -152,17 +154,41 @@ describe 'Invites' do
context 'email confirmation enabled' do
let(:send_email_confirmation) { true }
- it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
- fill_in_sign_up_form(new_user)
- confirm_email_and_sign_in(new_user)
+ context 'when soft email confirmation is not enabled' do
+ before do
+ # stub_feature_flags(soft_email_confirmation: false)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
+ end
- expect(current_path).to eq(root_path)
- expect(page).to have_content(project.full_name)
- visit group_path(group)
- expect(page).to have_content(group.full_name)
+ it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
+ fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
+ fill_in_sign_in_form(new_user)
+
+ expect(current_path).to eq(root_path)
+ expect(page).to have_content(project.full_name)
+ visit group_path(group)
+ expect(page).to have_content(group.full_name)
+ end
+ end
+
+ context 'when soft email confirmation is enabled' do
+ before do
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
+ fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
+
+ expect(current_path).to eq(root_path)
+ expect(page).to have_content(project.full_name)
+ visit group_path(group)
+ expect(page).to have_content(group.full_name)
+ end
end
- it "doesn't accept invitations until the user confirm his email" do
+ it "doesn't accept invitations until the user confirms his email" do
fill_in_sign_up_form(new_user)
sign_in(owner)
@@ -173,11 +199,32 @@ describe 'Invites' do
context 'the user sign-up using a different email address' do
let(:invite_email) { build_stubbed(:user).email }
- it 'signs up and redirects to the invitation page' do
- fill_in_sign_up_form(new_user)
- confirm_email_and_sign_in(new_user)
+ context 'when soft email confirmation is not enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: false)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
+ end
- expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ it 'signs up and redirects to the invitation page' do
+ fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
+ fill_in_sign_in_form(new_user)
+
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ end
+ end
+
+ context 'when soft email confirmation is enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ it 'signs up and redirects to the invitation page' do
+ fill_in_sign_up_form(new_user)
+
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ end
end
end
end
diff --git a/spec/features/issuables/close_reopen_report_toggle_spec.rb b/spec/features/issuables/close_reopen_report_toggle_spec.rb
index 26c44baeb21..8805018902f 100644
--- a/spec/features/issuables/close_reopen_report_toggle_spec.rb
+++ b/spec/features/issuables/close_reopen_report_toggle_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Issuables Close/Reopen/Report toggle' do
diff --git a/spec/features/issuables/discussion_lock_spec.rb b/spec/features/issuables/discussion_lock_spec.rb
index 7ea29ff252b..0cd2c077081 100644
--- a/spec/features/issuables/discussion_lock_spec.rb
+++ b/spec/features/issuables/discussion_lock_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Discussion Lock', :js do
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index 225b858742d..dc68e633e27 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'issuable list' do
diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb
index 870e92b8de8..e84629dc17f 100644
--- a/spec/features/issuables/markdown_references/internal_references_spec.rb
+++ b/spec/features/issuables/markdown_references/internal_references_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "Internal references", :js do
diff --git a/spec/features/issuables/markdown_references/jira_spec.rb b/spec/features/issuables/markdown_references/jira_spec.rb
index 8eaccfc0949..aecdeffe4a4 100644
--- a/spec/features/issuables/markdown_references/jira_spec.rb
+++ b/spec/features/issuables/markdown_references/jira_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "Jira", :js do
diff --git a/spec/features/issuables/shortcuts_issuable_spec.rb b/spec/features/issuables/shortcuts_issuable_spec.rb
index a19101366a0..da8a0dd7b0f 100644
--- a/spec/features/issuables/shortcuts_issuable_spec.rb
+++ b/spec/features/issuables/shortcuts_issuable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Blob shortcuts', :js do
diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb
index 3a46a4e0167..b4531f5da4e 100644
--- a/spec/features/issuables/sorting_list_spec.rb
+++ b/spec/features/issuables/sorting_list_spec.rb
@@ -102,7 +102,7 @@ describe 'Sort Issuable List' do
expect(first_merge_request).to include(last_updated_issuable.title)
expect(last_merge_request).to include(first_updated_issuable.title)
- find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click
+ find('.issues-other-filters .filter-dropdown-container .rspec-reverse-sort').click
expect(first_merge_request).to include(first_updated_issuable.title)
expect(last_merge_request).to include(last_updated_issuable.title)
@@ -204,7 +204,7 @@ describe 'Sort Issuable List' do
expect(first_issue).to include(last_updated_issuable.title)
expect(last_issue).to include(first_updated_issuable.title)
- find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click
+ find('.issues-other-filters .filter-dropdown-container .rspec-reverse-sort').click
expect(first_issue).to include(first_updated_issuable.title)
expect(last_issue).to include(last_updated_issuable.title)
diff --git a/spec/features/issuables/user_sees_sidebar_spec.rb b/spec/features/issuables/user_sees_sidebar_spec.rb
index c6c2e58ecea..d1e10c70414 100644
--- a/spec/features/issuables/user_sees_sidebar_spec.rb
+++ b/spec/features/issuables/user_sees_sidebar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Sidebar on Mobile' do
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 06cb2e36334..e8b828bd264 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issues > Labels bulk assignment' do
@@ -381,7 +383,7 @@ describe 'Issues > Labels bulk assignment' do
if unmark
items.map do |item|
# Make sure we are unmarking the item no matter the state it has currently
- click_link item until find('a', text: item)[:class] == 'label-item'
+ click_link item until find('a', text: item)[:class].include? 'label-item'
end
end
end
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 ada57285abf..f2e482faf5f 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
@@ -1,13 +1,15 @@
+# frozen_string_literal: true
+
require 'rails_helper'
-describe 'Resolving all open discussions in a merge request from an issue', :js do
+describe 'Resolving all open threads in a merge request from an issue', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
let!(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project).to_discussion }
def resolve_all_discussions_link_selector
- text = "Resolve all discussions in new issue"
+ text = "Resolve all threads in new issue"
url = new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
%Q{a[data-original-title="#{text}"][href="#{url}"]}
end
@@ -19,15 +21,15 @@ describe 'Resolving all open discussions in a merge request from an issue', :js
visit project_merge_request_path(project, merge_request)
end
- it 'shows a button to resolve all discussions by creating a new issue' do
+ it 'shows a button to resolve all threads by creating a new issue' do
within('.line-resolve-all-container') do
expect(page).to have_selector resolve_all_discussions_link_selector
end
end
- context 'resolving the discussion' do
+ context 'resolving the thread' do
before do
- click_button 'Resolve discussion'
+ click_button 'Resolve thread'
end
it 'hides the link for creating a new issue' do
@@ -35,15 +37,15 @@ describe 'Resolving all open discussions in a merge request from an issue', :js
end
end
- context 'creating an issue for discussions' do
+ context 'creating an issue for threads' do
before do
find(resolve_all_discussions_link_selector).click
end
- it_behaves_like 'creating an issue for a discussion'
+ it_behaves_like 'creating an issue for a thread'
end
- context 'for a project where all discussions need to be resolved before merging' do
+ context 'for a project where all threads need to be resolved before merging' do
before do
project.update_attribute(:only_allow_merge_if_all_discussions_are_resolved, true)
end
@@ -59,27 +61,27 @@ describe 'Resolving all open discussions in a merge request from an issue', :js
end
end
- context 'merge request has discussions that need to be resolved' do
+ context 'merge request has threads that need to be resolved' do
before do
visit project_merge_request_path(project, merge_request)
end
- it 'shows a warning that the merge request contains unresolved discussions' do
- expect(page).to have_content 'There are unresolved discussions.'
+ it 'shows a warning that the merge request contains unresolved threads' do
+ expect(page).to have_content 'There are unresolved threads.'
end
- it 'has a link to resolve all discussions by creating an issue' do
+ it 'has a link to resolve all threads by creating an issue' do
page.within '.mr-widget-body' do
expect(page).to have_link 'Create an issue to resolve them later', href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
end
- context 'creating an issue for discussions' do
+ context 'creating an issue for threads' do
before do
page.click_link 'Create an issue to resolve them later', href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
- it_behaves_like 'creating an issue for a discussion'
+ it_behaves_like 'creating an issue for a thread'
end
end
end
@@ -92,8 +94,8 @@ describe 'Resolving all open discussions in a merge request from an issue', :js
visit new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
- it 'Shows a notice to ask someone else to resolve the discussions' do
- expect(page).to have_content("The discussions at #{merge_request.to_reference} will stay unresolved. Ask someone with permission to resolve them.")
+ it 'Shows a notice to ask someone else to resolve the threads' do
+ expect(page).to have_content("The threads at #{merge_request.to_reference} will stay unresolved. Ask someone with permission to resolve them.")
end
end
end
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 b20730bdb22..08c98358544 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
@@ -1,13 +1,15 @@
+# frozen_string_literal: true
+
require 'rails_helper'
-describe 'Resolve an open discussion in a merge request by creating an issue', :js do
+describe 'Resolve an open thread in a merge request by creating an issue', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, only_allow_merge_if_all_discussions_are_resolved: true) }
let(:merge_request) { create(:merge_request, source_project: project) }
let!(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project).to_discussion }
def resolve_discussion_selector
- title = 'Resolve this discussion in a new issue'
+ title = 'Resolve this thread in a new issue'
url = new_project_issue_path(project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
"a[data-original-title=\"#{title}\"][href=\"#{url}\"]"
end
@@ -30,40 +32,40 @@ describe 'Resolve an open discussion in a merge request by creating an issue', :
end
end
- context 'resolving the discussion' do
+ context 'resolving the thread' do
before do
- click_button 'Resolve discussion'
+ click_button 'Resolve thread'
end
it 'hides the link for creating a new issue' do
expect(page).not_to have_css resolve_discussion_selector
end
- it 'shows the link for creating a new issue when unresolving a discussion' do
+ it 'shows the link for creating a new issue when unresolving a thread' do
page.within '.diff-content' do
- click_button 'Unresolve discussion'
+ click_button 'Unresolve thread'
end
expect(page).to have_css resolve_discussion_selector
end
end
- it 'has a link to create a new issue for a discussion' do
+ it 'has a link to create a new issue for a thread' do
expect(page).to have_css resolve_discussion_selector
end
context 'creating the issue' do
before do
- find(resolve_discussion_selector).click
+ find(resolve_discussion_selector, match: :first).click
end
- it 'has a hidden field for the discussion' do
+ it 'has a hidden field for the thread' do
discussion_field = find('#discussion_to_resolve', visible: false)
expect(discussion_field.value).to eq(discussion.id.to_s)
end
- it_behaves_like 'creating an issue for a discussion'
+ it_behaves_like 'creating an issue for a thread'
end
end
@@ -75,8 +77,8 @@ describe 'Resolve an open discussion in a merge request by creating an issue', :
discussion_to_resolve: discussion.id)
end
- it 'Shows a notice to ask someone else to resolve the discussions' do
- expect(page).to have_content("The discussion at #{merge_request.to_reference}"\
+ it 'Shows a notice to ask someone else to resolve the threads' do
+ expect(page).to have_content("The thread at #{merge_request.to_reference}"\
" (discussion #{discussion.first_note.id}) will stay unresolved."\
" Ask someone with permission to resolve it.")
end
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 75313442b65..2ca551cec77 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Dropdown assignee', :js do
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index bc8d9bc8450..b2905fc1dde 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Dropdown author', :js do
diff --git a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
index a5c3ab7e7d0..45344aeb8f5 100644
--- a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Dropdown emoji', :js do
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
index 1f4e9e79179..34313553b34 100644
--- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Dropdown hint', :js do
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
index f502061dfce..f7f9f0de4db 100644
--- a/spec/features/issues/filtered_search/dropdown_label_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dropdown label', :js do
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index 7a6f76cb382..8429fe44c43 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Dropdown milestone', :js do
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index fa8e5cb0ca9..8b5e7934ec1 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Filter issues', :js do
diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb
index 41b9ada988a..c038281d825 100644
--- a/spec/features/issues/filtered_search/recent_searches_spec.rb
+++ b/spec/features/issues/filtered_search/recent_searches_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Recent searches', :js do
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index da23aea1fc9..0fe03160e7a 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Search bar', :js do
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index 9fd661d80ae..b20add1ea33 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Visual tokens', :js do
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 597af566f9c..e840ac79916 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'New/edit issue', :js do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 40845ec48f9..566441dc6f6 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'GFM autocomplete', :js do
diff --git a/spec/features/issues/group_label_sidebar_spec.rb b/spec/features/issues/group_label_sidebar_spec.rb
index 9c10f78f67a..746c55b6a26 100644
--- a/spec/features/issues/group_label_sidebar_spec.rb
+++ b/spec/features/issues/group_label_sidebar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Group label on issue' do
diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb
index 791bd003597..8f2e5b237ea 100644
--- a/spec/features/issues/issue_detail_spec.rb
+++ b/spec/features/issues/issue_detail_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Detail', :js do
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 321da8f44d7..b88d1bbeae5 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Sidebar' do
diff --git a/spec/features/issues/keyboard_shortcut_spec.rb b/spec/features/issues/keyboard_shortcut_spec.rb
index 961de9d3d25..e1325e63e6b 100644
--- a/spec/features/issues/keyboard_shortcut_spec.rb
+++ b/spec/features/issues/keyboard_shortcut_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issues shortcut', :js do
diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb
index 94bd7c53bb5..b80b47a4c99 100644
--- a/spec/features/issues/markdown_toolbar_spec.rb
+++ b/spec/features/issues/markdown_toolbar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue markdown toolbar', :js do
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 2abc50b04e4..9e0be7d8935 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'issue move to another project' do
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
index 3cd7ce6dada..04ad012d57e 100644
--- a/spec/features/issues/note_polling_spec.rb
+++ b/spec/features/issues/note_polling_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Issue notes polling', :js do
diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb
index fed77453cbb..5247baa58a1 100644
--- a/spec/features/issues/notes_on_issues_spec.rb
+++ b/spec/features/issues/notes_on_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Create notes on issues', :js do
diff --git a/spec/features/issues/rss_spec.rb b/spec/features/issues/rss_spec.rb
index 0e1383cd607..d6a406f4f44 100644
--- a/spec/features/issues/rss_spec.rb
+++ b/spec/features/issues/rss_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Issues RSS' do
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index 7cce45ff206..85c72c42f45 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'New issue', :js do
diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb
index 0114178b9be..9282c31751a 100644
--- a/spec/features/issues/todo_spec.rb
+++ b/spec/features/issues/todo_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Manually create a todo item from issue', :js do
@@ -13,8 +15,8 @@ describe 'Manually create a todo item from issue', :js do
it 'creates todo when clicking button' do
page.within '.issuable-sidebar' do
- click_button 'Add todo'
- expect(page).to have_content 'Mark todo as done'
+ click_button 'Add a To Do'
+ expect(page).to have_content 'Mark as done'
end
page.within '.header-content .todos-count' do
@@ -30,8 +32,8 @@ describe 'Manually create a todo item from issue', :js do
it 'marks a todo as done' do
page.within '.issuable-sidebar' do
- click_button 'Add todo'
- click_button 'Mark todo as done'
+ click_button 'Add a To Do'
+ click_button 'Mark as done'
end
expect(page).to have_selector('.todos-count', visible: false)
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index fd8629ae504..0e27d0231d0 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Multiple issue updating from issues#index', :js do
diff --git a/spec/features/issues/user_comments_on_issue_spec.rb b/spec/features/issues/user_comments_on_issue_spec.rb
index b4b9a589ba3..bdaaea5bf7f 100644
--- a/spec/features/issues/user_comments_on_issue_spec.rb
+++ b/spec/features/issues/user_comments_on_issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User comments on issue", :js do
@@ -41,16 +43,17 @@ describe "User comments on issue", :js do
expect(page.find('pre code').text).to eq code_block_content
end
- it "does not render html content in mermaid" do
+ it "renders escaped HTML content in Mermaid" do
html_content = "<img onerror=location=`javascript\\u003aalert\\u0028document.domain\\u0029` src=x>"
mermaid_content = "graph LR\n B-->D(#{html_content});"
+ escaped_content = CGI.escapeHTML(html_content).gsub('=', "&equals;")
comment = "```mermaid\n#{mermaid_content}\n```"
add_note(comment)
wait_for_requests
- expect(page.find('svg.mermaid')).to have_content html_content
+ expect(page.find('svg.mermaid')).to have_content escaped_content
end
end
diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
index f9103d83ba0..878a73718d7 100644
--- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
+++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User creates branch and merge request on issue page', :js do
diff --git a/spec/features/issues/user_creates_confidential_merge_request_spec.rb b/spec/features/issues/user_creates_confidential_merge_request_spec.rb
new file mode 100644
index 00000000000..4a32b7e2b73
--- /dev/null
+++ b/spec/features/issues/user_creates_confidential_merge_request_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'User creates confidential merge request on issue page', :js do
+ include ProjectForksHelper
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, :public) }
+ let(:issue) { create(:issue, project: project, confidential: true) }
+
+ def visit_confidential_issue
+ sign_in(user)
+ visit project_issue_path(project, issue)
+ wait_for_requests
+ end
+
+ before do
+ project.add_developer(user)
+ end
+
+ context 'user has no private fork' do
+ before do
+ fork_project(project, user, repository: true)
+ visit_confidential_issue
+ end
+
+ it 'shows that user has no fork available' do
+ click_button 'Create confidential merge request'
+
+ page.within '.create-confidential-merge-request-dropdown-menu' do
+ expect(page).to have_content('No forks available to you')
+ end
+ end
+ end
+
+ describe 'user has private fork' do
+ let(:forked_project) { fork_project(project, user, repository: true) }
+
+ before do
+ forked_project.update(visibility: Gitlab::VisibilityLevel::PRIVATE)
+ visit_confidential_issue
+ end
+
+ it 'create merge request in fork' do
+ click_button 'Create confidential merge request'
+
+ page.within '.create-confidential-merge-request-dropdown-menu' do
+ expect(page).to have_button(forked_project.name_with_namespace)
+ click_button 'Create confidential merge request'
+ end
+
+ expect(page).to have_content(forked_project.namespace.name)
+ end
+ end
+end
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index 2789d574156..a71395c0e47 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -28,7 +28,7 @@ describe "User creates issue" do
fill_in("Title", with: issue_title)
first('.js-md').click
- first('.qa-issuable-form-description').native.send_keys('Description')
+ first('.rspec-issuable-form-description').native.send_keys('Description')
click_button("Submit issue")
diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb
index 60b88ef4bdf..0afc19d9519 100644
--- a/spec/features/issues/user_edits_issue_spec.rb
+++ b/spec/features/issues/user_edits_issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User edits issue", :js do
diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb
index d117620a2b1..eab18b72c86 100644
--- a/spec/features/issues/user_interacts_with_awards_spec.rb
+++ b/spec/features/issues/user_interacts_with_awards_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User interacts with awards' do
diff --git a/spec/features/issues/user_sees_breadcrumb_links_spec.rb b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
index 43369f7609f..db8d3c5dd53 100644
--- a/spec/features/issues/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'New issue breadcrumb' do
diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb
index eebd2d57cca..79938785633 100644
--- a/spec/features/issues/user_sorts_issues_spec.rb
+++ b/spec/features/issues/user_sorts_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User sorts issues" do
diff --git a/spec/features/issues/user_toggles_subscription_spec.rb b/spec/features/issues/user_toggles_subscription_spec.rb
index c2b2a193682..165d41950da 100644
--- a/spec/features/issues/user_toggles_subscription_spec.rb
+++ b/spec/features/issues/user_toggles_subscription_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User toggles subscription", :js do
@@ -25,4 +27,14 @@ describe "User toggles subscription", :js do
# Check we're unsubscribed.
expect(subscription_button).to have_css("button:not(.is-checked)")
end
+
+ context 'when project emails are disabled' do
+ let(:project) { create(:project_empty_repo, :public, emails_disabled: true) }
+
+ it 'is disabled' do
+ expect(page).to have_content('Notifications have been disabled by the project or group owner')
+ expect(page).to have_selector('.js-emails-disabled', visible: true)
+ expect(page).not_to have_selector('.js-issuable-subscribe-button')
+ end
+ end
end
diff --git a/spec/features/issues/user_views_issue_spec.rb b/spec/features/issues/user_views_issue_spec.rb
index 330b6f0e77a..dd04ac94105 100644
--- a/spec/features/issues/user_views_issue_spec.rb
+++ b/spec/features/issues/user_views_issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User views issue" do
diff --git a/spec/features/issues/user_views_issues_spec.rb b/spec/features/issues/user_views_issues_spec.rb
index 58afb4efb86..b986991f38f 100644
--- a/spec/features/issues/user_views_issues_spec.rb
+++ b/spec/features/issues/user_views_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User views issues" do
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index 489651fea15..b7a45905845 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'Labels Hierarchy', :js, :nested_groups do
+describe 'Labels Hierarchy', :js do
include FilteredSearchHelpers
let!(:user) { create(:user) }
diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb
index c30ac9c4ae2..c098a1b3e3a 100644
--- a/spec/features/markdown/copy_as_gfm_spec.rb
+++ b/spec/features/markdown/copy_as_gfm_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Copy as GFM', :js do
@@ -416,8 +418,8 @@ describe 'Copy as GFM', :js do
html = <<~HTML
<div class="md-suggestion">
- <div class="md-suggestion-header border-bottom-0 mt-2 qa-suggestion-diff-header">
- <div class="qa-suggestion-diff-header font-weight-bold">
+ <div class="md-suggestion-header border-bottom-0 mt-2 qa-suggestion-diff-header js-suggestion-diff-header">
+ <div class="qa-suggestion-diff-header js-suggestion-diff-header font-weight-bold">
Suggested change
<a href="/gitlab/help/user/discussions/index.md#suggest-changes" aria-label="Help" class="js-help-btn">
<svg aria-hidden="true" class="s16 ic-question-o link-highlight">
@@ -426,7 +428,7 @@ describe 'Copy as GFM', :js do
</a>
</div>
<!---->
- <button type="button" class="btn qa-apply-btn">Apply suggestion</button>
+ <button type="button" class="btn qa-apply-btn js-apply-btn">Apply suggestion</button>
</div>
<table class="mb-3 md-suggestion-diff js-syntax-highlight code white">
<tbody>
@@ -796,7 +798,7 @@ describe 'Copy as GFM', :js do
context 'selecting one word of text' do
it 'copies as inline code' do
verify(
- '.line[id="LC27"] .s2',
+ '.line[id="LC27"] .nl',
'`"bio"`'
)
diff --git a/spec/features/markdown/gitlab_flavored_markdown_spec.rb b/spec/features/markdown/gitlab_flavored_markdown_spec.rb
index 8fda3c7193e..f34af268630 100644
--- a/spec/features/markdown/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/markdown/gitlab_flavored_markdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "GitLab Flavored Markdown" do
diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb
index 8815643ca96..0efeffe3232 100644
--- a/spec/features/markdown/markdown_spec.rb
+++ b/spec/features/markdown/markdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'erb'
diff --git a/spec/features/markdown/math_spec.rb b/spec/features/markdown/math_spec.rb
index 16ad0d456be..68d99b4241a 100644
--- a/spec/features/markdown/math_spec.rb
+++ b/spec/features/markdown/math_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Math rendering', :js do
diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb
index 7008b361394..aaaceebc216 100644
--- a/spec/features/markdown/mermaid_spec.rb
+++ b/spec/features/markdown/mermaid_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Mermaid rendering', :js do
@@ -21,4 +23,22 @@ describe 'Mermaid rendering', :js do
expect(page).to have_selector('svg text', text: label)
end
end
+
+ it 'renders linebreaks in Mermaid diagrams' do
+ description = <<~MERMAID
+ ```mermaid
+ graph TD;
+ A(Line 1<br>Line 2)-->B(Line 1<br/>Line 2);
+ C(Line 1<br />Line 2)-->D(Line 1<br />Line 2);
+ ```
+ MERMAID
+
+ project = create(:project, :public)
+ issue = create(:issue, project: project, description: description)
+
+ visit project_issue_path(project, issue)
+
+ expected = '<text><tspan xml:space="preserve" dy="1em" x="1">Line 1</tspan><tspan xml:space="preserve" dy="1em" x="1">Line 2</tspan></text>'
+ expect(page.html.scan(expected).count).to be(4)
+ end
end
diff --git a/spec/features/markdown/metrics_spec.rb b/spec/features/markdown/metrics_spec.rb
new file mode 100644
index 00000000000..4de67cfcdbe
--- /dev/null
+++ b/spec/features/markdown/metrics_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Metrics rendering', :js, :use_clean_rails_memory_store_caching do
+ include PrometheusHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:prometheus_project) }
+ let(:environment) { create(:environment, project: project) }
+ let(:issue) { create(:issue, project: project, description: description) }
+ let(:description) { "See [metrics dashboard](#{metrics_url}) for info." }
+ let(:metrics_url) { metrics_project_environment_url(project, environment) }
+
+ before do
+ configure_host
+ import_common_metrics
+ stub_any_prometheus_request_with_response
+
+ project.add_developer(user)
+
+ sign_in(user)
+ end
+
+ after do
+ restore_host
+ end
+
+ it 'shows embedded metrics' do
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_css('div.prometheus-graph')
+ expect(page).to have_text('Memory Usage (Total)')
+ expect(page).to have_text('Core Usage (Total)')
+ end
+
+ context 'when dashboard params are in included the url' do
+ let(:metrics_url) { metrics_project_environment_url(project, environment, **chart_params) }
+
+ let(:chart_params) do
+ {
+ group: 'System metrics (Kubernetes)',
+ title: 'Memory Usage (Pod average)',
+ y_label: 'Memory Used per Pod (MB)'
+ }
+ end
+
+ it 'shows embedded metrics for the specifiec chart' do
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_css('div.prometheus-graph')
+ expect(page).to have_text(chart_params[:title])
+ expect(page).to have_text(chart_params[:y_label])
+ end
+ end
+
+ def import_common_metrics
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
+ end
+
+ def configure_host
+ @original_default_host = default_url_options[:host]
+ @original_gitlab_url = Gitlab.config.gitlab[:url]
+
+ # Ensure we create a metrics url with the right host.
+ # Configure host for route helpers in specs (also updates root_url):
+ default_url_options[:host] = Capybara.server_host
+
+ # Ensure we identify urls with the appropriate host.
+ # Configure host to include port in app:
+ Gitlab.config.gitlab[:url] = root_url.chomp('/')
+ end
+
+ def restore_host
+ default_url_options[:host] = @original_default_host
+ Gitlab.config.gitlab[:url] = @original_gitlab_url
+ end
+end
diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb
index b35f985126c..030638cba71 100644
--- a/spec/features/merge_request/maintainer_edits_fork_spec.rb
+++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'a maintainer edits files on a source-branch of an MR from a fork', :js do
diff --git a/spec/features/merge_request/user_accepts_merge_request_spec.rb b/spec/features/merge_request/user_accepts_merge_request_spec.rb
index 3d029ccec1a..4d305d43351 100644
--- a/spec/features/merge_request/user_accepts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_accepts_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User accepts a merge request', :js do
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 b8c4a78e24f..be403abcc4d 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'create a merge request, allowing commits from members who can merge to the target branch', :js do
diff --git a/spec/features/merge_request/user_assigns_themselves_spec.rb b/spec/features/merge_request/user_assigns_themselves_spec.rb
index 41cc7cef777..d7918a9e9d7 100644
--- a/spec/features/merge_request/user_assigns_themselves_spec.rb
+++ b/spec/features/merge_request/user_assigns_themselves_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User assigns themselves' do
diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb
index 93376bc8ce0..5e9b232760b 100644
--- a/spec/features/merge_request/user_awards_emoji_spec.rb
+++ b/spec/features/merge_request/user_awards_emoji_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User awards emoji', :js do
diff --git a/spec/features/merge_request/user_closes_merge_request_spec.rb b/spec/features/merge_request/user_closes_merge_request_spec.rb
index 2d12d690151..c5125c38ed7 100644
--- a/spec/features/merge_request/user_closes_merge_request_spec.rb
+++ b/spec/features/merge_request/user_closes_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User closes a merge requests', :js do
diff --git a/spec/features/merge_request/user_comments_on_commit_spec.rb b/spec/features/merge_request/user_comments_on_commit_spec.rb
index 8ea358bcc70..6b869d93e4c 100644
--- a/spec/features/merge_request/user_comments_on_commit_spec.rb
+++ b/spec/features/merge_request/user_comments_on_commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User comments on a commit', :js do
diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb
index eb4b2cf5bd0..19b8a7f74b7 100644
--- a/spec/features/merge_request/user_comments_on_diff_spec.rb
+++ b/spec/features/merge_request/user_comments_on_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User comments on a diff', :js do
diff --git a/spec/features/merge_request/user_comments_on_merge_request_spec.rb b/spec/features/merge_request/user_comments_on_merge_request_spec.rb
index 69bdab85d81..c7845b4cce4 100644
--- a/spec/features/merge_request/user_comments_on_merge_request_spec.rb
+++ b/spec/features/merge_request/user_comments_on_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User comments on a merge request', :js do
@@ -37,7 +39,7 @@ describe 'User comments on a merge request', :js do
wait_for_requests
page.within('.notes .discussion') do
- expect(page).to have_content("#{user.name} #{user.to_reference} started a discussion")
+ expect(page).to have_content("#{user.name} #{user.to_reference} started a thread")
expect(page).to have_content(sample_commit.line_code_path)
expect(page).to have_content('Line is wrong')
end
diff --git a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
index 3a9a06a6bc3..e0724a04ea3 100644
--- a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Merge request > User creates image diff notes', :js do
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 d05ef2a8f12..f92791cc810 100644
--- a/spec/features/merge_request/user_creates_merge_request_spec.rb
+++ b/spec/features/merge_request/user_creates_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User creates a merge request", :js do
@@ -78,7 +80,7 @@ describe "User creates a merge request", :js do
click_button("Submit merge request")
- expect(page).to have_content(title).and have_content("Request to merge #{user.namespace.name}:#{source_branch} into master")
+ expect(page).to have_content(title).and have_content("Request to merge #{user.namespace.path}:#{source_branch} into master")
end
end
end
diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb
index c9dedab048a..267097bd466 100644
--- a/spec/features/merge_request/user_creates_mr_spec.rb
+++ b/spec/features/merge_request/user_creates_mr_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User creates MR' do
diff --git a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
index c6b11fce388..3213efcb60b 100644
--- a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
+++ b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request < User customizes merge commit message', :js do
diff --git a/spec/features/merge_request/user_edits_merge_request_spec.rb b/spec/features/merge_request/user_edits_merge_request_spec.rb
index 7de0f9daac6..81c56855961 100644
--- a/spec/features/merge_request/user_edits_merge_request_spec.rb
+++ b/spec/features/merge_request/user_edits_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User edits a merge request', :js do
diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb
index 25979513ead..e6b847c5e7f 100644
--- a/spec/features/merge_request/user_edits_mr_spec.rb
+++ b/spec/features/merge_request/user_edits_mr_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Merge request > User edits MR' do
diff --git a/spec/features/merge_request/user_expands_diff_spec.rb b/spec/features/merge_request/user_expands_diff_spec.rb
index 3560b8d90bb..f7317ec5ca7 100644
--- a/spec/features/merge_request/user_expands_diff_spec.rb
+++ b/spec/features/merge_request/user_expands_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User expands diff', :js do
diff --git a/spec/features/merge_request/user_locks_discussion_spec.rb b/spec/features/merge_request/user_locks_discussion_spec.rb
index 76c759ab8d3..365e9b60d90 100644
--- a/spec/features/merge_request/user_locks_discussion_spec.rb
+++ b/spec/features/merge_request/user_locks_discussion_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User locks discussion', :js do
diff --git a/spec/features/merge_request/user_manages_subscription_spec.rb b/spec/features/merge_request/user_manages_subscription_spec.rb
index 68a835e7f77..5c6072c57ff 100644
--- a/spec/features/merge_request/user_manages_subscription_spec.rb
+++ b/spec/features/merge_request/user_manages_subscription_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User manages subscription', :js do
diff --git a/spec/features/merge_request/user_merges_immediately_spec.rb b/spec/features/merge_request/user_merges_immediately_spec.rb
index 84636ae355c..2f01971c2e9 100644
--- a/spec/features/merge_request/user_merges_immediately_spec.rb
+++ b/spec/features/merge_request/user_merges_immediately_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge requests > User merges immediately', :js do
diff --git a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
index 8372b61f872..3f9c27bbf9d 100644
--- a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User merges only if pipeline succeeds', :js do
diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
index 85c4d778fd0..93938ac221a 100644
--- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User merges when pipeline succeeds', :js do
diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb
index 19edce1b562..abae6ffbd71 100644
--- a/spec/features/merge_request/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User posts diff notes', :js do
@@ -66,12 +68,12 @@ describe 'Merge request > User posts diff notes', :js do
context 'with a match line' do
it 'does not allow commenting on the left side' do
line_holder = find('.match', match: :first).find(:xpath, '..')
- should_not_allow_commenting(line_holder, 'left')
+ match_should_not_allow_commenting(line_holder)
end
it 'does not allow commenting on the right side' do
line_holder = find('.match', match: :first).find(:xpath, '..')
- should_not_allow_commenting(line_holder, 'right')
+ match_should_not_allow_commenting(line_holder)
end
end
@@ -134,7 +136,7 @@ describe 'Merge request > User posts diff notes', :js do
context 'with a match line' do
it 'does not allow commenting' do
- should_not_allow_commenting(find('.match', match: :first))
+ match_should_not_allow_commenting(find('.match', match: :first))
end
end
@@ -220,7 +222,7 @@ describe 'Merge request > User posts diff notes', :js do
context 'with a match line' do
it 'does not allow commenting' do
- should_not_allow_commenting(find('.match', match: :first))
+ match_should_not_allow_commenting(find('.match', match: :first))
end
end
end
@@ -249,6 +251,10 @@ describe 'Merge request > User posts diff notes', :js do
expect(line[:num]).not_to have_css comment_button_class
end
+ def match_should_not_allow_commenting(line_holder)
+ expect(line_holder).not_to have_css comment_button_class
+ end
+
def write_comment_on_line(line_holder, diff_side)
click_diff_line(line_holder, diff_side)
diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb
index 8ff24449b39..1fad9eac1bd 100644
--- a/spec/features/merge_request/user_posts_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_notes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User posts notes', :js do
diff --git a/spec/features/merge_request/user_rebases_merge_request_spec.rb b/spec/features/merge_request/user_rebases_merge_request_spec.rb
index 92e1c9942b1..34f009000dc 100644
--- a/spec/features/merge_request/user_rebases_merge_request_spec.rb
+++ b/spec/features/merge_request/user_rebases_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User rebases a merge request", :js do
diff --git a/spec/features/merge_request/user_reopens_merge_request_spec.rb b/spec/features/merge_request/user_reopens_merge_request_spec.rb
index 745b4537e72..6dee5770d0c 100644
--- a/spec/features/merge_request/user_reopens_merge_request_spec.rb
+++ b/spec/features/merge_request/user_reopens_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User reopens a merge requests', :js do
diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb
index 8fd44b87e5a..fb089a9ffcb 100644
--- a/spec/features/merge_request/user_resolves_conflicts_spec.rb
+++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User resolves conflicts', :js 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 260eec7a9ed..b8e4852a748 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
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'rails_helper'
-describe 'Merge request > User resolves diff notes and discussions', :js do
+describe 'Merge request > User resolves diff notes and threads', :js do
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
let(:guest) { create(:user) }
@@ -17,7 +19,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
)
end
- context 'no discussions' do
+ context 'no threads' do
before do
project.add_maintainer(user)
sign_in(user)
@@ -25,8 +27,8 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
visit_merge_request
end
- it 'displays no discussion resolved data' do
- expect(page).not_to have_content('discussion resolved')
+ it 'displays no thread resolved data' do
+ expect(page).not_to have_content('thread resolved')
expect(page).not_to have_selector('.discussion-next-btn')
end
end
@@ -38,10 +40,10 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
visit_merge_request
end
- context 'single discussion' do
- it 'shows text with how many discussions' do
+ context 'single thread' do
+ it 'shows text with how many threads' do
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 discussion resolved')
+ expect(page).to have_content('0/1 thread resolved')
end
end
@@ -54,18 +56,18 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
page.within '.diff-content' do
- expect(page).to have_selector('.btn', text: 'Unresolve discussion')
+ expect(page).to have_selector('.btn', text: 'Unresolve thread')
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 discussion resolved')
+ expect(page).to have_content('1/1 thread resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
- it 'allows user to mark discussion as resolved' do
+ it 'allows user to mark thread as resolved' do
page.within '.diff-content' do
- click_button 'Resolve discussion'
+ click_button 'Resolve thread'
end
expect(page).to have_selector('.discussion-body', visible: false)
@@ -75,38 +77,38 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 discussion resolved')
+ expect(page).to have_content('1/1 thread resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
- it 'allows user to unresolve discussion' do
+ it 'allows user to unresolve thread' do
page.within '.diff-content' do
- click_button 'Resolve discussion'
- click_button 'Unresolve discussion'
+ click_button 'Resolve thread'
+ click_button 'Unresolve thread'
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 discussion resolved')
+ expect(page).to have_content('0/1 thread resolved')
end
end
- describe 'resolved discussion' do
+ describe 'resolved thread' do
before do
page.within '.diff-content' do
- click_button 'Resolve discussion'
+ click_button 'Resolve thread'
end
visit_merge_request
end
describe 'timeline view' do
- it 'hides when resolve discussion is clicked' do
+ it 'hides when resolve thread is clicked' do
expect(page).to have_selector('.discussion-header')
expect(page).not_to have_selector('.discussion-body')
end
- it 'shows resolved discussion when toggled' do
+ it 'shows resolved thread when toggled' do
find(".timeline-content .discussion[data-discussion-id='#{note.discussion_id}'] .discussion-toggle-button").click
expect(page.find(".line-holder-placeholder")).to be_visible
@@ -130,11 +132,11 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
page.find('#parallel-diff-btn').click
end
- it 'hides when resolve discussion is clicked' do
+ it 'hides when resolve thread is clicked' do
expect(page).not_to have_selector('.diffs .diff-file .notes_holder')
end
- it 'shows resolved discussion when toggled' do
+ it 'shows resolved thread when toggled' do
find('.diff-comment-avatar-holders').click
expect(find('.diffs .diff-file .notes_holder')).to be_visible
@@ -143,7 +145,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
describe 'reply form' do
before do
- click_button 'Toggle discussion'
+ click_button 'Toggle thread'
page.within '.diff-content' do
click_button 'Reply...'
@@ -160,34 +162,34 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 discussion resolved')
+ expect(page).to have_content('1/1 thread resolved')
end
end
it 'allows user to unresolve from reply form without a comment' do
page.within '.diff-content' do
- click_button 'Unresolve discussion'
+ click_button 'Unresolve thread'
wait_for_requests
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 discussion resolved')
+ expect(page).to have_content('0/1 thread resolved')
expect(page).not_to have_selector('.line-resolve-btn.is-active')
end
end
- it 'allows user to comment & unresolve discussion' do
+ it 'allows user to comment & unresolve thread' do
page.within '.diff-content' do
find('.js-note-text').set 'testing'
- click_button 'Comment & unresolve discussion'
+ click_button 'Comment & unresolve thread'
wait_for_requests
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 discussion resolved')
+ expect(page).to have_content('0/1 thread resolved')
end
end
end
@@ -197,31 +199,31 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
page.within '.diff-content' do
click_button 'Reply...'
- click_button 'Resolve discussion'
+ click_button 'Resolve thread'
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 discussion resolved')
+ expect(page).to have_content('1/1 thread resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
- it 'allows user to comment & resolve discussion' do
+ it 'allows user to comment & resolve thread' do
page.within '.diff-content' do
click_button 'Reply...'
find('.js-note-text').set 'testing'
- click_button 'Comment & resolve discussion'
+ click_button 'Comment & resolve thread'
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 discussion resolved')
+ expect(page).to have_content('1/1 thread resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
- it 'allows user to quickly scroll to next unresolved discussion' do
+ it 'allows user to quickly scroll to next unresolved thread' do
page.within '.line-resolve-all-container' do
page.find('.discussion-next-btn').click
end
@@ -231,7 +233,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
it 'hides jump to next button when all resolved' do
page.within '.diff-content' do
- click_button 'Resolve discussion'
+ click_button 'Resolve thread'
end
expect(page).to have_selector('.discussion-next-btn', visible: false)
@@ -248,7 +250,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
end
- it 'hides jump to next discussion button' do
+ it 'hides jump to next thread button' do
page.within '.discussion-reply-holder' do
expect(page).not_to have_selector('.discussion-next-btn')
end
@@ -261,7 +263,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
visit_merge_request
end
- it 'does not mark discussion as resolved when resolving single note' do
+ it 'does not mark thread as resolved when resolving single note' do
page.within("#note_#{note.id}") do
first('.line-resolve-btn').click
@@ -273,11 +275,11 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
expect(page).to have_content('Last updated')
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 discussion resolved')
+ expect(page).to have_content('0/1 thread resolved')
end
end
- it 'resolves discussion' do
+ it 'resolves thread' do
resolve_buttons = page.all('.note .line-resolve-btn', count: 2)
resolve_buttons.each do |button|
button.click
@@ -290,28 +292,28 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 discussion resolved')
+ expect(page).to have_content('1/1 thread resolved')
end
end
end
- context 'muliple discussions' do
+ context 'muliple threads' do
before do
create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request)
visit_merge_request
end
- it 'shows text with how many discussions' do
+ it 'shows text with how many threads' do
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/2 discussions resolved')
+ expect(page).to have_content('0/2 threads resolved')
end
end
it 'allows user to mark a single note as resolved' do
- click_button('Resolve discussion', match: :first)
+ click_button('Resolve thread', match: :first)
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/2 discussions resolved')
+ expect(page).to have_content('1/2 threads resolved')
end
end
@@ -321,27 +323,27 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('2/2 discussions resolved')
+ expect(page).to have_content('2/2 threads resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
- it 'allows user to mark all discussions as resolved' 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 discussion'
+ click_button 'Resolve thread'
end
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('2/2 discussions resolved')
+ expect(page).to have_content('2/2 threads resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
- it 'allows user to quickly scroll to next unresolved discussion' do
+ it 'allows user to quickly scroll to next unresolved thread' do
page.within('.discussion-reply-holder', match: :first) do
- click_button 'Resolve discussion'
+ click_button 'Resolve thread'
end
page.within '.line-resolve-all-container' do
@@ -372,15 +374,15 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
expect(all_discussion_replies.last.all('.discussion-next-btn').count).to eq(1)
end
- it 'displays next discussion even if hidden' do
+ it 'displays next thread even if hidden' do
page.all('.note-discussion', count: 2).each do |discussion|
page.within discussion do
- click_button 'Toggle discussion'
+ click_button 'Toggle thread'
end
end
page.within('.issuable-discussion #notes') do
- expect(page).not_to have_selector('.btn', text: 'Resolve discussion')
+ expect(page).not_to have_selector('.btn', text: 'Resolve thread')
end
page.within '.line-resolve-all-container' do
@@ -388,19 +390,19 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
page.all('.note-discussion').first do
- expect(page.find('.discussion-with-resolve-btn')).to have_selector('.btn', text: 'Resolve discussion')
+ expect(page.find('.discussion-with-resolve-btn')).to have_selector('.btn', text: 'Resolve thread')
end
page.all('.note-discussion').last do
- expect(page.find('.discussion-with-resolve-btn')).not.to have_selector('.btn', text: 'Resolve discussion')
+ expect(page.find('.discussion-with-resolve-btn')).not.to have_selector('.btn', text: 'Resolve thread')
end
end
end
context 'changes tab' do
- it 'shows text with how many discussions' do
+ it 'shows text with how many threads' do
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 discussion resolved')
+ expect(page).to have_content('0/1 thread resolved')
end
end
@@ -412,18 +414,18 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
page.within '.diff-content' do
- expect(page).to have_selector('.btn', text: 'Unresolve discussion')
+ expect(page).to have_selector('.btn', text: 'Unresolve thread')
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 discussion resolved')
+ expect(page).to have_content('1/1 thread resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
- it 'allows user to mark discussion as resolved' do
+ it 'allows user to mark thread as resolved' do
page.within '.diff-content' do
- click_button 'Resolve discussion'
+ click_button 'Resolve thread'
end
page.within '.diff-content .note' do
@@ -431,50 +433,50 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 discussion resolved')
+ expect(page).to have_content('1/1 thread resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
- it 'allows user to unresolve discussion' do
+ it 'allows user to unresolve thread' do
page.within '.diff-content' do
- click_button 'Resolve discussion'
- click_button 'Unresolve discussion'
+ click_button 'Resolve thread'
+ click_button 'Unresolve thread'
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 discussion resolved')
+ expect(page).to have_content('0/1 thread resolved')
end
end
- it 'allows user to comment & resolve discussion' do
+ it 'allows user to comment & resolve thread' do
page.within '.diff-content' do
click_button 'Reply...'
find('.js-note-text').set 'testing'
- click_button 'Comment & resolve discussion'
+ click_button 'Comment & resolve thread'
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 discussion resolved')
+ expect(page).to have_content('1/1 thread resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
- it 'allows user to comment & unresolve discussion' do
+ it 'allows user to comment & unresolve thread' do
page.within '.diff-content' do
- click_button 'Resolve discussion'
+ click_button 'Resolve thread'
click_button 'Reply...'
find('.js-note-text').set 'testing'
- click_button 'Comment & unresolve discussion'
+ click_button 'Comment & unresolve thread'
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 discussion resolved')
+ expect(page).to have_content('0/1 thread resolved')
end
end
end
@@ -497,13 +499,13 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 discussion resolved')
+ expect(page).to have_content('0/1 thread resolved')
end
end
- it 'does not allow user to mark discussion as resolved' do
+ it 'does not allow user to mark thread as resolved' do
page.within '.diff-content .note' do
- expect(page).not_to have_selector('.btn', text: 'Resolve discussion')
+ expect(page).not_to have_selector('.btn', text: 'Resolve thread')
end
end
end
@@ -523,11 +525,11 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
page.within '.diff-content' do
- expect(page).to have_selector('.btn', text: 'Unresolve discussion')
+ expect(page).to have_selector('.btn', text: 'Unresolve thread')
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 discussion resolved')
+ expect(page).to have_content('1/1 thread resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -546,7 +548,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 discussion resolved')
+ expect(page).to have_content('0/1 thread resolved')
end
end
end
@@ -558,15 +560,15 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
it 'shows resolved icon' do
- expect(page).to have_content '1/1 discussion resolved'
+ expect(page).to have_content '1/1 thread resolved'
- click_button 'Toggle discussion'
+ click_button 'Toggle thread'
expect(page).to have_selector('.line-resolve-btn.is-active')
end
it 'does not allow user to click resolve button' do
expect(page).to have_selector('.line-resolve-btn.is-disabled')
- click_button 'Toggle discussion'
+ click_button 'Toggle thread'
expect(page).to have_selector('.line-resolve-btn.is-disabled')
end
diff --git a/spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb b/spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb
index 777464ef841..b40c11f0d47 100644
--- a/spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb
+++ b/spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Merge request > User resolves outdated diff discussions', :js do
diff --git a/spec/features/merge_request/user_reverts_merge_request_spec.rb b/spec/features/merge_request/user_reverts_merge_request_spec.rb
index 67b6aefb2d8..71270b13c14 100644
--- a/spec/features/merge_request/user_reverts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_reverts_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User reverts a merge request', :js do
diff --git a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
index 2f7d359575e..23b37c218e1 100644
--- a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
+++ b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User scrolls to note on load', :js do
diff --git a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
index b58c433bbfe..fae81758086 100644
--- a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees avatars on diff notes', :js do
diff --git a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
index 18d204da17a..e1f0ddc4c6a 100644
--- a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'New merge request breadcrumb' do
diff --git a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
index 86086a58f18..93ae408a173 100644
--- a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
+++ b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees check out branch modal', :js do
diff --git a/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb b/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb
index aa499493dbe..8a2614c53af 100644
--- a/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb
+++ b/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User cherry-picks', :js do
diff --git a/spec/features/merge_request/user_sees_closing_issues_message_spec.rb b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
index d7c784b14c5..52163571175 100644
--- a/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
+++ b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees closing issues message', :js do
diff --git a/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb
index 46c21a2b155..f46921af1cd 100644
--- a/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb
+++ b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees deleted target branch', :js do
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index fe8e0b07d39..14fbfc2fd3f 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees deployment widget', :js do
diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb
index 8dc5912b8be..32429bac5f9 100644
--- a/spec/features/merge_request/user_sees_diff_spec.rb
+++ b/spec/features/merge_request/user_sees_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees diff', :js do
diff --git a/spec/features/merge_request/user_sees_discussions_spec.rb b/spec/features/merge_request/user_sees_discussions_spec.rb
index 57be1d06708..a11baf1dca3 100644
--- a/spec/features/merge_request/user_sees_discussions_spec.rb
+++ b/spec/features/merge_request/user_sees_discussions_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'rails_helper'
-describe 'Merge request > User sees discussions', :js do
+describe 'Merge request > User sees threads', :js do
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project) }
@@ -30,7 +32,7 @@ describe 'Merge request > User sees discussions', :js do
visit project_merge_request_path(project, merge_request)
end
- context 'active discussions' do
+ context 'active threads' do
it 'shows a link to the diff' do
within(".discussion[data-discussion-id='#{active_discussion.id}']") do
path = diffs_project_merge_request_path(project, merge_request, anchor: active_discussion.line_code)
@@ -39,7 +41,7 @@ describe 'Merge request > User sees discussions', :js do
end
end
- context 'outdated discussions' do
+ context 'outdated threads' do
it 'shows a link to the outdated diff' do
within(".discussion[data-discussion-id='#{outdated_discussion.id}']") do
path = diffs_project_merge_request_path(project, merge_request, diff_id: old_merge_request_diff.id, anchor: outdated_discussion.line_code)
@@ -85,7 +87,7 @@ describe 'Merge request > User sees discussions', :js do
it_behaves_like 'a functional discussion'
it 'displays correct header' do
- expect(page).to have_content "started a discussion on commit #{note.commit_id[0...7]}"
+ expect(page).to have_content "started a thread on commit #{note.commit_id[0...7]}"
end
end
diff --git a/spec/features/merge_request/user_sees_empty_state_spec.rb b/spec/features/merge_request/user_sees_empty_state_spec.rb
index 012bfd6e458..f1ad5c2dffc 100644
--- a/spec/features/merge_request/user_sees_empty_state_spec.rb
+++ b/spec/features/merge_request/user_sees_empty_state_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees empty state' do
diff --git a/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
index f6b771facf8..53cafb60dfb 100644
--- a/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'rails_helper'
-describe 'Merge request > User sees merge button depending on unresolved discussions', :js do
+describe 'Merge request > User sees merge button depending on unresolved threads', :js do
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
let!(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) }
@@ -16,14 +18,14 @@ describe 'Merge request > User sees merge button depending on unresolved discuss
visit project_merge_request_path(project, merge_request)
end
- context 'with unresolved discussions' do
+ context 'with unresolved threads' do
it 'does not allow to merge' do
expect(page).not_to have_button 'Merge'
- expect(page).to have_content('There are unresolved discussions.')
+ expect(page).to have_content('There are unresolved threads.')
end
end
- context 'with all discussions resolved' do
+ context 'with all threads resolved' do
before do
merge_request.discussions.each { |d| d.resolve!(user) }
visit project_merge_request_path(project, merge_request)
@@ -41,13 +43,13 @@ describe 'Merge request > User sees merge button depending on unresolved discuss
visit project_merge_request_path(project, merge_request)
end
- context 'with unresolved discussions' do
+ context 'with unresolved threads' do
it 'does not allow to merge' do
expect(page).to have_button 'Merge'
end
end
- context 'with all discussions resolved' do
+ context 'with all threads resolved' do
before do
merge_request.discussions.each { |d| d.resolve!(user) }
visit project_merge_request_path(project, merge_request)
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index 733e8aa3eba..b5d0240e9de 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees merge widget', :js do
@@ -519,6 +521,8 @@ describe 'Merge request > User sees merge widget', :js do
end
before do
+ allow_any_instance_of(TestSuiteComparerEntity)
+ .to receive(:max_tests).and_return(2)
allow_any_instance_of(MergeRequest)
.to receive(:has_test_reports?).and_return(true)
allow_any_instance_of(MergeRequest)
@@ -551,7 +555,7 @@ describe 'Merge request > User sees merge widget', :js do
expect(page).to have_content('rspec found no changed test results out of 1 total test')
expect(page).to have_content('junit found 1 failed test result out of 1 total test')
expect(page).to have_content('New')
- expect(page).to have_content('subtractTest')
+ expect(page).to have_content('addTest')
end
end
end
@@ -562,7 +566,7 @@ describe 'Merge request > User sees merge widget', :js do
click_button 'Expand'
within(".js-report-section-container") do
- click_button 'subtractTest'
+ click_button 'addTest'
expect(page).to have_content('6.66')
expect(page).to have_content(sample_java_failed_message.gsub!(/\s+/, ' ').strip)
@@ -596,7 +600,7 @@ describe 'Merge request > User sees merge widget', :js do
expect(page).to have_content('rspec found 1 failed test result out of 1 total test')
expect(page).to have_content('junit found no changed test results out of 1 total test')
expect(page).not_to have_content('New')
- expect(page).to have_content('Test#sum when a is 2 and b is 2 returns summary')
+ expect(page).to have_content('Test#sum when a is 1 and b is 3 returns summary')
end
end
end
@@ -607,7 +611,7 @@ describe 'Merge request > User sees merge widget', :js do
click_button 'Expand'
within(".js-report-section-container") do
- click_button 'Test#sum when a is 2 and b is 2 returns summary'
+ click_button 'Test#sum when a is 1 and b is 3 returns summary'
expect(page).to have_content('2.22')
expect(page).to have_content(sample_rspec_failed_message.gsub!(/\s+/, ' ').strip)
@@ -628,13 +632,7 @@ describe 'Merge request > User sees merge widget', :js do
let(:head_reports) do
Gitlab::Ci::Reports::TestReports.new.tap do |reports|
reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
- reports.get_suite('junit').add_test_case(create_test_case_java_resolved)
- end
- end
-
- let(:create_test_case_java_resolved) do
- create_test_case_java_failed.tap do |test_case|
- test_case.instance_variable_set("@status", Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS)
+ reports.get_suite('junit').add_test_case(create_test_case_java_success)
end
end
@@ -646,7 +644,7 @@ describe 'Merge request > User sees merge widget', :js do
within(".js-report-section-container") do
expect(page).to have_content('rspec found no changed test results out of 1 total test')
expect(page).to have_content('junit found 1 fixed test result out of 1 total test')
- expect(page).to have_content('subtractTest')
+ expect(page).to have_content('addTest')
end
end
end
@@ -657,15 +655,53 @@ describe 'Merge request > User sees merge widget', :js do
click_button 'Expand'
within(".js-report-section-container") do
- click_button 'subtractTest'
+ click_button 'addTest'
- expect(page).to have_content('6.66')
+ expect(page).to have_content('5.55')
end
end
end
end
end
+ context 'properly truncates the report' do
+ let(:base_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ 10.times do |index|
+ reports.get_suite('rspec').add_test_case(
+ create_test_case_rspec_failed(index))
+ reports.get_suite('junit').add_test_case(
+ create_test_case_java_success(index))
+ end
+ end
+ end
+
+ let(:head_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ 10.times do |index|
+ reports.get_suite('rspec').add_test_case(
+ create_test_case_rspec_failed(index))
+ reports.get_suite('junit').add_test_case(
+ create_test_case_java_failed(index))
+ end
+ end
+ end
+
+ it 'shows test reports summary which includes the resolved failure' do
+ within(".js-reports-container") do
+ click_button 'Expand'
+
+ expect(page).to have_content('Test summary contained 20 failed test results out of 20 total tests')
+ within(".js-report-section-container") do
+ expect(page).to have_content('rspec found 10 failed test results out of 10 total tests')
+ expect(page).to have_content('junit found 10 failed test results out of 10 total tests')
+
+ expect(page).to have_content('Test#sum when a is 1 and b is 3 returns summary', count: 2)
+ end
+ end
+ end
+ end
+
def comparer
Gitlab::Ci::Reports::TestReportsComparer.new(base_reports, head_reports)
end
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 dd8900a3698..eb89d616b14 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request < User sees mini pipeline graph', :js do
diff --git a/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb
index fc4a188d4a7..80ee35f2a91 100644
--- a/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb
+++ b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees MR from deleted forked project', :js do
diff --git a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
index fd4175d5227..d9dc32d0594 100644
--- a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
+++ b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
# This test serves as a regression test for a bug that caused an error
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 a6118453540..195592531f2 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees notes from forked project', :js do
diff --git a/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb
index 97093bbc2f6..68c9e0b123d 100644
--- a/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb
+++ b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees pipelines from forked project', :js do
diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb
index 8faddee4daa..e057f59e00c 100644
--- a/spec/features/merge_request/user_sees_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_pipelines_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees pipelines', :js do
diff --git a/spec/features/merge_request/user_sees_system_notes_spec.rb b/spec/features/merge_request/user_sees_system_notes_spec.rb
index c6811d4161a..26cdd5ba21c 100644
--- a/spec/features/merge_request/user_sees_system_notes_spec.rb
+++ b/spec/features/merge_request/user_sees_system_notes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees system notes', :js do
diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb
index 6eae3fd4676..dae5845adec 100644
--- a/spec/features/merge_request/user_sees_versions_spec.rb
+++ b/spec/features/merge_request/user_sees_versions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees versions', :js do
diff --git a/spec/features/merge_request/user_sees_wip_help_message_spec.rb b/spec/features/merge_request/user_sees_wip_help_message_spec.rb
index 92cc73ddf1f..46209237faf 100644
--- a/spec/features/merge_request/user_sees_wip_help_message_spec.rb
+++ b/spec/features/merge_request/user_sees_wip_help_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees WIP help message' do
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 ae41cf90576..a1f027cbb1d 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User selects branches for new MR', :js do
diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
index e8b4fc8f160..4363b359038 100644
--- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
+++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
@@ -186,12 +186,12 @@ describe 'User comments on a diff', :js do
it 'resolves discussion when applied' do
page.within('.diff-discussions') do
- expect(page).not_to have_content('Unresolve discussion')
+ expect(page).not_to have_content('Unresolve thread')
click_button('Apply suggestion')
wait_for_requests
- expect(page).to have_content('Unresolve discussion')
+ expect(page).to have_content('Unresolve thread')
end
end
end
diff --git a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
index 0decdfe3a14..61b9904dde5 100644
--- a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
+++ b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User toggles whitespace changes', :js do
diff --git a/spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb b/spec/features/merge_request/user_tries_to_access_private_project_info_through_new_mr_spec.rb
index 9318b5f1ebb..1ebe9e2e409 100644
--- a/spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb
+++ b/spec/features/merge_request/user_tries_to_access_private_project_info_through_new_mr_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'Merge Request > Tries to access private repo of public project' do
+describe 'Merge Request > User tries to access private project information through the new mr page' do
let(:current_user) { create(:user) }
let(:private_project) do
create(:project, :public, :repository,
@@ -33,5 +35,22 @@ describe 'Merge Request > Tries to access private repo of public project' do
it "does not mention the project the user can't see the repo of" do
expect(page).not_to have_content('nothing-to-see-here')
end
+
+ context 'when the user enters label information from the private project in the querystring' do
+ let(:inaccessible_label) { create(:label, project: private_project) }
+ let(:mr_path) do
+ project_new_merge_request_path(
+ owned_project,
+ merge_request: {
+ label_ids: [inaccessible_label.id],
+ source_branch: 'feature'
+ }
+ )
+ end
+
+ it 'does not expose the label name' do
+ expect(page).not_to have_content(inaccessible_label.name)
+ end
+ end
end
end
diff --git a/spec/features/merge_request/user_views_diffs_spec.rb b/spec/features/merge_request/user_views_diffs_spec.rb
index 74342b16cb2..2d1eb260236 100644
--- a/spec/features/merge_request/user_views_diffs_spec.rb
+++ b/spec/features/merge_request/user_views_diffs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views diffs', :js do
@@ -15,11 +17,25 @@ describe 'User views diffs', :js do
end
shared_examples 'unfold diffs' do
- it 'unfolds diffs' do
+ it 'unfolds diffs upwards' do
first('.js-unfold').click
-
expect(find('.file-holder[id="a5cc2925ca8258af241be7e5b0381edf30266302"] .text-file')).to have_content('.bundle')
end
+
+ it 'unfolds diffs to the start' do
+ first('.js-unfold-all').click
+ expect(find('.file-holder[id="a5cc2925ca8258af241be7e5b0381edf30266302"] .text-file')).to have_content('.rbc')
+ end
+
+ it 'unfolds diffs downwards' do
+ first('.js-unfold-down').click
+ expect(find('.file-holder[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd"] .text-file')).to have_content('.popen3')
+ end
+
+ it 'unfolds diffs to the end' do
+ page.all('.js-unfold-down').last
+ expect(find('.file-holder[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9"] .text-file')).to have_content('end')
+ end
end
it 'shows diffs' do
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 849fab62fc6..a788fc71286 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views an open merge request' do
diff --git a/spec/features/merge_requests/filters_generic_behavior_spec.rb b/spec/features/merge_requests/filters_generic_behavior_spec.rb
index 0e7fac6b409..404e591cf4b 100644
--- a/spec/features/merge_requests/filters_generic_behavior_spec.rb
+++ b/spec/features/merge_requests/filters_generic_behavior_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge Requests > Filters generic behavior', :js do
diff --git a/spec/features/merge_requests/user_filters_by_assignees_spec.rb b/spec/features/merge_requests/user_filters_by_assignees_spec.rb
index 0cbf1bcae30..b896b6392f1 100644
--- a/spec/features/merge_requests/user_filters_by_assignees_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_assignees_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge Requests > User filters by assignees', :js do
diff --git a/spec/features/merge_requests/user_filters_by_labels_spec.rb b/spec/features/merge_requests/user_filters_by_labels_spec.rb
index 08d741af93d..9fb149ca58d 100644
--- a/spec/features/merge_requests/user_filters_by_labels_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge Requests > User filters by labels', :js do
diff --git a/spec/features/merge_requests/user_filters_by_milestones_spec.rb b/spec/features/merge_requests/user_filters_by_milestones_spec.rb
index 727a236d980..2fb3c86ea94 100644
--- a/spec/features/merge_requests/user_filters_by_milestones_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_milestones_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge Requests > User filters by milestones', :js do
diff --git a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
index 4627931f26a..3dd0f93ddfa 100644
--- a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge requests > User filters by multiple criteria', :js do
diff --git a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
index ffbdacc68f6..b5969f656b0 100644
--- a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge Requests > User filters by target branch', :js do
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index 2dee0e26954..69d4650d1ac 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge requests > User lists merge requests' do
diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb
index c2dd105324d..48c92a87c63 100644
--- a/spec/features/merge_requests/user_mass_updates_spec.rb
+++ b/spec/features/merge_requests/user_mass_updates_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge requests > User mass updates', :js do
diff --git a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
index fa887110c13..ca3e24d7036 100644
--- a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User sorts merge requests' do
diff --git a/spec/features/merge_requests/user_squashes_merge_request_spec.rb b/spec/features/merge_requests/user_squashes_merge_request_spec.rb
index bf9c55cf22c..4fc8c71e47e 100644
--- a/spec/features/merge_requests/user_squashes_merge_request_spec.rb
+++ b/spec/features/merge_requests/user_squashes_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User squashes a merge request', :js do
diff --git a/spec/features/merge_requests/user_views_all_merge_requests_spec.rb b/spec/features/merge_requests/user_views_all_merge_requests_spec.rb
index 6c695bd7aa9..60496d61e87 100644
--- a/spec/features/merge_requests/user_views_all_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_views_all_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views all merge requests' do
diff --git a/spec/features/merge_requests/user_views_closed_merge_requests_spec.rb b/spec/features/merge_requests/user_views_closed_merge_requests_spec.rb
index 853809fe87a..dc0d35e4fea 100644
--- a/spec/features/merge_requests/user_views_closed_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_views_closed_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views closed merge requests' do
diff --git a/spec/features/merge_requests/user_views_merged_merge_requests_spec.rb b/spec/features/merge_requests/user_views_merged_merge_requests_spec.rb
index eb012694f1e..ddb354204c9 100644
--- a/spec/features/merge_requests/user_views_merged_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_views_merged_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views merged merge requests' do
diff --git a/spec/features/merge_requests/user_views_open_merge_requests_spec.rb b/spec/features/merge_requests/user_views_open_merge_requests_spec.rb
index 115e548b691..cefac9690ce 100644
--- a/spec/features/merge_requests/user_views_open_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_views_open_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views open merge requests' do
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index adac59b89ef..6d5acc894b1 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Milestone' do
diff --git a/spec/features/milestones/user_creates_milestone_spec.rb b/spec/features/milestones/user_creates_milestone_spec.rb
index 5de0c381cdf..73f4f187501 100644
--- a/spec/features/milestones/user_creates_milestone_spec.rb
+++ b/spec/features/milestones/user_creates_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "User creates milestone", :js do
diff --git a/spec/features/milestones/user_deletes_milestone_spec.rb b/spec/features/milestones/user_deletes_milestone_spec.rb
index f68ed1cde07..5666c1dd507 100644
--- a/spec/features/milestones/user_deletes_milestone_spec.rb
+++ b/spec/features/milestones/user_deletes_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "User deletes milestone", :js do
diff --git a/spec/features/milestones/user_edits_milestone_spec.rb b/spec/features/milestones/user_edits_milestone_spec.rb
index 077295f1cc0..f8e96eac3ea 100644
--- a/spec/features/milestones/user_edits_milestone_spec.rb
+++ b/spec/features/milestones/user_edits_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "User edits milestone", :js do
diff --git a/spec/features/milestones/user_promotes_milestone_spec.rb b/spec/features/milestones/user_promotes_milestone_spec.rb
index df1bc502134..acceb5cad62 100644
--- a/spec/features/milestones/user_promotes_milestone_spec.rb
+++ b/spec/features/milestones/user_promotes_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User promotes milestone' do
diff --git a/spec/features/milestones/user_sees_breadcrumb_links_spec.rb b/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
index d3906ea73bd..f58242759cc 100644
--- a/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'New project milestone breadcrumb' do
diff --git a/spec/features/milestones/user_views_milestone_spec.rb b/spec/features/milestones/user_views_milestone_spec.rb
index 83d8e2ff9e9..aa0cdf66b75 100644
--- a/spec/features/milestones/user_views_milestone_spec.rb
+++ b/spec/features/milestones/user_views_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "User views milestone" do
diff --git a/spec/features/milestones/user_views_milestones_spec.rb b/spec/features/milestones/user_views_milestones_spec.rb
index bebe40f73fd..6868791f584 100644
--- a/spec/features/milestones/user_views_milestones_spec.rb
+++ b/spec/features/milestones/user_views_milestones_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "User views milestones" do
diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb
index 5ebfc32952d..c6e69fa3fb0 100644
--- a/spec/features/oauth_login_spec.rb
+++ b/spec/features/oauth_login_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'OAuth Login', :js, :allow_forgery_protection do
@@ -16,16 +18,8 @@ describe 'OAuth Login', :js, :allow_forgery_protection do
providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2,
:facebook, :cas3, :auth0, :authentiq, :salesforce]
- before(:all) do
- # The OmniAuth `full_host` parameter doesn't get set correctly (it gets set to something like `http://localhost`
- # here), and causes integration tests to fail with 404s. We set the `full_host` by removing the request path (and
- # anything after it) from the request URI.
- @omniauth_config_full_host = OmniAuth.config.full_host
- OmniAuth.config.full_host = ->(request) { request['REQUEST_URI'].sub(/#{request['REQUEST_PATH']}.*/, '') }
- end
-
- after(:all) do
- OmniAuth.config.full_host = @omniauth_config_full_host
+ around(:all) do |example|
+ with_omniauth_full_host { example.run }
end
def login_with_provider(provider, enter_two_factor: false)
@@ -42,6 +36,7 @@ describe 'OAuth Login', :js, :allow_forgery_protection do
before do
stub_omniauth_config(provider)
+ expect(ActiveSession).to receive(:cleanup).with(user).at_least(:once).and_call_original
end
context 'when two-factor authentication is disabled' do
@@ -60,6 +55,18 @@ describe 'OAuth Login', :js, :allow_forgery_protection do
expect(current_path).to eq root_path
end
+
+ it 'when bypass-two-factor is enabled' do
+ allow(Gitlab.config.omniauth).to receive_messages(allow_bypass_two_factor: true)
+ login_via(provider.to_s, user, uid, remember_me: false)
+ expect(current_path).to eq root_path
+ end
+
+ it 'when bypass-two-factor is disabled' do
+ allow(Gitlab.config.omniauth).to receive_messages(allow_bypass_two_factor: false)
+ login_with_provider(provider, enter_two_factor: true)
+ expect(current_path).to eq root_path
+ end
end
context 'when "remember me" is checked' do
diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb
index 134731a4639..312285ca2cb 100644
--- a/spec/features/participants_autocomplete_spec.rb
+++ b/spec/features/participants_autocomplete_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Member autocomplete', :js do
diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb
index dcc63dff9f5..8ceca1e3175 100644
--- a/spec/features/password_reset_spec.rb
+++ b/spec/features/password_reset_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Password reset' do
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 2e0753c3bfb..e80a3cd32cc 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Profile account page', :js do
diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb
index 90d0e9bb77c..741e41adbf1 100644
--- a/spec/features/profiles/account_spec.rb
+++ b/spec/features/profiles/account_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Profile > Account', :js do
diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb
index 2aa0177af5d..709cca7d178 100644
--- a/spec/features/profiles/active_sessions_spec.rb
+++ b/spec/features/profiles/active_sessions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb
index c72069f6262..0219dacbc38 100644
--- a/spec/features/profiles/chat_names_spec.rb
+++ b/spec/features/profiles/chat_names_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Profile > Chat' do
diff --git a/spec/features/profiles/emails_spec.rb b/spec/features/profiles/emails_spec.rb
index bc6d54b5ed7..40e2988730b 100644
--- a/spec/features/profiles/emails_spec.rb
+++ b/spec/features/profiles/emails_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Profile > Emails' do
diff --git a/spec/features/profiles/gpg_keys_spec.rb b/spec/features/profiles/gpg_keys_spec.rb
index ec3ec795b63..4237f037c27 100644
--- a/spec/features/profiles/gpg_keys_spec.rb
+++ b/spec/features/profiles/gpg_keys_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Profile > GPG Keys' do
diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb
index e6586fc8a0a..c1b142c4e12 100644
--- a/spec/features/profiles/keys_spec.rb
+++ b/spec/features/profiles/keys_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Profile > SSH Keys' do
diff --git a/spec/features/profiles/oauth_applications_spec.rb b/spec/features/profiles/oauth_applications_spec.rb
index 7d204f89fba..94c9897a7a9 100644
--- a/spec/features/profiles/oauth_applications_spec.rb
+++ b/spec/features/profiles/oauth_applications_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Profile > Applications' do
diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb
index 5e3569e4beb..8c0c426f689 100644
--- a/spec/features/profiles/password_spec.rb
+++ b/spec/features/profiles/password_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Profile > Password' do
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index dee213a11d4..22f9c8d8afc 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Profile > Personal Access Tokens', :js do
diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
index f618bc330ea..5af48c4503d 100644
--- a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
+++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Profile > Notifications > User changes notified_of_own_activity setting', :js do
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index a53da94ef7d..1ab7742b36e 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User edit profile' do
@@ -40,7 +42,7 @@ describe 'User edit profile' do
simulate_input('#user_name', 'Martin 😀')
submit_settings
- page.within('.qa-full-name') do
+ page.within('.rspec-full-name') do
expect(page).to have_css '.gl-field-error-outline'
expect(find('.gl-field-error')).not_to have_selector('.hidden')
expect(find('.gl-field-error')).to have_content('Using emojis in names seems fun, but please try to set a status message instead')
diff --git a/spec/features/profiles/user_manages_applications_spec.rb b/spec/features/profiles/user_manages_applications_spec.rb
index 34aaab240cc..7a961855c92 100644
--- a/spec/features/profiles/user_manages_applications_spec.rb
+++ b/spec/features/profiles/user_manages_applications_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User manages applications' do
diff --git a/spec/features/profiles/user_manages_emails_spec.rb b/spec/features/profiles/user_manages_emails_spec.rb
index 7283c76eb54..09da819a187 100644
--- a/spec/features/profiles/user_manages_emails_spec.rb
+++ b/spec/features/profiles/user_manages_emails_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User manages emails' do
diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb
index db797bb586f..d788c0574e2 100644
--- a/spec/features/profiles/user_visits_notifications_tab_spec.rb
+++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User visits the notifications tab', :js do
@@ -18,4 +20,12 @@ describe 'User visits the notifications tab', :js do
expect(page).to have_selector('#notifications-button', text: 'On mention')
end
+
+ context 'when project emails are disabled' do
+ let(:project) { create(:project, emails_disabled: true) }
+
+ it 'notification button is disabled' do
+ expect(page).to have_selector('.notifications-btn.disabled', visible: true)
+ end
+ end
end
diff --git a/spec/features/profiles/user_visits_profile_account_page_spec.rb b/spec/features/profiles/user_visits_profile_account_page_spec.rb
index a8c08a680d7..f576a2bf9e1 100644
--- a/spec/features/profiles/user_visits_profile_account_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_account_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User visits the profile account page' do
diff --git a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb
index 0f419c3c2c0..2f6f8ebee9c 100644
--- a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb
+++ b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User visits the authentication log' do
diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
index 1b3718968b9..4dbdea02e27 100644
--- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User visits the profile preferences page' do
@@ -36,7 +38,7 @@ describe 'User visits the profile preferences page' do
describe 'User changes their default dashboard', :js do
it 'creates a flash message' do
- select 'Starred Projects', from: 'user_dashboard'
+ select2('stars', from: '#user_dashboard')
click_button 'Save'
wait_for_requests
@@ -45,7 +47,7 @@ describe 'User visits the profile preferences page' do
end
it 'updates their preference' do
- select 'Starred Projects', from: 'user_dashboard'
+ select2('stars', from: '#user_dashboard')
click_button 'Save'
wait_for_requests
diff --git a/spec/features/profiles/user_visits_profile_spec.rb b/spec/features/profiles/user_visits_profile_spec.rb
index 2dc4547b2d8..1c90a794099 100644
--- a/spec/features/profiles/user_visits_profile_spec.rb
+++ b/spec/features/profiles/user_visits_profile_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User visits their profile' do
diff --git a/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb b/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb
index 685bf44619d..05ad9096f65 100644
--- a/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User visits the profile SSH keys page' do
diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb
index 95685a3c7ff..9e3f8a843a1 100644
--- a/spec/features/project_variables_spec.rb
+++ b/spec/features/project_variables_spec.rb
@@ -17,4 +17,27 @@ describe 'Project variables', :js do
end
it_behaves_like 'variable list'
+
+ it 'adds new variable with a special environment scope' do
+ page.within('.js-ci-variable-list-section .js-row:last-child') do
+ find('.js-ci-variable-input-key').set('somekey')
+ find('.js-ci-variable-input-value').set('somevalue')
+
+ find('.js-variable-environment-toggle').click
+ find('.js-variable-environment-dropdown-wrapper .dropdown-input-field').set('review/*')
+ find('.js-variable-environment-dropdown-wrapper .js-dropdown-create-new-item').click
+
+ expect(find('input[name="variables[variables_attributes][][environment_scope]"]', visible: false).value).to eq('review/*')
+ end
+
+ click_button('Save variables')
+ wait_for_requests
+
+ visit page_path
+
+ page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
+ expect(find('.js-ci-variable-input-key').value).to eq('somekey')
+ expect(page).to have_content('review/*')
+ end
+ end
end
diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb
index 411134e7b8e..e1efe6ca64d 100644
--- a/spec/features/projects/activity/rss_spec.rb
+++ b/spec/features/projects/activity/rss_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Activity RSS' do
diff --git a/spec/features/projects/activity/user_sees_activity_spec.rb b/spec/features/projects/activity/user_sees_activity_spec.rb
index bb4b2abc3c7..664002d909c 100644
--- a/spec/features/projects/activity/user_sees_activity_spec.rb
+++ b/spec/features/projects/activity/user_sees_activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Activity > User sees activity' do
diff --git a/spec/features/projects/activity/user_sees_private_activity_spec.rb b/spec/features/projects/activity/user_sees_private_activity_spec.rb
index 61ec2ce9d29..0ec4752d418 100644
--- a/spec/features/projects/activity/user_sees_private_activity_spec.rb
+++ b/spec/features/projects/activity/user_sees_private_activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project > Activity > User sees private activity', :js do
diff --git a/spec/features/projects/actve_tabs_spec.rb b/spec/features/projects/actve_tabs_spec.rb
index 7c6110c533b..56f587f23ee 100644
--- a/spec/features/projects/actve_tabs_spec.rb
+++ b/spec/features/projects/actve_tabs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project active tab' do
diff --git a/spec/features/projects/artifacts/file_spec.rb b/spec/features/projects/artifacts/file_spec.rb
index 993d0040434..f7eaae12072 100644
--- a/spec/features/projects/artifacts/file_spec.rb
+++ b/spec/features/projects/artifacts/file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Artifact file', :js do
diff --git a/spec/features/projects/artifacts/raw_spec.rb b/spec/features/projects/artifacts/raw_spec.rb
index d8ee9adda6b..0606ab0ed08 100644
--- a/spec/features/projects/artifacts/raw_spec.rb
+++ b/spec/features/projects/artifacts/raw_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Raw artifact', :js do
diff --git a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
index a1fcd4024c0..ecc07181d09 100644
--- a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
+++ b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User browses artifacts" do
diff --git a/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb b/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb
index 5cb015e80be..254ebfb839a 100644
--- a/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb
+++ b/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User downloads artifacts" do
- set(:project) { create(:project, :public) }
- set(:pipeline) { create(:ci_empty_pipeline, status: :success, project: project) }
+ set(:project) { create(:project, :repository, :public) }
+ set(:pipeline) { create(:ci_empty_pipeline, status: :success, sha: project.commit.id, project: project) }
set(:job) { create(:ci_build, :artifacts, :success, pipeline: pipeline) }
shared_examples "downloading" do
diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb
index 8522ea747fa..46aa104fdd7 100644
--- a/spec/features/projects/badges/coverage_spec.rb
+++ b/spec/features/projects/badges/coverage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'test coverage badge' do
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index e30b908c60d..7a90457c942 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'list of badges' do
diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb
index 4ac4e8f0fcb..f2c57d702a5 100644
--- a/spec/features/projects/badges/pipeline_badge_spec.rb
+++ b/spec/features/projects/badges/pipeline_badge_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Pipeline Badge' 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 96f514f4f04..1fc490ecbfe 100644
--- a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
+++ b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Blob button line permalinks (BlobLinePermalinkUpdater)', :js do
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index aa2e538cc8e..af6bb8c271f 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'File blob', :js do
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index 57d21f3e182..3b32d213754 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Editing file blob', :js do
diff --git a/spec/features/projects/blobs/shortcuts_blob_spec.rb b/spec/features/projects/blobs/shortcuts_blob_spec.rb
index 3925de6cfb9..bc12a8ff007 100644
--- a/spec/features/projects/blobs/shortcuts_blob_spec.rb
+++ b/spec/features/projects/blobs/shortcuts_blob_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Blob shortcuts', :js do
diff --git a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
index 8a0b92190dd..b90129d6176 100644
--- a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
+++ b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User creates blob in new project', :js do
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index 3e75890725e..401425187b0 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Download buttons in branches page' do
diff --git a/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb b/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
index 0faf73db7da..4b6b07f6cda 100644
--- a/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
+++ b/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'New Branch Ref Dropdown', :js do
diff --git a/spec/features/projects/branches/user_creates_branch_spec.rb b/spec/features/projects/branches/user_creates_branch_spec.rb
index b706ad64954..156edb973cd 100644
--- a/spec/features/projects/branches/user_creates_branch_spec.rb
+++ b/spec/features/projects/branches/user_creates_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User creates branch", :js do
diff --git a/spec/features/projects/branches/user_deletes_branch_spec.rb b/spec/features/projects/branches/user_deletes_branch_spec.rb
index 96f215e1606..1f053b69646 100644
--- a/spec/features/projects/branches/user_deletes_branch_spec.rb
+++ b/spec/features/projects/branches/user_deletes_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User deletes branch", :js do
diff --git a/spec/features/projects/branches/user_views_branches_spec.rb b/spec/features/projects/branches/user_views_branches_spec.rb
index 777d30fdffd..f3810611094 100644
--- a/spec/features/projects/branches/user_views_branches_spec.rb
+++ b/spec/features/projects/branches/user_views_branches_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User views branches" do
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index ee71c843b80..b35067d0f4d 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Branches' do
diff --git a/spec/features/projects/ci/lint_spec.rb b/spec/features/projects/ci/lint_spec.rb
index 313950072e7..521cfb54cd2 100644
--- a/spec/features/projects/ci/lint_spec.rb
+++ b/spec/features/projects/ci/lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CI Lint', :js do
diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb
index 527508b3519..3d15095e2da 100644
--- a/spec/features/projects/clusters/applications_spec.rb
+++ b/spec/features/projects/clusters/applications_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Clusters Applications', :js do
@@ -20,10 +22,8 @@ describe 'Clusters Applications', :js do
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
it 'user is unable to install applications' do
- page.within('.js-cluster-application-row-helm') do
- expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Install')
- end
+ expect(page).not_to have_css('.js-cluster-application-row-helm')
+ expect(page).not_to have_css('.js-cluster-application-install-button')
end
end
@@ -53,19 +53,17 @@ describe 'Clusters Applications', :js do
it 'they see status transition' do
page.within('.js-cluster-application-row-helm') do
# FE sends request and gets the response, then the buttons is "Installing"
- expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+ expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing')
Clusters::Cluster.last.application_helm.make_installing!
# FE starts polling and update the buttons to "Installing"
- expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+ expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing')
Clusters::Cluster.last.application_helm.make_installed!
- expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed')
+ expect(page).not_to have_css('.js-cluster-application-install-button')
+ expect(page).to have_css('.js-cluster-application-uninstall-button:not([disabled])', exact_text: 'Uninstall')
end
expect(page).to have_content('Helm Tiller was successfully installed on your Kubernetes cluster')
@@ -128,7 +126,7 @@ describe 'Clusters Applications', :js do
it 'shows status transition' do
page.within('.js-cluster-application-row-knative') do
expect(domainname_form_value).to eq('domain.example.org')
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed')
+ expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
expect(page).to have_content('Knative was successfully installed on your Kubernetes cluster')
@@ -185,7 +183,7 @@ describe 'Clusters Applications', :js do
Clusters::Cluster.last.application_cert_manager.make_installed!
expect(email_form_value).to eq('new_email@example.org')
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed')
+ expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
expect(page).to have_content('Cert-Manager was successfully installed on your Kubernetes cluster')
@@ -212,26 +210,25 @@ describe 'Clusters Applications', :js do
it 'they see status transition' do
page.within('.js-cluster-application-row-ingress') do
# FE sends request and gets the response, then the buttons is "Installing"
- expect(page).to have_css('.js-cluster-application-install-button[disabled]')
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+ expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing')
Clusters::Cluster.last.application_ingress.make_installing!
# FE starts polling and update the buttons to "Installing"
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
- expect(page).to have_css('.js-cluster-application-install-button[disabled]')
+ expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing')
# The application becomes installed but we keep waiting for external IP address
Clusters::Cluster.last.application_ingress.make_installed!
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed')
- expect(page).to have_css('.js-cluster-application-install-button[disabled]')
+ expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installed')
expect(page).to have_selector('.js-no-endpoint-message')
expect(page).to have_selector('.js-ingress-ip-loading-icon')
# We receive the external IP address and display
Clusters::Cluster.last.application_ingress.update!(external_ip: '192.168.1.100')
+ expect(page).not_to have_css('.js-cluster-application-install-button')
+ expect(page).to have_css('.js-cluster-application-uninstall-button:not([disabled])', exact_text: 'Uninstall')
expect(page).not_to have_selector('.js-no-endpoint-message')
expect(page.find('.js-endpoint').value).to eq('192.168.1.100')
end
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 974e0f84681..820ce48e52c 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Gcp Cluster', :js do
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index 31cc09ae911..3899aab8170 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User Cluster', :js do
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
index d72476f36a9..d28ff5d3b5f 100644
--- a/spec/features/projects/commit/builds_spec.rb
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'project commit pipelines', :js do
diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb
index acfb582dba9..46a6f62ba14 100644
--- a/spec/features/projects/commit/cherry_pick_spec.rb
+++ b/spec/features/projects/commit/cherry_pick_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Cherry-pick Commits' do
diff --git a/spec/features/projects/commit/comments/user_adds_comment_spec.rb b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
index 586e2e33112..bae8e6dc827 100644
--- a/spec/features/projects/commit/comments/user_adds_comment_spec.rb
+++ b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User adds a comment on a commit", :js do
diff --git a/spec/features/projects/commit/comments/user_deletes_comments_spec.rb b/spec/features/projects/commit/comments/user_deletes_comments_spec.rb
index a727cab4ac7..2993a402e37 100644
--- a/spec/features/projects/commit/comments/user_deletes_comments_spec.rb
+++ b/spec/features/projects/commit/comments/user_deletes_comments_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User deletes comments on a commit", :js do
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 75bccd99f59..0fa2b2ff232 100644
--- a/spec/features/projects/commit/comments/user_edits_comments_spec.rb
+++ b/spec/features/projects/commit/comments/user_edits_comments_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User edits a comment on a commit", :js do
diff --git a/spec/features/projects/commit/diff_notes_spec.rb b/spec/features/projects/commit/diff_notes_spec.rb
index e2aefa35fad..04bd66df28d 100644
--- a/spec/features/projects/commit/diff_notes_spec.rb
+++ b/spec/features/projects/commit/diff_notes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Commit diff', :js do
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
index 614f11c8392..1199a3bd226 100644
--- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb
+++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Mini Pipeline Graph in Commit View', :js do
diff --git a/spec/features/projects/commit/user_comments_on_commit_spec.rb b/spec/features/projects/commit/user_comments_on_commit_spec.rb
index 73ce8d2b996..f4fea9b9ae0 100644
--- a/spec/features/projects/commit/user_comments_on_commit_spec.rb
+++ b/spec/features/projects/commit/user_comments_on_commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User comments on commit", :js do
diff --git a/spec/features/projects/commit/user_reverts_commit_spec.rb b/spec/features/projects/commit/user_reverts_commit_spec.rb
index 42844a03ea6..39ee72a4a99 100644
--- a/spec/features/projects/commit/user_reverts_commit_spec.rb
+++ b/spec/features/projects/commit/user_reverts_commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User reverts a commit', :js do
diff --git a/spec/features/projects/commits/rss_spec.rb b/spec/features/projects/commits/rss_spec.rb
index cfc2637f1b2..0266df48d4a 100644
--- a/spec/features/projects/commits/rss_spec.rb
+++ b/spec/features/projects/commits/rss_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Commits RSS' do
diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb
index fc74a370e72..085d8d63d52 100644
--- a/spec/features/projects/commits/user_browses_commits_spec.rb
+++ b/spec/features/projects/commits/user_browses_commits_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User browses commits' do
@@ -13,7 +15,7 @@ describe 'User browses commits' do
it 'renders commit' do
visit project_commit_path(project, sample_commit.id)
- expect(page).to have_content(sample_commit.message.gsub!(/\s+/, ' '))
+ expect(page).to have_content(sample_commit.message.gsub(/\s+/, ' '))
.and have_content("Showing #{sample_commit.files_changed_count} changed files")
.and have_content('Side-by-side')
end
diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb
index 5f7cf68987e..2674617bcfc 100644
--- a/spec/features/projects/compare_spec.rb
+++ b/spec/features/projects/compare_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "Compare", :js do
diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb
index 1fa9babaff5..0f3e7646673 100644
--- a/spec/features/projects/deploy_keys_spec.rb
+++ b/spec/features/projects/deploy_keys_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project deploy keys', :js do
diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb
index df05625d105..df94d6debd6 100644
--- a/spec/features/projects/diffs/diff_show_spec.rb
+++ b/spec/features/projects/diffs/diff_show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Diff file viewer', :js do
diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb
index b08ccdc2a7c..c027b776d67 100644
--- a/spec/features/projects/environments/environment_metrics_spec.rb
+++ b/spec/features/projects/environments/environment_metrics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Environment > Metrics' do
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index fbaf12be64e..497880a7835 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Environment' do
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 1b5d9083932..1a2302b3d0c 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Environments page', :js do
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index 254e885ce46..ca383da5f5c 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Edit Project Settings' do
diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb
index 004585f7c9e..2e0c589e168 100644
--- a/spec/features/projects/files/dockerfile_dropdown_spec.rb
+++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User wants to add a Dockerfile file' do
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
index 111972a6b00..a4889f8d4c4 100644
--- a/spec/features/projects/files/download_buttons_spec.rb
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > Download buttons in files tree' do
diff --git a/spec/features/projects/files/edit_file_soft_wrap_spec.rb b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
index 41af70d8ebc..df6bc6883a9 100644
--- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb
+++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User uses soft wrap whilst editing file', :js do
diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb
index 4074e67e2d2..085276f96a8 100644
--- a/spec/features/projects/files/editing_a_file_spec.rb
+++ b/spec/features/projects/files/editing_a_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User wants to edit a file' do
diff --git a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
index 51c884201a6..0e43f2fd26b 100644
--- a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
+++ b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User views files page' do
diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb
index cd0235f2b9e..b680be09444 100644
--- a/spec/features/projects/files/find_file_keyboard_spec.rb
+++ b/spec/features/projects/files/find_file_keyboard_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > Find file keyboard shortcuts', :js do
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index 9fa4c053a40..dcb960b880a 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User wants to add a .gitignore file' do
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 53aff183562..875ae5d34d1 100644
--- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
+++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User wants to add a .gitlab-ci.yml file' do
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index 44715261b8b..2944089358f 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > Project owner creates a license file', :js do
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 0b8474fb87a..556b7227403 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > Project owner sees a link to create a license file in empty project', :js do
diff --git a/spec/features/projects/files/template_selector_menu_spec.rb b/spec/features/projects/files/template_selector_menu_spec.rb
index 6b313824acd..838a484d62e 100644
--- a/spec/features/projects/files/template_selector_menu_spec.rb
+++ b/spec/features/projects/files/template_selector_menu_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Template selector menu', :js do
diff --git a/spec/features/projects/files/template_type_dropdown_spec.rb b/spec/features/projects/files/template_type_dropdown_spec.rb
index 342a93b328f..8b385185e2e 100644
--- a/spec/features/projects/files/template_type_dropdown_spec.rb
+++ b/spec/features/projects/files/template_type_dropdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > Template type dropdown selector', :js do
diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb
index 91618145391..d3f8d36a0a9 100644
--- a/spec/features/projects/files/undo_template_spec.rb
+++ b/spec/features/projects/files/undo_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > Template Undo Button', :js do
diff --git a/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb b/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb
index 2d67837763c..6bd569e5ee2 100644
--- a/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb
+++ b/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb
@@ -1,17 +1,17 @@
+# frozen_string_literal: true
+
require 'spec_helper'
# This is a regression test for https://gitlab.com/gitlab-org/gitlab-ce/issues/37569
-describe 'Projects > Files > User browses a tree with a folder containing only a folder' do
+describe 'Projects > Files > User browses a tree with a folder containing only a folder', :js do
let(:project) { create(:project, :empty_repo) }
let(:user) { project.owner }
before do
- # We need to disable the tree.flat_path provided by Gitaly to reproduce the issue
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
-
project.repository.create_dir(user, 'foo/bar', branch_name: 'master', message: 'Add the foo/bar folder')
sign_in(user)
visit(project_tree_path(project, project.repository.root_ref))
+ wait_for_requests
end
it 'shows the nested folder on a single row' do
diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb
index a5d849db8a3..a090461261b 100644
--- a/spec/features/projects/files/user_browses_files_spec.rb
+++ b/spec/features/projects/files/user_browses_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User browses files" do
diff --git a/spec/features/projects/files/user_browses_lfs_files_spec.rb b/spec/features/projects/files/user_browses_lfs_files_spec.rb
index d5cb8f9212d..08ebeed2cdd 100644
--- a/spec/features/projects/files/user_browses_lfs_files_spec.rb
+++ b/spec/features/projects/files/user_browses_lfs_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User browses LFS files' do
diff --git a/spec/features/projects/files/user_creates_directory_spec.rb b/spec/features/projects/files/user_creates_directory_spec.rb
index e29e867492e..19d95c87c6c 100644
--- a/spec/features/projects/files/user_creates_directory_spec.rb
+++ b/spec/features/projects/files/user_creates_directory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User creates a directory', :js do
diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb
index 264b288ab38..74c037641cd 100644
--- a/spec/features/projects/files/user_creates_files_spec.rb
+++ b/spec/features/projects/files/user_creates_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User creates files' do
diff --git a/spec/features/projects/files/user_deletes_files_spec.rb b/spec/features/projects/files/user_deletes_files_spec.rb
index 11ee87f245b..fd4783cfb6b 100644
--- a/spec/features/projects/files/user_deletes_files_spec.rb
+++ b/spec/features/projects/files/user_deletes_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User deletes files', :js do
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index e0fa9dbb5fa..56430721ed6 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User edits files', :js do
@@ -162,6 +164,7 @@ describe 'Projects > Files > User edits files', :js do
expect_fork_status
+ expect(page).to have_css('.ide-sidebar-project-title', text: "#{project2.name} #{user.namespace.full_path}/#{project2.path}")
expect(page).to have_css('.ide .multi-file-tab', text: '.gitignore')
end
diff --git a/spec/features/projects/files/user_find_file_spec.rb b/spec/features/projects/files/user_find_file_spec.rb
index e2d881b34d2..72f6ccb20d6 100644
--- a/spec/features/projects/files/user_find_file_spec.rb
+++ b/spec/features/projects/files/user_find_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User find project file' do
diff --git a/spec/features/projects/files/user_reads_pipeline_status_spec.rb b/spec/features/projects/files/user_reads_pipeline_status_spec.rb
index 5bce96d9b80..15f8fa7438d 100644
--- a/spec/features/projects/files/user_reads_pipeline_status_spec.rb
+++ b/spec/features/projects/files/user_reads_pipeline_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'user reads pipeline status', :js do
diff --git a/spec/features/projects/files/user_replaces_files_spec.rb b/spec/features/projects/files/user_replaces_files_spec.rb
index bfd612e4cc8..d50bc0a7d18 100644
--- a/spec/features/projects/files/user_replaces_files_spec.rb
+++ b/spec/features/projects/files/user_replaces_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User replaces files', :js do
diff --git a/spec/features/projects/files/user_searches_for_files_spec.rb b/spec/features/projects/files/user_searches_for_files_spec.rb
index a90e4918fb1..e82f54fbe50 100644
--- a/spec/features/projects/files/user_searches_for_files_spec.rb
+++ b/spec/features/projects/files/user_searches_for_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User searches for files' do
diff --git a/spec/features/projects/files/user_uploads_files_spec.rb b/spec/features/projects/files/user_uploads_files_spec.rb
index 25ff3fdf411..74b5d7c5041 100644
--- a/spec/features/projects/files/user_uploads_files_spec.rb
+++ b/spec/features/projects/files/user_uploads_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User uploads files' do
diff --git a/spec/features/projects/fork_spec.rb b/spec/features/projects/fork_spec.rb
index 7c71b4c52e0..2aed402652b 100644
--- a/spec/features/projects/fork_spec.rb
+++ b/spec/features/projects/fork_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project fork' do
@@ -50,7 +52,7 @@ describe 'Project fork' do
click_link('New merge request')
end
- expect(current_path).to have_content(/#{user.namespace.name}/i)
+ expect(current_path).to have_content(/#{user.namespace.path}/i)
end
it 'shows avatars when Gravatar is disabled' do
diff --git a/spec/features/projects/forks/fork_list_spec.rb b/spec/features/projects/forks/fork_list_spec.rb
index 2c41c61a660..2dbe3d90bad 100644
--- a/spec/features/projects/forks/fork_list_spec.rb
+++ b/spec/features/projects/forks/fork_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'listing forks of a project' do
diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb
index 1c988726ae6..ad39dec0a43 100644
--- a/spec/features/projects/gfm_autocomplete_load_spec.rb
+++ b/spec/features/projects/gfm_autocomplete_load_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'GFM autocomplete loading', :js do
diff --git a/spec/features/projects/graph_spec.rb b/spec/features/projects/graph_spec.rb
index e1bc18519a2..6082eb03374 100644
--- a/spec/features/projects/graph_spec.rb
+++ b/spec/features/projects/graph_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Graph', :js do
diff --git a/spec/features/projects/hook_logs/user_reads_log_spec.rb b/spec/features/projects/hook_logs/user_reads_log_spec.rb
index 086cd4b9f03..8c666f5d67a 100644
--- a/spec/features/projects/hook_logs/user_reads_log_spec.rb
+++ b/spec/features/projects/hook_logs/user_reads_log_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Hook logs' do
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index c71a778fc84..a1002f38936 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
# Integration test that exports a file using the Import/Export feature
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 8d2b1fc7e30..a12fc8b18ed 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Import/Export - project import integration test', :js do
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index a57edc394f9..18eadb7c4a3 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'issuable templates', :js do
diff --git a/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb b/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb
index a8612d77a5e..6c8f4b51ea0 100644
--- a/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb
+++ b/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'viewing an issue with cross project references' do
diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb
index b5e711997a0..44309a9c4bf 100644
--- a/spec/features/projects/jobs/permissions_spec.rb
+++ b/spec/features/projects/jobs/permissions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Jobs Permissions' do
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index fbe765d4c44..1b277e17b0c 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User browses a job', :js do
@@ -48,6 +50,20 @@ describe 'User browses a job', :js do
expect(page).not_to have_content(text_to_hide)
expect(page).to have_content(text_to_show)
end
+
+ it 'collapses the section header clicked' do
+ wait_for_requests
+ text_to_hide = "Cloning into '/nolith/ci-tests'"
+ text_to_show = 'Waiting for pod'
+
+ expect(page).to have_content(text_to_hide)
+ expect(page).to have_content(text_to_show)
+
+ first('.js-section-header.js-s-get-sources').click
+
+ expect(page).not_to have_content(text_to_hide)
+ expect(page).to have_content(text_to_show)
+ end
end
context 'when job trace contains sections' do
diff --git a/spec/features/projects/jobs/user_browses_jobs_spec.rb b/spec/features/projects/jobs/user_browses_jobs_spec.rb
index ebc20d15d67..44709cb1230 100644
--- a/spec/features/projects/jobs/user_browses_jobs_spec.rb
+++ b/spec/features/projects/jobs/user_browses_jobs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User browses jobs' do
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index f4ed89adc0f..8ed420300af 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -701,12 +701,12 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'shows manual action empty state', :js do
expect(page).to have_content(job.detailed_status(user).illustration[:title])
expect(page).to have_content('This job requires a manual action')
- expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments')
- expect(page).to have_link('Trigger this manual action')
+ expect(page).to have_content('This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes.')
+ expect(page).to have_button('Trigger this manual action')
end
it 'plays manual action and shows pending status', :js do
- click_link 'Trigger this manual action'
+ click_button 'Trigger this manual action'
wait_for_requests
expect(page).to have_content('This job has not started yet')
@@ -734,8 +734,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
wait_for_requests
expect(page).to have_content('This job requires a manual action')
- expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments')
- expect(page).to have_link('Trigger this manual action')
+ expect(page).to have_content('This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes.')
+ expect(page).to have_button('Trigger this manual action')
end
end
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 25417cf4955..55629376007 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Issue prioritization' do
diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb
index 49227eebf3d..1d0f9e73a1b 100644
--- a/spec/features/projects/labels/subscription_spec.rb
+++ b/spec/features/projects/labels/subscription_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Labels subscription' do
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index f32b155790f..3a37ee6623d 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Prioritize labels' do
diff --git a/spec/features/projects/labels/user_creates_labels_spec.rb b/spec/features/projects/labels/user_creates_labels_spec.rb
index c71b04fea09..257e064ae3d 100644
--- a/spec/features/projects/labels/user_creates_labels_spec.rb
+++ b/spec/features/projects/labels/user_creates_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User creates labels" do
diff --git a/spec/features/projects/labels/user_edits_labels_spec.rb b/spec/features/projects/labels/user_edits_labels_spec.rb
index 0708bbd40ce..da33ae3af3a 100644
--- a/spec/features/projects/labels/user_edits_labels_spec.rb
+++ b/spec/features/projects/labels/user_edits_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User edits labels" do
diff --git a/spec/features/projects/labels/user_removes_labels_spec.rb b/spec/features/projects/labels/user_removes_labels_spec.rb
index 7f49ddf560f..459adeeec30 100644
--- a/spec/features/projects/labels/user_removes_labels_spec.rb
+++ b/spec/features/projects/labels/user_removes_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User removes labels" do
diff --git a/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb b/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
index 0c0501f438a..35c84204910 100644
--- a/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'New project label breadcrumb' do
diff --git a/spec/features/projects/labels/user_sees_links_to_issuables.rb b/spec/features/projects/labels/user_sees_links_to_issuables_spec.rb
index c404fc8d66f..7a9b9e6eac2 100644
--- a/spec/features/projects/labels/user_sees_links_to_issuables.rb
+++ b/spec/features/projects/labels/user_sees_links_to_issuables_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Labels > User sees links to issuables' do
@@ -17,8 +19,10 @@ describe 'Projects > Labels > User sees links to issuables' do
let(:project) { create(:project, :public) }
it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
end
end
@@ -26,8 +30,10 @@ describe 'Projects > Labels > User sees links to issuables' do
let(:project) { create(:project, :public, issues_access_level: ProjectFeature::DISABLED) }
it 'shows links to MRs but not to issues' do
- expect(page).to have_link('view merge requests')
- expect(page).not_to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).not_to have_link('Issues')
+ end
end
end
@@ -35,8 +41,10 @@ describe 'Projects > Labels > User sees links to issuables' do
let(:project) { create(:project, :public, merge_requests_access_level: ProjectFeature::DISABLED) }
it 'shows links to issues but not to MRs' do
- expect(page).not_to have_link('view merge requests')
- expect(page).to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).not_to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
end
end
end
@@ -49,8 +57,10 @@ describe 'Projects > Labels > User sees links to issuables' do
let(:project) { create(:project, :public, namespace: group) }
it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
end
end
@@ -58,8 +68,10 @@ describe 'Projects > Labels > User sees links to issuables' do
let(:project) { create(:project, :public, namespace: group, issues_access_level: ProjectFeature::DISABLED) }
it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
end
end
@@ -67,8 +79,10 @@ describe 'Projects > Labels > User sees links to issuables' do
let(:project) { create(:project, :public, namespace: group, merge_requests_access_level: ProjectFeature::DISABLED) }
it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
end
end
end
diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
index b3ed725f602..096cf97551a 100644
--- a/spec/features/projects/members/anonymous_user_sees_members_spec.rb
+++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Anonymous user sees members' do
diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
index a645b917568..b6f6e2ca85f 100644
--- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Group member cannot leave group project' do
diff --git a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
index bb475ea95e5..1f4d555c6ae 100644
--- a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Group member cannot request access to his group project' do
diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb
index 0b2cd13b8ec..dd5fc82e058 100644
--- a/spec/features/projects/members/group_members_spec.rb
+++ b/spec/features/projects/members/group_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects members' do
diff --git a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
index ea3894c92bd..fb4238f0a1f 100644
--- a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
+++ b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Group requester cannot request access to project', :js do
diff --git a/spec/features/projects/members/groups_with_access_list_spec.rb b/spec/features/projects/members/groups_with_access_list_spec.rb
index c0b5d943e96..6e8d1a945e1 100644
--- a/spec/features/projects/members/groups_with_access_list_spec.rb
+++ b/spec/features/projects/members/groups_with_access_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Groups with access list', :js do
@@ -50,18 +52,18 @@ describe 'Projects > Members > Groups with access list', :js do
context 'search in existing members (yes, this filters the groups list as well)' do
it 'finds no results' do
- page.within '.member-search-form' do
+ page.within '.user-search-form' do
fill_in 'search', with: 'testing 123'
- find('.member-search-btn').click
+ find('.user-search-btn').click
end
expect(page).not_to have_selector('.group_member')
end
it 'finds results' do
- page.within '.member-search-form' do
+ page.within '.user-search-form' do
fill_in 'search', with: group.name
- find('.member-search-btn').click
+ find('.user-search-btn').click
end
expect(page).to have_selector('.group_member', count: 1)
diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb
index 7432c600c1e..e76637039c6 100644
--- a/spec/features/projects/members/invite_group_spec.rb
+++ b/spec/features/projects/members/invite_group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project > Members > Invite group', :js do
@@ -58,7 +60,7 @@ describe 'Project > Members > Invite group', :js do
end
end
- context 'for a project in a subgroup', :nested_groups do
+ context 'for a project in a subgroup' do
let!(:group_to_share_with) { create(:group) }
let(:root_group) { create(:group) }
let(:subgroup) { create(:group, parent: root_group) }
@@ -181,7 +183,7 @@ describe 'Project > Members > Invite group', :js do
group_to_share_with.add_maintainer(maintainer)
end
- it 'the groups dropdown does not show ancestors', :nested_groups do
+ it 'the groups dropdown does not show ancestors' do
visit project_settings_members_path(project)
click_on 'invite-group-tab'
diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb
index cf309492808..6d92c777033 100644
--- a/spec/features/projects/members/list_spec.rb
+++ b/spec/features/projects/members/list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project members list' do
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index 26de6fb33fd..501dd05300a 100644
--- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Maintainer adds member with expiration date', :js do
diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb
index adc8202cde7..17d6efbcaa5 100644
--- a/spec/features/projects/members/master_manages_access_requests_spec.rb
+++ b/spec/features/projects/members/master_manages_access_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Maintainer manages access requests' do
diff --git a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
index f612ad8d551..606444b36a2 100644
--- a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
+++ b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Member cannot request access to his project' do
diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb
index bd2ef9c07c4..fb1165838c7 100644
--- a/spec/features/projects/members/member_leaves_project_spec.rb
+++ b/spec/features/projects/members/member_leaves_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Member leaves project' do
diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
index 0aa005adb4d..781c584796d 100644
--- a/spec/features/projects/members/owner_cannot_leave_project_spec.rb
+++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Owner cannot leave project' do
diff --git a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
index eb1b720af05..2fb76da36ad 100644
--- a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
+++ b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Owner cannot request access to his project' do
diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb
index 220775b514d..12f485317d8 100644
--- a/spec/features/projects/members/sorting_spec.rb
+++ b/spec/features/projects/members/sorting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Sorting' do
@@ -16,7 +18,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end
it 'sorts by access level ascending' do
@@ -24,7 +26,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
end
it 'sorts by access level descending' do
@@ -32,7 +34,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
end
it 'sorts by last joined' do
@@ -40,7 +42,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
end
it 'sorts by oldest joined' do
@@ -48,7 +50,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
end
it 'sorts by name ascending' do
@@ -56,7 +58,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end
it 'sorts by name descending' do
@@ -64,7 +66,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
end
it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
@@ -72,7 +74,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
end
it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
@@ -80,7 +82,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
end
def visit_members_list(sort:)
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index f26941ab567..9f7327cd6e4 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > User requests access', :js do
diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb
index 69561b4d733..950af8b0ae0 100644
--- a/spec/features/projects/merge_request_button_spec.rb
+++ b/spec/features/projects/merge_request_button_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Merge Request button' do
diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb
index ff31092b910..5e94b2f721e 100644
--- a/spec/features/projects/milestones/milestone_spec.rb
+++ b/spec/features/projects/milestones/milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project milestone' do
diff --git a/spec/features/projects/milestones/milestones_sorting_spec.rb b/spec/features/projects/milestones/milestones_sorting_spec.rb
index dc711377e6e..77cf696fb7c 100644
--- a/spec/features/projects/milestones/milestones_sorting_spec.rb
+++ b/spec/features/projects/milestones/milestones_sorting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Milestones sorting', :js do
diff --git a/spec/features/projects/milestones/new_spec.rb b/spec/features/projects/milestones/new_spec.rb
index 0b5ab547dce..b1b74bed59d 100644
--- a/spec/features/projects/milestones/new_spec.rb
+++ b/spec/features/projects/milestones/new_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Creating a new project milestone', :js do
diff --git a/spec/features/projects/milestones/user_interacts_with_labels_spec.rb b/spec/features/projects/milestones/user_interacts_with_labels_spec.rb
index a6d58be7b13..0177871599a 100644
--- a/spec/features/projects/milestones/user_interacts_with_labels_spec.rb
+++ b/spec/features/projects/milestones/user_interacts_with_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User interacts with labels' do
diff --git a/spec/features/projects/network_graph_spec.rb b/spec/features/projects/network_graph_spec.rb
index 9f9a7787093..2f6a2e90ab9 100644
--- a/spec/features/projects/network_graph_spec.rb
+++ b/spec/features/projects/network_graph_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Network Graph', :js do
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 033e1afe866..22a0d268243 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -1,302 +1,328 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'New project' do
include Select2Helper
- let(:user) { create(:admin) }
+ context 'as a user' do
+ let(:user) { create(:user) }
- before do
- sign_in(user)
- end
+ before do
+ sign_in(user)
+ end
- it 'shows "New project" page', :js do
- visit new_project_path
+ it 'shows a message if multiple levels are restricted' do
+ Gitlab::CurrentSettings.update!(
+ restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::INTERNAL]
+ )
- expect(page).to have_content('Project name')
- expect(page).to have_content('Project URL')
- expect(page).to have_content('Project slug')
+ visit new_project_path
+
+ expect(page).to have_content 'Other visibility settings have been disabled by the administrator.'
+ end
- find('#import-project-tab').click
+ it 'shows a message if all levels are restricted' do
+ Gitlab::CurrentSettings.update!(
+ restricted_visibility_levels: Gitlab::VisibilityLevel.values
+ )
- expect(page).to have_link('GitHub')
- expect(page).to have_link('Bitbucket')
- expect(page).to have_link('GitLab.com')
- expect(page).to have_link('Google Code')
- expect(page).to have_button('Repo by URL')
- expect(page).to have_link('GitLab export')
+ visit new_project_path
+
+ expect(page).to have_content 'Visibility settings have been disabled by the administrator.'
+ end
end
- describe 'manifest import option' do
+ context 'as an admin' do
+ let(:user) { create(:admin) }
+
before do
+ sign_in(user)
+ end
+
+ it 'shows "New project" page', :js do
visit new_project_path
+ expect(page).to have_content('Project name')
+ expect(page).to have_content('Project URL')
+ expect(page).to have_content('Project slug')
+
find('#import-project-tab').click
- end
- context 'when using postgres', :postgresql do
- it { expect(page).to have_link('Manifest file') }
+ expect(page).to have_link('GitHub')
+ expect(page).to have_link('Bitbucket')
+ expect(page).to have_link('GitLab.com')
+ expect(page).to have_link('Google Code')
+ expect(page).to have_button('Repo by URL')
+ expect(page).to have_link('GitLab export')
end
- context 'when using mysql', :mysql do
- it { expect(page).not_to have_link('Manifest file') }
+ describe 'manifest import option' do
+ before do
+ visit new_project_path
+
+ find('#import-project-tab').click
+ end
+
+ it { expect(page).to have_link('Manifest file') }
end
- end
- context 'Visibility level selector', :js do
- Gitlab::VisibilityLevel.options.each do |key, level|
- it "sets selector to #{key}" do
- stub_application_setting(default_project_visibility: level)
+ context 'Visibility level selector', :js do
+ Gitlab::VisibilityLevel.options.each do |key, level|
+ it "sets selector to #{key}" do
+ stub_application_setting(default_project_visibility: level)
- visit new_project_path
- page.within('#blank-project-pane') do
- expect(find_field("project_visibility_level_#{level}")).to be_checked
+ visit new_project_path
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{level}")).to be_checked
+ end
end
- end
- it "saves visibility level #{level} on validation error" do
- visit new_project_path
+ it "saves visibility level #{level} on validation error" do
+ visit new_project_path
- choose(s_(key))
- click_button('Create project')
- page.within('#blank-project-pane') do
- expect(find_field("project_visibility_level_#{level}")).to be_checked
+ choose(s_(key))
+ click_button('Create project')
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{level}")).to be_checked
+ end
end
end
- end
- context 'when group visibility is private but default is internal' do
- before do
- stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
- end
+ context 'when group visibility is private but default is internal' do
+ before do
+ stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
+ end
- it 'has private selected' do
- group = create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
- visit new_project_path(namespace_id: group.id)
+ it 'has private selected' do
+ group = create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ visit new_project_path(namespace_id: group.id)
- page.within('#blank-project-pane') do
- expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ end
end
end
- end
- context 'when group visibility is public but user requests private' do
- before do
- stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
- end
+ context 'when group visibility is public but user requests private' do
+ before do
+ stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
+ end
- it 'has private selected' do
- group = create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
- visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
+ it 'has private selected' do
+ group = create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
- page.within('#blank-project-pane') do
- expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ end
end
end
end
- end
- context 'Readme selector' do
- it 'shows the initialize with Readme checkbox on "Blank project" tab' do
- visit new_project_path
+ context 'Readme selector' do
+ it 'shows the initialize with Readme checkbox on "Blank project" tab' do
+ visit new_project_path
- expect(page).to have_css('input#project_initialize_with_readme')
- expect(page).to have_content('Initialize repository with a README')
- end
+ expect(page).to have_css('input#project_initialize_with_readme')
+ expect(page).to have_content('Initialize repository with a README')
+ end
- it 'does not show the initialize with Readme checkbox on "Create from template" tab' do
- visit new_project_path
- find('#create-from-template-pane').click
- first('.choose-template').click
+ it 'does not show the initialize with Readme checkbox on "Create from template" tab' do
+ visit new_project_path
+ find('#create-from-template-pane').click
+ first('.choose-template').click
- page.within '.project-fields-form' do
- expect(page).not_to have_css('input#project_initialize_with_readme')
- expect(page).not_to have_content('Initialize repository with a README')
+ page.within '.project-fields-form' do
+ expect(page).not_to have_css('input#project_initialize_with_readme')
+ expect(page).not_to have_content('Initialize repository with a README')
+ end
end
- end
- it 'does not show the initialize with Readme checkbox on "Import project" tab' do
- visit new_project_path
- find('#import-project-tab').click
- first('.js-import-git-toggle-button').click
+ it 'does not show the initialize with Readme checkbox on "Import project" tab' do
+ visit new_project_path
+ find('#import-project-tab').click
+ first('.js-import-git-toggle-button').click
- page.within '.toggle-import-form' do
- expect(page).not_to have_css('input#project_initialize_with_readme')
- expect(page).not_to have_content('Initialize repository with a README')
+ page.within '.toggle-import-form' do
+ expect(page).not_to have_css('input#project_initialize_with_readme')
+ expect(page).not_to have_content('Initialize repository with a README')
+ end
end
end
- end
- context 'Namespace selector' do
- context 'with user namespace' do
- before do
- visit new_project_path
- end
+ context 'Namespace selector' do
+ context 'with user namespace' do
+ before do
+ visit new_project_path
+ end
- it 'selects the user namespace' do
- page.within('#blank-project-pane') do
- namespace = find('#project_namespace_id')
+ it 'selects the user namespace' do
+ page.within('#blank-project-pane') do
+ namespace = find('#project_namespace_id')
- expect(namespace.text).to eq user.username
+ expect(namespace.text).to eq user.username
+ end
end
end
- end
- context 'with group namespace' do
- let(:group) { create(:group, :private) }
+ context 'with group namespace' do
+ let(:group) { create(:group, :private) }
- before do
- group.add_owner(user)
- visit new_project_path(namespace_id: group.id)
- end
+ before do
+ group.add_owner(user)
+ visit new_project_path(namespace_id: group.id)
+ end
- it 'selects the group namespace' do
- page.within('#blank-project-pane') do
- namespace = find('#project_namespace_id option[selected]')
+ it 'selects the group namespace' do
+ page.within('#blank-project-pane') do
+ namespace = find('#project_namespace_id option[selected]')
- expect(namespace.text).to eq group.name
+ expect(namespace.text).to eq group.name
+ end
end
end
- end
- context 'with subgroup namespace' do
- let(:group) { create(:group) }
- let(:subgroup) { create(:group, parent: group) }
+ context 'with subgroup namespace' do
+ let(:group) { create(:group) }
+ let(:subgroup) { create(:group, parent: group) }
- before do
- group.add_maintainer(user)
- visit new_project_path(namespace_id: subgroup.id)
- end
+ before do
+ group.add_maintainer(user)
+ visit new_project_path(namespace_id: subgroup.id)
+ end
- it 'selects the group namespace' do
- page.within('#blank-project-pane') do
- namespace = find('#project_namespace_id option[selected]')
+ it 'selects the group namespace' do
+ page.within('#blank-project-pane') do
+ namespace = find('#project_namespace_id option[selected]')
- expect(namespace.text).to eq subgroup.full_path
+ expect(namespace.text).to eq subgroup.full_path
+ end
end
end
- end
- context 'when changing namespaces dynamically', :js do
- let(:public_group) { create(:group, :public) }
- let(:internal_group) { create(:group, :internal) }
- let(:private_group) { create(:group, :private) }
+ context 'when changing namespaces dynamically', :js do
+ let(:public_group) { create(:group, :public) }
+ let(:internal_group) { create(:group, :internal) }
+ let(:private_group) { create(:group, :private) }
- before do
- public_group.add_owner(user)
- internal_group.add_owner(user)
- private_group.add_owner(user)
- visit new_project_path(namespace_id: public_group.id)
- end
+ before do
+ public_group.add_owner(user)
+ internal_group.add_owner(user)
+ private_group.add_owner(user)
+ visit new_project_path(namespace_id: public_group.id)
+ end
- it 'enables the correct visibility options' do
- select2(user.namespace_id, from: '#project_namespace_id')
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled
-
- select2(public_group.id, from: '#project_namespace_id')
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled
-
- select2(internal_group.id, from: '#project_namespace_id')
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled
-
- select2(private_group.id, from: '#project_namespace_id')
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled
+ it 'enables the correct visibility options' do
+ select2(user.namespace_id, from: '#project_namespace_id')
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled
+
+ select2(public_group.id, from: '#project_namespace_id')
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled
+
+ select2(internal_group.id, from: '#project_namespace_id')
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled
+
+ select2(private_group.id, from: '#project_namespace_id')
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled
+ end
end
end
- end
-
- context 'Import project options', :js do
- before do
- visit new_project_path
- find('#import-project-tab').click
- end
- context 'from git repository url, "Repo by URL"' do
+ context 'Import project options', :js do
before do
- first('.js-import-git-toggle-button').click
+ visit new_project_path
+ find('#import-project-tab').click
end
- it 'does not autocomplete sensitive git repo URL' do
- autocomplete = find('#project_import_url')['autocomplete']
+ context 'from git repository url, "Repo by URL"' do
+ before do
+ first('.js-import-git-toggle-button').click
+ end
+
+ it 'does not autocomplete sensitive git repo URL' do
+ autocomplete = find('#project_import_url')['autocomplete']
- expect(autocomplete).to eq('off')
- end
+ expect(autocomplete).to eq('off')
+ end
- it 'shows import instructions' do
- git_import_instructions = first('.js-toggle-content')
+ it 'shows import instructions' do
+ git_import_instructions = first('.js-toggle-content')
- expect(git_import_instructions).to be_visible
- expect(git_import_instructions).to have_content 'Git repository URL'
- end
+ expect(git_import_instructions).to be_visible
+ expect(git_import_instructions).to have_content 'Git repository URL'
+ end
- it 'keeps "Import project" tab open after form validation error' do
- collision_project = create(:project, name: 'test-name-collision', namespace: user.namespace)
+ it 'keeps "Import project" tab open after form validation error' do
+ collision_project = create(:project, name: 'test-name-collision', namespace: user.namespace)
- fill_in 'project_import_url', with: collision_project.http_url_to_repo
- fill_in 'project_name', with: collision_project.name
+ fill_in 'project_import_url', with: collision_project.http_url_to_repo
+ fill_in 'project_name', with: collision_project.name
- click_on 'Create project'
+ click_on 'Create project'
- expect(page).to have_css('#import-project-pane.active')
- expect(page).not_to have_css('.toggle-import-form.hide')
+ expect(page).to have_css('#import-project-pane.active')
+ expect(page).not_to have_css('.toggle-import-form.hide')
+ end
end
- end
- context 'from GitHub' do
- before do
- first('.js-import-github').click
- end
+ context 'from GitHub' do
+ before do
+ first('.js-import-github').click
+ end
- it 'shows import instructions' do
- expect(page).to have_content('Import repositories from GitHub')
- expect(current_path).to eq new_import_github_path
+ it 'shows import instructions' do
+ expect(page).to have_content('Authenticate with GitHub')
+ expect(current_path).to eq new_import_github_path
+ end
end
- end
- context 'from Google Code' do
- before do
- first('.import_google_code').click
- end
+ context 'from Google Code' do
+ before do
+ first('.import_google_code').click
+ end
- it 'shows import instructions' do
- expect(page).to have_content('Import projects from Google Code')
- expect(current_path).to eq new_import_google_code_path
+ it 'shows import instructions' do
+ expect(page).to have_content('Import projects from Google Code')
+ expect(current_path).to eq new_import_google_code_path
+ end
end
- end
- context 'from manifest file', :postgresql do
- before do
- first('.import_manifest').click
- end
+ context 'from manifest file' do
+ before do
+ first('.import_manifest').click
+ end
- it 'shows import instructions' do
- expect(page).to have_content('Manifest file import')
- expect(current_path).to eq new_import_manifest_path
+ it 'shows import instructions' do
+ expect(page).to have_content('Manifest file import')
+ expect(current_path).to eq new_import_manifest_path
+ end
end
end
- end
- context 'Namespace selector' do
- context 'with group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do
- let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
+ context 'Namespace selector' do
+ context 'with group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do
+ let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
- before do
- group.add_developer(user)
- visit new_project_path(namespace_id: group.id)
- end
+ before do
+ group.add_developer(user)
+ visit new_project_path(namespace_id: group.id)
+ end
- it 'selects the group namespace' do
- page.within('#blank-project-pane') do
- namespace = find('#project_namespace_id option[selected]')
+ it 'selects the group namespace' do
+ page.within('#blank-project-pane') do
+ namespace = find('#project_namespace_id option[selected]')
- expect(namespace.text).to eq group.full_path
+ expect(namespace.text).to eq group.full_path
+ end
end
end
end
diff --git a/spec/features/projects/pages_lets_encrypt_spec.rb b/spec/features/projects/pages_lets_encrypt_spec.rb
index a5f8702302c..8b5964b2eee 100644
--- a/spec/features/projects/pages_lets_encrypt_spec.rb
+++ b/spec/features/projects/pages_lets_encrypt_spec.rb
@@ -75,12 +75,10 @@ describe "Pages with Let's Encrypt", :https_pages_enabled do
end
shared_examples 'user sees private keys only for user provided certificate' do
- before do
- visit edit_project_pages_domain_path(project, domain)
- end
-
shared_examples 'user do not see private key' do
it 'user do not see private key' do
+ visit edit_project_pages_domain_path(project, domain)
+
expect(find_field('Key (PEM)', visible: :all, disabled: :all).value).to be_blank
end
end
@@ -101,6 +99,8 @@ describe "Pages with Let's Encrypt", :https_pages_enabled do
let(:domain) { create(:pages_domain, project: project) }
it 'user sees private key' do
+ visit edit_project_pages_domain_path(project, domain)
+
expect(find_field('Key (PEM)').value).not_to be_blank
end
end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 24041a51383..c21b1e36f9a 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Pipeline Schedules', :js do
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 885d5f85989..4fb72eb8737 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Pipelines', :js do
diff --git a/spec/features/projects/raw/user_interacts_with_raw_endpoint_spec.rb b/spec/features/projects/raw/user_interacts_with_raw_endpoint_spec.rb
new file mode 100644
index 00000000000..6d587053b4f
--- /dev/null
+++ b/spec/features/projects/raw/user_interacts_with_raw_endpoint_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Projects > Raw > User interacts with raw endpoint' do
+ include RepoHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, :public) }
+ let(:file_path) { 'master/README.md' }
+
+ before do
+ stub_application_setting(raw_blob_request_limit: 3)
+ project.add_developer(user)
+ create_file_in_repo(project, 'master', 'master', 'README.md', 'readme content')
+
+ sign_in(user)
+ end
+
+ context 'when user access a raw file' do
+ it 'renders the page successfully' do
+ visit project_raw_url(project, file_path)
+
+ expect(source).to eq('') # Body is filled in by gitlab-workhorse
+ end
+ end
+
+ context 'when user goes over the rate requests limit' do
+ it 'returns too many requests' do
+ 4.times do
+ visit project_raw_url(project, file_path)
+ end
+
+ expect(source).to have_content('You are being redirected')
+ click_link('redirected')
+ expect(page).to have_content('You cannot access the raw file. Please wait a minute.')
+ end
+ end
+end
diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb
index 317ffb6a2ff..a9b8ff9dc4d 100644
--- a/spec/features/projects/releases/user_views_releases_spec.rb
+++ b/spec/features/projects/releases/user_views_releases_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views releases', :js do
@@ -16,6 +18,7 @@ describe 'User views releases', :js do
expect(page).to have_content(release.name)
expect(page).to have_content(release.tag)
+ expect(page).not_to have_content('Upcoming Release')
end
context 'when there is a link as an asset' do
@@ -43,4 +46,15 @@ describe 'User views releases', :js do
end
end
end
+
+ context 'with an upcoming release' do
+ let(:tomorrow) { Time.zone.now + 1.day }
+ let!(:release) { create(:release, project: project, released_at: tomorrow ) }
+
+ it 'sees the upcoming tag' do
+ visit project_releases_path(project)
+
+ expect(page).to have_content('Upcoming Release')
+ end
+ end
end
diff --git a/spec/features/projects/remote_mirror_spec.rb b/spec/features/projects/remote_mirror_spec.rb
index 33e9b73efe8..d357aabead7 100644
--- a/spec/features/projects/remote_mirror_spec.rb
+++ b/spec/features/projects/remote_mirror_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project remote mirror', :feature do
diff --git a/spec/features/projects/serverless/functions_spec.rb b/spec/features/projects/serverless/functions_spec.rb
index 9865dbbfb3c..e82e5b81021 100644
--- a/spec/features/projects/serverless/functions_spec.rb
+++ b/spec/features/projects/serverless/functions_spec.rb
@@ -39,17 +39,19 @@ describe 'Functions', :js do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:service) { cluster.platform_kubernetes }
let(:project) { cluster.project }
- let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
+ let(:environment) { create(:environment, project: project) }
+ let!(:deployment) { create(:deployment, :success, cluster: cluster, environment: environment) }
+ let(:knative_services_finder) { environment.knative_services_finder }
let(:namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
- cluster_project: cluster.cluster_project,
- project: cluster.cluster_project.project)
+ project: cluster.cluster_project.project,
+ environment: environment)
end
before do
- allow_any_instance_of(Clusters::Cluster)
- .to receive(:knative_services_finder)
+ allow(Clusters::KnativeServicesFinder)
+ .to receive(:new)
.and_return(knative_services_finder)
synchronous_reactive_cache(knative_services_finder)
stub_kubeclient_knative_services(stub_get_services_options)
diff --git a/spec/features/projects/services/disable_triggers_spec.rb b/spec/features/projects/services/disable_triggers_spec.rb
index 1a13fe03a67..2785f74bee2 100644
--- a/spec/features/projects/services/disable_triggers_spec.rb
+++ b/spec/features/projects/services/disable_triggers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Disable individual triggers' do
diff --git a/spec/features/projects/services/user_activates_asana_spec.rb b/spec/features/projects/services/user_activates_asana_spec.rb
index c44e07dd3b4..b07c77da554 100644
--- a/spec/features/projects/services/user_activates_asana_spec.rb
+++ b/spec/features/projects/services/user_activates_asana_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Asana' do
diff --git a/spec/features/projects/services/user_activates_assembla_spec.rb b/spec/features/projects/services/user_activates_assembla_spec.rb
index 9c3884a7c74..56f7beb8f4b 100644
--- a/spec/features/projects/services/user_activates_assembla_spec.rb
+++ b/spec/features/projects/services/user_activates_assembla_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Assembla' do
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 19573565265..c694eeb1cc2 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Atlassian Bamboo CI' do
diff --git a/spec/features/projects/services/user_activates_emails_on_push_spec.rb b/spec/features/projects/services/user_activates_emails_on_push_spec.rb
index cc55f7b2060..2015b658295 100644
--- a/spec/features/projects/services/user_activates_emails_on_push_spec.rb
+++ b/spec/features/projects/services/user_activates_emails_on_push_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Emails on push' do
diff --git a/spec/features/projects/services/user_activates_flowdock_spec.rb b/spec/features/projects/services/user_activates_flowdock_spec.rb
index f981b7e9da9..fc8e75daa0d 100644
--- a/spec/features/projects/services/user_activates_flowdock_spec.rb
+++ b/spec/features/projects/services/user_activates_flowdock_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Flowdock' do
diff --git a/spec/features/projects/services/user_activates_irker_spec.rb b/spec/features/projects/services/user_activates_irker_spec.rb
index 4c8e321b411..898e16ce835 100644
--- a/spec/features/projects/services/user_activates_irker_spec.rb
+++ b/spec/features/projects/services/user_activates_irker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Irker (IRC gateway)' do
diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
index 74b9a2b20cd..5f3bb794b48 100644
--- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb
+++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates issue tracker', :js do
@@ -59,7 +61,7 @@ describe 'User activates issue tracker', :js do
context 'when the connection test fails' do
it 'activates the service' do
- stub_request(:head, url).to_raise(HTTParty::Error)
+ stub_request(:head, url).to_raise(Gitlab::HTTP::Error)
click_link(tracker)
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 c50fd93e4cb..9842141285a 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates JetBrains TeamCity CI' do
diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb
index c52f38e2806..7847b7d5177 100644
--- a/spec/features/projects/services/user_activates_jira_spec.rb
+++ b/spec/features/projects/services/user_activates_jira_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Jira', :js do
diff --git a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
index 70f3a812ee9..f8179979018 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Set up Mattermost slash commands', :js do
diff --git a/spec/features/projects/services/user_activates_packagist_spec.rb b/spec/features/projects/services/user_activates_packagist_spec.rb
index 756e9b33c07..85bd15adbe5 100644
--- a/spec/features/projects/services/user_activates_packagist_spec.rb
+++ b/spec/features/projects/services/user_activates_packagist_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Packagist' do
diff --git a/spec/features/projects/services/user_activates_pivotaltracker_spec.rb b/spec/features/projects/services/user_activates_pivotaltracker_spec.rb
index 1d6b19e0b0c..67ff99c0295 100644
--- a/spec/features/projects/services/user_activates_pivotaltracker_spec.rb
+++ b/spec/features/projects/services/user_activates_pivotaltracker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates PivotalTracker' do
diff --git a/spec/features/projects/services/user_activates_prometheus_spec.rb b/spec/features/projects/services/user_activates_prometheus_spec.rb
index 61361c8a2e3..a83d3e2e8be 100644
--- a/spec/features/projects/services/user_activates_prometheus_spec.rb
+++ b/spec/features/projects/services/user_activates_prometheus_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Prometheus' do
diff --git a/spec/features/projects/services/user_activates_pushover_spec.rb b/spec/features/projects/services/user_activates_pushover_spec.rb
index 24612ee1457..34e1cf33f36 100644
--- a/spec/features/projects/services/user_activates_pushover_spec.rb
+++ b/spec/features/projects/services/user_activates_pushover_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Pushover' do
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 24b5d5259db..f23b1d3102a 100644
--- a/spec/features/projects/services/user_activates_slack_notifications_spec.rb
+++ b/spec/features/projects/services/user_activates_slack_notifications_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Slack notifications' do
diff --git a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
index 08cfddf7993..752ef8d592d 100644
--- a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
+++ b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Slack slash commands' do
diff --git a/spec/features/projects/services/user_activates_youtrack_spec.rb b/spec/features/projects/services/user_activates_youtrack_spec.rb
index bb6a030c1cf..8fdeddfdfb4 100644
--- a/spec/features/projects/services/user_activates_youtrack_spec.rb
+++ b/spec/features/projects/services/user_activates_youtrack_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates issue tracker', :js do
@@ -46,7 +48,7 @@ describe 'User activates issue tracker', :js do
context 'when the connection test fails' do
it 'activates the service' do
- stub_request(:head, url).to_raise(HTTParty::Error)
+ stub_request(:head, url).to_raise(Gitlab::HTTP::Error)
click_link(tracker)
fill_form
diff --git a/spec/features/projects/services/user_views_services_spec.rb b/spec/features/projects/services/user_views_services_spec.rb
index e9c8cf0fe34..d9358a40602 100644
--- a/spec/features/projects/services/user_views_services_spec.rb
+++ b/spec/features/projects/services/user_views_services_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views services' do
diff --git a/spec/features/projects/settings/forked_project_settings_spec.rb b/spec/features/projects/settings/forked_project_settings_spec.rb
index df33d215602..a2c6dd8e288 100644
--- a/spec/features/projects/settings/forked_project_settings_spec.rb
+++ b/spec/features/projects/settings/forked_project_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > For a forked project', :js do
diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb
index 016ccf63f58..26ea4ec5944 100644
--- a/spec/features/projects/settings/integration_settings_spec.rb
+++ b/spec/features/projects/settings/integration_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > Integration settings' do
diff --git a/spec/features/projects/settings/lfs_settings_spec.rb b/spec/features/projects/settings/lfs_settings_spec.rb
index befb306b48d..56606df5a78 100644
--- a/spec/features/projects/settings/lfs_settings_spec.rb
+++ b/spec/features/projects/settings/lfs_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Projects > Settings > LFS settings' do
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index bf0c0de89b2..23358d5cd67 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Projects > Settings > Pipelines settings" do
@@ -126,7 +128,7 @@ describe "Projects > Settings > Pipelines settings" do
end
end
- context 'when auto devops is turned on group parent level', :nested_groups do
+ context 'when auto devops is turned on group parent level' do
before do
group = create(:group, parent: create(:group, :auto_devops_enabled))
project.update!(namespace: group)
diff --git a/spec/features/projects/settings/project_badges_spec.rb b/spec/features/projects/settings/project_badges_spec.rb
index 42b5547d43b..5791e30a495 100644
--- a/spec/features/projects/settings/project_badges_spec.rb
+++ b/spec/features/projects/settings/project_badges_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Badges' do
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 8c7bc192c50..1294c8822b6 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > Repository settings' do
@@ -112,11 +114,17 @@ describe 'Projects > Settings > Repository settings' do
it 'add a new deploy token' do
fill_in 'deploy_token_name', with: 'new_deploy_key'
fill_in 'deploy_token_expires_at', with: (Date.today + 1.month).to_s
+ fill_in 'deploy_token_username', with: 'deployer'
check 'deploy_token_read_repository'
check 'deploy_token_read_registry'
click_button 'Create deploy token'
expect(page).to have_content('Your new project deploy token has been created')
+
+ within('.created-deploy-token-container') do
+ expect(page).to have_selector("input[name='deploy-token-user'][value='deployer']")
+ expect(page).to have_selector("input[name='deploy-token'][readonly='readonly']")
+ end
end
end
@@ -242,11 +250,11 @@ describe 'Projects > Settings > Repository settings' do
visit project_settings_repository_path(project)
- mirror = find('.qa-mirrored-repository-row')
+ mirror = find('.rspec-mirrored-repository-row')
- expect(mirror).to have_selector('.qa-delete-mirror')
- expect(mirror).to have_selector('.qa-disabled-mirror-badge')
- expect(mirror).not_to have_selector('.qa-update-now-button')
+ expect(mirror).to have_selector('.rspec-delete-mirror')
+ expect(mirror).to have_selector('.rspec-disabled-mirror-badge')
+ expect(mirror).not_to have_selector('.rspec-update-now-button')
end
end
end
diff --git a/spec/features/projects/settings/user_archives_project_spec.rb b/spec/features/projects/settings/user_archives_project_spec.rb
index 5008eab4d39..7667fad7b03 100644
--- a/spec/features/projects/settings/user_archives_project_spec.rb
+++ b/spec/features/projects/settings/user_archives_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User archives a project' do
diff --git a/spec/features/projects/settings/user_changes_avatar_spec.rb b/spec/features/projects/settings/user_changes_avatar_spec.rb
index 64335163016..67789b869da 100644
--- a/spec/features/projects/settings/user_changes_avatar_spec.rb
+++ b/spec/features/projects/settings/user_changes_avatar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User changes avatar' do
diff --git a/spec/features/projects/settings/user_changes_default_branch_spec.rb b/spec/features/projects/settings/user_changes_default_branch_spec.rb
index 7dc18601f50..411fc0c7e07 100644
--- a/spec/features/projects/settings/user_changes_default_branch_spec.rb
+++ b/spec/features/projects/settings/user_changes_default_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User changes default branch' do
diff --git a/spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb b/spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb
index ecfb49b9efe..cd9299150b2 100644
--- a/spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb
+++ b/spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User interacts with deploy keys", :js do
diff --git a/spec/features/projects/settings/user_manages_group_links_spec.rb b/spec/features/projects/settings/user_manages_group_links_spec.rb
index e5a58c44e41..7df0bbb9d02 100644
--- a/spec/features/projects/settings/user_manages_group_links_spec.rb
+++ b/spec/features/projects/settings/user_manages_group_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User manages group links' do
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 0739726f52c..9f09c5c4501 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
@@ -94,7 +94,7 @@ describe 'Projects > Settings > User manages merge request settings' do
it 'when unchecked sets :printing_merge_request_link_enabled to false' do
uncheck('project_printing_merge_request_link_enabled')
within('.merge-request-settings-form') do
- find('.qa-save-merge-request-changes')
+ find('.rspec-save-merge-request-changes')
click_on('Save changes')
end
diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb
index b8ca11d53f0..6d94388a6e2 100644
--- a/spec/features/projects/settings/user_manages_project_members_spec.rb
+++ b/spec/features/projects/settings/user_manages_project_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User manages project members' do
diff --git a/spec/features/projects/settings/user_renames_a_project_spec.rb b/spec/features/projects/settings/user_renames_a_project_spec.rb
index d3979b79910..d2daf8b922d 100644
--- a/spec/features/projects/settings/user_renames_a_project_spec.rb
+++ b/spec/features/projects/settings/user_renames_a_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User renames a project' do
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 069704a1305..9c77a08718e 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Repository Settings > User sees revoke deploy token modal', :js do
diff --git a/spec/features/projects/settings/user_tags_project_spec.rb b/spec/features/projects/settings/user_tags_project_spec.rb
index e3f06c042b9..a919dd0e4af 100644
--- a/spec/features/projects/settings/user_tags_project_spec.rb
+++ b/spec/features/projects/settings/user_tags_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User tags a project' do
diff --git a/spec/features/projects/settings/user_transfers_a_project_spec.rb b/spec/features/projects/settings/user_transfers_a_project_spec.rb
index 2fdbc04fa62..8989eac77b5 100644
--- a/spec/features/projects/settings/user_transfers_a_project_spec.rb
+++ b/spec/features/projects/settings/user_transfers_a_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User transfers a project', :js do
@@ -68,7 +70,7 @@ describe 'Projects > Settings > User transfers a project', :js do
end
end
- context 'when nested groups are available', :nested_groups do
+ context 'when nested groups are available' do
it 'allows transferring a project to a subgroup' do
subgroup = create(:group, parent: group)
diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb
index 1fbc108697f..0e757e647a0 100644
--- a/spec/features/projects/settings/visibility_settings_spec.rb
+++ b/spec/features/projects/settings/visibility_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > Visibility settings', :js do
@@ -57,6 +59,12 @@ describe 'Projects > Settings > Visibility settings', :js do
end
end
end
+
+ context 'disable email notifications' do
+ it 'is visible' do
+ expect(page).to have_selector('.js-emails-disabled', visible: true)
+ end
+ end
end
context 'as maintainer' do
@@ -74,5 +82,11 @@ describe 'Projects > Settings > Visibility settings', :js do
expect(visibility_select_container).to have_selector 'select[name="project[visibility_level]"]:disabled'
expect(visibility_select_container).to have_content 'The project can be accessed by anyone, regardless of authentication.'
end
+
+ context 'disable email notifications' do
+ it 'is not available' do
+ expect(page).not_to have_selector('.js-emails-disabled', visible: true)
+ end
+ end
end
end
diff --git a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
index 8ba91fe7fd7..6f176c260a2 100644
--- a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
+++ b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Projects > Show > Developer views empty project instructions' do
diff --git a/spec/features/projects/show/download_buttons_spec.rb b/spec/features/projects/show/download_buttons_spec.rb
index fee5f8001b0..5e7453bcdb7 100644
--- a/spec/features/projects/show/download_buttons_spec.rb
+++ b/spec/features/projects/show/download_buttons_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > Download buttons' do
diff --git a/spec/features/projects/show/no_password_spec.rb b/spec/features/projects/show/no_password_spec.rb
index 8259d482fd9..0048b1bf017 100644
--- a/spec/features/projects/show/no_password_spec.rb
+++ b/spec/features/projects/show/no_password_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'No Password Alert' do
diff --git a/spec/features/projects/show/redirects_spec.rb b/spec/features/projects/show/redirects_spec.rb
index 8d41c547d77..1b579ab0121 100644
--- a/spec/features/projects/show/redirects_spec.rb
+++ b/spec/features/projects/show/redirects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > Redirects' do
diff --git a/spec/features/projects/show/rss_spec.rb b/spec/features/projects/show/rss_spec.rb
index 4d9135b9677..4fe1fde5bdd 100644
--- a/spec/features/projects/show/rss_spec.rb
+++ b/spec/features/projects/show/rss_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > RSS' do
diff --git a/spec/features/projects/show/user_interacts_with_stars_spec.rb b/spec/features/projects/show/user_interacts_with_stars_spec.rb
index ba28c0e1b8a..e4cd8294f7a 100644
--- a/spec/features/projects/show/user_interacts_with_stars_spec.rb
+++ b/spec/features/projects/show/user_interacts_with_stars_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User interacts with project stars' do
diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb
index e9dd1dc0f66..851a09cf28a 100644
--- a/spec/features/projects/show/user_manages_notifications_spec.rb
+++ b/spec/features/projects/show/user_manages_notifications_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User manages notifications', :js do
@@ -63,4 +65,12 @@ describe 'Projects > Show > User manages notifications', :js do
end
end
end
+
+ context 'when project emails are disabled' do
+ let(:project) { create(:project, :public, :repository, emails_disabled: true) }
+
+ it 'is disabled' do
+ expect(page).to have_selector('.notifications-btn.disabled', visible: true)
+ end
+ end
end
diff --git a/spec/features/projects/show/user_sees_collaboration_links_spec.rb b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
index 46586b891e7..bbb3a066ed5 100644
--- a/spec/features/projects/show/user_sees_collaboration_links_spec.rb
+++ b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > Collaboration links' do
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 d9d57298929..bdd0ab688f0 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User sees a deletion failure message' do
diff --git a/spec/features/projects/show/user_sees_git_instructions_spec.rb b/spec/features/projects/show/user_sees_git_instructions_spec.rb
index 0d59ef4a727..dde9490a5e1 100644
--- a/spec/features/projects/show/user_sees_git_instructions_spec.rb
+++ b/spec/features/projects/show/user_sees_git_instructions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User sees Git instructions' do
diff --git a/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
index 89ce4b50781..fdc238d55cf 100644
--- a/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
+++ b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User sees last commit CI status' do
@@ -16,7 +18,7 @@ describe 'Projects > Show > User sees last commit CI status' do
page.within '.blob-commit-info' do
expect(page).to have_content(project.commit.sha[0..6])
- expect(page).to have_link('Commit: skipped')
+ expect(page).to have_link('Pipeline: skipped')
end
end
end
diff --git a/spec/features/projects/show/user_sees_readme_spec.rb b/spec/features/projects/show/user_sees_readme_spec.rb
index d80606c1c23..98906de4620 100644
--- a/spec/features/projects/show/user_sees_readme_spec.rb
+++ b/spec/features/projects/show/user_sees_readme_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User sees README' do
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 58bd20d7551..c136d7607fd 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User sees setup shortcut buttons' do
diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb
index 6d8a72dd6a3..430883fdf29 100644
--- a/spec/features/projects/snippets/create_snippet_spec.rb
+++ b/spec/features/projects/snippets/create_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Projects > Snippets > Create Snippet', :js do
diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb
index f3dc13fb52f..e448309356d 100644
--- a/spec/features/projects/snippets/show_spec.rb
+++ b/spec/features/projects/snippets/show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Snippets > Project snippet', :js 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 4e1e2f330ec..239d19d35d1 100644
--- a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
+++ b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Snippets > User comments on a snippet', :js do
diff --git a/spec/features/projects/snippets/user_deletes_snippet_spec.rb b/spec/features/projects/snippets/user_deletes_snippet_spec.rb
index 2bd8bb9d551..1b56d7bf26d 100644
--- a/spec/features/projects/snippets/user_deletes_snippet_spec.rb
+++ b/spec/features/projects/snippets/user_deletes_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Snippets > User deletes a snippet' do
diff --git a/spec/features/projects/snippets/user_updates_snippet_spec.rb b/spec/features/projects/snippets/user_updates_snippet_spec.rb
index 33f77d55f89..c7ff4f89fd6 100644
--- a/spec/features/projects/snippets/user_updates_snippet_spec.rb
+++ b/spec/features/projects/snippets/user_updates_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Snippets > User updates a snippet' do
diff --git a/spec/features/projects/snippets/user_views_snippets_spec.rb b/spec/features/projects/snippets/user_views_snippets_spec.rb
index 1243db9d9f7..59869244b4a 100644
--- a/spec/features/projects/snippets/user_views_snippets_spec.rb
+++ b/spec/features/projects/snippets/user_views_snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Snippets > User views snippets' do
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
index 50e7e934cf6..d6faec2078d 100644
--- a/spec/features/projects/sub_group_issuables_spec.rb
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'Subgroup Issuables', :js, :nested_groups do
+describe 'Subgroup Issuables', :js do
let!(:group) { create(:group, name: 'group') }
let!(:subgroup) { create(:group, parent: group, name: 'subgroup') }
let!(:project) { create(:project, namespace: subgroup, name: 'project') }
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
index 4c8ec53836a..76b2704ae49 100644
--- a/spec/features/projects/tags/download_buttons_spec.rb
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Download buttons in tags page' do
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
index 2cb2a23b7be..99285011405 100644
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ b/spec/features/projects/tree/create_directory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Multi-file editor new directory', :js do
@@ -30,10 +32,12 @@ describe 'Multi-file editor new directory', :js do
click_button('Create directory')
end
+ expect(page).to have_content('folder name')
+
first('.ide-tree-actions button').click
- page.within('.modal-dialog') do
- find('.form-control').set('file name')
+ page.within('.modal') do
+ find('.form-control').set('folder name/file name')
click_button('Create file')
end
@@ -42,12 +46,19 @@ describe 'Multi-file editor new directory', :js do
find('.js-ide-commit-mode').click
- find('.multi-file-commit-list-item').hover
click_button 'Stage'
fill_in('commit-message', with: 'commit message ide')
- click_button('Commit')
+ find(:css, ".js-ide-commit-new-mr input").set(false)
+
+ wait_for_requests
+
+ page.within '.multi-file-commit-form' do
+ click_button('Commit')
+
+ wait_for_requests
+ end
find('.js-ide-edit-mode').click
diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb
index 9f5524da8e9..780575a5975 100644
--- a/spec/features/projects/tree/create_file_spec.rb
+++ b/spec/features/projects/tree/create_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Multi-file editor new file', :js do
@@ -34,12 +36,19 @@ describe 'Multi-file editor new file', :js do
find('.js-ide-commit-mode').click
- find('.multi-file-commit-list-item').hover
click_button 'Stage'
fill_in('commit-message', with: 'commit message ide')
- click_button('Commit')
+ find(:css, ".js-ide-commit-new-mr input").set(false)
+
+ page.within '.multi-file-commit-form' do
+ click_button('Commit')
+
+ wait_for_requests
+ end
+
+ find('.js-ide-edit-mode').click
expect(page).to have_content('file name')
end
diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb
index 022167d9c5f..4300574210f 100644
--- a/spec/features/projects/tree/rss_spec.rb
+++ b/spec/features/projects/tree/rss_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Tree RSS' do
diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb
index 3ccea2db705..ca616be341d 100644
--- a/spec/features/projects/tree/tree_show_spec.rb
+++ b/spec/features/projects/tree/tree_show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects tree', :js do
diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb
index e5dd2f40fdf..38c29263b1e 100644
--- a/spec/features/projects/tree/upload_file_spec.rb
+++ b/spec/features/projects/tree/upload_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Multi-file editor upload file', :js do
diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb
index c0932539131..361367f1a3d 100644
--- a/spec/features/projects/user_creates_project_spec.rb
+++ b/spec/features/projects/user_creates_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User creates a project', :js do
@@ -26,7 +28,7 @@ describe 'User creates a project', :js do
expect(page).to have_content(project.url_to_repo)
end
- context 'in a subgroup they do not own', :nested_groups do
+ context 'in a subgroup they do not own' do
let(:parent) { create(:group) }
let!(:subgroup) { create(:group, parent: parent) }
diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb
index 383e8824b7b..4226cdcc759 100644
--- a/spec/features/projects/user_sees_sidebar_spec.rb
+++ b/spec/features/projects/user_sees_sidebar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > User sees sidebar' do
diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb
index 64f9a4fcd39..ff24730acef 100644
--- a/spec/features/projects/user_uses_shortcuts_spec.rb
+++ b/spec/features/projects/user_uses_shortcuts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User uses shortcuts', :js do
diff --git a/spec/features/projects/user_views_empty_project_spec.rb b/spec/features/projects/user_views_empty_project_spec.rb
index b7c0834d33a..cb6b63d4dd5 100644
--- a/spec/features/projects/user_views_empty_project_spec.rb
+++ b/spec/features/projects/user_views_empty_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views an empty project' do
diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb
index 7bfcd46713e..beb32104809 100644
--- a/spec/features/projects/view_on_env_spec.rb
+++ b/spec/features/projects/view_on_env_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'View on environment', :js do
diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb
index 49058d1372a..5c6b04a7141 100644
--- a/spec/features/projects/wiki/markdown_preview_spec.rb
+++ b/spec/features/projects/wiki/markdown_preview_spec.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Wiki > User previews markdown changes', :js do
- let(:user) { create(:user) }
+ set(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'home', content: '[some link](other-page)' }) }
let(:wiki_content) do
@@ -18,23 +20,12 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
project.add_maintainer(user)
sign_in(user)
-
- visit project_wiki_path(project, wiki_page)
end
context "while creating a new wiki page" do
context "when there are no spaces or hyphens in the page name" do
it "rewrites relative links as expected" do
- find('.add-new-wiki').click
- page.within '#modal-new-wiki' do
- fill_in :new_wiki_path, with: 'a/b/c/d'
- click_button 'Create page'
- end
-
- page.within '.wiki-form' do
- fill_in :wiki_content, with: wiki_content
- click_on "Preview"
- end
+ create_wiki_page('a/b/c/d', content: wiki_content)
expect(page).to have_content("regular link")
@@ -48,16 +39,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
context "when there are spaces in the page name" do
it "rewrites relative links as expected" do
- click_link 'New page'
- page.within '#modal-new-wiki' do
- fill_in :new_wiki_path, with: 'a page/b page/c page/d page'
- click_button 'Create page'
- end
-
- page.within '.wiki-form' do
- fill_in :wiki_content, with: wiki_content
- click_on "Preview"
- end
+ create_wiki_page('a page/b page/c page/d page', content: wiki_content)
expect(page).to have_content("regular link")
@@ -71,16 +53,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
context "when there are hyphens in the page name" do
it "rewrites relative links as expected" do
- click_link 'New page'
- page.within '#modal-new-wiki' do
- fill_in :new_wiki_path, with: 'a-page/b-page/c-page/d-page'
- click_button 'Create page'
- end
-
- page.within '.wiki-form' do
- fill_in :wiki_content, with: wiki_content
- click_on "Preview"
- end
+ create_wiki_page('a-page/b-page/c-page/d-page', content: wiki_content)
expect(page).to have_content("regular link")
@@ -94,23 +67,9 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
end
context "while editing a wiki page" do
- def create_wiki_page(path)
- find('.add-new-wiki').click
-
- page.within '#modal-new-wiki' do
- fill_in :new_wiki_path, with: path
- click_button 'Create page'
- end
-
- page.within '.wiki-form' do
- fill_in :wiki_content, with: 'content'
- click_on "Create page"
- end
- end
-
context "when there are no spaces or hyphens in the page name" do
it "rewrites relative links as expected" do
- create_wiki_page 'a/b/c/d'
+ create_wiki_page('a/b/c/d')
click_link 'Edit'
fill_in :wiki_content, with: wiki_content
@@ -128,7 +87,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
context "when there are spaces in the page name" do
it "rewrites relative links as expected" do
- create_wiki_page 'a page/b page/c page/d page'
+ create_wiki_page('a page/b page/c page/d page')
click_link 'Edit'
fill_in :wiki_content, with: wiki_content
@@ -146,7 +105,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
context "when there are hyphens in the page name" do
it "rewrites relative links as expected" do
- create_wiki_page 'a-page/b-page/c-page/d-page'
+ create_wiki_page('a-page/b-page/c-page/d-page')
click_link 'Edit'
fill_in :wiki_content, with: wiki_content
@@ -164,7 +123,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
context 'when rendering the preview' do
it 'renders content with CommonMark' do
- create_wiki_page 'a-page/b-page/c-page/common-mark'
+ create_wiki_page('a-page/b-page/c-page/common-mark')
click_link 'Edit'
fill_in :wiki_content, with: "1. one\n - sublist\n"
@@ -178,25 +137,31 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
end
it "does not linkify double brackets inside code blocks as expected" do
- click_link 'New page'
- page.within '#modal-new-wiki' do
- fill_in :new_wiki_path, with: 'linkify_test'
- click_button 'Create page'
- end
+ wiki_content = <<-HEREDOC
+ `[[do_not_linkify]]`
+ ```
+ [[also_do_not_linkify]]
+ ```
+ HEREDOC
- page.within '.wiki-form' do
- fill_in :wiki_content, with: <<-HEREDOC
- `[[do_not_linkify]]`
- ```
- [[also_do_not_linkify]]
- ```
- HEREDOC
- click_on "Preview"
- end
+ create_wiki_page('linkify_test', wiki_content)
expect(page).to have_content("do_not_linkify")
expect(page.html).to include('[[do_not_linkify]]')
expect(page.html).to include('[[also_do_not_linkify]]')
end
+
+ private
+
+ def create_wiki_page(path, content = 'content')
+ visit project_wiki_path(project, wiki_page)
+
+ click_link 'New page'
+
+ fill_in :wiki_title, with: path
+ fill_in :wiki_content, with: content
+
+ click_button 'Create page'
+ end
end
diff --git a/spec/features/projects/wiki/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb
index c01be1f14ed..806d2f28bb9 100644
--- a/spec/features/projects/wiki/shortcuts_spec.rb
+++ b/spec/features/projects/wiki/shortcuts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Wiki shortcuts', :js do
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index aac095bfa6b..56d0518015d 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User creates wiki page" do
@@ -40,10 +42,10 @@ describe "User creates wiki page" do
click_link("link test")
- expect(page).to have_content("Create Page")
+ expect(page).to have_content("Create New Page")
end
- it "shows non-escaped link in the pages list", :js, :quarantine do
+ it "shows non-escaped link in the pages list", :quarantine do
fill_in(:wiki_title, with: "one/two/three-test")
page.within(".wiki-form") do
@@ -56,7 +58,9 @@ describe "User creates wiki page" do
expect(page).to have_xpath("//a[@href='/#{project.full_path}/wikis/one/two/three-test']")
end
- it "has `Create home` as a commit message" do
+ it "has `Create home` as a commit message", :js do
+ wait_for_requests
+
expect(page).to have_field("wiki[message]", with: "Create home")
end
@@ -79,7 +83,7 @@ describe "User creates wiki page" do
expect(current_path).to eq(project_wiki_path(project, "test"))
page.within(:css, ".nav-text") do
- expect(page).to have_content("test").and have_content("Create Page")
+ expect(page).to have_content("Create New Page")
end
click_link("Home")
@@ -91,7 +95,7 @@ describe "User creates wiki page" do
expect(current_path).to eq(project_wiki_path(project, "api"))
page.within(:css, ".nav-text") do
- expect(page).to have_content("Create").and have_content("api")
+ expect(page).to have_content("Create")
end
click_link("Home")
@@ -103,7 +107,7 @@ describe "User creates wiki page" do
expect(current_path).to eq(project_wiki_path(project, "raketasks"))
page.within(:css, ".nav-text") do
- expect(page).to have_content("Create").and have_content("rake")
+ expect(page).to have_content("Create")
end
end
@@ -148,6 +152,8 @@ describe "User creates wiki page" do
let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) }
it "has `Create home` as a commit message" do
+ wait_for_requests
+
expect(page).to have_field("wiki[message]", with: "Create home")
end
@@ -179,20 +185,15 @@ describe "User creates wiki page" do
it "creates a page with a single word" do
click_link("New page")
- page.within("#modal-new-wiki") do
- fill_in(:new_wiki_path, with: "foo")
-
- click_button("Create page")
+ page.within(".wiki-form") do
+ fill_in(:wiki_title, with: "foo")
+ fill_in(:wiki_content, with: "My awesome wiki!")
end
# Commit message field should have correct value.
expect(page).to have_field("wiki[message]", with: "Create foo")
- page.within(".wiki-form") do
- fill_in(:wiki_content, with: "My awesome wiki!")
-
- click_button("Create page")
- end
+ click_button("Create page")
expect(page).to have_content("foo")
.and have_content("Last edited by #{user.name}")
@@ -202,20 +203,15 @@ describe "User creates wiki page" do
it "creates a page with spaces in the name" do
click_link("New page")
- page.within("#modal-new-wiki") do
- fill_in(:new_wiki_path, with: "Spaces in the name")
-
- click_button("Create page")
+ page.within(".wiki-form") do
+ fill_in(:wiki_title, with: "Spaces in the name")
+ fill_in(:wiki_content, with: "My awesome wiki!")
end
# Commit message field should have correct value.
expect(page).to have_field("wiki[message]", with: "Create Spaces in the name")
- page.within(".wiki-form") do
- fill_in(:wiki_content, with: "My awesome wiki!")
-
- click_button("Create page")
- end
+ click_button("Create page")
expect(page).to have_content("Spaces in the name")
.and have_content("Last edited by #{user.name}")
@@ -225,10 +221,9 @@ describe "User creates wiki page" do
it "creates a page with hyphens in the name" do
click_link("New page")
- page.within("#modal-new-wiki") do
- fill_in(:new_wiki_path, with: "hyphens-in-the-name")
-
- click_button("Create page")
+ page.within(".wiki-form") do
+ fill_in(:wiki_title, with: "hyphens-in-the-name")
+ fill_in(:wiki_content, with: "My awesome wiki!")
end
# Commit message field should have correct value.
@@ -249,12 +244,6 @@ describe "User creates wiki page" do
it "shows the emoji autocompletion dropdown" do
click_link("New page")
- page.within("#modal-new-wiki") do
- fill_in(:new_wiki_path, with: "test-autocomplete")
-
- click_button("Create page")
- end
-
page.within(".wiki-form") do
find("#wiki_content").native.send_keys("")
@@ -272,20 +261,15 @@ describe "User creates wiki page" do
it "creates a page" do
click_link("New page")
- page.within("#modal-new-wiki") do
- fill_in(:new_wiki_path, with: "foo")
-
- click_button("Create page")
+ page.within(".wiki-form") do
+ fill_in(:wiki_title, with: "foo")
+ fill_in(:wiki_content, with: "My awesome wiki!")
end
# Commit message field should have correct value.
expect(page).to have_field("wiki[message]", with: "Create foo")
- page.within(".wiki-form") do
- fill_in(:wiki_content, with: "My awesome wiki!")
-
- click_button("Create page")
- end
+ click_button("Create page")
expect(page).to have_content("foo")
.and have_content("Last edited by #{user.name}")
diff --git a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb
index 18ccd31f3d0..38e5e292064 100644
--- a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User deletes wiki page', :js do
diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
index db97d59e918..ab3d912dd15 100644
--- a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Wiki > User views Git access wiki page' do
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
index dbf8af3e5bb..3f3711f9eb8 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User updates wiki page' do
@@ -68,7 +70,7 @@ describe 'User updates wiki page' do
context 'in a user namespace' do
let(:project) { create(:project, :wiki_repo) }
- it 'updates a page' do
+ it 'updates a page', :js do
# Commit message field should have correct value.
expect(page).to have_field('wiki[message]', with: 'Update home')
@@ -80,6 +82,18 @@ describe 'User updates wiki page' do
expect(page).to have_content('My awesome wiki!')
end
+ it 'updates the commit message as the title is changed', :js do
+ fill_in(:wiki_title, with: 'Wiki title')
+
+ expect(page).to have_field('wiki[message]', with: 'Update Wiki title')
+ end
+
+ it 'does not allow XSS', :js do
+ fill_in(:wiki_title, with: '<script>')
+
+ expect(page).to have_field('wiki[message]', with: 'Update &lt;script&gt;')
+ end
+
it 'shows a validation error message' do
fill_in(:wiki_content, with: '')
click_button('Save changes')
@@ -127,7 +141,7 @@ describe 'User updates wiki page' do
context 'in a group namespace' do
let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) }
- it 'updates a page' do
+ it 'updates a page', :js do
# Commit message field should have correct value.
expect(page).to have_field('wiki[message]', with: 'Update home')
diff --git a/spec/features/projects/wiki/user_views_wiki_empty_spec.rb b/spec/features/projects/wiki/user_views_wiki_empty_spec.rb
index e94b3a9432b..ab0f9b750d2 100644
--- a/spec/features/projects/wiki/user_views_wiki_empty_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_empty_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views empty wiki' do
diff --git a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
index fb0ebe22bf7..471e80b27dc 100644
--- a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Wiki > User views wiki in project page' do
diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
index 6e28ec0d7b2..77e725e7f11 100644
--- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views a wiki page' do
@@ -99,8 +101,7 @@ describe 'User views a wiki page' do
click_on('image')
expect(current_path).to match("wikis/#{path}")
- expect(page).to have_content('New Wiki Page')
- expect(page).to have_content('Create page')
+ expect(page).to have_content('Create New Page')
end
end
@@ -129,7 +130,7 @@ describe 'User views a wiki page' do
end
context 'when page has invalid content encoding' do
- let(:content) { 'whatever'.force_encoding('ISO-8859-1') }
+ let(:content) { (+'whatever').force_encoding('ISO-8859-1') }
before do
allow(Gitlab::EncodingHelper).to receive(:encode!).and_return(content)
@@ -154,6 +155,6 @@ describe 'User views a wiki page' do
find('.shortcuts-wiki').click
click_link "Create your first page"
- expect(page).to have_content('Home · Create Page')
+ expect(page).to have_content('Create New Page')
end
end
diff --git a/spec/features/projects/wiki/user_views_wiki_pages_spec.rb b/spec/features/projects/wiki/user_views_wiki_pages_spec.rb
index 5c16d7783f0..6740df1d4ed 100644
--- a/spec/features/projects/wiki/user_views_wiki_pages_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_pages_spec.rb
@@ -42,7 +42,7 @@ describe 'User views wiki pages' do
context 'desc' do
before do
page.within('.wiki-sort-dropdown') do
- page.find('.qa-reverse-sort').click
+ page.find('.rspec-reverse-sort').click
end
end
@@ -75,7 +75,7 @@ describe 'User views wiki pages' do
context 'desc' do
before do
page.within('.wiki-sort-dropdown') do
- page.find('.qa-reverse-sort').click
+ page.find('.rspec-reverse-sort').click
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index b5112758475..67ae26d8d1e 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -314,7 +314,7 @@ describe 'Project' do
end
end
- context 'for subgroups', :js, :nested_groups do
+ context 'for subgroups', :js do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
let(:project) { create(:project, :repository, group: subgroup) }
@@ -387,7 +387,7 @@ describe 'Project' do
end
it_behaves_like 'dirty submit form', [{ form: '.js-general-settings-form', input: 'input[name="project[name]"]' },
- { form: '.qa-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
+ { form: '.rspec-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
end
def remove_with_confirm(button_text, confirm_with)
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 0dbff5a2701..80937223016 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Protected Branches', :js do
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index 652542b1719..3322a747cf5 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Protected Tags', :js do
@@ -14,6 +16,7 @@ describe 'Protected Tags', :js do
it "allows creating explicit protected tags" do
visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") { expect(page).to have_content('some-tag') }
@@ -27,6 +30,7 @@ describe 'Protected Tags', :js do
visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") { expect(page).to have_content(commit.id[0..7]) }
@@ -35,6 +39,7 @@ describe 'Protected Tags', :js do
it "displays an error message if the named tag does not exist" do
visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") { expect(page).to have_content('tag was removed') }
@@ -45,6 +50,7 @@ describe 'Protected Tags', :js do
it "allows creating protected tags with a wildcard" do
visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") { expect(page).to have_content('*-stable') }
@@ -58,6 +64,7 @@ describe 'Protected Tags', :js do
visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") do
@@ -73,6 +80,7 @@ describe 'Protected Tags', :js do
visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
visit project_protected_tags_path(project)
diff --git a/spec/features/raven_js_spec.rb b/spec/features/raven_js_spec.rb
index a4dd79b3179..38699f0cc1b 100644
--- a/spec/features/raven_js_spec.rb
+++ b/spec/features/raven_js_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'RavenJS' do
diff --git a/spec/features/read_only_spec.rb b/spec/features/read_only_spec.rb
index 8bfaf558466..3af4b51b9b1 100644
--- a/spec/features/read_only_spec.rb
+++ b/spec/features/read_only_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'read-only message' do
diff --git a/spec/features/reportable_note/commit_spec.rb b/spec/features/reportable_note/commit_spec.rb
index 54ebda9dcab..3502401095e 100644
--- a/spec/features/reportable_note/commit_spec.rb
+++ b/spec/features/reportable_note/commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Reportable note on commit', :js do
diff --git a/spec/features/reportable_note/issue_spec.rb b/spec/features/reportable_note/issue_spec.rb
index bce1f7a3780..c45ef77df55 100644
--- a/spec/features/reportable_note/issue_spec.rb
+++ b/spec/features/reportable_note/issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Reportable note on issue', :js do
diff --git a/spec/features/reportable_note/merge_request_spec.rb b/spec/features/reportable_note/merge_request_spec.rb
index d00324156c4..2e4d032754b 100644
--- a/spec/features/reportable_note/merge_request_spec.rb
+++ b/spec/features/reportable_note/merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Reportable note on merge request', :js do
diff --git a/spec/features/reportable_note/snippets_spec.rb b/spec/features/reportable_note/snippets_spec.rb
index 06218d9b286..c2c853cdb05 100644
--- a/spec/features/reportable_note/snippets_spec.rb
+++ b/spec/features/reportable_note/snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Reportable note on snippets', :js do
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index 6eae0be4b9f..63d21d94b5f 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Runners' do
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index ecec2f3e043..4ca79ccaea8 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -1,9 +1,26 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for code' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
+ def submit_search(search, with_send_keys: false)
+ page.within('.search') do
+ field = find_field('search')
+ field.fill_in(with: search)
+
+ if with_send_keys
+ field.send_keys(:enter)
+ else
+ click_button("Go")
+ end
+ end
+
+ click_link('Code')
+ end
+
context 'when signed in' do
before do
project.add_maintainer(user)
@@ -13,12 +30,7 @@ describe 'User searches for code' do
it 'finds a file' do
visit(project_path(project))
- page.within('.search') do
- fill_in('search', with: 'application.js')
- click_button('Go')
- end
-
- click_link('Code')
+ submit_search('application.js')
expect(page).to have_selector('.file-content .code')
expect(page).to have_selector("span.line[lang='javascript']")
@@ -27,17 +39,16 @@ describe 'User searches for code' do
context 'when on a project page', :js do
before do
visit(search_path)
- end
-
- include_examples 'top right search form'
-
- it 'finds code' do
find('.js-search-project-dropdown').click
page.within('.project-filter') do
click_link(project.full_name)
end
+ end
+ include_examples 'top right search form'
+
+ it 'finds code' do
fill_in('dashboard_search', with: 'rspec')
find('.btn-search').click
@@ -45,6 +56,71 @@ describe 'User searches for code' do
expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions')
end
end
+
+ it 'search mutiple words with refs switching' do
+ expected_result = 'Use `snake_case` for naming files'
+ search = 'for naming files'
+
+ fill_in('dashboard_search', with: search)
+ find('.btn-search').click
+
+ page.within('.results') do
+ expect(find('.search-results')).to have_content(expected_result)
+ end
+
+ find('.js-project-refs-dropdown').click
+ find('.dropdown-page-one .dropdown-content').click_link('v1.0.0')
+
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_content(expected_result)
+ end
+
+ expect(find_field('dashboard_search').value).to eq(search)
+ end
+ end
+
+ context 'search code within refs', :js do
+ let(:ref_name) { 'v1.0.0' }
+
+ before do
+ visit(project_tree_path(project, ref_name))
+ submit_search('gitlab-grack', with_send_keys: true)
+ end
+
+ it 'shows ref switcher in code result summary' do
+ expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
+ end
+ it 'persists branch name across search' do
+ find('.btn-search').click
+ expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
+ end
+
+ # this example is use to test the desgine that the refs is not
+ # only repersent the branch as well as the tags.
+ it 'ref swither list all the branchs and tags' do
+ find('.js-project-refs-dropdown').click
+ expect(find('.dropdown-page-one .dropdown-content')).to have_link('sha-starting-with-large-number')
+ expect(find('.dropdown-page-one .dropdown-content')).to have_link('v1.0.0')
+ end
+
+ it 'search result changes when refs switched' do
+ expect(find('.search-results')).not_to have_content('path = gitlab-grack')
+ find('.js-project-refs-dropdown').click
+ find('.dropdown-page-one .dropdown-content').click_link('master')
+ expect(find('.search-results')).to have_content('path = gitlab-grack')
+ end
+ end
+
+ it 'no ref switcher shown in issue result summary', :js do
+ issue = create(:issue, title: 'test', project: project)
+ visit(project_tree_path(project))
+ submit_search('test', with_send_keys: true)
+ expect(page).to have_selector('.js-project-refs-dropdown')
+ page.within('.search-filter') do
+ click_link('Issues')
+ end
+ expect(find(:css, '.search-results')).to have_link(issue.title)
+ expect(page).not_to have_selector('.js-project-refs-dropdown')
end
end
@@ -56,8 +132,7 @@ describe 'User searches for code' do
end
it 'finds code' do
- fill_in('search', with: 'rspec')
- click_button('Go')
+ submit_search('rspec')
page.within('.results') do
expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions')
diff --git a/spec/features/search/user_searches_for_comments_spec.rb b/spec/features/search/user_searches_for_comments_spec.rb
index c7c469a262c..2ce3fa4735f 100644
--- a/spec/features/search/user_searches_for_comments_spec.rb
+++ b/spec/features/search/user_searches_for_comments_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for comments' do
diff --git a/spec/features/search/user_searches_for_commits_spec.rb b/spec/features/search/user_searches_for_commits_spec.rb
index 998f8521384..81c299752ea 100644
--- a/spec/features/search/user_searches_for_commits_spec.rb
+++ b/spec/features/search/user_searches_for_commits_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for commits' do
diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb
index 4bff269f89e..f0fcf6df70c 100644
--- a/spec/features/search/user_searches_for_issues_spec.rb
+++ b/spec/features/search/user_searches_for_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for issues', :js do
diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb
index 75d44e413cb..d005b87cdfe 100644
--- a/spec/features/search/user_searches_for_merge_requests_spec.rb
+++ b/spec/features/search/user_searches_for_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for merge requests', :js do
diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb
index 7d52c4c8bcc..00964ab4f1d 100644
--- a/spec/features/search/user_searches_for_milestones_spec.rb
+++ b/spec/features/search/user_searches_for_milestones_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for milestones', :js do
diff --git a/spec/features/search/user_searches_for_projects_spec.rb b/spec/features/search/user_searches_for_projects_spec.rb
index 242e437e41c..082c1ae8e4a 100644
--- a/spec/features/search/user_searches_for_projects_spec.rb
+++ b/spec/features/search/user_searches_for_projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for projects' do
diff --git a/spec/features/search/user_searches_for_users_spec.rb b/spec/features/search/user_searches_for_users_spec.rb
index 3725143291d..e10c1afc0b8 100644
--- a/spec/features/search/user_searches_for_users_spec.rb
+++ b/spec/features/search/user_searches_for_users_spec.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for users' do
context 'when on the dashboard' do
- it 'finds the user' do
+ it 'finds the user', :js do
create(:user, username: 'gob_bluth', name: 'Gob Bluth')
sign_in(create(:user))
@@ -10,7 +12,7 @@ describe 'User searches for users' do
visit dashboard_projects_path
fill_in 'search', with: 'gob'
- click_button 'Go'
+ find('#search').send_keys(:enter)
expect(page).to have_content('Users 1')
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index ee43755262e..0a5abfbf46a 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for wiki pages', :js 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 1cc47cd6bd1..5006631cc14 100644
--- a/spec/features/search/user_uses_header_search_field_spec.rb
+++ b/spec/features/search/user_uses_header_search_field_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'User uses header search field' do
+describe 'User uses header search field', :js do
include FilteredSearchHelpers
let(:project) { create(:project) }
@@ -11,89 +13,45 @@ describe 'User uses header search field' do
sign_in(user)
end
- context 'when user is in a global scope', :js do
+ shared_examples 'search field examples' do
before do
- visit(root_path)
- page.find('#search').click
+ visit(url)
end
- context 'when clicking issues' do
- it 'shows assigned issues' do
- find('.search-input-container .dropdown-menu').click_link('Issues assigned to me')
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'shows created issues' do
- find('.search-input-container .dropdown-menu').click_link("Issues I've created")
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input_empty
- end
- end
-
- context 'when clicking merge requests' do
- let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignees: [user]) }
-
- it 'shows assigned merge requests' do
- find('.search-input-container .dropdown-menu').click_link('Merge requests assigned to me')
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'shows created merge requests' do
- find('.search-input-container .dropdown-menu').click_link("Merge requests I've created")
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input_empty
- end
- end
- end
-
- context 'when user is in a project scope' do
- before do
- visit(project_path(project))
- end
-
- it 'starts searching by pressing the enter key', :js do
+ it 'starts searching by pressing the enter key' do
fill_in('search', with: 'gitlab')
find('#search').native.send_keys(:enter)
- page.within('.breadcrumbs-sub-title') do
+ page.within('.page-title') do
expect(page).to have_content('Search')
end
end
- context 'when clicking the search field', :js do
+ context 'when clicking the search field' do
before do
page.find('#search').click
+ wait_for_all_requests
end
it 'shows category search dropdown' do
- expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i)
+ expect(page).to have_selector('.dropdown-header', text: /#{scope_name}/i)
end
context 'when clicking issues' do
let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
it 'shows assigned issues' do
- find('.dropdown-menu').click_link('Issues assigned to me')
+ find('.search-input-container .dropdown-menu').click_link('Issues assigned to me')
- expect(page).to have_selector('.filtered-search')
+ expect(page).to have_selector('.issues-list .issue')
expect_tokens([assignee_token(user.name)])
expect_filtered_search_input_empty
end
it 'shows created issues' do
- find('.dropdown-menu').click_link("Issues I've created")
+ find('.search-input-container .dropdown-menu').click_link("Issues I've created")
- expect(page).to have_selector('.filtered-search')
+ expect(page).to have_selector('.issues-list .issue')
expect_tokens([author_token(user.name)])
expect_filtered_search_input_empty
end
@@ -103,33 +61,94 @@ describe 'User uses header search field' do
let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignees: [user]) }
it 'shows assigned merge requests' do
- find('.dropdown-menu').click_link('Merge requests assigned to me')
+ find('.search-input-container .dropdown-menu').click_link('Merge requests assigned to me')
- expect(page).to have_selector('.merge-requests-holder')
+ expect(page).to have_selector('.mr-list .merge-request')
expect_tokens([assignee_token(user.name)])
expect_filtered_search_input_empty
end
it 'shows created merge requests' do
- find('.dropdown-menu').click_link("Merge requests I've created")
+ find('.search-input-container .dropdown-menu').click_link("Merge requests I've created")
- expect(page).to have_selector('.merge-requests-holder')
+ expect(page).to have_selector('.mr-list .merge-request')
expect_tokens([author_token(user.name)])
expect_filtered_search_input_empty
end
end
end
- context 'when entering text into the search field', :js do
+ context 'when entering text into the search field' do
before do
page.within('.search-input-wrap') do
- fill_in('search', with: project.name[0..3])
+ fill_in('search', with: scope_name.first(4))
end
end
it 'does not display the category search dropdown' do
- expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i)
+ expect(page).not_to have_selector('.dropdown-header', text: /#{scope_name}/i)
end
end
end
+
+ context 'when user is in a global scope' do
+ include_examples 'search field examples' do
+ let(:url) { root_path }
+ let(:scope_name) { 'All GitLab' }
+ end
+
+ context 'when searching through the search field' do
+ before do
+ create(:issue, project: project, title: 'project issue')
+
+ fill_in('search', with: 'project')
+ find('#search').send_keys(:enter)
+ end
+
+ it 'displays result counts for all categories' do
+ expect(page).to have_content('Projects 1')
+ expect(page).to have_content('Issues 1')
+ expect(page).to have_content('Merge requests 0')
+ expect(page).to have_content('Milestones 0')
+ expect(page).to have_content('Users 0')
+ end
+ end
+ end
+
+ context 'when user is in a project scope' do
+ include_examples 'search field examples' do
+ let(:url) { project_path(project) }
+ let(:scope_name) { project.name }
+ end
+ end
+
+ context 'when user is in a group scope' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
+
+ before do
+ group.add_maintainer(user)
+ end
+
+ include_examples 'search field examples' do
+ let(:url) { group_path(group) }
+ let(:scope_name) { group.name }
+ end
+ end
+
+ context 'when user is in a subgroup scope' do
+ let(:group) { create(:group) }
+ let(:subgroup) { create(:group, :public, parent: group) }
+ let(:project) { create(:project, namespace: subgroup) }
+
+ before do
+ group.add_owner(user)
+ subgroup.add_owner(user)
+ end
+
+ include_examples 'search field examples' do
+ let(:url) { group_path(subgroup) }
+ let(:scope_name) { subgroup.name }
+ end
+ end
end
diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb
index 0725ff178ac..fbd7da3c643 100644
--- a/spec/features/search/user_uses_search_filters_spec.rb
+++ b/spec/features/search/user_uses_search_filters_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User uses search filters', :js do
@@ -20,7 +22,7 @@ describe 'User uses search filters', :js do
wait_for_requests
- page.within('.search-holder') do
+ page.within('.search-page-form') do
click_link(group.name)
end
diff --git a/spec/features/security/admin_access_spec.rb b/spec/features/security/admin_access_spec.rb
index ff679034a36..9b2c873b2aa 100644
--- a/spec/features/security/admin_access_spec.rb
+++ b/spec/features/security/admin_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Admin::Projects" do
diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb
index 07cddc92ac4..13fafd88a4c 100644
--- a/spec/features/security/dashboard_access_spec.rb
+++ b/spec/features/security/dashboard_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Dashboard access" do
diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb
index 51b32ba6c03..f4f3872aa09 100644
--- a/spec/features/security/group/internal_access_spec.rb
+++ b/spec/features/security/group/internal_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Internal Group access' do
diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb
index a776169a8e5..9cef8ef777c 100644
--- a/spec/features/security/group/private_access_spec.rb
+++ b/spec/features/security/group/private_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Private Group access' do
diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb
index 3a53c3c2bc7..bbe74f0dab0 100644
--- a/spec/features/security/group/public_access_spec.rb
+++ b/spec/features/security/group/public_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Public Group access' do
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index e23000fa676..42c747c674f 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Internal Project Access" do
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index f380bc122a7..a86d240b7d6 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Private Project Access" do
@@ -126,7 +128,7 @@ describe "Private Project Access" do
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
- subject { project_blob_path(project, File.join(commit.id, '.gitignore'))}
+ subject { project_blob_path(project, File.join(commit.id, '.gitignore')) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 57d56371719..8d7f8c84358 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Public Project Access" do
diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb
index 0c58fdf2f12..4b6c7d2c8fb 100644
--- a/spec/features/security/project/snippet/internal_access_spec.rb
+++ b/spec/features/security/project/snippet/internal_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Internal Project Snippets Access" do
diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb
index 420f1938763..3135d25cc10 100644
--- a/spec/features/security/project/snippet/private_access_spec.rb
+++ b/spec/features/security/project/snippet/private_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Private Project Snippets Access" do
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
index 6c75902c6e9..81689a7bcb5 100644
--- a/spec/features/security/project/snippet/public_access_spec.rb
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Public Project Snippets Access" do
diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb
index e2b3444272e..70e6978a7b6 100644
--- a/spec/features/signed_commits_spec.rb
+++ b/spec/features/signed_commits_spec.rb
@@ -15,8 +15,8 @@ describe 'GPG signed commits' do
visit project_commit_path(project, ref)
- expect(page).to have_link 'Unverified'
- expect(page).not_to have_link 'Verified'
+ expect(page).to have_button 'Unverified'
+ expect(page).not_to have_button 'Verified'
# user changes his email which makes the gpg key verified
perform_enqueued_jobs do
@@ -26,8 +26,8 @@ describe 'GPG signed commits' do
visit project_commit_path(project, ref)
- expect(page).not_to have_link 'Unverified'
- expect(page).to have_link 'Verified'
+ expect(page).not_to have_button 'Unverified'
+ expect(page).to have_button 'Verified'
end
it 'changes from unverified to verified when the user adds the missing gpg key' do
@@ -36,8 +36,8 @@ describe 'GPG signed commits' do
visit project_commit_path(project, ref)
- expect(page).to have_link 'Unverified'
- expect(page).not_to have_link 'Verified'
+ expect(page).to have_button 'Unverified'
+ expect(page).not_to have_button 'Verified'
# user adds the gpg key which makes the signature valid
perform_enqueued_jobs do
@@ -46,8 +46,8 @@ describe 'GPG signed commits' do
visit project_commit_path(project, ref)
- expect(page).not_to have_link 'Unverified'
- expect(page).to have_link 'Verified'
+ expect(page).not_to have_button 'Unverified'
+ expect(page).to have_button 'Verified'
end
context 'shows popover badges', :js do
@@ -136,7 +136,7 @@ describe 'GPG signed commits' do
visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA)
# wait for the signature to get generated
- expect(page).to have_link 'Verified'
+ expect(page).to have_button 'Verified'
user_1.destroy!
diff --git a/spec/features/snippets/embedded_snippet_spec.rb b/spec/features/snippets/embedded_snippet_spec.rb
index ab661f6fc69..d6275b5a265 100644
--- a/spec/features/snippets/embedded_snippet_spec.rb
+++ b/spec/features/snippets/embedded_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Embedded Snippets' do
diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb
index c08f25875f8..b48a5691e96 100644
--- a/spec/features/snippets/explore_spec.rb
+++ b/spec/features/snippets/explore_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Explore Snippets' do
diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb
index f6215b481dc..8454a347382 100644
--- a/spec/features/snippets/internal_snippet_spec.rb
+++ b/spec/features/snippets/internal_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Internal Snippets', :js do
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index 78e0a43ce6d..2bd01be25e9 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Comments on personal snippets', :js do
diff --git a/spec/features/snippets/public_snippets_spec.rb b/spec/features/snippets/public_snippets_spec.rb
index 71c72b98fad..e32a9292e22 100644
--- a/spec/features/snippets/public_snippets_spec.rb
+++ b/spec/features/snippets/public_snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Public Snippets', :js do
diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb
index c137b0bcd96..4a8c5f9b1fe 100644
--- a/spec/features/snippets/search_snippets_spec.rb
+++ b/spec/features/snippets/search_snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Search Snippets' do
diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb
index 74fdfcf492e..edf7d37fd6d 100644
--- a/spec/features/snippets/show_spec.rb
+++ b/spec/features/snippets/show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Snippet', :js do
diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index 1c97d5ec5b4..a4a5407d1f7 100644
--- a/spec/features/snippets/user_creates_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User creates snippet', :js do
@@ -41,7 +43,7 @@ describe 'User creates snippet', :js do
expect(page).to have_content('My Snippet')
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
- expect(link).to match(%r{/uploads/-/system/temp/\h{32}/banana_sample\.gif\z})
+ expect(link).to match(%r{/uploads/-/system/user/#{user.id}/\h{32}/banana_sample\.gif\z})
reqs = inspect_requests { visit(link) }
expect(reqs.first.status_code).to eq(200)
diff --git a/spec/features/snippets/user_deletes_snippet_spec.rb b/spec/features/snippets/user_deletes_snippet_spec.rb
index 7bdccacb0fa..9773aca849a 100644
--- a/spec/features/snippets/user_deletes_snippet_spec.rb
+++ b/spec/features/snippets/user_deletes_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User deletes snippet' do
diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb
index 77f62990158..5ff12c37aff 100644
--- a/spec/features/snippets/user_edits_snippet_spec.rb
+++ b/spec/features/snippets/user_edits_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User edits snippet', :js do
@@ -32,7 +34,7 @@ describe 'User edits snippet', :js do
click_button('Save changes')
wait_for_requests
- link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
+ link = find('a.no-attachment-icon img:not(.lazy)[alt="banana_sample"]')['src']
expect(link).to match(%r{/uploads/-/system/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z})
end
diff --git a/spec/features/snippets/user_sees_breadcrumb_links.rb b/spec/features/snippets/user_sees_breadcrumb_links.rb
deleted file mode 100644
index 696f2b93390..00000000000
--- a/spec/features/snippets/user_sees_breadcrumb_links.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'rails_helper'
-
-describe 'New user snippet breadcrumbs' do
- let(:user) { create(:user) }
-
- before do
- sign_in(user)
- visit new_snippet_path
- end
-
- it 'display a link to user snippets and new user snippet pages' do
- page.within '.breadcrumbs' do
- expect(find_link('Snippets')[:href]).to end_with(dashboard_snippets_path)
- expect(find_link('New')[:href]).to end_with(new_snippet_path)
- end
- end
-end
diff --git a/spec/features/snippets/user_snippets_spec.rb b/spec/features/snippets/user_snippets_spec.rb
index e3065a899cc..4e9215db945 100644
--- a/spec/features/snippets/user_snippets_spec.rb
+++ b/spec/features/snippets/user_snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User Snippets' do
diff --git a/spec/features/snippets_spec.rb b/spec/features/snippets_spec.rb
index 96c50f6c804..9df6fe7d16b 100644
--- a/spec/features/snippets_spec.rb
+++ b/spec/features/snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Snippets' do
diff --git a/spec/features/tags/developer_creates_tag_spec.rb b/spec/features/tags/developer_creates_tag_spec.rb
index b2ad7ed8f3f..a2d53b04c9d 100644
--- a/spec/features/tags/developer_creates_tag_spec.rb
+++ b/spec/features/tags/developer_creates_tag_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Developer creates tag' do
diff --git a/spec/features/tags/developer_deletes_tag_spec.rb b/spec/features/tags/developer_deletes_tag_spec.rb
index dc4c7a4fb0a..82b416c3a7f 100644
--- a/spec/features/tags/developer_deletes_tag_spec.rb
+++ b/spec/features/tags/developer_deletes_tag_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Developer deletes tag' do
diff --git a/spec/features/tags/developer_updates_tag_spec.rb b/spec/features/tags/developer_updates_tag_spec.rb
index 1e11fc9e5d5..0cdd953b9ae 100644
--- a/spec/features/tags/developer_updates_tag_spec.rb
+++ b/spec/features/tags/developer_updates_tag_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Developer updates tag' do
diff --git a/spec/features/tags/developer_views_tags_spec.rb b/spec/features/tags/developer_views_tags_spec.rb
index 09e644c6b97..b892b2b4d12 100644
--- a/spec/features/tags/developer_views_tags_spec.rb
+++ b/spec/features/tags/developer_views_tags_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Developer views tags' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 33d9c10f5e8..acffc4ce580 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Task Lists' do
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 919859c145a..19cd21e4161 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Triggers', :js do
@@ -66,7 +68,7 @@ describe 'Triggers', :js do
it 'edit "legacy" trigger and save' do
# Create new trigger without owner association, i.e. Legacy trigger
- create(:ci_trigger, owner: nil, project: @project)
+ create(:ci_trigger, owner: user, project: @project).update_attribute(:owner, nil)
visit project_settings_ci_cd_path(@project)
# See if the trigger can be edited and description is blank
@@ -81,29 +83,6 @@ describe 'Triggers', :js do
end
end
- describe 'trigger "Take ownership" workflow' do
- before do
- create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
- visit project_settings_ci_cd_path(@project)
- end
-
- it 'button "Take ownership" has correct alert' do
- expected_alert = 'By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?'
- expect(page.find('a.btn-trigger-take-ownership')['data-confirm']).to eq expected_alert
- end
-
- it 'take trigger ownership' do
- # See if "Take ownership" on trigger works post trigger creation
- page.accept_confirm do
- first(:link, "Take ownership").send_keys(:return)
- end
-
- expect(page.find('.flash-notice')).to have_content 'Trigger was re-assigned.'
- expect(page.find('.triggers-list')).to have_content trigger_title
- expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
- end
- end
-
describe 'trigger "Revoke" workflow' do
before do
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
@@ -127,17 +106,19 @@ describe 'Triggers', :js do
end
describe 'show triggers workflow' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ end
+
it 'contains trigger description placeholder' do
expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description'
end
- it 'show "legacy" badge for legacy trigger' do
- create(:ci_trigger, owner: nil, project: @project)
+ it 'show "invalid" badge for legacy trigger' do
+ create(:ci_trigger, owner: user, project: @project).update_attribute(:owner, nil)
visit project_settings_ci_cd_path(@project)
- # See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable
- expect(page.find('.triggers-list')).to have_content 'legacy'
- expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
+ expect(page.find('.triggers-list')).to have_content 'invalid'
end
it 'show "invalid" badge for trigger with owner having insufficient permissions' do
@@ -149,6 +130,19 @@ describe 'Triggers', :js do
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
end
+ it 'do not show "Edit" or full token for legacy trigger' do
+ create(:ci_trigger, owner: user, project: @project, description: trigger_title)
+ .update_attribute(:owner, nil)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger not owned shows only first few token chars and doesn't have copy-to-clipboard button
+ expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
+ expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard')
+
+ # See if trigger is non-editable
+ expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
+ end
+
it 'do not show "Edit" or full token for not owned trigger' do
# Create trigger with user different from current_user
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
@@ -175,5 +169,56 @@ describe 'Triggers', :js do
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
end
+
+ context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: true)
+ end
+
+ it 'show "legacy" badge for legacy trigger' do
+ create(:ci_trigger, owner: nil, project: @project)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable
+ expect(page.find('.triggers-list')).to have_content 'legacy'
+ expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
+ end
+
+ it 'show "invalid" badge for trigger with owner having insufficient permissions' do
+ create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger without owner (i.e. legacy) shows "legacy" badge and is non-editable
+ expect(page.find('.triggers-list')).to have_content 'invalid'
+ expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
+ end
+
+ it 'do not show "Edit" or full token for not owned trigger' do
+ # Create trigger with user different from current_user
+ create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button
+ expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
+ expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard')
+
+ # See if trigger owner name doesn't match with current_user and trigger is non-editable
+ expect(page.find('.triggers-list .trigger-owner')).not_to have_content user.name
+ expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
+ end
+
+ it 'show "Edit" and full token for owned trigger' do
+ create(:ci_trigger, owner: user, project: @project, description: trigger_title)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger shows full token and has copy-to-clipboard button
+ expect(page.find('.triggers-list')).to have_content @project.triggers.first.token
+ expect(page.find('.triggers-list')).to have_selector('button.btn-clipboard')
+
+ # See if trigger owner name matches with current_user and is editable
+ expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
+ expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
+ end
+ end
end
end
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index ea02f36d9d0..bb18703f90e 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb
index 392d8e3e1c1..2f8b715289c 100644
--- a/spec/features/unsubscribe_links_spec.rb
+++ b/spec/features/unsubscribe_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Unsubscribe links' do
diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
index 72b53bae46a..40d864e0002 100644
--- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User uploads avatar to group' do
diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
index 48f8b8bf77e..4c694365e3b 100644
--- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User uploads avatar to profile' do
diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb
index 24a00c86b0a..6bf8e8ea74f 100644
--- a/spec/features/uploads/user_uploads_file_to_note_spec.rb
+++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User uploads file to note' do
diff --git a/spec/features/usage_stats_consent_spec.rb b/spec/features/usage_stats_consent_spec.rb
index dd8f3179895..14232b1b370 100644
--- a/spec/features/usage_stats_consent_spec.rb
+++ b/spec/features/usage_stats_consent_spec.rb
@@ -8,7 +8,15 @@ describe 'Usage stats consent' do
let(:message) { 'To help improve GitLab, we would like to periodically collect usage information.' }
before do
- allow(user).to receive(:has_current_license?).and_return false
+ if Gitlab.ee?
+ allow_any_instance_of(EE::User)
+ .to receive(:has_current_license?)
+ .and_return(false)
+ else
+ allow(user)
+ .to receive(:has_current_license?)
+ .and_return(false)
+ end
gitlab_sign_in(user)
end
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
index e069c2fddd1..b2036108d42 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User can display performance bar', :js do
diff --git a/spec/features/user_opens_link_to_comment.rb b/spec/features/user_opens_link_to_comment_spec.rb
index f1e07e55799..9533a4fe40d 100644
--- a/spec/features/user_opens_link_to_comment.rb
+++ b/spec/features/user_opens_link_to_comment_spec.rb
@@ -18,8 +18,13 @@ describe 'User opens link to comment', :js do
visit Gitlab::UrlBuilder.build(note)
+ wait_for_requests
+
expect(page.find('#discussion-filter-dropdown')).to have_content('Show all activity')
expect(page).not_to have_content('Something went wrong while fetching comments')
+
+ # Auto-switching to show all notes shouldn't be persisted
+ expect(user.reload.notes_filter_for(note.noteable)).to eq(UserPreference::NOTES_FILTERS[:only_activity])
end
end
diff --git a/spec/features/user_sees_revert_modal_spec.rb b/spec/features/user_sees_revert_modal_spec.rb
index d2cdade88d1..35828b5f086 100644
--- a/spec/features/user_sees_revert_modal_spec.rb
+++ b/spec/features/user_sees_revert_modal_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees revert modal', :js do
diff --git a/spec/features/user_sorts_things_spec.rb b/spec/features/user_sorts_things_spec.rb
index 0295f588326..41f8f3761e8 100644
--- a/spec/features/user_sorts_things_spec.rb
+++ b/spec/features/user_sorts_things_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
# The main goal of this spec is not to check whether the sorting UI works, but
diff --git a/spec/features/users/active_sessions_spec.rb b/spec/features/users/active_sessions_spec.rb
index 25349b5d036..c717e89eafb 100644
--- a/spec/features/users/active_sessions_spec.rb
+++ b/spec/features/users/active_sessions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Active user sessions', :clean_gitlab_redis_shared_state do
diff --git a/spec/features/users/add_email_to_existing_account.rb b/spec/features/users/add_email_to_existing_account_spec.rb
index 4355f769429..42e352399a8 100644
--- a/spec/features/users/add_email_to_existing_account.rb
+++ b/spec/features/users/add_email_to_existing_account_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'AdditionalEmailToExistingAccount' do
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index efba303033b..8e4db2ca840 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Login' do
@@ -93,6 +95,42 @@ describe 'Login' do
end
end
+ describe 'with an unconfirmed email address' do
+ let!(:user) { create(:user, confirmed_at: nil) }
+ let(:grace_period) { 2.days }
+
+ before do
+ stub_application_setting(send_user_confirmation_email: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return grace_period
+ end
+
+ context 'within the grace period' do
+ it 'allows to login' do
+ expect(authentication_metrics).to increment(:user_authenticated_counter)
+
+ gitlab_sign_in(user)
+
+ expect(page).not_to have_content('You have to confirm your email address before continuing.')
+ expect(page).not_to have_link('Resend confirmation email', href: new_user_confirmation_path)
+ end
+ end
+
+ context 'when the confirmation grace period is expired' do
+ it 'prevents the user from logging in and renders a resend confirmation email link' do
+ travel_to((grace_period + 1.day).from_now) do
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+ .and increment(:user_session_destroyed_counter).twice
+
+ gitlab_sign_in(user)
+
+ expect(page).to have_content('You have to confirm your email address before continuing.')
+ expect(page).to have_link('Resend confirmation email', href: new_user_confirmation_path)
+ end
+ end
+ end
+ end
+
describe 'with the ghost user' do
it 'disallows login' do
expect(authentication_metrics)
@@ -132,7 +170,6 @@ describe 'Login' do
it 'does not show a "You are already signed in." error message' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
enter_code(user.current_otp)
@@ -144,7 +181,6 @@ describe 'Login' do
it 'allows login with valid code' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
enter_code(user.current_otp)
@@ -170,7 +206,6 @@ describe 'Login' do
it 'allows login with invalid code, then valid code' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
enter_code('foo')
@@ -179,6 +214,15 @@ describe 'Login' do
enter_code(user.current_otp)
expect(current_path).to eq root_path
end
+
+ it 'triggers ActiveSession.cleanup for the user' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_two_factor_authenticated_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
+
+ enter_code(user.current_otp)
+ end
end
context 'using backup code' do
@@ -195,7 +239,6 @@ describe 'Login' do
it 'allows login' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
enter_code(codes.sample)
@@ -206,7 +249,6 @@ describe 'Login' do
it 'invalidates the used code' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
expect { enter_code(codes.sample) }
@@ -216,7 +258,6 @@ describe 'Login' do
it 'invalidates backup codes twice in a row' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter).twice
- .and increment(:user_session_override_counter).twice
.and increment(:user_two_factor_authenticated_counter).twice
.and increment(:user_session_destroyed_counter)
@@ -230,6 +271,15 @@ describe 'Login' do
expect { enter_code(codes.sample) }
.to change { user.reload.otp_backup_codes.size }.by(-1)
end
+
+ it 'triggers ActiveSession.cleanup for the user' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_two_factor_authenticated_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
+
+ enter_code(codes.sample)
+ end
end
context 'with invalid code' do
@@ -274,7 +324,7 @@ describe 'Login' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
sign_in_using_saml!
@@ -287,8 +337,8 @@ describe 'Login' do
it 'shows 2FA prompt after OAuth login' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
sign_in_using_saml!
@@ -329,6 +379,14 @@ describe 'Login' do
expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated'))
end
+
+ it 'triggers ActiveSession.cleanup for the user' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
+
+ gitlab_sign_in(user)
+ end
end
context 'with invalid username and password' do
@@ -649,7 +707,6 @@ describe 'Login' do
it 'asks the user to accept the terms' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
visit new_user_session_path
@@ -708,7 +765,6 @@ describe 'Login' do
it 'asks the user to accept the terms before setting an email' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
gitlab_sign_in_via('saml', user, 'my-uid')
@@ -725,4 +781,39 @@ describe 'Login' do
end
end
end
+
+ context 'when sending confirmation email and not yet confirmed' do
+ let!(:user) { create(:user, confirmed_at: nil) }
+ let(:grace_period) { 2.days }
+
+ before do
+ stub_application_setting(send_user_confirmation_email: true)
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return grace_period
+ end
+
+ it 'allows login and shows a flash warning to confirm the email address' do
+ expect(authentication_metrics).to increment(:user_authenticated_counter)
+
+ gitlab_sign_in(user)
+
+ expect(current_path).to eq root_path
+ expect(page).to have_content("Please check your email (#{user.email}) to verify that you own this address.")
+ end
+
+ context "when not having confirmed within Devise's allow_unconfirmed_access_for time" do
+ it 'does not allow login and shows a flash alert to confirm the email address' do
+ travel_to((grace_period + 1.day).from_now) do
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+ .and increment(:user_session_destroyed_counter).twice
+
+ gitlab_sign_in(user)
+
+ expect(current_path).to eq new_user_session_path
+ expect(page).to have_content('You have to confirm your email address before continuing.')
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/users/logout_spec.rb b/spec/features/users/logout_spec.rb
index 635729efa53..64202776b9b 100644
--- a/spec/features/users/logout_spec.rb
+++ b/spec/features/users/logout_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Logout/Sign out', :js do
diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb
index bfa85696e19..b3531d040e4 100644
--- a/spec/features/users/overview_spec.rb
+++ b/spec/features/users/overview_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Overview tab on a user profile', :js do
diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb
index 9af4447056e..ecdbf032623 100644
--- a/spec/features/users/rss_spec.rb
+++ b/spec/features/users/rss_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User RSS' do
diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb
index 351750c0179..932c1d8d4bd 100644
--- a/spec/features/users/show_spec.rb
+++ b/spec/features/users/show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User page' do
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index 50befa7028d..fb927a9ca3b 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -1,8 +1,14 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Signup' do
include TermsHelper
+ before do
+ stub_feature_flags(invisible_captcha: false)
+ end
+
let(:new_user) { build_stubbed(:user) }
describe 'username validation', :js do
@@ -160,24 +166,51 @@ describe 'Signup' do
end
context 'with no errors' do
- context "when sending confirmation email" do
+ context 'when sending confirmation email' do
before do
stub_application_setting(send_user_confirmation_email: true)
end
- it 'creates the user account and sends a confirmation email' do
- visit root_path
+ context 'when soft email confirmation is not enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: false)
+ end
- fill_in 'new_user_name', with: new_user.name
- fill_in 'new_user_username', with: new_user.username
- fill_in 'new_user_email', with: new_user.email
- fill_in 'new_user_email_confirmation', with: new_user.email
- fill_in 'new_user_password', with: new_user.password
+ it 'creates the user account and sends a confirmation email' do
+ visit root_path
+
+ fill_in 'new_user_name', with: new_user.name
+ fill_in 'new_user_username', with: new_user.username
+ fill_in 'new_user_email', with: new_user.email
+ fill_in 'new_user_email_confirmation', with: new_user.email
+ fill_in 'new_user_password', with: new_user.password
+
+ expect { click_button 'Register' }.to change { User.count }.by(1)
+
+ expect(current_path).to eq users_almost_there_path
+ expect(page).to have_content('Please check your email to confirm your account')
+ end
+ end
+
+ context 'when soft email confirmation is enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ end
+
+ it 'creates the user account and sends a confirmation email' do
+ visit root_path
+
+ fill_in 'new_user_name', with: new_user.name
+ fill_in 'new_user_username', with: new_user.username
+ fill_in 'new_user_email', with: new_user.email
+ fill_in 'new_user_email_confirmation', with: new_user.email
+ fill_in 'new_user_password', with: new_user.password
- expect { click_button 'Register' }.to change { User.count }.by(1)
+ expect { click_button 'Register' }.to change { User.count }.by(1)
- expect(current_path).to eq users_almost_there_path
- expect(page).to have_content("Please check your email to confirm your account")
+ expect(current_path).to eq dashboard_projects_path
+ expect(page).to have_content("Please check your email (#{new_user.email}) to verify that you own this address.")
+ end
end
end
diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb
index 8c697e33436..8b6bf54b642 100644
--- a/spec/features/users/snippets_spec.rb
+++ b/spec/features/users/snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Snippets tab on a user profile', :js do
diff --git a/spec/features/users/terms_spec.rb b/spec/features/users/terms_spec.rb
index 84df1016594..d44e3622a56 100644
--- a/spec/features/users/terms_spec.rb
+++ b/spec/features/users/terms_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Users > Terms' do
@@ -81,15 +83,18 @@ describe 'Users > Terms' do
enforce_terms
- within('.nav-sidebar') do
- click_link 'Issues'
- end
+ # Application settings are cached for a minute
+ Timecop.travel 2.minutes do
+ within('.nav-sidebar') do
+ click_link 'Issues'
+ end
- expect_to_be_on_terms_page
+ expect_to_be_on_terms_page
- click_button('Accept terms')
+ click_button('Accept terms')
- expect(current_path).to eq(project_issues_path(project))
+ expect(current_path).to eq(project_issues_path(project))
+ end
end
# Disabled until https://gitlab.com/gitlab-org/gitlab-ce/issues/37162 is solved properly
diff --git a/spec/features/users/user_browses_projects_on_user_page_spec.rb b/spec/features/users/user_browses_projects_on_user_page_spec.rb
index 6a9b281fb4c..9a1ee7715d6 100644
--- a/spec/features/users/user_browses_projects_on_user_page_spec.rb
+++ b/spec/features/users/user_browses_projects_on_user_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Users > User browses projects on user page', :js do
@@ -53,6 +55,19 @@ describe 'Users > User browses projects on user page', :js do
expect(page).to have_content(project2.name)
end
+ it 'does not have incorrectly interpolated message', :js do
+ project = create(:project, namespace: user.namespace, updated_at: 2.minutes.since)
+
+ sign_in(user)
+ visit user_path(user)
+ click_nav_link('Personal projects')
+
+ wait_for_requests
+
+ expect(page).to have_content(project.name)
+ expect(page).not_to have_content("_('Updated')")
+ end
+
context 'when not signed in' do
it 'renders user public project' do
visit user_path(user)
diff --git a/spec/finders/autocomplete/move_to_project_finder_spec.rb b/spec/finders/autocomplete/move_to_project_finder_spec.rb
index c3bc410a7f6..4a87b47bd08 100644
--- a/spec/finders/autocomplete/move_to_project_finder_spec.rb
+++ b/spec/finders/autocomplete/move_to_project_finder_spec.rb
@@ -6,9 +6,9 @@ describe Autocomplete::MoveToProjectFinder do
let(:no_access_project) { create(:project) }
let(:guest_project) { create(:project) }
- let(:reporter_project) { create(:project) }
- let(:developer_project) { create(:project) }
- let(:maintainer_project) { create(:project) }
+ let(:reporter_project) { create(:project, name: 'name') }
+ let(:developer_project) { create(:project, name: 'name2') }
+ let(:maintainer_project) { create(:project, name: 'name3') }
describe '#execute' do
context 'filter' do
@@ -20,14 +20,14 @@ describe Autocomplete::MoveToProjectFinder do
expect(finder.execute).to be_empty
end
- it 'returns projects equal or above Gitlab::Access::REPORTER ordered by id in descending order' do
+ it 'returns projects equal or above Gitlab::Access::REPORTER ordered by name' do
reporter_project.add_reporter(user)
developer_project.add_developer(user)
maintainer_project.add_maintainer(user)
finder = described_class.new(user, project_id: project.id)
- expect(finder.execute.to_a).to eq([maintainer_project, developer_project, reporter_project])
+ expect(finder.execute.to_a).to eq([reporter_project, developer_project, maintainer_project])
end
it 'does not include the source project' do
@@ -60,46 +60,32 @@ describe Autocomplete::MoveToProjectFinder do
expect(finder.execute.to_a).to eq([other_reporter_project])
end
- it 'returns a page of projects ordered by id in descending order' do
- allow(Kaminari.config).to receive(:default_per_page).and_return(2)
+ it 'returns a page of projects ordered by name' do
+ stub_const('Autocomplete::MoveToProjectFinder::LIMIT', 2)
- projects = create_list(:project, 2) do |project|
+ projects = create_list(:project, 3) do |project|
project.add_developer(user)
end
finder = described_class.new(user, project_id: project.id)
page = finder.execute.to_a
- expect(page.length).to eq(Kaminari.config.default_per_page)
- expect(page[0]).to eq(projects.last)
- end
-
- it 'returns projects after the given offset id' do
- reporter_project.add_reporter(user)
- developer_project.add_developer(user)
- maintainer_project.add_maintainer(user)
-
- expect(described_class.new(user, project_id: project.id, offset_id: maintainer_project.id).execute.to_a)
- .to eq([developer_project, reporter_project])
-
- expect(described_class.new(user, project_id: project.id, offset_id: developer_project.id).execute.to_a)
- .to eq([reporter_project])
-
- expect(described_class.new(user, project_id: project.id, offset_id: reporter_project.id).execute.to_a)
- .to be_empty
+ expected_projects = projects.sort_by(&:name).first(2)
+ expect(page.length).to eq(2)
+ expect(page).to eq(expected_projects)
end
end
context 'search' do
it 'returns projects matching a search query' do
- foo_project = create(:project)
+ foo_project = create(:project, name: 'foo')
foo_project.add_maintainer(user)
wadus_project = create(:project, name: 'wadus')
wadus_project.add_maintainer(user)
expect(described_class.new(user, project_id: project.id).execute.to_a)
- .to eq([wadus_project, foo_project])
+ .to eq([foo_project, wadus_project])
expect(described_class.new(user, project_id: project.id, search: 'wadus').execute.to_a)
.to eq([wadus_project])
diff --git a/spec/finders/autocomplete/users_finder_spec.rb b/spec/finders/autocomplete/users_finder_spec.rb
index bcde115b1a6..f3b54ca0461 100644
--- a/spec/finders/autocomplete/users_finder_spec.rb
+++ b/spec/finders/autocomplete/users_finder_spec.rb
@@ -50,7 +50,7 @@ describe Autocomplete::UsersFinder do
it { is_expected.to match_array([user1]) }
end
- context 'when passed a subgroup', :nested_groups do
+ context 'when passed a subgroup' do
let(:grandparent) { create(:group, :public) }
let(:parent) { create(:group, :public, parent: grandparent) }
let(:child) { create(:group, :public, parent: parent) }
diff --git a/spec/finders/award_emojis_finder_spec.rb b/spec/finders/award_emojis_finder_spec.rb
new file mode 100644
index 00000000000..ccac475daad
--- /dev/null
+++ b/spec/finders/award_emojis_finder_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojisFinder do
+ set(:issue_1) { create(:issue) }
+ set(:issue_1_thumbsup) { create(:award_emoji, name: 'thumbsup', awardable: issue_1) }
+ set(:issue_1_thumbsdown) { create(:award_emoji, name: 'thumbsdown', awardable: issue_1) }
+ # Create a matching set of emoji for a second issue.
+ # These should never appear in our finder results
+ set(:issue_2) { create(:issue) }
+ set(:issue_2_thumbsup) { create(:award_emoji, name: 'thumbsup', awardable: issue_2) }
+ set(:issue_2_thumbsdown) { create(:award_emoji, name: 'thumbsdown', awardable: issue_2) }
+
+ describe 'param validation' do
+ it 'raises an error if `name` is invalid' do
+ expect { described_class.new(issue_1, { name: 'invalid' }).execute }.to raise_error(
+ ArgumentError,
+ 'Invalid name param'
+ )
+ end
+
+ it 'raises an error if `awarded_by` is invalid' do
+ expectation = [ArgumentError, 'Invalid awarded_by param']
+
+ expect { described_class.new(issue_1, { awarded_by: issue_2 }).execute }.to raise_error(*expectation)
+ expect { described_class.new(issue_1, { awarded_by: 'not-an-id' }).execute }.to raise_error(*expectation)
+ expect { described_class.new(issue_1, { awarded_by: 1.123 }).execute }.to raise_error(*expectation)
+ end
+ end
+
+ describe '#execute' do
+ it 'scopes to the awardable' do
+ expect(described_class.new(issue_1).execute).to contain_exactly(
+ issue_1_thumbsup, issue_1_thumbsdown
+ )
+ end
+
+ it 'filters by emoji name' do
+ expect(described_class.new(issue_1, { name: 'thumbsup' }).execute).to contain_exactly(issue_1_thumbsup)
+ expect(described_class.new(issue_1, { name: '8ball' }).execute).to be_empty
+ end
+
+ it 'filters by user' do
+ expect(described_class.new(issue_1, { awarded_by: issue_1_thumbsup.user }).execute).to contain_exactly(issue_1_thumbsup)
+ expect(described_class.new(issue_1, { awarded_by: issue_2_thumbsup.user }).execute).to be_empty
+ end
+ end
+end
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
index 7d164539d9a..3fc86f3e408 100644
--- a/spec/finders/branches_finder_spec.rb
+++ b/spec/finders/branches_finder_spec.rb
@@ -62,6 +62,15 @@ describe BranchesFinder do
expect(result.count).to eq(0)
end
+
+ it 'filters branches by provided names' do
+ branches_finder = described_class.new(repository, { names: ['fix', 'csv', 'lfs', 'does-not-exist'] })
+
+ result = branches_finder.execute
+
+ expect(result.count).to eq(3)
+ expect(result.map(&:name)).to eq(%w{csv fix lfs})
+ end
end
context 'filter and sort' do
diff --git a/spec/finders/cluster_ancestors_finder_spec.rb b/spec/finders/cluster_ancestors_finder_spec.rb
index 750042b6b54..4aedb41d446 100644
--- a/spec/finders/cluster_ancestors_finder_spec.rb
+++ b/spec/finders/cluster_ancestors_finder_spec.rb
@@ -32,7 +32,7 @@ describe ClusterAncestorsFinder, '#execute' do
is_expected.to eq([project_cluster, group_cluster, instance_cluster])
end
- context 'nested groups', :nested_groups do
+ context 'nested groups' do
let(:group) { create(:group, parent: parent_group) }
let(:parent_group) { create(:group) }
@@ -65,7 +65,7 @@ describe ClusterAncestorsFinder, '#execute' do
is_expected.to eq([group_cluster, instance_cluster])
end
- context 'nested groups', :nested_groups do
+ context 'nested groups' do
let(:group) { create(:group, parent: parent_group) }
let(:parent_group) { create(:group) }
diff --git a/spec/finders/clusters/knative_services_finder_spec.rb b/spec/finders/clusters/knative_services_finder_spec.rb
index b731c2bd6bf..159724b3c1f 100644
--- a/spec/finders/clusters/knative_services_finder_spec.rb
+++ b/spec/finders/clusters/knative_services_finder_spec.rb
@@ -7,15 +7,19 @@ describe Clusters::KnativeServicesFinder do
include ReactiveCachingHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:service) { cluster.platform_kubernetes }
+ let(:service) { environment.deployment_platform }
let(:project) { cluster.cluster_project.project }
+ let(:environment) { create(:environment, project: project) }
+ let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
let(:namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
- cluster_project: cluster.cluster_project,
- project: project)
+ project: project,
+ environment: environment)
end
+ let(:finder) { described_class.new(cluster, environment) }
+
before do
stub_kubeclient_knative_services(namespace: namespace.namespace)
stub_kubeclient_service_pods(
@@ -35,7 +39,7 @@ describe Clusters::KnativeServicesFinder do
context 'when using synchronous reactive cache' do
before do
- synchronous_reactive_cache(cluster.knative_services_finder(project))
+ synchronous_reactive_cache(finder)
end
context 'when there are functions for cluster namespace' do
@@ -60,21 +64,21 @@ describe Clusters::KnativeServicesFinder do
end
describe '#service_pod_details' do
- subject { cluster.knative_services_finder(project).service_pod_details(project.name) }
+ subject { finder.service_pod_details(project.name) }
it_behaves_like 'a cached data'
end
describe '#services' do
- subject { cluster.knative_services_finder(project).services }
+ subject { finder.services }
it_behaves_like 'a cached data'
end
describe '#knative_detected' do
- subject { cluster.knative_services_finder(project).knative_detected }
+ subject { finder.knative_detected }
before do
- synchronous_reactive_cache(cluster.knative_services_finder(project))
+ synchronous_reactive_cache(finder)
end
context 'when knative is installed' do
@@ -85,7 +89,7 @@ describe Clusters::KnativeServicesFinder do
it { is_expected.to be_truthy }
it "discovers knative installation" do
expect { subject }
- .to change { cluster.kubeclient.knative_client.discovered }
+ .to change { finder.cluster.kubeclient.knative_client.discovered }
.from(false)
.to(true)
end
diff --git a/spec/finders/clusters/kubernetes_namespace_finder_spec.rb b/spec/finders/clusters/kubernetes_namespace_finder_spec.rb
new file mode 100644
index 00000000000..8beba0b99a4
--- /dev/null
+++ b/spec/finders/clusters/kubernetes_namespace_finder_spec.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Clusters::KubernetesNamespaceFinder do
+ let(:finder) do
+ described_class.new(
+ cluster,
+ project: project,
+ environment_slug: 'production',
+ allow_blank_token: allow_blank_token
+ )
+ end
+
+ def create_namespace(environment, with_token: true)
+ create(:cluster_kubernetes_namespace,
+ (with_token ? :with_token : :without_token),
+ cluster: cluster,
+ project: project,
+ environment: environment
+ )
+ end
+
+ describe '#execute' do
+ let(:production) { create(:environment, project: project, slug: 'production') }
+ let(:staging) { create(:environment, project: project, slug: 'staging') }
+
+ let(:cluster) { create(:cluster, :group, :provided_by_user) }
+ let(:project) { create(:project) }
+ let(:allow_blank_token) { false }
+
+ subject { finder.execute }
+
+ before do
+ allow(cluster).to receive(:namespace_per_environment?).and_return(namespace_per_environment)
+ end
+
+ context 'cluster supports separate namespaces per environment' do
+ let(:namespace_per_environment) { true }
+
+ context 'no persisted namespace is present' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'a namespace with an environment is present' do
+ context 'environment matches' do
+ let!(:namespace_with_environment) { create_namespace(production) }
+
+ it { is_expected.to eq namespace_with_environment }
+
+ context 'project cluster' do
+ let(:cluster) { create(:cluster, :project, :provided_by_user, projects: [project]) }
+
+ it { is_expected.to eq namespace_with_environment }
+ end
+
+ context 'service account token is blank' do
+ let!(:namespace_with_environment) { create_namespace(production, with_token: false) }
+
+ it { is_expected.to be_nil }
+
+ context 'allow_blank_token is true' do
+ let(:allow_blank_token) { true }
+
+ it { is_expected.to eq namespace_with_environment }
+ end
+ end
+ end
+
+ context 'environment does not match' do
+ let!(:namespace_with_environment) { create_namespace(staging) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+
+ context 'cluster does not support separate namespaces per environment' do
+ let(:namespace_per_environment) { false }
+
+ context 'no persisted namespace is present' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'a legacy namespace with no environment is present' do
+ let!(:legacy_namespace) { create_namespace(nil) }
+
+ it { is_expected.to eq legacy_namespace }
+
+ context 'project cluster' do
+ let(:cluster) { create(:cluster, :project, :provided_by_user, projects: [project]) }
+
+ it { is_expected.to eq legacy_namespace }
+ end
+
+ context 'service account token is blank' do
+ let!(:legacy_namespace) { create_namespace(nil, with_token: false) }
+
+ it { is_expected.to be_nil }
+
+ context 'allow_blank_token is true' do
+ let(:allow_blank_token) { true }
+
+ it { is_expected.to eq legacy_namespace }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/finders/container_repositories_finder_spec.rb b/spec/finders/container_repositories_finder_spec.rb
new file mode 100644
index 00000000000..deec62d6598
--- /dev/null
+++ b/spec/finders/container_repositories_finder_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ContainerRepositoriesFinder do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:project_repository) { create(:container_repository, project: project) }
+
+ describe '#execute' do
+ let(:id) { nil }
+
+ subject { described_class.new(id: id, container_type: container_type).execute }
+
+ context 'when container_type is group' do
+ let(:other_project) { create(:project, group: group) }
+
+ let(:other_repository) do
+ create(:container_repository, name: 'test_repository2', project: other_project)
+ end
+
+ let(:container_type) { :group }
+ let(:id) { group.id }
+
+ it { is_expected.to match_array([project_repository, other_repository]) }
+ end
+
+ context 'when container_type is project' do
+ let(:container_type) { :project }
+ let(:id) { project.id }
+
+ it { is_expected.to match_array([project_repository]) }
+ end
+
+ context 'with invalid id' do
+ let(:container_type) { :project }
+ let(:id) { 123456789 }
+
+ it 'raises an error' do
+ expect { subject.execute }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+end
diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb
index c28fd7cad11..5fb6739d6e2 100644
--- a/spec/finders/group_descendants_finder_spec.rb
+++ b/spec/finders/group_descendants_finder_spec.rb
@@ -19,7 +19,7 @@ describe GroupDescendantsFinder do
expect(finder.has_children?).to be_truthy
end
- context 'when there are subgroups', :nested_groups do
+ context 'when there are subgroups' do
it 'is true when there are projects' do
create(:group, parent: group)
@@ -99,7 +99,7 @@ describe GroupDescendantsFinder do
)
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let!(:subgroup1) { create(:group, parent: group, name: 'a', path: 'sub-a') }
let!(:subgroup2) { create(:group, parent: group, name: 'z', path: 'sub-z') }
@@ -126,7 +126,7 @@ describe GroupDescendantsFinder do
end
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let!(:project) { create(:project, namespace: group) }
let!(:subgroup) { create(:group, :private, parent: group) }
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
index 8975ea0f063..49b0e14241e 100644
--- a/spec/finders/group_members_finder_spec.rb
+++ b/spec/finders/group_members_finder_spec.rb
@@ -18,7 +18,7 @@ describe GroupMembersFinder, '#execute' do
expect(result.to_a).to match_array([member3, member2, member1])
end
- it 'returns members for nested group', :nested_groups do
+ it 'returns members for nested group' do
group.add_developer(user2)
nested_group.request_access(user4)
member1 = group.add_maintainer(user1)
@@ -30,7 +30,7 @@ describe GroupMembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member3, member4])
end
- it 'returns members for descendant groups if requested', :nested_groups do
+ it 'returns members for descendant groups if requested' do
member1 = group.add_maintainer(user2)
member2 = group.add_maintainer(user1)
nested_group.add_maintainer(user2)
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index f8fcc2d0e40..f4bd8a3f6ba 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -19,7 +19,7 @@ describe GroupProjectsFinder do
context "only owned" do
let(:options) { { only_owned: true } }
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -33,7 +33,7 @@ describe GroupProjectsFinder do
end
context "all" do
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -78,7 +78,7 @@ describe GroupProjectsFinder do
subgroup_private_project.add_maintainer(current_user)
end
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -96,7 +96,7 @@ describe GroupProjectsFinder do
current_user.update(external: true)
end
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -111,7 +111,7 @@ describe GroupProjectsFinder do
end
context "all" do
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -153,7 +153,7 @@ describe GroupProjectsFinder do
context "only owned" do
let(:options) { { only_owned: true } }
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb
index 367ca43bdfe..c8875d1f92d 100644
--- a/spec/finders/groups_finder_spec.rb
+++ b/spec/finders/groups_finder_spec.rb
@@ -65,7 +65,7 @@ describe GroupsFinder do
end
end
- context 'subgroups', :nested_groups do
+ context 'subgroups' do
let(:user) { create(:user) }
let!(:parent_group) { create(:group, :public) }
let!(:public_subgroup) { create(:group, :public, parent: parent_group) }
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index bf38d083ca6..879ff01f294 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -51,7 +51,7 @@ describe IssuesFinder do
end
end
- context 'when include_subgroup param is true', :nested_groups do
+ context 'when include_subgroup param is true' do
before do
params[:include_subgroups] = true
end
@@ -690,7 +690,6 @@ describe IssuesFinder do
let(:finder) { described_class.new(nil, params) }
before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
stub_feature_flags(attempt_group_search_optimizations: true)
end
@@ -702,18 +701,6 @@ describe IssuesFinder do
end
end
- context 'when the database is not Postgres' do
- let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
-
- before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
- end
-
- it 'returns false' do
- expect(finder.use_cte_for_search?).to be_falsey
- end
- end
-
context 'when the force_cte param is falsey' do
let(:params) { { search: 'foo' } }
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index 98b4933fef6..ba41ded112a 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -89,7 +89,7 @@ describe LabelsFinder do
end
end
- context 'when including labels from group ancestors', :nested_groups do
+ context 'when including labels from group ancestors' do
it 'returns labels from group and its ancestors' do
private_group_1.add_developer(user)
private_subgroup_1.add_developer(user)
@@ -108,7 +108,7 @@ describe LabelsFinder do
end
end
- context 'when including labels from group descendants', :nested_groups do
+ context 'when including labels from group descendants' do
it 'returns labels from group and its descendants' do
private_group_1.add_developer(user)
private_subgroup_1.add_developer(user)
@@ -128,7 +128,7 @@ describe LabelsFinder do
end
end
- context 'filtering by project_id', :nested_groups do
+ context 'filtering by project_id' do
context 'when include_ancestor_groups is true' do
let!(:sub_project) { create(:project, namespace: private_subgroup_1 ) }
let!(:project_label) { create(:label, project: sub_project, title: 'Label 5') }
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index 83348457caa..4203f58fe81 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -9,7 +9,7 @@ describe MembersFinder, '#execute' do
set(:user3) { create(:user) }
set(:user4) { create(:user) }
- it 'returns members for project and parent groups', :nested_groups do
+ it 'returns members for project and parent groups' do
nested_group.request_access(user1)
member1 = group.add_maintainer(user2)
member2 = nested_group.add_maintainer(user3)
@@ -20,7 +20,7 @@ describe MembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member2, member3])
end
- it 'includes nested group members if asked', :nested_groups do
+ it 'includes nested group members if asked' do
project = create(:project, namespace: group)
nested_group.request_access(user1)
member1 = group.add_maintainer(user2)
@@ -32,7 +32,7 @@ describe MembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member2, member3])
end
- context 'when include_invited_groups_members == true', :nested_groups do
+ context 'when include_invited_groups_members == true' do
subject { described_class.new(project, user2).execute(include_invited_groups_members: true) }
set(:linked_group) { create(:group, :public, :access_requestable) }
@@ -40,7 +40,7 @@ describe MembersFinder, '#execute' do
set(:linked_group_member) { linked_group.add_developer(user1) }
set(:nested_linked_group_member) { nested_linked_group.add_developer(user2) }
- it 'includes all the invited_groups members including members inherited from ancestor groups', :nested_groups do
+ it 'includes all the invited_groups members including members inherited from ancestor groups' do
create(:project_group_link, project: project, group: nested_linked_group)
expect(subject).to contain_exactly(linked_group_member, nested_linked_group_member)
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index da5e9dab058..78224f0b9da 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -42,7 +42,7 @@ describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request1, merge_request2)
end
- it 'filters by group including subgroups', :nested_groups do
+ it 'filters by group including subgroups' do
params = { group_id: group.id, include_subgroups: true }
merge_requests = described_class.new(user, params).execute
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 87bde4ca2f6..88906adfeeb 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -14,7 +14,7 @@ describe NotesFinder do
let!(:system_note) { create(:note_on_issue, project: project, system: true) }
it 'returns only user notes when using only_comments filter' do
- finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_comments])
+ finder = described_class.new(user, project: project, notes_filter: UserPreference::NOTES_FILTERS[:only_comments])
notes = finder.execute
@@ -22,7 +22,7 @@ describe NotesFinder do
end
it 'returns only system notes when using only_activity filters' do
- finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_activity])
+ finder = described_class.new(user, project: project, notes_filter: UserPreference::NOTES_FILTERS[:only_activity])
notes = finder.execute
@@ -30,7 +30,7 @@ describe NotesFinder do
end
it 'gets all notes' do
- finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:all_activity])
+ finder = described_class.new(user, project: project, notes_filter: UserPreference::NOTES_FILTERS[:all_activity])
notes = finder.execute
@@ -41,7 +41,7 @@ describe NotesFinder do
it 'finds notes on merge requests' do
create(:note_on_merge_request, project: project)
- notes = described_class.new(project, user).execute
+ notes = described_class.new(user, project: project).execute
expect(notes.count).to eq(1)
end
@@ -49,7 +49,7 @@ describe NotesFinder do
it 'finds notes on snippets' do
create(:note_on_project_snippet, project: project)
- notes = described_class.new(project, user).execute
+ notes = described_class.new(user, project: project).execute
expect(notes.count).to eq(1)
end
@@ -59,13 +59,13 @@ describe NotesFinder do
note = create(:note_on_commit, project: project)
params = { target_type: 'commit', target_id: note.noteable.id }
- notes = described_class.new(project, create(:user), params).execute
+ notes = described_class.new(create(:user), params).execute
expect(notes.count).to eq(0)
end
it 'succeeds when no notes found' do
- notes = described_class.new(project, create(:user)).execute
+ notes = described_class.new(create(:user), project: project).execute
expect(notes.count).to eq(0)
end
@@ -82,7 +82,7 @@ describe NotesFinder do
it 'publicly excludes notes on merge requests' do
create(:note_on_merge_request, project: project)
- notes = described_class.new(project, create(:user)).execute
+ notes = described_class.new(create(:user), project: project).execute
expect(notes.count).to eq(0)
end
@@ -90,7 +90,7 @@ describe NotesFinder do
it 'publicly excludes notes on issues' do
create(:note_on_issue, project: project)
- notes = described_class.new(project, create(:user)).execute
+ notes = described_class.new(create(:user), project: project).execute
expect(notes.count).to eq(0)
end
@@ -98,7 +98,7 @@ describe NotesFinder do
it 'publicly excludes notes on snippets' do
create(:note_on_project_snippet, project: project)
- notes = described_class.new(project, create(:user)).execute
+ notes = described_class.new(create(:user), project: project).execute
expect(notes.count).to eq(0)
end
@@ -110,7 +110,7 @@ describe NotesFinder do
let!(:note2) { create :note_on_commit, project: project }
it 'finds only notes for the selected type' do
- notes = described_class.new(project, user, target_type: 'issue').execute
+ notes = described_class.new(user, project: project, target_type: 'issue').execute
expect(notes).to eq([note1])
end
@@ -118,56 +118,51 @@ describe NotesFinder do
context 'for target' do
let(:project) { create(:project, :repository) }
- let(:note1) { create :note_on_commit, project: project }
- let(:note2) { create :note_on_commit, project: project }
+ let!(:note1) { create :note_on_commit, project: project }
+ let!(:note2) { create :note_on_commit, project: project }
let(:commit) { note1.noteable }
- let(:params) { { target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago.to_i } }
-
- before do
- note1
- note2
- end
+ let(:params) { { project: project, target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago.to_i } }
it 'finds all notes' do
- notes = described_class.new(project, user, params).execute
+ notes = described_class.new(user, params).execute
expect(notes.size).to eq(2)
end
it 'finds notes on merge requests' do
note = create(:note_on_merge_request, project: project)
- params = { target_type: 'merge_request', target_id: note.noteable.id }
+ params = { project: project, target_type: 'merge_request', target_id: note.noteable.id }
- notes = described_class.new(project, user, params).execute
+ notes = described_class.new(user, params).execute
expect(notes).to include(note)
end
it 'finds notes on snippets' do
note = create(:note_on_project_snippet, project: project)
- params = { target_type: 'snippet', target_id: note.noteable.id }
+ params = { project: project, target_type: 'snippet', target_id: note.noteable.id }
- notes = described_class.new(project, user, params).execute
+ notes = described_class.new(user, params).execute
expect(notes.count).to eq(1)
end
it 'finds notes on personal snippets' do
note = create(:note_on_personal_snippet)
- params = { target_type: 'personal_snippet', target_id: note.noteable_id }
+ params = { project: project, target_type: 'personal_snippet', target_id: note.noteable_id }
- notes = described_class.new(project, user, params).execute
+ notes = described_class.new(user, params).execute
expect(notes.count).to eq(1)
end
it 'raises an exception for an invalid target_type' do
params[:target_type] = 'invalid'
- expect { described_class.new(project, user, params).execute }.to raise_error("invalid target_type '#{params[:target_type]}'")
+ expect { described_class.new(user, params).execute }.to raise_error("invalid target_type '#{params[:target_type]}'")
end
it 'filters out old notes' do
note2.update_attribute(:updated_at, 2.hours.ago)
- notes = described_class.new(project, user, params).execute
+ notes = described_class.new(user, params).execute
expect(notes).to eq([note1])
end
@@ -175,25 +170,47 @@ describe NotesFinder do
let(:confidential_issue) { create(:issue, :confidential, project: project, author: user) }
let!(:confidential_note) { create(:note, noteable: confidential_issue, project: confidential_issue.project) }
- let(:params) { { target_id: confidential_issue.id, target_type: 'issue', last_fetched_at: 1.hour.ago.to_i } }
+ let(:params) { { project: confidential_issue.project, target_id: confidential_issue.id, target_type: 'issue', last_fetched_at: 1.hour.ago.to_i } }
it 'returns notes if user can see the issue' do
- expect(described_class.new(project, user, params).execute).to eq([confidential_note])
+ expect(described_class.new(user, params).execute).to eq([confidential_note])
end
it 'raises an error if user can not see the issue' do
user = create(:user)
- expect { described_class.new(project, user, params).execute }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { described_class.new(user, params).execute }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'raises an error for project members with guest role' do
user = create(:user)
project.add_guest(user)
- expect { described_class.new(project, user, params).execute }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { described_class.new(user, params).execute }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
+
+ context 'for explicit target' do
+ let(:project) { create(:project, :repository) }
+ let!(:note1) { create :note_on_commit, project: project, created_at: 1.day.ago, updated_at: 2.hours.ago }
+ let!(:note2) { create :note_on_commit, project: project }
+ let(:commit) { note1.noteable }
+ let(:params) { { project: project, target: commit } }
+
+ it 'returns the expected notes' do
+ expect(described_class.new(user, params).execute).to eq([note1, note2])
+ end
+
+ it 'returns the expected notes when last_fetched_at is given' do
+ params = { project: project, target: commit, last_fetched_at: 1.hour.ago.to_i }
+ expect(described_class.new(user, params).execute).to eq([note2])
+ end
+
+ it 'fails when nil is provided' do
+ params = { project: project, target: nil }
+ expect { described_class.new(user, params).execute }.to raise_error(RuntimeError)
+ end
+ end
end
describe '.search' do
@@ -201,17 +218,17 @@ describe NotesFinder do
let(:note) { create(:note_on_issue, note: 'WoW', project: project) }
it 'returns notes with matching content' do
- expect(described_class.new(note.project, nil, search: note.note).execute).to eq([note])
+ expect(described_class.new(nil, project: note.project, search: note.note).execute).to eq([note])
end
it 'returns notes with matching content regardless of the casing' do
- expect(described_class.new(note.project, nil, search: 'WOW').execute).to eq([note])
+ expect(described_class.new(nil, project: note.project, search: 'WOW').execute).to eq([note])
end
it 'returns commit notes user can access' do
note = create(:note_on_commit, project: project)
- expect(described_class.new(note.project, create(:user), search: note.note).execute).to eq([note])
+ expect(described_class.new(create(:user), project: note.project, search: note.note).execute).to eq([note])
end
context "confidential issues" do
@@ -220,27 +237,27 @@ describe NotesFinder do
let(:confidential_note) { create(:note, note: "Random", noteable: confidential_issue, project: confidential_issue.project) }
it "returns notes with matching content if user can see the issue" do
- expect(described_class.new(confidential_note.project, user, search: confidential_note.note).execute).to eq([confidential_note])
+ expect(described_class.new(user, project: confidential_note.project, search: confidential_note.note).execute).to eq([confidential_note])
end
it "does not return notes with matching content if user can not see the issue" do
user = create(:user)
- expect(described_class.new(confidential_note.project, user, search: confidential_note.note).execute).to be_empty
+ expect(described_class.new(user, project: confidential_note.project, search: confidential_note.note).execute).to be_empty
end
it "does not return notes with matching content for project members with guest role" do
user = create(:user)
project.add_guest(user)
- expect(described_class.new(confidential_note.project, user, search: confidential_note.note).execute).to be_empty
+ expect(described_class.new(user, project: confidential_note.project, search: confidential_note.note).execute).to be_empty
end
it "does not return notes with matching content for unauthenticated users" do
- expect(described_class.new(confidential_note.project, nil, search: confidential_note.note).execute).to be_empty
+ expect(described_class.new(nil, project: confidential_note.project, search: confidential_note.note).execute).to be_empty
end
end
context 'inlines SQL filters on subqueries for performance' do
- let(:sql) { described_class.new(note.project, nil, search: note.note).execute.to_sql }
+ let(:sql) { described_class.new(nil, project: note.project, search: note.note).execute.to_sql }
let(:number_of_noteable_types) { 4 }
specify 'project_id check' do
@@ -254,11 +271,11 @@ describe NotesFinder do
end
describe '#target' do
- subject { described_class.new(project, user, params) }
+ subject { described_class.new(user, params) }
context 'for a issue target' do
let(:issue) { create(:issue, project: project) }
- let(:params) { { target_type: 'issue', target_id: issue.id } }
+ let(:params) { { project: project, target_type: 'issue', target_id: issue.id } }
it 'returns the issue' do
expect(subject.target).to eq(issue)
@@ -267,7 +284,7 @@ describe NotesFinder do
context 'for a merge request target' do
let(:merge_request) { create(:merge_request, source_project: project) }
- let(:params) { { target_type: 'merge_request', target_id: merge_request.id } }
+ let(:params) { { project: project, target_type: 'merge_request', target_id: merge_request.id } }
it 'returns the merge_request' do
expect(subject.target).to eq(merge_request)
@@ -276,7 +293,7 @@ describe NotesFinder do
context 'for a snippet target' do
let(:snippet) { create(:project_snippet, project: project) }
- let(:params) { { target_type: 'snippet', target_id: snippet.id } }
+ let(:params) { { project: project, target_type: 'snippet', target_id: snippet.id } }
it 'returns the snippet' do
expect(subject.target).to eq(snippet)
@@ -286,7 +303,7 @@ describe NotesFinder do
context 'for a commit target' do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit }
- let(:params) { { target_type: 'commit', target_id: commit.id } }
+ let(:params) { { project: project, target_type: 'commit', target_id: commit.id } }
it 'returns the commit' do
expect(subject.target).to eq(commit)
@@ -298,24 +315,24 @@ describe NotesFinder do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
it 'finds issues by iid' do
- iid_params = { target_type: 'issue', target_iid: issue.iid }
- expect(described_class.new(project, user, iid_params).target).to eq(issue)
+ iid_params = { project: project, target_type: 'issue', target_iid: issue.iid }
+ expect(described_class.new(user, iid_params).target).to eq(issue)
end
it 'finds merge requests by iid' do
- iid_params = { target_type: 'merge_request', target_iid: merge_request.iid }
- expect(described_class.new(project, user, iid_params).target).to eq(merge_request)
+ iid_params = { project: project, target_type: 'merge_request', target_iid: merge_request.iid }
+ expect(described_class.new(user, iid_params).target).to eq(merge_request)
end
it 'returns nil if both target_id and target_iid are not given' do
- params_without_any_id = { target_type: 'issue' }
- expect(described_class.new(project, user, params_without_any_id).target).to be_nil
+ params_without_any_id = { project: project, target_type: 'issue' }
+ expect(described_class.new(user, params_without_any_id).target).to be_nil
end
it 'prioritizes target_id over target_iid' do
issue2 = create(:issue, project: project)
- iid_params = { target_type: 'issue', target_id: issue2.id, target_iid: issue.iid }
- expect(described_class.new(project, user, iid_params).target).to eq(issue2)
+ iid_params = { project: project, target_type: 'issue', target_id: issue2.id, target_iid: issue.iid }
+ expect(described_class.new(user, iid_params).target).to eq(issue2)
end
end
end
diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb
index 8aea45b457c..589e4000d46 100644
--- a/spec/finders/projects/serverless/functions_finder_spec.rb
+++ b/spec/finders/projects/serverless/functions_finder_spec.rb
@@ -11,12 +11,15 @@ describe Projects::Serverless::FunctionsFinder do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:service) { cluster.platform_kubernetes }
let(:project) { cluster.project }
+ let(:environment) { create(:environment, project: project) }
+ let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
+ let(:knative_services_finder) { environment.knative_services_finder }
let(:namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
- cluster_project: cluster.cluster_project,
- project: cluster.cluster_project.project)
+ project: project,
+ environment: environment)
end
before do
@@ -29,11 +32,9 @@ describe Projects::Serverless::FunctionsFinder do
end
context 'when reactive_caching has finished' do
- let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
-
before do
- allow_any_instance_of(Clusters::Cluster)
- .to receive(:knative_services_finder)
+ allow(Clusters::KnativeServicesFinder)
+ .to receive(:new)
.and_return(knative_services_finder)
synchronous_reactive_cache(knative_services_finder)
end
@@ -47,8 +48,6 @@ describe Projects::Serverless::FunctionsFinder do
end
context 'reactive_caching is finished and knative is installed' do
- let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
-
it 'returns true' do
stub_kubeclient_knative_services(namespace: namespace.namespace)
stub_kubeclient_service_pods(nil, namespace: namespace.namespace)
@@ -74,24 +73,24 @@ describe Projects::Serverless::FunctionsFinder do
it 'there are functions', :use_clean_rails_memory_store_caching do
stub_kubeclient_service_pods
- stub_reactive_cache(cluster.knative_services_finder(project),
+ stub_reactive_cache(knative_services_finder,
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
},
- *cluster.knative_services_finder(project).cache_args)
+ *knative_services_finder.cache_args)
expect(finder.execute).not_to be_empty
end
it 'has a function', :use_clean_rails_memory_store_caching do
stub_kubeclient_service_pods
- stub_reactive_cache(cluster.knative_services_finder(project),
+ stub_reactive_cache(knative_services_finder,
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
},
- *cluster.knative_services_finder(project).cache_args)
+ *knative_services_finder.cache_args)
result = finder.service(cluster.environment_scope, cluster.project.name)
expect(result).not_to be_empty
@@ -109,7 +108,7 @@ describe Projects::Serverless::FunctionsFinder do
let(:finder) { described_class.new(project) }
before do
- allow(finder).to receive(:prometheus_adapter).and_return(prometheus_adapter)
+ allow(Prometheus::AdapterService).to receive(:new).and_return(double(prometheus_adapter: prometheus_adapter))
allow(prometheus_adapter).to receive(:query).and_return(prometheus_empty_body('matrix'))
end
diff --git a/spec/finders/releases_finder_spec.rb b/spec/finders/releases_finder_spec.rb
index 32ee15134a2..5ffb8c74bf5 100644
--- a/spec/finders/releases_finder_spec.rb
+++ b/spec/finders/releases_finder_spec.rb
@@ -12,8 +12,8 @@ describe ReleasesFinder do
subject { described_class.new(project, user)}
before do
- v1_0_0.update_attribute(:created_at, 2.days.ago)
- v1_1_0.update_attribute(:created_at, 1.day.ago)
+ v1_0_0.update_attribute(:released_at, 2.days.ago)
+ v1_1_0.update_attribute(:released_at, 1.day.ago)
end
describe '#execute' do
@@ -30,7 +30,7 @@ describe ReleasesFinder do
project.add_developer(user)
end
- it 'sorts by creation date' do
+ it 'sorts by release date' do
releases = subject.execute
expect(releases).to be_present
diff --git a/spec/finders/runner_jobs_finder_spec.rb b/spec/finders/runner_jobs_finder_spec.rb
index 97304170c4e..01f45a37ba8 100644
--- a/spec/finders/runner_jobs_finder_spec.rb
+++ b/spec/finders/runner_jobs_finder_spec.rb
@@ -35,5 +35,27 @@ describe RunnerJobsFinder do
end
end
end
+
+ context 'when order_by and sort are specified' do
+ context 'when order_by id and sort is asc' do
+ let(:params) { { order_by: 'id', sort: 'asc' } }
+ let!(:jobs) { create_list(:ci_build, 2, runner: runner, project: project, user: create(:user)) }
+
+ it 'sorts as id: :asc' do
+ is_expected.to eq(jobs.sort_by(&:id))
+ end
+ end
+ end
+
+ context 'when order_by is specified and sort is not specified' do
+ context 'when order_by id and sort is not specified' do
+ let(:params) { { order_by: 'id' } }
+ let!(:jobs) { create_list(:ci_build, 2, runner: runner, project: project, user: create(:user)) }
+
+ it 'sorts as id: :desc' do
+ is_expected.to eq(jobs.sort_by(&:id).reverse)
+ end
+ end
+ end
end
end
diff --git a/spec/finders/starred_projects_finder_spec.rb b/spec/finders/starred_projects_finder_spec.rb
new file mode 100644
index 00000000000..7aa8251c3ab
--- /dev/null
+++ b/spec/finders/starred_projects_finder_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe StarredProjectsFinder do
+ let(:project1) { create(:project, :public, :empty_repo) }
+ let(:project2) { create(:project, :public, :empty_repo) }
+ let(:other_project) { create(:project, :public, :empty_repo) }
+
+ let(:user) { create(:user) }
+ let(:other_user) { create(:user) }
+
+ before do
+ user.toggle_star(project1)
+ user.toggle_star(project2)
+ end
+
+ describe '#execute' do
+ let(:finder) { described_class.new(user, params: {}, current_user: current_user) }
+
+ subject { finder.execute }
+
+ describe 'as same user' do
+ let(:current_user) { user }
+
+ it { is_expected.to contain_exactly(project1, project2) }
+ end
+
+ describe 'as other user' do
+ let(:current_user) { other_user }
+
+ it { is_expected.to contain_exactly(project1, project2) }
+ end
+
+ describe 'as no user' do
+ let(:current_user) { nil }
+
+ it { is_expected.to contain_exactly(project1, project2) }
+ end
+ end
+end
diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb
index 22318a9946a..f7b35e76925 100644
--- a/spec/finders/todos_finder_spec.rb
+++ b/spec/finders/todos_finder_spec.rb
@@ -36,7 +36,7 @@ describe TodosFinder do
expect(todos).to match_array([todo1])
end
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let(:subgroup) { create(:group, parent: group) }
let!(:todo3) { create(:todo, user: user, group: subgroup, target: issue) }
diff --git a/spec/finders/users_star_projects_finder_spec.rb b/spec/finders/users_star_projects_finder_spec.rb
new file mode 100644
index 00000000000..fb1d8088f44
--- /dev/null
+++ b/spec/finders/users_star_projects_finder_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe UsersStarProjectsFinder do
+ let(:project) { create(:project, :public, :empty_repo) }
+
+ let(:user) { create(:user) }
+ let(:private_user) { create(:user, private_profile: true) }
+ let(:other_user) { create(:user) }
+
+ before do
+ user.toggle_star(project)
+ private_user.toggle_star(project)
+ end
+
+ describe '#execute' do
+ let(:finder) { described_class.new(project, {}, current_user: current_user) }
+ let(:public_stars) { user.users_star_projects }
+ let(:private_stars) { private_user.users_star_projects }
+
+ subject { finder.execute }
+
+ describe 'as same user' do
+ let(:current_user) { private_user }
+
+ it { is_expected.to match_array(private_stars + public_stars) }
+ end
+
+ describe 'as other user' do
+ let(:current_user) { other_user }
+
+ it { is_expected.to match_array(public_stars) }
+ end
+
+ describe 'as no user' do
+ let(:current_user) { nil }
+
+ it { is_expected.to match_array(public_stars) }
+ end
+ end
+end
diff --git a/spec/fixtures/api/schemas/current-board.json b/spec/fixtures/api/schemas/current-board.json
new file mode 100644
index 00000000000..2ddc038e908
--- /dev/null
+++ b/spec/fixtures/api/schemas/current-board.json
@@ -0,0 +1,16 @@
+{
+ "type": "object",
+ "allOf": [
+ { "$ref": "board.json" },
+ {
+ "required" : [
+ "id",
+ "name"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" }
+ }
+ }
+ ]
+}
diff --git a/spec/fixtures/api/schemas/deployment.json b/spec/fixtures/api/schemas/deployment.json
index 0828f113495..fe725b97c21 100644
--- a/spec/fixtures/api/schemas/deployment.json
+++ b/spec/fixtures/api/schemas/deployment.json
@@ -3,6 +3,7 @@
"required": [
"sha",
"created_at",
+ "deployed_at",
"iid",
"tag",
"last?",
@@ -11,6 +12,7 @@
],
"properties": {
"created_at": { "type": "string" },
+ "deployed_at": { "type": ["string", "null"] },
"id": { "type": "integer" },
"iid": { "type": "integer" },
"last?": { "type": "boolean" },
diff --git a/spec/fixtures/api/schemas/entities/discussion.json b/spec/fixtures/api/schemas/entities/discussion.json
new file mode 100644
index 00000000000..bcc1db79e83
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/discussion.json
@@ -0,0 +1,67 @@
+{
+ "type": "object",
+ "required" : [
+ "id",
+ "notes",
+ "individual_note"
+ ],
+ "properties" : {
+ "id": { "type": "string" },
+ "individual_note": { "type": "boolean" },
+ "notes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties" : {
+ "id": { "type": "string" },
+ "type": { "type": ["string", "null"] },
+ "body": { "type": "string" },
+ "attachment": { "type": ["string", "null"]},
+ "award_emoji": { "type": "array" },
+ "author": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" },
+ "status_tooltip_html": { "type": ["string", "null"] },
+ "path": { "type": "string" }
+ },
+ "additionalProperties": false
+ },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "system": { "type": "boolean" },
+ "noteable_id": { "type": "integer" },
+ "noteable_iid": { "type": "integer" },
+ "noteable_type": { "type": "string" },
+ "resolved": { "type": "boolean" },
+ "resolvable": { "type": "boolean" },
+ "resolved_by": { "type": ["string", "null"] },
+ "note": { "type": "string" },
+ "note_html": { "type": "string" },
+ "current_user": { "type": "object" },
+ "suggestions": { "type": "array" },
+ "discussion_id": { "type": "string" },
+ "emoji_awardable": { "type": "boolean" },
+ "report_abuse_path": { "type": "string" },
+ "noteable_note_url": { "type": "string" },
+ "resolve_path": { "type": "string" },
+ "resolve_with_issue_path": { "type": "string" },
+ "cached_markdown_version": { "type": "integer" },
+ "human_access": { "type": ["string", "null"] },
+ "toggle_award_path": { "type": "string" },
+ "path": { "type": "string" }
+ },
+ "required": [
+ "id", "attachment", "author", "created_at", "updated_at",
+ "system", "noteable_id", "noteable_type"
+ ],
+ "additionalProperties": false
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/entities/discussions.json b/spec/fixtures/api/schemas/entities/discussions.json
new file mode 100644
index 00000000000..5a837429776
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/discussions.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "discussion.json" }
+}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_poll_cached_widget.json b/spec/fixtures/api/schemas/entities/merge_request_poll_cached_widget.json
new file mode 100644
index 00000000000..e8959307767
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/merge_request_poll_cached_widget.json
@@ -0,0 +1,46 @@
+{
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "description": { "type": ["string", "null"] },
+ "title": { "type": "string" },
+ "auto_merge_enabled": { "type": "boolean" },
+ "state": { "type": "string" },
+ "merge_commit_sha": { "type": ["string", "null"] },
+ "short_merge_commit_sha": { "type": ["string", "null"] },
+ "merge_error": { "type": ["string", "null"] },
+ "merge_status": { "type": "string" },
+ "merge_user_id": { "type": ["integer", "null"] },
+ "source_branch": { "type": "string" },
+ "source_project_id": { "type": "integer" },
+ "target_branch": { "type": "string" },
+ "target_branch_sha": { "type": "string" },
+ "target_project_id": { "type": "integer" },
+ "squash": { "type": "boolean" },
+ "rebase_in_progress": { "type": "boolean" },
+ "default_squash_commit_message": { "type": "string" },
+ "commits_count": { "type": ["integer", "null"] },
+ "merge_ongoing": { "type": "boolean" },
+ "work_in_progress": { "type": "boolean" },
+ "has_conflicts": { "type": "boolean" },
+ "can_be_merged": { "type": "boolean" },
+ "remove_source_branch": { "type": ["boolean", "null"] },
+ "source_branch_exists": { "type": "boolean" },
+ "branch_missing": { "type": "boolean" },
+ "commits_without_merge_commits": { "type": "array" },
+ "diff_head_sha": { "type": ["string", "null"] },
+ "metrics": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "merge_request_metrics.json" }
+ ]
+ },
+ "diverged_commits_count": { "type": "integer" },
+ "target_branch_commits_path": { "type": "string" },
+ "target_branch_tree_path": { "type": "string" },
+ "merge_commit_path": { "type": ["string", "null"] },
+ "source_branch_with_namespace_link": { "type": "string" },
+ "source_branch_path": { "type": "string" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json b/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json
new file mode 100644
index 00000000000..2052892dfa3
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json
@@ -0,0 +1,55 @@
+{
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "description": { "type": ["string", "null"] },
+ "title": { "type": "string" },
+ "auto_merge_strategy": { "type": ["string", "null"] },
+ "available_auto_merge_strategies": { "type": "array" },
+ "source_branch_protected": { "type": "boolean" },
+ "allow_collaboration": { "type": "boolean"},
+ "should_be_rebased": { "type": "boolean" },
+ "ff_only_enabled": { "type": ["boolean", false] },
+ "merge_user": { "type": ["object", "null"] },
+ "pipeline": { "type": ["object", "null"] },
+ "merge_pipeline": { "type": ["object", "null"] },
+ "default_merge_commit_message": { "type": ["string", "null"] },
+ "mergeable": { "type": "boolean" },
+ "default_merge_commit_message_with_description": { "type": "string" },
+ "mergeable_discussions_state": { "type": "boolean" },
+ "project_archived": { "type": "boolean" },
+ "only_allow_merge_if_pipeline_succeeds": { "type": "boolean" },
+ "has_ci": { "type": "boolean" },
+ "ci_status": { "type": ["string", "null"] },
+ "cancel_auto_merge_path": { "type": ["string", "null"] },
+ "test_reports_path": { "type": ["string", "null"] },
+ "can_receive_suggestion": { "type": "boolean" },
+ "create_issue_to_resolve_discussions_path": { "type": ["string", "null"] },
+ "current_user": {
+ "type": "object",
+ "required": [
+ "can_remove_source_branch",
+ "can_revert_on_current_merge_request",
+ "can_cherry_pick_on_current_merge_request"
+ ],
+ "properties": {
+ "can_remove_source_branch": { "type": "boolean" },
+ "can_revert_on_current_merge_request": { "type": ["boolean", "null"] },
+ "can_cherry_pick_on_current_merge_request": { "type": ["boolean", "null"] },
+ "can_create_note": { "type": "boolean" },
+ "can_create_issue": { "type": "boolean" },
+ "can_update": { "type": "boolean" }
+ },
+ "additionalProperties": false
+ },
+ "can_push_to_source_branch": { "type": "boolean" },
+ "new_blob_path": { "type": ["string", "null"] },
+ "rebase_path": { "type": ["string", "null"] },
+ "conflict_resolution_path": { "type": ["string", "null"] },
+ "remove_wip_path": { "type": ["string", "null"] },
+ "merge_path": { "type": ["string", "null"] },
+ "cherry_pick_in_fork_path": { "type": ["string", "null"] },
+ "revert_in_fork_path": { "type": ["string", "null"] }
+ }
+}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json
index 214b67a9a0f..9945de8a856 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json
@@ -2,6 +2,7 @@
"type": "object",
"properties" : {
"id": { "type": "integer" },
+ "iid": { "type": "integer" },
"type": { "type": "string" },
"author_id": { "type": "integer" },
"project_id": { "type": "integer" },
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index eac1dbc6474..779a47222b7 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -1,132 +1,35 @@
{
"type": "object",
- "properties" : {
- "id": { "type": "integer" },
- "iid": { "type": "integer" },
- "author_id": { "type": "integer" },
- "description": { "type": ["string", "null"] },
- "lock_version": { "type": ["string", "null"] },
- "milestone_id": { "type": ["string", "null"] },
- "position": { "type": "integer" },
- "state": { "type": "string" },
- "title": { "type": "string" },
- "updated_by_id": { "type": ["string", "null"] },
- "created_at": { "type": "string" },
- "updated_at": { "type": "string" },
- "time_estimate": { "type": "integer" },
- "total_time_spent": { "type": "integer" },
- "human_time_estimate": { "type": ["integer", "null"] },
- "human_total_time_spent": { "type": ["integer", "null"] },
- "milestone": { "type": ["object", "null"] },
- "labels": { "type": ["array", "null"] },
- "in_progress_merge_commit_sha": { "type": ["string", "null"] },
- "merge_error": { "type": ["string", "null"] },
- "merge_commit_sha": { "type": ["string", "null"] },
- "short_merge_commit_sha": { "type": ["string", "null"] },
- "merge_params": { "type": ["object", "null"] },
- "merge_status": { "type": "string" },
- "merge_user_id": { "type": ["integer", "null"] },
- "merge_when_pipeline_succeeds": { "type": "boolean" },
- "source_branch": { "type": "string" },
- "source_project_id": { "type": "integer" },
- "source_project_full_path": { "type": ["string", "null"]},
- "target_branch": { "type": "string" },
- "target_project_id": { "type": "integer" },
- "target_project_full_path": { "type": ["string", "null"]},
- "allow_collaboration": { "type": "boolean"},
- "metrics": {
- "oneOf": [
- { "type": "null" },
- { "$ref": "merge_request_metrics.json" }
- ]
- },
- "author": { "type": ["object", "null"] },
- "merge_user": { "type": ["object", "null"] },
- "diff_head_sha": { "type": ["string", "null"] },
- "diff_head_commit_short_id": { "type": ["string", "null"] },
- "default_merge_commit_message": { "type": ["string", "null"] },
- "pipeline": { "type": ["object", "null"] },
- "merge_pipeline": { "type": ["object", "null"] },
- "work_in_progress": { "type": "boolean" },
- "source_branch_exists": { "type": "boolean" },
- "mergeable_discussions_state": { "type": "boolean" },
- "conflicts_can_be_resolved_in_ui": { "type": "boolean" },
- "branch_missing": { "type": "boolean" },
- "commits_count": { "type": ["integer", "null"] },
- "has_conflicts": { "type": "boolean" },
- "can_be_merged": { "type": "boolean" },
- "mergeable": { "type": "boolean" },
- "project_archived": { "type": "boolean" },
- "only_allow_merge_if_pipeline_succeeds": { "type": "boolean" },
- "has_ci": { "type": "boolean" },
- "ci_status": { "type": ["string", "null"] },
- "issues_links": {
- "type": "object",
- "required": ["closing", "mentioned_but_not_closing", "assign_to_closing"],
+ "allOf": [
+ { "$ref": "merge_request_poll_cached_widget.json" },
+ { "$ref": "merge_request_poll_widget.json" },
+ {
"properties" : {
- "closing": { "type": "string" },
- "mentioned_but_not_closing": { "type": "string" },
- "assign_to_closing": { "type": ["string", "null"] }
- },
- "additionalProperties": false
- },
- "source_branch_with_namespace_link": { "type": "string" },
- "current_user": {
- "type": "object",
- "required": [
- "can_remove_source_branch",
- "can_revert_on_current_merge_request",
- "can_cherry_pick_on_current_merge_request"
- ],
- "properties": {
- "can_remove_source_branch": { "type": "boolean" },
- "can_revert_on_current_merge_request": { "type": ["boolean", "null"] },
- "can_cherry_pick_on_current_merge_request": { "type": ["boolean", "null"] },
- "can_create_note": { "type": "boolean" },
- "can_create_issue": { "type": "boolean" },
- "can_update": { "type": "boolean" }
- },
- "additionalProperties": false
- },
- "target_branch_commits_path": { "type": "string" },
- "target_branch_tree_path": { "type": "string" },
- "source_branch_path": { "type": "string" },
- "conflict_resolution_path": { "type": ["string", "null"] },
- "cancel_merge_when_pipeline_succeeds_path": { "type": ["string", "null"] },
- "create_issue_to_resolve_discussions_path": { "type": ["string", "null"] },
- "merge_path": { "type": ["string", "null"] },
- "cherry_pick_in_fork_path": { "type": ["string", "null"] },
- "revert_in_fork_path": { "type": ["string", "null"] },
- "email_patches_path": { "type": "string" },
- "plain_diff_path": { "type": "string" },
- "merge_request_basic_path": { "type": "string" },
- "merge_request_widget_path": { "type": "string" },
- "new_blob_path": { "type": ["string", "null"] },
- "merge_check_path": { "type": "string" },
- "ci_environments_status_path": { "type": "string" },
- "default_merge_commit_message_with_description": { "type": "string" },
- "default_squash_commit_message": { "type": "string" },
- "commits_without_merge_commits": { "type": "array" },
- "diverged_commits_count": { "type": "integer" },
- "commit_change_content_path": { "type": "string" },
- "merge_commit_path": { "type": ["string", "null"] },
- "remove_wip_path": { "type": ["string", "null"] },
- "commits_count": { "type": "integer" },
- "remove_source_branch": { "type": ["boolean", "null"] },
- "merge_ongoing": { "type": "boolean" },
- "ff_only_enabled": { "type": ["boolean", false] },
- "should_be_rebased": { "type": "boolean" },
- "create_note_path": { "type": ["string", "null"] },
- "preview_note_path": { "type": ["string", "null"] },
- "rebase_commit_sha": { "type": ["string", "null"] },
- "rebase_in_progress": { "type": "boolean" },
- "can_push_to_source_branch": { "type": "boolean" },
- "rebase_path": { "type": ["string", "null"] },
- "squash": { "type": "boolean" },
- "test_reports_path": { "type": ["string", "null"] },
- "can_receive_suggestion": { "type": "boolean" },
- "source_branch_protected": { "type": "boolean" },
- "conflicts_docs_path": { "type": ["string", "null"] },
- "merge_request_pipelines_docs_path": { "type": ["string", "null"] }
- }
+ "merge_params": { "type": ["object", "null"] },
+ "source_project_full_path": { "type": ["string", "null"]},
+ "target_project_full_path": { "type": ["string", "null"]},
+ "email_patches_path": { "type": "string" },
+ "plain_diff_path": { "type": "string" },
+ "merge_request_basic_path": { "type": "string" },
+ "merge_request_widget_path": { "type": "string" },
+ "merge_request_cached_widget_path": { "type": "string" },
+ "create_note_path": { "type": ["string", "null"] },
+ "commit_change_content_path": { "type": "string" },
+ "preview_note_path": { "type": ["string", "null"] },
+ "conflicts_docs_path": { "type": ["string", "null"] },
+ "merge_request_pipelines_docs_path": { "type": ["string", "null"] },
+ "ci_environments_status_path": { "type": "string" },
+ "issues_links": {
+ "type": "object",
+ "required": ["closing", "mentioned_but_not_closing", "assign_to_closing"],
+ "properties" : {
+ "closing": { "type": "string" },
+ "mentioned_but_not_closing": { "type": "string" },
+ "assign_to_closing": { "type": ["string", "null"] }
+ },
+ "additionalProperties": false
+ }
+ }
+ }
+ ]
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/group_labels.json b/spec/fixtures/api/schemas/public_api/v4/group_labels.json
deleted file mode 100644
index fbde45f2904..00000000000
--- a/spec/fixtures/api/schemas/public_api/v4/group_labels.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "type": "array",
- "items": {
- "type": "object",
- "properties" : {
- "id" : { "type": "integer" },
- "name" : { "type": "string "},
- "color" : { "type": "string "},
- "text_color" : { "type": "string "},
- "description" : { "type": "string "},
- "open_issues_count" : { "type": "integer "},
- "closed_issues_count" : { "type": "integer "},
- "open_merge_requests_count" : { "type": "integer "},
- "subscribed" : { "type": "boolean" },
- "priority" : { "type": "null" }
- },
- "additionalProperties": false
- }
-}
diff --git a/spec/fixtures/api/schemas/public_api/v4/labels/label.json b/spec/fixtures/api/schemas/public_api/v4/labels/label.json
new file mode 100644
index 00000000000..a116b33572d
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/labels/label.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" },
+ "color": { "type": "string" },
+ "text_color": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "subscribed": { "type": "boolean" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json b/spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json
new file mode 100644
index 00000000000..2331932e07d
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json
@@ -0,0 +1,16 @@
+{
+ "type": "object",
+ "properties": {
+ "allOf": [
+ { "$ref": "label.json" },
+ {
+ "type": "object",
+ "properties": {
+ "open_issues_count": { "type": "integer" },
+ "closed_issues_count": { "type": "integer" },
+ "open_merge_requests_count": { "type": "integer" }
+ }
+ }
+ ]
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/labels/project_label.json b/spec/fixtures/api/schemas/public_api/v4/labels/project_label.json
new file mode 100644
index 00000000000..a9a065ee71f
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/labels/project_label.json
@@ -0,0 +1,15 @@
+{
+ "type": "object",
+ "properties": {
+ "allOf": [
+ { "$ref": "label.json" },
+ {
+ "type": "object",
+ "properties": {
+ "priority": { "type": ["integer", "null"] },
+ "is_project_label": { "type": "boolean" }
+ }
+ }
+ ]
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json b/spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json
new file mode 100644
index 00000000000..87b90b2b3b5
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json
@@ -0,0 +1,9 @@
+{
+ "type": "object",
+ "properties": {
+ "allOf": [
+ { "$ref": "project_label.json" },
+ { "$ref": "label_with_counts.json" }
+ ]
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json
index 6ea0781c1ed..ec3fa59cdb1 100644
--- a/spec/fixtures/api/schemas/public_api/v4/release.json
+++ b/spec/fixtures/api/schemas/public_api/v4/release.json
@@ -1,12 +1,14 @@
{
"type": "object",
- "required": ["name", "tag_name", "commit"],
+ "required": ["name", "tag_name", "commit", "released_at"],
"properties": {
"name": { "type": "string" },
"tag_name": { "type": "string" },
"description": { "type": "string" },
"description_html": { "type": "string" },
"created_at": { "type": "date" },
+ "released_at": { "type": "date" },
+ "upcoming_release": { "type": "boolean" },
"commit": {
"oneOf": [{ "type": "null" }, { "$ref": "commit/basic.json" }]
},
diff --git a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
index e78398ad1d5..0c1e8fd5fb3 100644
--- a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
+++ b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
@@ -1,11 +1,13 @@
{
"type": "object",
- "required": ["name"],
+ "required": ["name", "released_at"],
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"description_html": { "type": "string" },
"created_at": { "type": "date" },
+ "released_at": { "type": "date" },
+ "upcoming_release": { "type": "boolean" },
"author": {
"oneOf": [{ "type": "null" }, { "$ref": "../user/basic.json" }]
},
diff --git a/spec/fixtures/api/schemas/registry/repository.json b/spec/fixtures/api/schemas/registry/repository.json
index e0fd4620c43..d0a068b65a7 100644
--- a/spec/fixtures/api/schemas/registry/repository.json
+++ b/spec/fixtures/api/schemas/registry/repository.json
@@ -17,6 +17,9 @@
"path": {
"type": "string"
},
+ "project_id": {
+ "type": "integer"
+ },
"location": {
"type": "string"
},
@@ -28,7 +31,8 @@
},
"destroy_path": {
"type": "string"
- }
+ },
+ "tags": { "$ref": "tags.json" }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/clusters/sample_key.key b/spec/fixtures/clusters/sample_key.key
new file mode 100644
index 00000000000..4ddb20b0922
--- /dev/null
+++ b/spec/fixtures/clusters/sample_key.key
@@ -0,0 +1,9 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOgIBAAJBAMA5sXIBE0HwgIB40iNidN4PGWzOyLQK0bsdOBNgpEXkDlZBvnak
+OUgAPF+rME4PB0Yl415DabUI40T5UNmlwxcCAwEAAQJAZtY2pSwIFm3JAXIh0cZZ
+iXcAfiJ+YzuqinUOS+eW2sBCAEzjcARlU/o6sFQgtsOi4FOMczAd1Yx8UDMXMmrw
+2QIhAPBgVhJiTF09pdmeFWutCvTJDlFFAQNbrbo2X2x/9WF9AiEAzLgqMKeStSRu
+H9N16TuDrUoO8R+DPqriCwkKrSHaWyMCIFzMhE4inuKcSywBaLmiG4m3GQzs++Al
+A6PRG/PSTpQtAiBxtBg6zdf+JC3GH3zt/dA0/10tL4OF2wORfYQghRzyYQIhAL2l
+0ZQW+yLIZAGrdBFWYEAa52GZosncmzBNlsoTgwE4
+-----END RSA PRIVATE KEY----- \ No newline at end of file
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json
index 1ee1205e29a..5d779a323c2 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json
@@ -1,6 +1,10 @@
{
"type": "object",
- "required": ["dashboard", "priority", "panel_groups"],
+ "required": [
+ "dashboard",
+ "priority",
+ "panel_groups"
+ ],
"properties": {
"dashboard": { "type": "string" },
"priority": { "type": "number" },
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
index 33393805464..9c1be32645a 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
@@ -16,7 +16,8 @@
"unit": { "type": "string" },
"label": { "type": "string" },
"track": { "type": "string" },
- "prometheus_endpoint_path": { "type": "string" }
+ "prometheus_endpoint_path": { "type": "string" },
+ "metric_id": { "type": "number" }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/phabricator_responses/user.search.json b/spec/fixtures/phabricator_responses/user.search.json
new file mode 100644
index 00000000000..f3ec653a23e
--- /dev/null
+++ b/spec/fixtures/phabricator_responses/user.search.json
@@ -0,0 +1,62 @@
+{
+ "result": {
+ "data": [
+ {
+ "id": 1,
+ "type": "USER",
+ "phid": "PHID-USER-hohoho",
+ "fields": {
+ "username": "jane",
+ "realName": "Jane Doe",
+ "roles": [
+ "admin",
+ "verified",
+ "approved",
+ "activated"
+ ],
+ "dateCreated": 1405970599,
+ "dateModified": 1406705963,
+ "policy": {
+ "view": "public",
+ "edit": "no-one"
+ }
+ },
+ "attachments": {}
+ },
+ {
+ "id": 2,
+ "type": "USER",
+ "phid": "PHID-USER-hihihi",
+ "fields": {
+ "username": "john",
+ "realName": "John Doe",
+ "roles": [
+ "admin",
+ "verified",
+ "approved",
+ "activated"
+ ],
+ "dateCreated": 1403609184,
+ "dateModified": 1559138722,
+ "policy": {
+ "view": "public",
+ "edit": "no-one"
+ }
+ },
+ "attachments": {}
+ }
+ ],
+ "maps": {},
+ "query": {
+ "queryKey": null
+ },
+ "cursor": {
+ "limit": "100",
+ "after": null,
+ "before": null,
+ "order": null
+ }
+ },
+ "error_code": null,
+ "error_info": null
+}
diff --git a/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json
deleted file mode 100644
index 1e62d020026..00000000000
--- a/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json
+++ /dev/null
@@ -1,422 +0,0 @@
-{
- "version": "2.1",
- "vulnerabilities": [
- {
- "category": "dependency_scanning",
- "name": "Vulnerabilities in libxml2",
- "message": "Vulnerabilities in libxml2 in nokogiri",
- "description": " The version of libxml2 packaged with Nokogiri contains several vulnerabilities.\r\n Nokogiri has mitigated these issues by upgrading to libxml 2.9.5.\r\n\r\n It was discovered that a type confusion error existed in libxml2. An\r\n attacker could use this to specially construct XML data that\r\n could cause a denial of service or possibly execute arbitrary\r\n code. (CVE-2017-0663)\r\n\r\n It was discovered that libxml2 did not properly validate parsed entity\r\n references. An attacker could use this to specially construct XML\r\n data that could expose sensitive information. (CVE-2017-7375)\r\n\r\n It was discovered that a buffer overflow existed in libxml2 when\r\n handling HTTP redirects. An attacker could use this to specially\r\n construct XML data that could cause a denial of service or possibly\r\n execute arbitrary code. (CVE-2017-7376)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered a buffer overflow in\r\n libxml2 when handling elements. An attacker could use this to specially\r\n construct XML data that could cause a denial of service or possibly\r\n execute arbitrary code. (CVE-2017-9047)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered a buffer overread\r\n in libxml2 when handling elements. An attacker could use this\r\n to specially construct XML data that could cause a denial of\r\n service. (CVE-2017-9048)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered multiple buffer overreads\r\n in libxml2 when handling parameter-entity references. An attacker\r\n could use these to specially construct XML data that could cause a\r\n denial of service. (CVE-2017-9049, CVE-2017-9050)",
- "cve": "rails/Gemfile.lock:nokogiri:gemnasium:06565b64-486d-4326-b906-890d9915804d",
- "severity": "Unknown",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d",
- "value": "06565b64-486d-4326-b906-890d9915804d",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "usn",
- "name": "USN-3424-1",
- "value": "USN-3424-1",
- "url": "https://usn.ubuntu.com/3424-1/"
- }
- ],
- "links": [
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Infinite recursion in parameter entities",
- "message": "Infinite recursion in parameter entities in nokogiri",
- "description": "libxml2 incorrectly handles certain parameter entities. An attacker can leverage this with specially constructed XML data to cause libxml2 to consume resources, leading to a denial of service.",
- "cve": "rails/Gemfile.lock:nokogiri:gemnasium:6a0d56f6-2441-492a-9b14-edb95ac31919",
- "severity": "Unknown",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-6a0d56f6-2441-492a-9b14-edb95ac31919",
- "value": "6a0d56f6-2441-492a-9b14-edb95ac31919",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-16932",
- "value": "CVE-2017-16932",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16932"
- }
- ],
- "links": [
- {
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16932"
- },
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1714"
- },
- {
- "url": "https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-16932.html"
- },
- {
- "url": "https://usn.ubuntu.com/usn/usn-3504-1/"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Denial of Service",
- "message": "Denial of Service in nokogiri",
- "description": "libxml2 incorrectly handles certain files. An attacker can use this issue with specially constructed XML data to cause libxml2 to consume resources, leading to a denial of service.\r\n\r\n",
- "cve": "rails/Gemfile.lock:nokogiri:gemnasium:78658378-bd8f-4d79-81c8-07c419302426",
- "severity": "Unknown",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-78658378-bd8f-4d79-81c8-07c419302426",
- "value": "78658378-bd8f-4d79-81c8-07c419302426",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-15412",
- "value": "CVE-2017-15412",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15412"
- }
- ],
- "links": [
- {
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15412"
- },
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1714"
- },
- {
- "url": "https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-15412.html"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Bypass of a protection mechanism in libxslt",
- "message": "Bypass of a protection mechanism in libxslt in nokogiri",
- "description": "libxslt through 1.1.33 allows bypass of a protection mechanism because callers of xsltCheckRead and xsltCheckWrite permit access even upon receiving a -1 error code. xsltCheckRead can return -1 for a crafted URL that is not actually invalid and is subsequently loaded. Vendored version of libxslt has been patched to remediate this vulnerability. Note that this patch is not yet (as of 2019-04-22) in an upstream release of libxslt.",
- "cve": "rails/Gemfile.lock:nokogiri:gemnasium:1a2e2e6e-67ba-4142-bfa1-3391f5416e4c",
- "severity": "Unknown",
- "solution": "Upgrade to latest version if using vendored version of libxslt OR update the system library libxslt to a fixed version",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-1a2e2e6e-67ba-4142-bfa1-3391f5416e4c",
- "value": "1a2e2e6e-67ba-4142-bfa1-3391f5416e4c",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2019-11068",
- "value": "CVE-2019-11068",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11068"
- }
- ],
- "links": [
- {
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11068"
- },
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1892"
- },
- {
- "url": "https://people.canonical.com/~ubuntu-security/cve/CVE-2019-11068"
- },
- {
- "url": "https://security-tracker.debian.org/tracker/CVE-2019-11068"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Regular Expression Denial of Service",
- "message": "Regular Expression Denial of Service in debug",
- "description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.",
- "cve": "yarn/yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a",
- "severity": "Unknown",
- "solution": "Upgrade to latest versions.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "yarn/yarn.lock",
- "dependency": {
- "package": {
- "name": "debug"
- },
- "version": "1.0.5"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a",
- "value": "37283ed4-0380-40d7-ada7-2d994afcc62a",
- "url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories"
- }
- ],
- "links": [
- {
- "url": "https://github.com/visionmedia/debug/issues/501"
- },
- {
- "url": "https://github.com/visionmedia/debug/pull/504"
- },
- {
- "url": "https://nodesecurity.io/advisories/534"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Authentication bypass via incorrect DOM traversal and canonicalization",
- "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
- "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
- "cve": "yarn/yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98",
- "severity": "Unknown",
- "solution": "Upgrade to fixed version.\r\n",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "yarn/yarn.lock",
- "dependency": {
- "package": {
- "name": "saml2-js"
- },
- "version": "1.5.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
- "value": "9952e574-7b5b-46fa-a270-aeb694198a98",
- "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-11429",
- "value": "CVE-2017-11429",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
- }
- ],
- "links": [
- {
- "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
- },
- {
- "url": "https://github.com/Clever/saml2/issues/127"
- },
- {
- "url": "https://www.kb.cert.org/vuls/id/475445"
- }
- ]
- }
- ],
- "remediations": [],
- "dependency_files": [
- {
- "path": "rails/Gemfile.lock",
- "package_manager": "bundler",
- "dependencies": [
- {
- "package": {
- "name": "mini_portile2"
- },
- "version": "2.2.0"
- },
- {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- ]
- },
- {
- "path": "yarn/yarn.lock",
- "package_manager": "yarn",
- "dependencies": [
- {
- "package": {
- "name": "async"
- },
- "version": "0.2.10"
- },
- {
- "package": {
- "name": "async"
- },
- "version": "1.5.2"
- },
- {
- "package": {
- "name": "debug"
- },
- "version": "1.0.5"
- },
- {
- "package": {
- "name": "ejs"
- },
- "version": "0.8.8"
- },
- {
- "package": {
- "name": "ms"
- },
- "version": "2.0.0"
- },
- {
- "package": {
- "name": "node-forge"
- },
- "version": "0.2.24"
- },
- {
- "package": {
- "name": "saml2-js"
- },
- "version": "1.5.0"
- },
- {
- "package": {
- "name": "sax"
- },
- "version": "1.2.4"
- },
- {
- "package": {
- "name": "underscore"
- },
- "version": "1.9.1"
- },
- {
- "package": {
- "name": "underscore"
- },
- "version": "1.6.0"
- },
- {
- "package": {
- "name": "xml-crypto"
- },
- "version": "0.8.5"
- },
- {
- "package": {
- "name": "xml-encryption"
- },
- "version": "0.7.4"
- },
- {
- "package": {
- "name": "xml2js"
- },
- "version": "0.4.19"
- },
- {
- "package": {
- "name": "xmlbuilder"
- },
- "version": "2.1.0"
- },
- {
- "package": {
- "name": "xmlbuilder"
- },
- "version": "9.0.7"
- },
- {
- "package": {
- "name": "xmldom"
- },
- "version": "0.1.19"
- },
- {
- "package": {
- "name": "xmldom"
- },
- "version": "0.1.27"
- },
- {
- "package": {
- "name": "xpath.js"
- },
- "version": "1.1.0"
- },
- {
- "package": {
- "name": "xpath"
- },
- "version": "0.0.5"
- }
- ]
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json
deleted file mode 100644
index ce66f562175..00000000000
--- a/spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json
+++ /dev/null
@@ -1,178 +0,0 @@
-[
- {
- "category": "dependency_scanning",
- "name": "io.netty/netty - CVE-2014-3488",
- "message": "DoS by CPU exhaustion when using malicious SSL packets",
- "cve": "app/pom.xml:io.netty/netty@3.9.1.Final:CVE-2014-3488",
- "severity": "Unknown",
- "solution": "Upgrade to the latest version",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/pom.xml",
- "dependency": {
- "package": {
- "name": "io.netty/netty"
- },
- "version": "3.9.1.Final"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "value": "d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "url": "https://deps.sec.gitlab.com/packages/maven/io.netty/netty/versions/3.9.1.Final/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2014-3488",
- "value": "CVE-2014-3488",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3488"
- }
- ],
- "links": [
- {
- "url": "https://bugzilla.redhat.com/CVE-2014-3488"
- },
- {
- "url": "http://netty.io/news/2014/06/11/3.html"
- },
- {
- "url": "https://github.com/netty/netty/issues/2562"
- }
- ],
- "priority": "Unknown",
- "file": "app/pom.xml",
- "url": "https://bugzilla.redhat.com/CVE-2014-3488",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "Django - CVE-2017-12794",
- "message": "Possible XSS in traceback section of technical 500 debug page",
- "cve": "app/requirements.txt:Django@1.11.3:CVE-2017-12794",
- "severity": "Unknown",
- "solution": "Upgrade to latest version or apply patch.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/requirements.txt",
- "dependency": {
- "package": {
- "name": "Django"
- },
- "version": "1.11.3"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-6162a015-8635-4a15-8d7c-dc9321db366f",
- "value": "6162a015-8635-4a15-8d7c-dc9321db366f",
- "url": "https://deps.sec.gitlab.com/packages/pypi/Django/versions/1.11.3/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-12794",
- "value": "CVE-2017-12794",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12794"
- }
- ],
- "links": [
- {
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/"
- }
- ],
- "priority": "Unknown",
- "file": "app/requirements.txt",
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "nokogiri - USN-3424-1",
- "message": "Vulnerabilities in libxml2",
- "cve": "rails/Gemfile.lock:nokogiri@1.8.0:USN-3424-1",
- "severity": "Unknown",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d",
- "value": "06565b64-486d-4326-b906-890d9915804d",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "usn",
- "name": "USN-3424-1",
- "value": "USN-3424-1",
- "url": "https://usn.ubuntu.com/3424-1/"
- }
- ],
- "links": [
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
- }
- ],
- "priority": "Unknown",
- "file": "rails/Gemfile.lock",
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "ffi - CVE-2018-1000201",
- "message": "ruby-ffi DDL loading issue on Windows OS",
- "cve": "ffi:1.9.18:CVE-2018-1000201",
- "severity": "High",
- "solution": "upgrade to \u003e= 1.9.24",
- "scanner": {
- "id": "bundler_audit",
- "name": "bundler-audit"
- },
- "location": {
- "file": "sast-sample-rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "ffi"
- },
- "version": "1.9.18"
- }
- },
- "identifiers": [
- {
- "type": "cve",
- "name": "CVE-2018-1000201",
- "value": "CVE-2018-1000201",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201"
- }
- ],
- "links": [
- {
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24"
- }
- ],
- "priority": "High",
- "file": "sast-sample-rails/Gemfile.lock",
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
- "tool": "bundler_audit"
- }
-]
diff --git a/spec/fixtures/security-reports/deprecated/gl-sast-report.json b/spec/fixtures/security-reports/deprecated/gl-sast-report.json
deleted file mode 100644
index a85b9be8b5f..00000000000
--- a/spec/fixtures/security-reports/deprecated/gl-sast-report.json
+++ /dev/null
@@ -1,944 +0,0 @@
-[
- {
- "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"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 47,
- "end_line": 47,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken2"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "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",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 41,
- "end_line": 41,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken1"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "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",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 14,
- "end_line": 14
- },
- "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": "bandit_test_id",
- "name": "Bandit Test ID B301",
- "value": "B301"
- }
- ],
- "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",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "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"
- },
- "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"
- }
- ],
- "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",
- "name": "Cipher with no integrity",
- "message": "Cipher with no integrity",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "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"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-CIPHER_INTEGRITY",
- "value": "CIPHER_INTEGRITY",
- "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": "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": "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": "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": "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"
- }
-]
diff --git a/spec/fixtures/security-reports/feature-branch.zip b/spec/fixtures/security-reports/feature-branch.zip
deleted file mode 100644
index dd49f4e9e1d..00000000000
--- a/spec/fixtures/security-reports/feature-branch.zip
+++ /dev/null
Binary files differ
diff --git a/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json b/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json
deleted file mode 100644
index 6f89d20d4bf..00000000000
--- a/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "image": "registry.gitlab.com/bikebilly/auto-devops-10-6/feature-branch:e7315ba964febb11bac8f5cd6ec433db8a3a1583",
- "unapproved": ["CVE-2017-15650"],
- "vulnerabilities": [
- {
- "featurename": "musl",
- "featureversion": "1.1.14-r15",
- "vulnerability": "CVE-2017-15650",
- "namespace": "alpine:v3.4",
- "description": "",
- "link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650",
- "severity": "Medium",
- "fixedby": "1.1.14-r16"
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-dast-report.json b/spec/fixtures/security-reports/feature-branch/gl-dast-report.json
deleted file mode 100644
index 3a308bf047e..00000000000
--- a/spec/fixtures/security-reports/feature-branch/gl-dast-report.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
- "site": {
- "alerts": [
- {
- "sourceid": "3",
- "wascid": "15",
- "cweid": "16",
- "reference": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>",
- "otherinfo": "<p>This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scanner will not alert on client or server error responses.</p>",
- "solution": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>",
- "count": "2",
- "pluginid": "10021",
- "alert": "X-Content-Type-Options Header Missing",
- "name": "X-Content-Type-Options Header Missing",
- "riskcode": "1",
- "confidence": "2",
- "riskdesc": "Low (Medium)",
- "desc": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>",
- "instances": [
- {
- "param": "X-Content-Type-Options",
- "method": "GET",
- "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
- },
- {
- "param": "X-Content-Type-Options",
- "method": "GET",
- "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/"
- }
- ]
- }
- ],
- "@ssl": "false",
- "@port": "80",
- "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io",
- "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
- },
- "@generated": "Fri, 13 Apr 2018 09:22:01",
- "@version": "2.7.0"
-}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json
deleted file mode 100644
index 8555be6618c..00000000000
--- a/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json
+++ /dev/null
@@ -1,181 +0,0 @@
-{
- "version": "1.3",
- "vulnerabilities": [
- {
- "category": "dependency_scanning",
- "name": "io.netty/netty - CVE-2014-3488",
- "message": "DoS by CPU exhaustion when using malicious SSL packets",
- "cve": "app/pom.xml:io.netty/netty@3.9.1.Final:CVE-2014-3488",
- "severity": "Unknown",
- "solution": "Upgrade to the latest version",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/pom.xml",
- "dependency": {
- "package": {
- "name": "io.netty/netty"
- },
- "version": "3.9.1.Final"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "value": "d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "url": "https://deps.sec.gitlab.com/packages/maven/io.netty/netty/versions/3.9.1.Final/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2014-3488",
- "value": "CVE-2014-3488",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3488"
- }
- ],
- "links": [
- {
- "url": "https://bugzilla.redhat.com/CVE-2014-3488"
- },
- {
- "url": "http://netty.io/news/2014/06/11/3.html"
- },
- {
- "url": "https://github.com/netty/netty/issues/2562"
- }
- ],
- "priority": "Unknown",
- "file": "app/pom.xml",
- "url": "https://bugzilla.redhat.com/CVE-2014-3488",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "Django - CVE-2017-12794",
- "message": "Possible XSS in traceback section of technical 500 debug page",
- "cve": "app/requirements.txt:Django@1.11.3:CVE-2017-12794",
- "severity": "Unknown",
- "solution": "Upgrade to latest version or apply patch.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/requirements.txt",
- "dependency": {
- "package": {
- "name": "Django"
- },
- "version": "1.11.3"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-6162a015-8635-4a15-8d7c-dc9321db366f",
- "value": "6162a015-8635-4a15-8d7c-dc9321db366f",
- "url": "https://deps.sec.gitlab.com/packages/pypi/Django/versions/1.11.3/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-12794",
- "value": "CVE-2017-12794",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12794"
- }
- ],
- "links": [
- {
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/"
- }
- ],
- "priority": "Unknown",
- "file": "app/requirements.txt",
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "nokogiri - USN-3424-1",
- "message": "Vulnerabilities in libxml2",
- "cve": "rails/Gemfile.lock:nokogiri@1.8.0:USN-3424-1",
- "severity": "Unknown",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d",
- "value": "06565b64-486d-4326-b906-890d9915804d",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "usn",
- "name": "USN-3424-1",
- "value": "USN-3424-1",
- "url": "https://usn.ubuntu.com/3424-1/"
- }
- ],
- "links": [
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
- }
- ],
- "priority": "Unknown",
- "file": "rails/Gemfile.lock",
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "ffi - CVE-2018-1000201",
- "message": "ruby-ffi DDL loading issue on Windows OS",
- "cve": "ffi:1.9.18:CVE-2018-1000201",
- "severity": "High",
- "solution": "upgrade to \u003e= 1.9.24",
- "scanner": {
- "id": "bundler_audit",
- "name": "bundler-audit"
- },
- "location": {
- "file": "sast-sample-rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "ffi"
- },
- "version": "1.9.18"
- }
- },
- "identifiers": [
- {
- "type": "cve",
- "name": "CVE-2018-1000201",
- "value": "CVE-2018-1000201",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201"
- }
- ],
- "links": [
- {
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24"
- }
- ],
- "priority": "High",
- "file": "sast-sample-rails/Gemfile.lock",
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
- "tool": "bundler_audit"
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json b/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json
deleted file mode 100644
index 5fd81fd69bd..00000000000
--- a/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "licenses": [
- {
- "count": 1,
- "name": "WTFPL"
- },
- {
- "count": 1,
- "name": "MIT"
- }
- ],
- "dependencies": [
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "actioncable",
- "url": "http://rubyonrails.org",
- "description": "WebSocket framework for Rails.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "WTFPL",
- "url": "http://www.wtfpl.net/"
- },
- "dependency": {
- "name": "wtfpl_init",
- "url": "https://rubygems.org/gems/wtfpl_init",
- "description": "Download WTFPL license file and rename to LICENSE.md or something",
- "pathes": [
- "."
- ]
- }
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-sast-report.json b/spec/fixtures/security-reports/feature-branch/gl-sast-report.json
deleted file mode 100644
index 4bef3d22f70..00000000000
--- a/spec/fixtures/security-reports/feature-branch/gl-sast-report.json
+++ /dev/null
@@ -1,947 +0,0 @@
-{
- "version": "1.2",
- "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"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 47,
- "end_line": 47,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken2"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "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",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 41,
- "end_line": 41,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken1"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "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",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 14,
- "end_line": 14
- },
- "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": "bandit_test_id",
- "name": "Bandit Test ID B301",
- "value": "B301"
- }
- ],
- "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",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "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"
- },
- "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"
- }
- ],
- "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",
- "name": "Cipher with no integrity",
- "message": "Cipher with no integrity",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "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"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-CIPHER_INTEGRITY",
- "value": "CIPHER_INTEGRITY",
- "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": "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": "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": "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": "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"
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/master.zip b/spec/fixtures/security-reports/master.zip
deleted file mode 100644
index 2261b5a1674..00000000000
--- a/spec/fixtures/security-reports/master.zip
+++ /dev/null
Binary files differ
diff --git a/spec/fixtures/security-reports/master/gl-container-scanning-report.json b/spec/fixtures/security-reports/master/gl-container-scanning-report.json
deleted file mode 100644
index 03dfc647162..00000000000
--- a/spec/fixtures/security-reports/master/gl-container-scanning-report.json
+++ /dev/null
@@ -1,105 +0,0 @@
-{
- "image": "registry.gitlab.com/groulot/container-scanning-test/master:5f21de6956aee99ddb68ae49498662d9872f50ff",
- "unapproved": [
- "CVE-2017-18269",
- "CVE-2017-16997",
- "CVE-2018-1000001",
- "CVE-2016-10228",
- "CVE-2018-18520",
- "CVE-2010-4052",
- "CVE-2018-16869",
- "CVE-2018-18311"
- ],
- "vulnerabilities": [
- {
- "featurename": "glibc",
- "featureversion": "2.24-11+deb9u3",
- "vulnerability": "CVE-2017-18269",
- "namespace": "debian:9",
- "description": "SSE2-optimized memmove implementation problem.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2017-18269",
- "severity": "Defcon1",
- "fixedby": "2.24-11+deb9u4"
- },
- {
- "featurename": "glibc",
- "featureversion": "2.24-11+deb9u3",
- "vulnerability": "CVE-2017-16997",
- "namespace": "debian:9",
- "description": "elf/dl-load.c in the GNU C Library (aka glibc or libc6) 2.19 through 2.26 mishandles RPATH and RUNPATH containing $ORIGIN for a privileged (setuid or AT_SECURE) program, which allows local users to gain privileges via a Trojan horse library in the current working directory, related to the fillin_rpath and decompose_rpath functions. This is associated with misinterpretion of an empty RPATH/RUNPATH token as the \"./\" directory. NOTE: this configuration of RPATH/RUNPATH for a privileged program is apparently very uncommon; most likely, no such program is shipped with any common Linux distribution.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2017-16997",
- "severity": "Critical",
- "fixedby": ""
- },
- {
- "featurename": "glibc",
- "featureversion": "2.24-11+deb9u3",
- "vulnerability": "CVE-2018-1000001",
- "namespace": "debian:9",
- "description": "In glibc 2.26 and earlier there is confusion in the usage of getcwd() by realpath() which can be used to write before the destination buffer leading to a buffer underflow and potential code execution.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2018-1000001",
- "severity": "High",
- "fixedby": ""
- },
- {
- "featurename": "glibc",
- "featureversion": "2.24-11+deb9u3",
- "vulnerability": "CVE-2016-10228",
- "namespace": "debian:9",
- "description": "The iconv program in the GNU C Library (aka glibc or libc6) 2.25 and earlier, when invoked with the -c option, enters an infinite loop when processing invalid multi-byte input sequences, leading to a denial of service.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2016-10228",
- "severity": "Medium",
- "fixedby": ""
- },
- {
- "featurename": "elfutils",
- "featureversion": "0.168-1",
- "vulnerability": "CVE-2018-18520",
- "namespace": "debian:9",
- "description": "An Invalid Memory Address Dereference exists in the function elf_end in libelf in elfutils through v0.174. Although eu-size is intended to support ar files inside ar files, handle_ar in size.c closes the outer ar file before handling all inner entries. The vulnerability allows attackers to cause a denial of service (application crash) with a crafted ELF file.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2018-18520",
- "severity": "Low",
- "fixedby": ""
- },
- {
- "featurename": "glibc",
- "featureversion": "2.24-11+deb9u3",
- "vulnerability": "CVE-2010-4052",
- "namespace": "debian:9",
- "description": "Stack consumption vulnerability in the regcomp implementation in the GNU C Library (aka glibc or libc6) through 2.11.3, and 2.12.x through 2.12.2, allows context-dependent attackers to cause a denial of service (resource exhaustion) via a regular expression containing adjacent repetition operators, as demonstrated by a {10,}{10,}{10,}{10,} sequence in the proftpd.gnu.c exploit for ProFTPD.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2010-4052",
- "severity": "Negligible",
- "fixedby": ""
- },
- {
- "featurename": "nettle",
- "featureversion": "3.3-1",
- "vulnerability": "CVE-2018-16869",
- "namespace": "debian:9",
- "description": "A Bleichenbacher type side-channel based padding oracle attack was found in the way nettle handles endian conversion of RSA decrypted PKCS#1 v1.5 data. An attacker who is able to run a process on the same physical core as the victim process, could use this flaw extract plaintext or in some cases downgrade any TLS connections to a vulnerable server.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2018-16869",
- "severity": "Unknown",
- "fixedby": ""
- },
- {
- "featurename": "perl",
- "featureversion": "5.24.1-3+deb9u4",
- "vulnerability": "CVE-2018-18311",
- "namespace": "debian:9",
- "description": "Perl before 5.26.3 and 5.28.x before 5.28.1 has a buffer overflow via a crafted regular expression that triggers invalid write operations.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2018-18311",
- "severity": "Unknown",
- "fixedby": "5.24.1-3+deb9u5"
- },
- {
- "featurename": "foo",
- "featureversion": "1.3",
- "vulnerability": "CVE-2018-666",
- "namespace": "debian:9",
- "description": "Foo has a vulnerability nobody cares about and whitelist.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2018-666",
- "severity": "Unknown",
- "fixedby": "1.4"
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/master/gl-dast-report.json b/spec/fixtures/security-reports/master/gl-dast-report.json
deleted file mode 100644
index df459d9419d..00000000000
--- a/spec/fixtures/security-reports/master/gl-dast-report.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "site": [
- {
- "alerts": [
- {
- "sourceid": "3",
- "wascid": "15",
- "cweid": "16",
- "reference": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>",
- "otherinfo": "<p>This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scanner will not alert on client or server error responses.</p>",
- "solution": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>",
- "count": "2",
- "pluginid": "10021",
- "alert": "X-Content-Type-Options Header Missing",
- "name": "X-Content-Type-Options Header Missing",
- "riskcode": "1",
- "confidence": "2",
- "riskdesc": "Low (Medium)",
- "desc": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>",
- "instances": [
- {
- "param": "X-Content-Type-Options",
- "method": "GET",
- "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
- },
- {
- "param": "X-Content-Type-Options",
- "method": "GET",
- "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/"
- }
- ]
- }
- ],
- "@ssl": "false",
- "@port": "80",
- "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io",
- "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
- }
- ],
- "@generated": "Fri, 13 Apr 2018 09:22:01",
- "@version": "2.7.0"
-}
diff --git a/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json
deleted file mode 100644
index 8555be6618c..00000000000
--- a/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json
+++ /dev/null
@@ -1,181 +0,0 @@
-{
- "version": "1.3",
- "vulnerabilities": [
- {
- "category": "dependency_scanning",
- "name": "io.netty/netty - CVE-2014-3488",
- "message": "DoS by CPU exhaustion when using malicious SSL packets",
- "cve": "app/pom.xml:io.netty/netty@3.9.1.Final:CVE-2014-3488",
- "severity": "Unknown",
- "solution": "Upgrade to the latest version",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/pom.xml",
- "dependency": {
- "package": {
- "name": "io.netty/netty"
- },
- "version": "3.9.1.Final"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "value": "d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "url": "https://deps.sec.gitlab.com/packages/maven/io.netty/netty/versions/3.9.1.Final/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2014-3488",
- "value": "CVE-2014-3488",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3488"
- }
- ],
- "links": [
- {
- "url": "https://bugzilla.redhat.com/CVE-2014-3488"
- },
- {
- "url": "http://netty.io/news/2014/06/11/3.html"
- },
- {
- "url": "https://github.com/netty/netty/issues/2562"
- }
- ],
- "priority": "Unknown",
- "file": "app/pom.xml",
- "url": "https://bugzilla.redhat.com/CVE-2014-3488",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "Django - CVE-2017-12794",
- "message": "Possible XSS in traceback section of technical 500 debug page",
- "cve": "app/requirements.txt:Django@1.11.3:CVE-2017-12794",
- "severity": "Unknown",
- "solution": "Upgrade to latest version or apply patch.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/requirements.txt",
- "dependency": {
- "package": {
- "name": "Django"
- },
- "version": "1.11.3"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-6162a015-8635-4a15-8d7c-dc9321db366f",
- "value": "6162a015-8635-4a15-8d7c-dc9321db366f",
- "url": "https://deps.sec.gitlab.com/packages/pypi/Django/versions/1.11.3/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-12794",
- "value": "CVE-2017-12794",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12794"
- }
- ],
- "links": [
- {
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/"
- }
- ],
- "priority": "Unknown",
- "file": "app/requirements.txt",
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "nokogiri - USN-3424-1",
- "message": "Vulnerabilities in libxml2",
- "cve": "rails/Gemfile.lock:nokogiri@1.8.0:USN-3424-1",
- "severity": "Unknown",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d",
- "value": "06565b64-486d-4326-b906-890d9915804d",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "usn",
- "name": "USN-3424-1",
- "value": "USN-3424-1",
- "url": "https://usn.ubuntu.com/3424-1/"
- }
- ],
- "links": [
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
- }
- ],
- "priority": "Unknown",
- "file": "rails/Gemfile.lock",
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "ffi - CVE-2018-1000201",
- "message": "ruby-ffi DDL loading issue on Windows OS",
- "cve": "ffi:1.9.18:CVE-2018-1000201",
- "severity": "High",
- "solution": "upgrade to \u003e= 1.9.24",
- "scanner": {
- "id": "bundler_audit",
- "name": "bundler-audit"
- },
- "location": {
- "file": "sast-sample-rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "ffi"
- },
- "version": "1.9.18"
- }
- },
- "identifiers": [
- {
- "type": "cve",
- "name": "CVE-2018-1000201",
- "value": "CVE-2018-1000201",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201"
- }
- ],
- "links": [
- {
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24"
- }
- ],
- "priority": "High",
- "file": "sast-sample-rails/Gemfile.lock",
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
- "tool": "bundler_audit"
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/master/gl-license-management-report.json b/spec/fixtures/security-reports/master/gl-license-management-report.json
deleted file mode 100644
index e0de6f58fdf..00000000000
--- a/spec/fixtures/security-reports/master/gl-license-management-report.json
+++ /dev/null
@@ -1,817 +0,0 @@
-{
- "licenses": [
- {
- "count": 52,
- "name": "MIT"
- },
- {
- "count": 3,
- "name": "New BSD"
- },
- {
- "count": 1,
- "name": "Apache 2.0"
- },
- {
- "count": 1,
- "name": "unknown"
- }
- ],
- "dependencies": [
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "actioncable",
- "url": "http://rubyonrails.org",
- "description": "WebSocket framework for Rails.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "actionmailer",
- "url": "http://rubyonrails.org",
- "description": "Email composition, delivery, and receiving framework (part of Rails).",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "actionpack",
- "url": "http://rubyonrails.org",
- "description": "Web-flow and rendering framework putting the VC in MVC (part of Rails).",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "actionview",
- "url": "http://rubyonrails.org",
- "description": "Rendering framework putting the V in MVC (part of Rails).",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "activejob",
- "url": "http://rubyonrails.org",
- "description": "Job framework with pluggable queues.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "activemodel",
- "url": "http://rubyonrails.org",
- "description": "A toolkit for building modeling frameworks (part of Rails).",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "activerecord",
- "url": "http://rubyonrails.org",
- "description": "Object-relational mapper framework (part of Rails).",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "activesupport",
- "url": "http://rubyonrails.org",
- "description": "A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "arel",
- "url": "https://github.com/rails/arel",
- "description": "Arel Really Exasperates Logicians Arel is a SQL AST manager for Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "builder",
- "url": "http://onestepback.org",
- "description": "Builders for MarkUp.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "bundler",
- "url": "http://bundler.io",
- "description": "The best way to manage your application's dependencies",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "coffee-rails",
- "url": "https://github.com/rails/coffee-rails",
- "description": "CoffeeScript adapter for the Rails asset pipeline.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "coffee-script",
- "url": "http://github.com/josh/ruby-coffee-script",
- "description": "Ruby CoffeeScript Compiler",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "coffee-script-source",
- "url": "http://coffeescript.org",
- "description": "The CoffeeScript Compiler",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "concurrent-ruby",
- "url": "http://www.concurrent-ruby.com",
- "description": "Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell, F#, C#, Java, and classic concurrency patterns.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "crass",
- "url": "https://github.com/rgrove/crass/",
- "description": "CSS parser based on the CSS Syntax Level 3 spec.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "erubis",
- "url": "http://www.kuwata-lab.com/erubis/",
- "description": "a fast and extensible eRuby implementation which supports multi-language",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "execjs",
- "url": "https://github.com/rails/execjs",
- "description": "Run JavaScript code from Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "New BSD",
- "url": "http://opensource.org/licenses/BSD-3-Clause"
- },
- "dependency": {
- "name": "ffi",
- "url": "http://wiki.github.com/ffi/ffi",
- "description": "Ruby FFI",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "globalid",
- "url": "http://www.rubyonrails.org",
- "description": "Refer to any model with a URI: gid://app/class/id",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "i18n",
- "url": "http://github.com/svenfuchs/i18n",
- "description": "New wave Internationalization support for Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "jbuilder",
- "url": "https://github.com/rails/jbuilder",
- "description": "Create JSON structures via a Builder-style DSL",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "loofah",
- "description": "",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "mail",
- "url": "https://github.com/mikel/mail",
- "description": "Mail provides a nice Ruby DSL for making, sending and reading emails.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "method_source",
- "url": "http://banisterfiend.wordpress.com",
- "description": "retrieve the sourcecode for a method",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "mini_mime",
- "url": "https://github.com/discourse/mini_mime",
- "description": "A lightweight mime type lookup toy",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "mini_portile2",
- "url": "http://github.com/flavorjones/mini_portile",
- "description": "Simplistic port-like solution for developers",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "minitest",
- "url": "https://github.com/seattlerb/minitest",
- "description": "minitest provides a complete suite of testing facilities supporting TDD, BDD, mocking, and benchmarking",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "multi_json",
- "url": "http://github.com/intridea/multi_json",
- "description": "A common interface to multiple JSON libraries.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "nio4r",
- "url": "https://github.com/celluloid/nio4r",
- "description": "NIO provides a high performance selector API for monitoring IO objects",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "nokogiri",
- "url": "http://nokogiri.org",
- "description": "Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "New BSD",
- "url": "http://opensource.org/licenses/BSD-3-Clause"
- },
- "dependency": {
- "name": "puma",
- "url": "http://puma.io",
- "description": "Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rack",
- "url": "https://rack.github.io/",
- "description": "a modular Ruby webserver interface",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rack-test",
- "url": "http://github.com/brynary/rack-test",
- "description": "Simple testing API built on Rack",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rails",
- "url": "http://rubyonrails.org",
- "description": "Full-stack web application framework.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rails-dom-testing",
- "url": "https://github.com/rails/rails-dom-testing",
- "description": "Dom and Selector assertions for Rails applications",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rails-html-sanitizer",
- "url": "https://github.com/rails/rails-html-sanitizer",
- "description": "This gem is responsible to sanitize HTML fragments in Rails applications.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "railties",
- "url": "http://rubyonrails.org",
- "description": "Tools for creating, working with, and running Rails applications.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rake",
- "url": "https://github.com/ruby/rake",
- "description": "Rake is a Make-like program implemented in Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rb-fsevent",
- "url": "http://rubygems.org/gems/rb-fsevent",
- "description": "Very simple & usable FSEvents API",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rb-inotify",
- "url": "https://github.com/guard/rb-inotify",
- "description": "A Ruby wrapper for Linux inotify, using FFI",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "unknown"
- },
- "dependency": {
- "name": "ruby-bundler-rails",
- "description": "",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "sass",
- "url": "http://sass-lang.com/",
- "description": "A powerful but elegant CSS compiler that makes CSS fun again.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "sass-listen",
- "url": "https://github.com/sass/listen",
- "description": "Fork of guard/listen",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "sass-rails",
- "url": "https://github.com/rails/sass-rails",
- "description": "Sass adapter for the Rails asset pipeline.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "sprockets",
- "url": "https://github.com/rails/sprockets",
- "description": "Rack-based asset packaging system",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "sprockets-rails",
- "url": "https://github.com/rails/sprockets-rails",
- "description": "Sprockets Rails integration",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "New BSD",
- "url": "http://opensource.org/licenses/BSD-3-Clause"
- },
- "dependency": {
- "name": "sqlite3",
- "url": "https://github.com/sparklemotion/sqlite3-ruby",
- "description": "This module allows Ruby programs to interface with the SQLite3 database engine (http://www.sqlite.org)",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "thor",
- "url": "http://whatisthor.com/",
- "description": "Thor is a toolkit for building powerful command-line interfaces.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "Apache 2.0",
- "url": "http://www.apache.org/licenses/LICENSE-2.0.txt"
- },
- "dependency": {
- "name": "thread_safe",
- "url": "https://github.com/ruby-concurrency/thread_safe",
- "description": "Thread-safe collections and utilities for Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "tilt",
- "url": "http://github.com/rtomayko/tilt/",
- "description": "Generic interface to multiple Ruby template engines",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "turbolinks",
- "url": "https://github.com/turbolinks/turbolinks",
- "description": "Turbolinks makes navigating your web application faster",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "turbolinks-source",
- "url": "https://github.com/turbolinks/turbolinks-source-gem",
- "description": "Turbolinks JavaScript assets",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "tzinfo",
- "url": "http://tzinfo.github.io",
- "description": "Daylight savings aware timezone library",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "uglifier",
- "url": "http://github.com/lautis/uglifier",
- "description": "Ruby wrapper for UglifyJS JavaScript compressor",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "websocket-driver",
- "url": "http://github.com/faye/websocket-driver-ruby",
- "description": "WebSocket protocol handler with pluggable I/O",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "websocket-extensions",
- "url": "https://github.com/faye/websocket-extensions-ruby",
- "description": "Generic extension manager for WebSocket connections",
- "pathes": [
- "."
- ]
- }
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/master/gl-sast-report.json b/spec/fixtures/security-reports/master/gl-sast-report.json
deleted file mode 100644
index 4bef3d22f70..00000000000
--- a/spec/fixtures/security-reports/master/gl-sast-report.json
+++ /dev/null
@@ -1,947 +0,0 @@
-{
- "version": "1.2",
- "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"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 47,
- "end_line": 47,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken2"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "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",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 41,
- "end_line": 41,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken1"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "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",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 14,
- "end_line": 14
- },
- "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": "bandit_test_id",
- "name": "Bandit Test ID B301",
- "value": "B301"
- }
- ],
- "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",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "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"
- },
- "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"
- }
- ],
- "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",
- "name": "Cipher with no integrity",
- "message": "Cipher with no integrity",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "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"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-CIPHER_INTEGRITY",
- "value": "CIPHER_INTEGRITY",
- "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": "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": "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": "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": "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"
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json
deleted file mode 100644
index c96e831b027..00000000000
--- a/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json
+++ /dev/null
@@ -1,104 +0,0 @@
-{
- "version": "2.0",
- "vulnerabilities": [
- {
- "category": "dependency_scanning",
- "name": "Regular Expression Denial of Service",
- "message": "Regular Expression Denial of Service in debug",
- "description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.",
- "cve": "yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a",
- "severity": "Unknown",
- "solution": "Upgrade to latest versions.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "yarn.lock",
- "dependency": {
- "package": {
- "name": "debug"
- },
- "version": "1.0.5"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a",
- "value": "37283ed4-0380-40d7-ada7-2d994afcc62a",
- "url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories"
- }
- ],
- "links": [
- {
- "url": "https://nodesecurity.io/advisories/534"
- },
- {
- "url": "https://github.com/visionmedia/debug/issues/501"
- },
- {
- "url": "https://github.com/visionmedia/debug/pull/504"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Authentication bypass via incorrect DOM traversal and canonicalization",
- "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
- "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
- "cve": "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98",
- "severity": "Unknown",
- "solution": "Upgrade to fixed version.\r\n",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "yarn.lock",
- "dependency": {
- "package": {
- "name": "saml2-js"
- },
- "version": "1.5.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
- "value": "9952e574-7b5b-46fa-a270-aeb694198a98",
- "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-11429",
- "value": "CVE-2017-11429",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
- }
- ],
- "links": [
- {
- "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
- },
- {
- "url": "https://github.com/Clever/saml2/issues/127"
- },
- {
- "url": "https://www.kb.cert.org/vuls/id/475445"
- }
- ]
- }
- ],
- "remediations": [
- {
- "fixes": [
- {
- "cve": "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98"
- }
- ],
- "summary": "Upgrade saml2-js",
- "diff": "ZGlmZiAtLWdpdCBhL3lhcm4ubG9jayBiL3lhcm4ubG9jawppbmRleCAwZWNjOTJmLi43ZmE0NTU0IDEwMDY0NAotLS0gYS95YXJuLmxvY2sKKysrIGIveWFybi5sb2NrCkBAIC0yLDEwMyArMiwxMjQgQEAKICMgeWFybiBsb2NrZmlsZSB2MQogCiAKLWFzeW5jQH4wLjIuNzoKLSAgdmVyc2lvbiAiMC4yLjEwIgotICByZXNvbHZlZCAiaHR0cDovL3JlZ2lzdHJ5Lm5wbWpzLm9yZy9hc3luYy8tL2FzeW5jLTAuMi4xMC50Z3ojYjZiYmUwYjA2NzRiOWQ3MTk3MDhjYTM4ZGU4YzIzN2NiNTI2YzNkMSIKLQotYXN5bmNAfjEuNS4yOgotICB2ZXJzaW9uICIxLjUuMiIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcvYXN5bmMvLS9hc3luYy0xLjUuMi50Z3ojZWM2YTYxYWU1NjQ4MGMwYzNjYjI0MWM5NTYxOGUyMDg5MmY5NjcyYSIKK2FzeW5jQF4yLjEuNSwgYXN5bmNAXjIuNS4wOgorICB2ZXJzaW9uICIyLjYuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vYXN5bmMvLS9hc3luYy0yLjYuMS50Z3ojYjI0NWEyM2NhNzE5MzAwNDRlYzUzZmE0NmFhMDBhM2U4N2M2YTYxMCIKKyAgaW50ZWdyaXR5IHNoYTUxMi1mTkVpTDIrQVp0NkFsQXcvMjlDcjBVRGU0c1JBSENwRUhoNTRXTXorQmI3UWZOY0Z3NGgzbG9vZnlKcExlUXM0WXg3eXVxdS8yZExnTTVoS09zNkhsUT09CisgIGRlcGVuZGVuY2llczoKKyAgICBsb2Rhc2ggIl40LjE3LjEwIgogCi1kZWJ1Z0BeMS4wLjQ6Ci0gIHZlcnNpb24gIjEuMC41IgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9kZWJ1Zy8tL2RlYnVnLTEuMC41LnRneiNmNzI0MTIxNzQzMGY5OWRlYzRjMmI0NzNlYWI5MjIyOGU4NzRjMmFjIgorZGVidWdAXjIuNi4wOgorICB2ZXJzaW9uICIyLjYuOSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vZGVidWcvLS9kZWJ1Zy0yLjYuOS50Z3ojNWQxMjg1MTVkZjEzNGZmMzI3ZTkwYTRjOTNmNGUwNzdhNTM2MzQxZiIKKyAgaW50ZWdyaXR5IHNoYTUxMi1iQzdFbHJkSmFKblBiQVArMUVvdFl2cVpzYjNlY2w1d2k2QmZpNkJKVFVjTm93cDZjdnNwZzBqWHpuUlRLRGptL0U3QWRnRkJWZUFQVk1OY0tHc0hNQT09CiAgIGRlcGVuZGVuY2llczoKICAgICBtcyAiMi4wLjAiCiAKLWVqc0B+MC44LjM6Ci0gIHZlcnNpb24gIjAuOC44IgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9lanMvLS9lanMtMC44LjgudGd6I2ZmZGM1NmRjYzM1ZDAyOTI2ZGQ1MGFkMTM0MzliYmM1NDA2MWQ1OTgiCitlanNAXjIuNS42OgorICB2ZXJzaW9uICIyLjYuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vZWpzLy0vZWpzLTIuNi4xLnRneiM0OThlYzBkNDk1NjU1YWJjNmYyM2NkNjE4NjhkOTI2NDY0MDcxYWEwIgorICBpbnRlZ3JpdHkgc2hhNTEyLTB4eTRBL3R3ZnJSQ25raGZrOEVyRGk1RHFkQXNBcWVHeGh0NHhrQ1Vyc3ZoaGJRTnM3RSs0alYwQ043K05LSVkwYUhFNzIrWHZxdEJJWHpEMzFaYlhRPT0KKworbG9kYXNoLW5vZGVAfjIuNC4xOgorICB2ZXJzaW9uICIyLjQuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vbG9kYXNoLW5vZGUvLS9sb2Rhc2gtbm9kZS0yLjQuMS50Z3ojZWE4MmY3YjEwMGM3MzNkMWE0MmFmNzY4MDFlNTA2MTA1ZTJhODBlYyIKKyAgaW50ZWdyaXR5IHNoYTEtNm9MM3NRREhNOUdrS3Zkb0FlVUdFRjRxZ093PQorCitsb2Rhc2hAXjQuMTcuMTA6CisgIHZlcnNpb24gIjQuMTcuMTEiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL2xvZGFzaC8tL2xvZGFzaC00LjE3LjExLnRneiNiMzllYTYyMjllZjYwN2VjZDg5ZTJjOGRmMTI1MzY4OTFjYWM5YjhkIgorICBpbnRlZ3JpdHkgc2hhNTEyLWNRS2g4aWdvNVFVaFo3bGczOERZV0F4TXZqU0FLRzBBOHdHU1ZpbVAwN1NJVUVLMlVPK2FyU1JLYlJaV3RlbE10TjVWMEhrd2g1cnlPdG8vU3NoWUlnPT0KIAogbXNAMi4wLjA6CiAgIHZlcnNpb24gIjIuMC4wIgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9tcy8tL21zLTIuMC4wLnRneiM1NjA4YWVhZGZjMDBiZTZjMjkwMWRmNWY5ODYxNzg4ZGUwZDU5N2M4IgorICBpbnRlZ3JpdHkgc2hhMS1WZ2l1cmZ3QXZtd3BBZDlmbUdGNGplRFZsOGc9CiAKLW5vZGUtZm9yZ2VAMC4yLjI0OgotICB2ZXJzaW9uICIwLjIuMjQiCi0gIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL25vZGUtZm9yZ2UvLS9ub2RlLWZvcmdlLTAuMi4yNC50Z3ojZmE2Zjg0NmY0MmZhOTNmNjNhMGEzMGM5ZmJmZjdiNGUxMzBlMDg1OCIKK25vZGUtZm9yZ2VAXjAuNy4wOgorICB2ZXJzaW9uICIwLjcuNiIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vbm9kZS1mb3JnZS8tL25vZGUtZm9yZ2UtMC43LjYudGd6I2ZkZjNiNDE4YWVlMWY5NGYwZWY2NDJjZDYzNDg2Yzc3Y2E5NzI0YWMiCisgIGludGVncml0eSBzaGE1MTItc29sMzBMVXB6MWpRRkJqT0t3Ymp4aWppRTNiNnBqZDc0WXdmRDBmSk9LUGpGK2ZPTktiMllnOHJZZ1M2K2JLNlZEbCsvd2ZyNElZcEM3akR6TFVJZnc9PQogCiBzYW1sMi1qc0BeMS41LjA6Ci0gIHZlcnNpb24gIjEuNS4wIgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9zYW1sMi1qcy8tL3NhbWwyLWpzLTEuNS4wLnRneiNjMGQyMjY4YTE3OWU3MzI5ZDI5ZWIyNWFhODJkZjU1MDM3NzRiMGQ5IgorICB2ZXJzaW9uICIxLjEyLjQiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3NhbWwyLWpzLy0vc2FtbDItanMtMS4xMi40LnRneiNjMjg4ZjIwYmRhNmQyYjkxMDczYjE2Yzk0ZWE3MmYyMjM0OWFjM2IzIgorICBpbnRlZ3JpdHkgc2hhMS13b2p5QzlwdEs1RUhPeGJKVHFjdklqU2F3N009CiAgIGRlcGVuZGVuY2llczoKLSAgICBhc3luYyAifjEuNS4yIgotICAgIGRlYnVnICJeMS4wLjQiCi0gICAgdW5kZXJzY29yZSAifjEuNi4wIgotICAgIHhtbC1jcnlwdG8gIl4wLjguMSIKLSAgICB4bWwtZW5jcnlwdGlvbiAifjAuNy40IgotICAgIHhtbDJqcyAifjAuNC4xIgotICAgIHhtbGJ1aWxkZXIgIn4yLjEuMCIKLSAgICB4bWxkb20gIn4wLjEuMTkiCisgICAgYXN5bmMgIl4yLjUuMCIKKyAgICBkZWJ1ZyAiXjIuNi4wIgorICAgIHVuZGVyc2NvcmUgIl4xLjguMCIKKyAgICB4bWwtY3J5cHRvICJeMC4xMC4wIgorICAgIHhtbC1lbmNyeXB0aW9uICJeMC4xMS4wIgorICAgIHhtbDJqcyAiXjAuNC4wIgorICAgIHhtbGJ1aWxkZXIgIn4yLjIuMCIKKyAgICB4bWxkb20gIl4wLjEuMCIKIAogc2F4QD49MC42LjA6CiAgIHZlcnNpb24gIjEuMi40IgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9zYXgvLS9zYXgtMS4yLjQudGd6IzI4MTYyMzRlMjM3OGJkZGM0ZTUzNTRmYWI1Y2FhODk1ZGY3MTAwZDkiCisgIGludGVncml0eSBzaGE1MTItTnFWRHY5VHBBTlVqRm0wTjh1TTVHeEwzNlVnS2k5L2F0WncreDdZRm5ROGNrd0ZHS3JsNHhYNHlXdHJleTNVSm01blAxa1VibllnTG9wcVdOU1JoV3c9PQogCi11bmRlcnNjb3JlQD49MS41Lng6Cit1bmRlcnNjb3JlQF4xLjguMDoKICAgdmVyc2lvbiAiMS45LjEiCiAgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3VuZGVyc2NvcmUvLS91bmRlcnNjb3JlLTEuOS4xLnRneiMwNmRjZTM0YTBlNjhhN2JhYmMyOWIzNjViOGU3NGI4OTI1MjAzOTYxIgorICBpbnRlZ3JpdHkgc2hhNTEyLTUvNGV0bkNrZDljOGd3Z293aTUvb20vbVlPNWFqQ2FPZ2R6ai9vVyswZVFWOVd4S0JEWnc1K3ljbUttZWFUWGpJblMvVzBCenBHTG8yeFIyYUJ3WmRnPT0KIAotdW5kZXJzY29yZUB+MS42LjA6Ci0gIHZlcnNpb24gIjEuNi4wIgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS91bmRlcnNjb3JlLy0vdW5kZXJzY29yZS0xLjYuMC50Z3ojOGIzOGIxMGNhY2RlZjYzMzM3YjhiMjRlNGZmODZkNDVhZWE1MjlhOCIKLQoteG1sLWNyeXB0b0BeMC44LjE6Ci0gIHZlcnNpb24gIjAuOC41IgotICByZXNvbHZlZCAiaHR0cDovL3JlZ2lzdHJ5Lm5wbWpzLm9yZy94bWwtY3J5cHRvLy0veG1sLWNyeXB0by0wLjguNS50Z3ojMmJiY2ZiM2ViMzNmM2E4MmEyMThiODIyYmY2NzJiNmIxYzIwZTUzOCIKK3htbC1jcnlwdG9AXjAuMTAuMDoKKyAgdmVyc2lvbiAiMC4xMC4xIgorICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94bWwtY3J5cHRvLy0veG1sLWNyeXB0by0wLjEwLjEudGd6I2Y4MzJmNzRjY2Y1NmYyNGFmY2FlMTE2M2ExZmNhYjQ0ZDk2Nzc0YTgiCisgIGludGVncml0eSBzaGExLStETDNUTTlXOGtyOHJoRmpvZnlyUk5sbmRLZz0KICAgZGVwZW5kZW5jaWVzOgogICAgIHhtbGRvbSAiPTAuMS4xOSIKICAgICB4cGF0aC5qcyAiPj0wLjAuMyIKIAoteG1sLWVuY3J5cHRpb25AfjAuNy40OgotICB2ZXJzaW9uICIwLjcuNCIKLSAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sLWVuY3J5cHRpb24vLS94bWwtZW5jcnlwdGlvbi0wLjcuNC50Z3ojNDI3OTFlYzY0ZDU1NmQyNDU1ZGNiOWRhMGE1NDEyMzY2NWFjNjVjNyIKK3htbC1lbmNyeXB0aW9uQF4wLjExLjA6CisgIHZlcnNpb24gIjAuMTEuMiIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sLWVuY3J5cHRpb24vLS94bWwtZW5jcnlwdGlvbi0wLjExLjIudGd6I2MyMTdmNTUwOTU0N2UzNGI1MDBiODI5ZjJjMGJjYTg1Y2NhNzNhMjEiCisgIGludGVncml0eSBzaGE1MTItalZ2RVM3aTVvdmRPN04rTmpnbmNBMzI2eFlLamhxZUFubnZJZ1JuWTdST0xDZkZxRURMd1AwU3hwLzMwU0hHMEFYUVYxMDQ4VDV5aW5PRnl2d0dGemc9PQogICBkZXBlbmRlbmNpZXM6Ci0gICAgYXN5bmMgIn4wLjIuNyIKLSAgICBlanMgIn4wLjguMyIKLSAgICBub2RlLWZvcmdlICIwLjIuMjQiCisgICAgYXN5bmMgIl4yLjEuNSIKKyAgICBlanMgIl4yLjUuNiIKKyAgICBub2RlLWZvcmdlICJeMC43LjAiCiAgICAgeG1sZG9tICJ+MC4xLjE1IgotICAgIHhwYXRoICIwLjAuNSIKKyAgICB4cGF0aCAiMC4wLjI3IgogCi14bWwyanNAfjAuNC4xOgoreG1sMmpzQF4wLjQuMDoKICAgdmVyc2lvbiAiMC40LjE5IgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94bWwyanMvLS94bWwyanMtMC40LjE5LnRneiM2ODZjMjBmMjEzMjA5ZTk0YWJmMGQxYmNmMWVmYWEyOTFjNzgyN2E3IgorICBpbnRlZ3JpdHkgc2hhNTEyLWVzWm5KWkpPaUpSOXdXS015dXZTRTF5NkRxNUxDdUphbnFoeHNsSDJieE02ZHVhaE5aK0hNcENMaEJRR1prYlg2eFJmOHgxWTJlSmxndDJxM3FvNDlRPT0KICAgZGVwZW5kZW5jaWVzOgogICAgIHNheCAiPj0wLjYuMCIKICAgICB4bWxidWlsZGVyICJ+OS4wLjEiCiAKLXhtbGJ1aWxkZXJAfjIuMS4wOgotICB2ZXJzaW9uICIyLjEuMCIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcveG1sYnVpbGRlci8tL3htbGJ1aWxkZXItMi4xLjAudGd6IzZkZGFlMzE2ODNiNmRmMTIxMDBiMjlmYzhhMGQ0ZjQ2MzQ5YWJiZWQiCit4bWxidWlsZGVyQH4yLjIuMDoKKyAgdmVyc2lvbiAiMi4yLjEiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGJ1aWxkZXIvLS94bWxidWlsZGVyLTIuMi4xLnRneiM5MzI2NDMwZjEzMGQ4NzQzNWQ0YzQwODY2NDNhYTI5MjZlMTA1YTMyIgorICBpbnRlZ3JpdHkgc2hhMS1reVpERHhNTmgwTmRURUNHWkRxaWttNFFXakk9CiAgIGRlcGVuZGVuY2llczoKLSAgICB1bmRlcnNjb3JlICI+PTEuNS54IgorICAgIGxvZGFzaC1ub2RlICJ+Mi40LjEiCiAKIHhtbGJ1aWxkZXJAfjkuMC4xOgogICB2ZXJzaW9uICI5LjAuNyIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcveG1sYnVpbGRlci8tL3htbGJ1aWxkZXItOS4wLjcudGd6IzEzMmVlNjNkMmVjNTU2NWM1NTdlMjBmNGMyMmRmOWFjYTY4NmIxMGQiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGJ1aWxkZXIvLS94bWxidWlsZGVyLTkuMC43LnRneiMxMzJlZTYzZDJlYzU1NjVjNTU3ZTIwZjRjMjJkZjlhY2E2ODZiMTBkIgorICBpbnRlZ3JpdHkgc2hhMS1FeTdtUFM3RlZseFZmaUQwd2kzNXJLYUdzUTA9CiAKIHhtbGRvbUA9MC4xLjE5OgogICB2ZXJzaW9uICIwLjEuMTkiCiAgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGRvbS8tL3htbGRvbS0wLjEuMTkudGd6IzYzMWZjMDc3NzZlZmQ4NDExOGJmMjUxNzFiMzdlZDRkMDc1YTBhYmMiCisgIGludGVncml0eSBzaGExLVl4L0FkM2J2MkVFWXZ5VVhHemZ0VFFkYUNydz0KIAoteG1sZG9tQH4wLjEuMTUsIHhtbGRvbUB+MC4xLjE5OgoreG1sZG9tQF4wLjEuMCwgeG1sZG9tQH4wLjEuMTU6CiAgIHZlcnNpb24gIjAuMS4yNyIKICAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sZG9tLy0veG1sZG9tLTAuMS4yNy50Z3ojZDUwMWY5N2IzYmRiNDAzYWY4ZWY5ZWNjMjA1NzMxODdhYWRhYzBlOSIKKyAgaW50ZWdyaXR5IHNoYTEtMVFINWV6dmJRRHI0NzU3TUlGY3hoNnJhd09rPQogCiB4cGF0aC5qc0A+PTAuMC4zOgogICB2ZXJzaW9uICIxLjEuMCIKICAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veHBhdGguanMvLS94cGF0aC5qcy0xLjEuMC50Z3ojMzgxNmE0NGVkNGJiMzUyMDkxMDgzZDAwMmEzODNkZDUxMDRhNWZmMSIKKyAgaW50ZWdyaXR5IHNoYTUxMi1qZytxa2ZTNEs4RTc5NjVzcWFVbDhtUm5nWGlLYjNXWkdmT05nRTE4cHIwM0ZVUWl1U1Y2RytFajR0UzU1QitySVFTRkVJdzNwaGRWQVE0cFBxTldmUT09CiAKLXhwYXRoQDAuMC41OgotICB2ZXJzaW9uICIwLjAuNSIKLSAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veHBhdGgvLS94cGF0aC0wLjAuNS50Z3ojNDU0MDM2ZjZlZjBmM2RmNWFmNWQ0YmE0YTExOWZiNzU2NzRiM2U2YyIKK3hwYXRoQDAuMC4yNzoKKyAgdmVyc2lvbiAiMC4wLjI3IgorICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94cGF0aC8tL3hwYXRoLTAuMC4yNy50Z3ojZGQzNDIxZmJkY2M1NjQ2YWMzMmM0ODUzMWI0ZDdlOWQwYzJjZmE5MiIKKyAgaW50ZWdyaXR5IHNoYTUxMi1mZzAzV1J4dGtDVjZvaENsZVBOQUVDWXNtcEtLVHY1TDh5L1gzRG4xaFFyZWMzUE94MmpIWi8wUDJxUTZIdnNyVTFCbWVxWGNvZjNOR0d1ZUc2THh3UT09Cg=="
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/remediations/remediation.patch b/spec/fixtures/security-reports/remediations/remediation.patch
deleted file mode 100644
index bbfb6874627..00000000000
--- a/spec/fixtures/security-reports/remediations/remediation.patch
+++ /dev/null
@@ -1,180 +0,0 @@
-diff --git a/yarn.lock b/yarn.lock
-index 0ecc92f..7fa4554 100644
---- a/yarn.lock
-+++ b/yarn.lock
-@@ -2,103 +2,124 @@
- # yarn lockfile v1
-
-
--async@~0.2.7:
-- version "0.2.10"
-- resolved "http://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
--
--async@~1.5.2:
-- version "1.5.2"
-- resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-+async@^2.1.5, async@^2.5.0:
-+ version "2.6.1"
-+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
-+ integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
-+ dependencies:
-+ lodash "^4.17.10"
-
--debug@^1.0.4:
-- version "1.0.5"
-- resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac"
-+debug@^2.6.0:
-+ version "2.6.9"
-+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
-+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
- dependencies:
- ms "2.0.0"
-
--ejs@~0.8.3:
-- version "0.8.8"
-- resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598"
-+ejs@^2.5.6:
-+ version "2.6.1"
-+ resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
-+ integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
-+
-+lodash-node@~2.4.1:
-+ version "2.4.1"
-+ resolved "https://registry.yarnpkg.com/lodash-node/-/lodash-node-2.4.1.tgz#ea82f7b100c733d1a42af76801e506105e2a80ec"
-+ integrity sha1-6oL3sQDHM9GkKvdoAeUGEF4qgOw=
-+
-+lodash@^4.17.10:
-+ version "4.17.11"
-+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
-+ integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
-
- ms@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
-+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
-
--node-forge@0.2.24:
-- version "0.2.24"
-- resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.2.24.tgz#fa6f846f42fa93f63a0a30c9fbff7b4e130e0858"
-+node-forge@^0.7.0:
-+ version "0.7.6"
-+ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
-+ integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==
-
- saml2-js@^1.5.0:
-- version "1.5.0"
-- resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.5.0.tgz#c0d2268a179e7329d29eb25aa82df5503774b0d9"
-+ version "1.12.4"
-+ resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.12.4.tgz#c288f20bda6d2b91073b16c94ea72f22349ac3b3"
-+ integrity sha1-wojyC9ptK5EHOxbJTqcvIjSaw7M=
- dependencies:
-- async "~1.5.2"
-- debug "^1.0.4"
-- underscore "~1.6.0"
-- xml-crypto "^0.8.1"
-- xml-encryption "~0.7.4"
-- xml2js "~0.4.1"
-- xmlbuilder "~2.1.0"
-- xmldom "~0.1.19"
-+ async "^2.5.0"
-+ debug "^2.6.0"
-+ underscore "^1.8.0"
-+ xml-crypto "^0.10.0"
-+ xml-encryption "^0.11.0"
-+ xml2js "^0.4.0"
-+ xmlbuilder "~2.2.0"
-+ xmldom "^0.1.0"
-
- sax@>=0.6.0:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
-+ integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
-
--underscore@>=1.5.x:
-+underscore@^1.8.0:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
-+ integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==
-
--underscore@~1.6.0:
-- version "1.6.0"
-- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
--
--xml-crypto@^0.8.1:
-- version "0.8.5"
-- resolved "http://registry.npmjs.org/xml-crypto/-/xml-crypto-0.8.5.tgz#2bbcfb3eb33f3a82a218b822bf672b6b1c20e538"
-+xml-crypto@^0.10.0:
-+ version "0.10.1"
-+ resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-0.10.1.tgz#f832f74ccf56f24afcae1163a1fcab44d96774a8"
-+ integrity sha1-+DL3TM9W8kr8rhFjofyrRNlndKg=
- dependencies:
- xmldom "=0.1.19"
- xpath.js ">=0.0.3"
-
--xml-encryption@~0.7.4:
-- version "0.7.4"
-- resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.7.4.tgz#42791ec64d556d2455dcb9da0a54123665ac65c7"
-+xml-encryption@^0.11.0:
-+ version "0.11.2"
-+ resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.11.2.tgz#c217f5509547e34b500b829f2c0bca85cca73a21"
-+ integrity sha512-jVvES7i5ovdO7N+NjgncA326xYKjhqeAnnvIgRnY7ROLCfFqEDLwP0Sxp/30SHG0AXQV1048T5yinOFyvwGFzg==
- dependencies:
-- async "~0.2.7"
-- ejs "~0.8.3"
-- node-forge "0.2.24"
-+ async "^2.1.5"
-+ ejs "^2.5.6"
-+ node-forge "^0.7.0"
- xmldom "~0.1.15"
-- xpath "0.0.5"
-+ xpath "0.0.27"
-
--xml2js@~0.4.1:
-+xml2js@^0.4.0:
- version "0.4.19"
- resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
-+ integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
- dependencies:
- sax ">=0.6.0"
- xmlbuilder "~9.0.1"
-
--xmlbuilder@~2.1.0:
-- version "2.1.0"
-- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.1.0.tgz#6ddae31683b6df12100b29fc8a0d4f46349abbed"
-+xmlbuilder@~2.2.0:
-+ version "2.2.1"
-+ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-2.2.1.tgz#9326430f130d87435d4c4086643aa2926e105a32"
-+ integrity sha1-kyZDDxMNh0NdTECGZDqikm4QWjI=
- dependencies:
-- underscore ">=1.5.x"
-+ lodash-node "~2.4.1"
-
- xmlbuilder@~9.0.1:
- version "9.0.7"
-- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
-+ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
-+ integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
-
- xmldom@=0.1.19:
- version "0.1.19"
- resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
-+ integrity sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw=
-
--xmldom@~0.1.15, xmldom@~0.1.19:
-+xmldom@^0.1.0, xmldom@~0.1.15:
- version "0.1.27"
- resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
-+ integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk=
-
- xpath.js@>=0.0.3:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1"
-+ integrity sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==
-
--xpath@0.0.5:
-- version "0.0.5"
-- resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.5.tgz#454036f6ef0f3df5af5d4ba4a119fb75674b3e6c"
-+xpath@0.0.27:
-+ version "0.0.27"
-+ resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92"
-+ integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==
diff --git a/spec/fixtures/security-reports/remediations/yarn.lock b/spec/fixtures/security-reports/remediations/yarn.lock
deleted file mode 100644
index 0ecc92fb711..00000000000
--- a/spec/fixtures/security-reports/remediations/yarn.lock
+++ /dev/null
@@ -1,104 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-async@~0.2.7:
- version "0.2.10"
- resolved "http://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
-
-async@~1.5.2:
- version "1.5.2"
- resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-
-debug@^1.0.4:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac"
- dependencies:
- ms "2.0.0"
-
-ejs@~0.8.3:
- version "0.8.8"
- resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598"
-
-ms@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
-
-node-forge@0.2.24:
- version "0.2.24"
- resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.2.24.tgz#fa6f846f42fa93f63a0a30c9fbff7b4e130e0858"
-
-saml2-js@^1.5.0:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.5.0.tgz#c0d2268a179e7329d29eb25aa82df5503774b0d9"
- dependencies:
- async "~1.5.2"
- debug "^1.0.4"
- underscore "~1.6.0"
- xml-crypto "^0.8.1"
- xml-encryption "~0.7.4"
- xml2js "~0.4.1"
- xmlbuilder "~2.1.0"
- xmldom "~0.1.19"
-
-sax@>=0.6.0:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
-
-underscore@>=1.5.x:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
-
-underscore@~1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
-
-xml-crypto@^0.8.1:
- version "0.8.5"
- resolved "http://registry.npmjs.org/xml-crypto/-/xml-crypto-0.8.5.tgz#2bbcfb3eb33f3a82a218b822bf672b6b1c20e538"
- dependencies:
- xmldom "=0.1.19"
- xpath.js ">=0.0.3"
-
-xml-encryption@~0.7.4:
- version "0.7.4"
- resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.7.4.tgz#42791ec64d556d2455dcb9da0a54123665ac65c7"
- dependencies:
- async "~0.2.7"
- ejs "~0.8.3"
- node-forge "0.2.24"
- xmldom "~0.1.15"
- xpath "0.0.5"
-
-xml2js@~0.4.1:
- version "0.4.19"
- resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
- dependencies:
- sax ">=0.6.0"
- xmlbuilder "~9.0.1"
-
-xmlbuilder@~2.1.0:
- version "2.1.0"
- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.1.0.tgz#6ddae31683b6df12100b29fc8a0d4f46349abbed"
- dependencies:
- underscore ">=1.5.x"
-
-xmlbuilder@~9.0.1:
- version "9.0.7"
- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
-
-xmldom@=0.1.19:
- version "0.1.19"
- resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
-
-xmldom@~0.1.15, xmldom@~0.1.19:
- version "0.1.27"
- resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
-
-xpath.js@>=0.0.3:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1"
-
-xpath@0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.5.tgz#454036f6ef0f3df5af5d4ba4a119fb75674b3e6c"
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index 0188d12a57d..62ba0d36982 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -151,6 +151,28 @@ describe('Api', () => {
});
});
+ describe('projectUsers', () => {
+ it('fetches all users of a particular project', done => {
+ const query = 'dummy query';
+ const options = { unused: 'option' };
+ const projectPath = 'gitlab-org%2Fgitlab-ce';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/users`;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
+
+ Api.projectUsers('gitlab-org/gitlab-ce', query, options)
+ .then(response => {
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('projectMergeRequests', () => {
const projectPath = 'abc';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests`;
@@ -412,6 +434,22 @@ describe('Api', () => {
});
});
+ describe('user counts', () => {
+ it('fetches single user counts', done => {
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/user_counts`;
+ mock.onGet(expectedUrl).reply(200, {
+ merge_requests: 4,
+ });
+
+ Api.userCounts()
+ .then(({ data }) => {
+ expect(data.merge_requests).toBe(4);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('user status', () => {
it('fetches single user status', done => {
const userId = '123456';
diff --git a/spec/frontend/autosave_spec.js b/spec/frontend/autosave_spec.js
index 4d9c8f96d62..33d402388c9 100644
--- a/spec/frontend/autosave_spec.js
+++ b/spec/frontend/autosave_spec.js
@@ -63,12 +63,15 @@ describe('Autosave', () => {
expect(field.trigger).toHaveBeenCalled();
});
- it('triggers native event', done => {
- autosave.field.get(0).addEventListener('change', () => {
- done();
- });
+ it('triggers native event', () => {
+ const fieldElement = autosave.field.get(0);
+ const eventHandler = jest.fn();
+ fieldElement.addEventListener('change', eventHandler);
Autosave.prototype.restore.call(autosave);
+
+ expect(eventHandler).toHaveBeenCalledTimes(1);
+ fieldElement.removeEventListener('change', eventHandler);
});
});
diff --git a/spec/frontend/behaviors/markdown/render_metrics_spec.js b/spec/frontend/behaviors/markdown/render_metrics_spec.js
new file mode 100644
index 00000000000..6db0eabc16b
--- /dev/null
+++ b/spec/frontend/behaviors/markdown/render_metrics_spec.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+import renderMetrics from '~/behaviors/markdown/render_metrics';
+import { TEST_HOST } from 'helpers/test_constants';
+
+const originalExtend = Vue.extend;
+
+describe('Render metrics for Gitlab Flavoured Markdown', () => {
+ const container = {
+ Metrics() {},
+ };
+
+ let spyExtend;
+
+ beforeEach(() => {
+ Vue.extend = () => container.Metrics;
+ spyExtend = jest.spyOn(Vue, 'extend');
+ });
+
+ afterEach(() => {
+ Vue.extend = originalExtend;
+ });
+
+ it('does nothing when no elements are found', () => {
+ renderMetrics([]);
+
+ expect(spyExtend).not.toHaveBeenCalled();
+ });
+
+ it('renders a vue component when elements are found', () => {
+ const element = document.createElement('div');
+ element.setAttribute('data-dashboard-url', TEST_HOST);
+
+ renderMetrics([element]);
+
+ expect(spyExtend).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/boards/services/board_service_spec.js b/spec/frontend/boards/services/board_service_spec.js
index de9fc998360..e106c2bf1f1 100644
--- a/spec/frontend/boards/services/board_service_spec.js
+++ b/spec/frontend/boards/services/board_service_spec.js
@@ -2,6 +2,7 @@ import BoardService from '~/boards/services/board_service';
import { TEST_HOST } from 'helpers/test_constants';
import AxiosMockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
+import boardsStore from '~/boards/stores/boards_store';
describe('BoardService', () => {
const dummyResponse = "without type checking this doesn't matter";
@@ -18,10 +19,11 @@ describe('BoardService', () => {
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
- service = new BoardService({
+ boardsStore.setEndpoints({
...endpoints,
boardId,
});
+ service = new BoardService();
});
describe('all', () => {
@@ -387,4 +389,163 @@ describe('BoardService', () => {
return expect(BoardService.toggleIssueSubscription(dummyEndpoint)).rejects.toThrow();
});
});
+
+ describe('allBoards', () => {
+ const url = `${endpoints.boardsEndpoint}.json`;
+
+ it('makes a request to fetch all boards', () => {
+ axiosMock.onGet(url).replyOnce(200, dummyResponse);
+ const expectedResponse = expect.objectContaining({ data: dummyResponse });
+
+ return expect(service.allBoards()).resolves.toEqual(expectedResponse);
+ });
+
+ it('fails for error response', () => {
+ axiosMock.onGet(url).replyOnce(500);
+
+ return expect(service.allBoards()).rejects.toThrow();
+ });
+ });
+
+ describe('recentBoards', () => {
+ const url = `${endpoints.recentBoardsEndpoint}.json`;
+
+ it('makes a request to fetch all boards', () => {
+ axiosMock.onGet(url).replyOnce(200, dummyResponse);
+ const expectedResponse = expect.objectContaining({ data: dummyResponse });
+
+ return expect(service.recentBoards()).resolves.toEqual(expectedResponse);
+ });
+
+ it('fails for error response', () => {
+ axiosMock.onGet(url).replyOnce(500);
+
+ return expect(service.recentBoards()).rejects.toThrow();
+ });
+ });
+
+ describe('createBoard', () => {
+ const labelIds = ['first label', 'second label'];
+ const assigneeId = 'as sign ee';
+ const milestoneId = 'vegetable soup';
+ const board = {
+ labels: labelIds.map(id => ({ id })),
+ assignee: { id: assigneeId },
+ milestone: { id: milestoneId },
+ };
+
+ describe('for existing board', () => {
+ const id = 'skate-board';
+ const url = `${endpoints.boardsEndpoint}/${id}.json`;
+ const expectedRequest = expect.objectContaining({
+ data: JSON.stringify({
+ board: {
+ ...board,
+ id,
+ label_ids: labelIds,
+ assignee_id: assigneeId,
+ milestone_id: milestoneId,
+ },
+ }),
+ });
+
+ let requestSpy;
+
+ beforeEach(() => {
+ requestSpy = jest.fn();
+ axiosMock.onPut(url).replyOnce(config => requestSpy(config));
+ });
+
+ it('makes a request to update the board', () => {
+ requestSpy.mockReturnValue([200, dummyResponse]);
+ const expectedResponse = expect.objectContaining({ data: dummyResponse });
+
+ return expect(
+ service.createBoard({
+ ...board,
+ id,
+ }),
+ )
+ .resolves.toEqual(expectedResponse)
+ .then(() => {
+ expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
+ });
+ });
+
+ it('fails for error response', () => {
+ requestSpy.mockReturnValue([500]);
+
+ return expect(
+ service.createBoard({
+ ...board,
+ id,
+ }),
+ )
+ .rejects.toThrow()
+ .then(() => {
+ expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
+ });
+ });
+ });
+
+ describe('for new board', () => {
+ const url = `${endpoints.boardsEndpoint}.json`;
+ const expectedRequest = expect.objectContaining({
+ data: JSON.stringify({
+ board: {
+ ...board,
+ label_ids: labelIds,
+ assignee_id: assigneeId,
+ milestone_id: milestoneId,
+ },
+ }),
+ });
+
+ let requestSpy;
+
+ beforeEach(() => {
+ requestSpy = jest.fn();
+ axiosMock.onPost(url).replyOnce(config => requestSpy(config));
+ });
+
+ it('makes a request to create a new board', () => {
+ requestSpy.mockReturnValue([200, dummyResponse]);
+ const expectedResponse = expect.objectContaining({ data: dummyResponse });
+
+ return expect(service.createBoard(board))
+ .resolves.toEqual(expectedResponse)
+ .then(() => {
+ expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
+ });
+ });
+
+ it('fails for error response', () => {
+ requestSpy.mockReturnValue([500]);
+
+ return expect(service.createBoard(board))
+ .rejects.toThrow()
+ .then(() => {
+ expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
+ });
+ });
+ });
+ });
+
+ describe('deleteBoard', () => {
+ const id = 'capsized';
+ const url = `${endpoints.boardsEndpoint}/${id}.json`;
+
+ it('makes a request to delete a boards', () => {
+ axiosMock.onDelete(url).replyOnce(200, dummyResponse);
+ const expectedResponse = expect.objectContaining({ data: dummyResponse });
+
+ return expect(service.deleteBoard({ id })).resolves.toEqual(expectedResponse);
+ });
+
+ it('fails for error response', () => {
+ axiosMock.onDelete(url).replyOnce(500);
+
+ return expect(service.deleteBoard({ id })).rejects.toThrow();
+ });
+ });
});
diff --git a/spec/frontend/branches/divergence_graph_spec.js b/spec/frontend/branches/divergence_graph_spec.js
new file mode 100644
index 00000000000..8283bc966e4
--- /dev/null
+++ b/spec/frontend/branches/divergence_graph_spec.js
@@ -0,0 +1,40 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import init from '~/branches/divergence_graph';
+
+describe('Divergence graph', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+
+ mock.onGet('/-/diverging_counts').reply(200, {
+ master: { ahead: 1, behind: 1 },
+ 'test/hello-world': { ahead: 1, behind: 1 },
+ });
+
+ jest.spyOn(axios, 'get');
+
+ document.body.innerHTML = `
+ <div class="js-branch-item" data-name="master"><div class="js-branch-divergence-graph"></div></div>
+ <div class="js-branch-item" data-name="test/hello-world"><div class="js-branch-divergence-graph"></div></div>
+ `;
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('calls axos get with list of branch names', () =>
+ init('/-/diverging_counts').then(() => {
+ expect(axios.get).toHaveBeenCalledWith('/-/diverging_counts', {
+ params: { names: ['master', 'test/hello-world'] },
+ });
+ }));
+
+ it('creates Vue components', () =>
+ init('/-/diverging_counts').then(() => {
+ expect(document.querySelector('[data-name="master"]').innerHTML).not.toEqual('');
+ expect(document.querySelector('[data-name="test/hello-world"]').innerHTML).not.toEqual('');
+ }));
+});
diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js
index 6de06a9e2d5..80816faa5fc 100644
--- a/spec/frontend/clusters/clusters_bundle_spec.js
+++ b/spec/frontend/clusters/clusters_bundle_spec.js
@@ -147,47 +147,80 @@ describe('Clusters', () => {
});
describe('updateContainer', () => {
+ const { location } = window;
+
+ beforeEach(() => {
+ delete window.location;
+ window.location = {
+ reload: jest.fn(),
+ hash: location.hash,
+ };
+ });
+
+ afterEach(() => {
+ window.location = location;
+ });
+
describe('when creating cluster', () => {
it('should show the creating container', () => {
cluster.updateContainer(null, 'creating');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
-
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ expect(window.location.reload).not.toHaveBeenCalled();
});
it('should continue to show `creating` banner with subsequent updates of the same status', () => {
+ cluster.updateContainer(null, 'creating');
cluster.updateContainer('creating', 'creating');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
-
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ expect(window.location.reload).not.toHaveBeenCalled();
});
});
describe('when cluster is created', () => {
- it('should show the success container and fresh the page', () => {
- cluster.updateContainer(null, 'created');
+ it('should hide the "creating" banner and refresh the page', () => {
+ jest.spyOn(cluster, 'setClusterNewlyCreated');
+ cluster.updateContainer(null, 'creating');
+ cluster.updateContainer('creating', 'created');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
+ expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
+ expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ expect(window.location.reload).toHaveBeenCalled();
+ expect(cluster.setClusterNewlyCreated).toHaveBeenCalledWith(true);
+ });
- expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy();
+ it('when the page is refreshed, it should show the "success" banner', () => {
+ jest.spyOn(cluster, 'setClusterNewlyCreated');
+ jest.spyOn(cluster, 'isClusterNewlyCreated').mockReturnValue(true);
+
+ cluster.updateContainer(null, 'created');
+ cluster.updateContainer('created', 'created');
+ expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
+ expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy();
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ expect(window.location.reload).not.toHaveBeenCalled();
+ expect(cluster.setClusterNewlyCreated).toHaveBeenCalledWith(false);
});
it('should not show a banner when status is already `created`', () => {
+ jest.spyOn(cluster, 'setClusterNewlyCreated');
+ jest.spyOn(cluster, 'isClusterNewlyCreated').mockReturnValue(false);
+
+ cluster.updateContainer(null, 'created');
cluster.updateContainer('created', 'created');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
-
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ expect(window.location.reload).not.toHaveBeenCalled();
+ expect(cluster.setClusterNewlyCreated).not.toHaveBeenCalled();
});
});
diff --git a/spec/frontend/commons/nav/user_merge_requests_spec.js b/spec/frontend/commons/nav/user_merge_requests_spec.js
new file mode 100644
index 00000000000..4da6d53557a
--- /dev/null
+++ b/spec/frontend/commons/nav/user_merge_requests_spec.js
@@ -0,0 +1,113 @@
+import {
+ openUserCountsBroadcast,
+ closeUserCountsBroadcast,
+ refreshUserMergeRequestCounts,
+} from '~/commons/nav/user_merge_requests';
+import Api from '~/api';
+
+jest.mock('~/api');
+
+const TEST_COUNT = 1000;
+const MR_COUNT_CLASS = 'merge-requests-count';
+
+describe('User Merge Requests', () => {
+ let channelMock;
+ let newBroadcastChannelMock;
+
+ beforeEach(() => {
+ global.gon.current_user_id = 123;
+
+ channelMock = {
+ postMessage: jest.fn(),
+ close: jest.fn(),
+ };
+ newBroadcastChannelMock = jest.fn().mockImplementation(() => channelMock);
+
+ global.BroadcastChannel = newBroadcastChannelMock;
+ setFixtures(`<div class="${MR_COUNT_CLASS}">0</div>`);
+ });
+
+ const findMRCountText = () => document.body.querySelector(`.${MR_COUNT_CLASS}`).textContent;
+
+ describe('refreshUserMergeRequestCounts', () => {
+ beforeEach(() => {
+ Api.userCounts.mockReturnValue(
+ Promise.resolve({
+ data: { merge_requests: TEST_COUNT },
+ }),
+ );
+ });
+
+ describe('with open broadcast channel', () => {
+ beforeEach(() => {
+ openUserCountsBroadcast();
+
+ return refreshUserMergeRequestCounts();
+ });
+
+ it('updates the top count of merge requests', () => {
+ expect(findMRCountText()).toEqual(TEST_COUNT.toLocaleString());
+ });
+
+ it('calls the API', () => {
+ expect(Api.userCounts).toHaveBeenCalled();
+ });
+
+ it('posts count to BroadcastChannel', () => {
+ expect(channelMock.postMessage).toHaveBeenCalledWith(TEST_COUNT);
+ });
+ });
+
+ describe('without open broadcast channel', () => {
+ beforeEach(() => refreshUserMergeRequestCounts());
+
+ it('does not post anything', () => {
+ expect(channelMock.postMessage).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('openUserCountsBroadcast', () => {
+ beforeEach(() => {
+ openUserCountsBroadcast();
+ });
+
+ it('creates BroadcastChannel that updates DOM on message received', () => {
+ expect(findMRCountText()).toEqual('0');
+
+ channelMock.onmessage({ data: TEST_COUNT });
+
+ expect(findMRCountText()).toEqual(TEST_COUNT.toLocaleString());
+ });
+
+ it('closes if called while already open', () => {
+ expect(channelMock.close).not.toHaveBeenCalled();
+
+ openUserCountsBroadcast();
+
+ expect(channelMock.close).toHaveBeenCalled();
+ });
+ });
+
+ describe('closeUserCountsBroadcast', () => {
+ describe('when not opened', () => {
+ it('does nothing', () => {
+ expect(channelMock.close).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when opened', () => {
+ beforeEach(() => {
+ openUserCountsBroadcast();
+ });
+
+ it('closes', () => {
+ expect(channelMock.close).not.toHaveBeenCalled();
+
+ closeUserCountsBroadcast();
+
+ expect(channelMock.close).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap b/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap
new file mode 100644
index 00000000000..47bdc677068
--- /dev/null
+++ b/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap
@@ -0,0 +1,101 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Confidential merge request project form group component renders empty state when response is empty 1`] = `
+<div
+ class="confidential-merge-request-fork-group form-group"
+>
+ <label>
+ Project
+ </label>
+
+ <div>
+ <!---->
+
+ <p
+ class="text-muted mt-1 mb-0"
+ >
+
+ No forks available to you.
+ <br />
+
+ <span>
+ To protect this issue's confidentiality,
+ <a
+ class="help-link"
+ href="https://test.com"
+ >
+ fork the project
+ </a>
+ and set the forks visiblity to private.
+ </span>
+
+ <gllink-stub
+ class="w-auto p-0 d-inline-block text-primary bg-transparent"
+ href="/help"
+ target="_blank"
+ >
+ <span
+ class="sr-only"
+ >
+ Read more
+ </span>
+
+ <i
+ aria-hidden="true"
+ class="fa fa-question-circle"
+ />
+ </gllink-stub>
+ </p>
+ </div>
+</div>
+`;
+
+exports[`Confidential merge request project form group component renders fork dropdown 1`] = `
+<div
+ class="confidential-merge-request-fork-group form-group"
+>
+ <label>
+ Project
+ </label>
+
+ <div>
+ <!---->
+
+ <p
+ class="text-muted mt-1 mb-0"
+ >
+
+ No forks available to you.
+ <br />
+
+ <span>
+ To protect this issue's confidentiality,
+ <a
+ class="help-link"
+ href="https://test.com"
+ >
+ fork the project
+ </a>
+ and set the forks visiblity to private.
+ </span>
+
+ <gllink-stub
+ class="w-auto p-0 d-inline-block text-primary bg-transparent"
+ href="/help"
+ target="_blank"
+ >
+ <span
+ class="sr-only"
+ >
+ Read more
+ </span>
+
+ <i
+ aria-hidden="true"
+ class="fa fa-question-circle"
+ />
+ </gllink-stub>
+ </p>
+ </div>
+</div>
+`;
diff --git a/spec/frontend/confidential_merge_request/components/dropdown_spec.js b/spec/frontend/confidential_merge_request/components/dropdown_spec.js
new file mode 100644
index 00000000000..69495f3c161
--- /dev/null
+++ b/spec/frontend/confidential_merge_request/components/dropdown_spec.js
@@ -0,0 +1,56 @@
+import { mount } from '@vue/test-utils';
+import { GlDropdownItem } from '@gitlab/ui';
+import Dropdown from '~/confidential_merge_request/components/dropdown.vue';
+
+let vm;
+
+function factory(projects = []) {
+ vm = mount(Dropdown, {
+ propsData: {
+ projects,
+ selectedProject: projects[0],
+ },
+ });
+}
+
+describe('Confidential merge request project dropdown component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('renders dropdown items', () => {
+ factory([
+ {
+ id: 1,
+ name: 'test',
+ },
+ {
+ id: 2,
+ name: 'test',
+ },
+ ]);
+
+ expect(vm.findAll(GlDropdownItem).length).toBe(2);
+ });
+
+ it('renders selected project icon', () => {
+ factory([
+ {
+ id: 1,
+ name: 'test',
+ },
+ {
+ id: 2,
+ name: 'test 2',
+ },
+ ]);
+
+ expect(vm.find('.js-active-project-check').classes()).not.toContain('icon');
+ expect(
+ vm
+ .findAll('.js-active-project-check')
+ .at(1)
+ .classes(),
+ ).toContain('icon');
+ });
+});
diff --git a/spec/frontend/confidential_merge_request/components/project_form_group_spec.js b/spec/frontend/confidential_merge_request/components/project_form_group_spec.js
new file mode 100644
index 00000000000..3001363f7b9
--- /dev/null
+++ b/spec/frontend/confidential_merge_request/components/project_form_group_spec.js
@@ -0,0 +1,77 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import ProjectFormGroup from '~/confidential_merge_request/components/project_form_group.vue';
+
+const localVue = createLocalVue();
+const mockData = [
+ {
+ id: 1,
+ name_with_namespace: 'root / gitlab-ce',
+ path_with_namespace: 'root/gitlab-ce',
+ namespace: {
+ full_path: 'root',
+ },
+ },
+ {
+ id: 2,
+ name_with_namespace: 'test / gitlab-ce',
+ path_with_namespace: 'test/gitlab-ce',
+ namespace: {
+ full_path: 'test',
+ },
+ },
+];
+let vm;
+let mock;
+
+function factory(projects = mockData) {
+ mock = new MockAdapter(axios);
+ mock.onGet(/api\/(.*)\/projects\/gitlab-org%2Fgitlab-ce\/forks/).reply(200, projects);
+
+ vm = shallowMount(ProjectFormGroup, {
+ localVue,
+ propsData: {
+ namespacePath: 'gitlab-org',
+ projectPath: 'gitlab-org/gitlab-ce',
+ newForkPath: 'https://test.com',
+ helpPagePath: '/help',
+ },
+ });
+}
+
+describe('Confidential merge request project form group component', () => {
+ afterEach(() => {
+ mock.restore();
+ vm.destroy();
+ });
+
+ it('renders fork dropdown', () => {
+ factory();
+
+ return localVue.nextTick(() => {
+ expect(vm.element).toMatchSnapshot();
+ });
+ });
+
+ it('sets selected project as first fork', () => {
+ factory();
+
+ return localVue.nextTick(() => {
+ expect(vm.vm.selectedProject).toEqual({
+ id: 1,
+ name: 'root / gitlab-ce',
+ pathWithNamespace: 'root/gitlab-ce',
+ namespaceFullpath: 'root',
+ });
+ });
+ });
+
+ it('renders empty state when response is empty', () => {
+ factory([]);
+
+ return localVue.nextTick(() => {
+ expect(vm.element).toMatchSnapshot();
+ });
+ });
+});
diff --git a/spec/javascripts/create_merge_request_dropdown_spec.js b/spec/frontend/create_merge_request_dropdown_spec.js
index 00fe3f451f5..dcc6fa96d18 100644
--- a/spec/javascripts/create_merge_request_dropdown_spec.js
+++ b/spec/frontend/create_merge_request_dropdown_spec.js
@@ -1,7 +1,8 @@
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
import CreateMergeRequestDropdown from '~/create_merge_request_dropdown';
-import { TEST_HOST } from 'spec/test_constants';
+import confidentialState from '~/confidential_merge_request/state';
+import { TEST_HOST } from './helpers/test_constants';
describe('CreateMergeRequestDropdown', () => {
let axiosMock;
@@ -10,7 +11,7 @@ describe('CreateMergeRequestDropdown', () => {
beforeEach(() => {
axiosMock = new MockAdapter(axios);
- setFixtures(`
+ document.body.innerHTML = `
<div id="dummy-wrapper-element">
<div class="available"></div>
<div class="unavailable">
@@ -18,11 +19,12 @@ describe('CreateMergeRequestDropdown', () => {
<div class="text"></div>
</div>
<div class="js-ref"></div>
+ <div class="js-create-mr"></div>
<div class="js-create-merge-request"></div>
<div class="js-create-target"></div>
<div class="js-dropdown-toggle"></div>
</div>
- `);
+ `;
const dummyElement = document.getElementById('dummy-wrapper-element');
dropdown = new CreateMergeRequestDropdown(dummyElement);
@@ -36,7 +38,7 @@ describe('CreateMergeRequestDropdown', () => {
describe('getRef', () => {
it('escapes branch names correctly', done => {
const endpoint = `${dropdown.refsPath}contains%23hash`;
- spyOn(axios, 'get').and.callThrough();
+ jest.spyOn(axios, 'get');
axiosMock.onGet(endpoint).replyOnce({});
dropdown
@@ -65,4 +67,37 @@ describe('CreateMergeRequestDropdown', () => {
);
});
});
+
+ describe('enable', () => {
+ beforeEach(() => {
+ dropdown.createMergeRequestButton.classList.add('disabled');
+ });
+
+ afterEach(() => {
+ confidentialState.selectedProject = {};
+ });
+
+ it('enables button when not confidential issue', () => {
+ dropdown.enable();
+
+ expect(dropdown.createMergeRequestButton.classList).not.toContain('disabled');
+ });
+
+ it('enables when can create confidential issue', () => {
+ document.querySelector('.js-create-mr').setAttribute('data-is-confidential', 'true');
+ confidentialState.selectedProject = { name: 'test' };
+
+ dropdown.enable();
+
+ expect(dropdown.createMergeRequestButton.classList).not.toContain('disabled');
+ });
+
+ it('does not enable when can not create confidential issue', () => {
+ document.querySelector('.js-create-mr').setAttribute('data-is-confidential', 'true');
+
+ dropdown.enable();
+
+ expect(dropdown.createMergeRequestButton.classList).toContain('disabled');
+ });
+ });
});
diff --git a/spec/frontend/cycle_analytics/stage_nav_item_spec.js b/spec/frontend/cycle_analytics/stage_nav_item_spec.js
new file mode 100644
index 00000000000..ff079082ca7
--- /dev/null
+++ b/spec/frontend/cycle_analytics/stage_nav_item_spec.js
@@ -0,0 +1,177 @@
+import { mount, shallowMount } from '@vue/test-utils';
+import StageNavItem from '~/cycle_analytics/components/stage_nav_item.vue';
+
+describe('StageNavItem', () => {
+ let wrapper = null;
+ const title = 'Cool stage';
+ const value = '1 day';
+
+ function createComponent(props, shallow = true) {
+ const func = shallow ? shallowMount : mount;
+ return func(StageNavItem, {
+ propsData: {
+ canEdit: false,
+ isActive: false,
+ isUserAllowed: false,
+ isDefaultStage: true,
+ title,
+ value,
+ ...props,
+ },
+ });
+ }
+
+ function hasStageName() {
+ const stageName = wrapper.find('.stage-name');
+ expect(stageName.exists()).toBe(true);
+ expect(stageName.text()).toEqual(title);
+ }
+
+ it('renders stage name', () => {
+ wrapper = createComponent({ isUserAllowed: true });
+ hasStageName();
+ wrapper.destroy();
+ });
+
+ describe('User has access', () => {
+ describe('with a value', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ isUserAllowed: true });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ it('renders the value for median value', () => {
+ expect(wrapper.find('.stage-empty').exists()).toBe(false);
+ expect(wrapper.find('.not-available').exists()).toBe(false);
+ expect(wrapper.find('.stage-median').text()).toEqual(value);
+ });
+ });
+
+ describe('without a value', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ isUserAllowed: true, value: null });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has the stage-empty class', () => {
+ expect(wrapper.find('.stage-empty').exists()).toBe(true);
+ });
+
+ it('renders Not enough data for the median value', () => {
+ expect(wrapper.find('.stage-median').text()).toEqual('Not enough data');
+ });
+ });
+ });
+
+ describe('is active', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ isActive: true }, false);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ it('has the active class', () => {
+ expect(wrapper.find('.stage-nav-item').classes('active')).toBe(true);
+ });
+ });
+
+ describe('is not active', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ it('emits the `select` event when clicked', () => {
+ expect(wrapper.emitted().select).toBeUndefined();
+ wrapper.trigger('click');
+ expect(wrapper.emitted().select.length).toBe(1);
+ });
+ });
+
+ describe('User does not have access', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ isUserAllowed: false }, false);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ it('renders stage name', () => {
+ hasStageName();
+ });
+
+ it('has class not-available', () => {
+ expect(wrapper.find('.stage-empty').exists()).toBe(false);
+ expect(wrapper.find('.not-available').exists()).toBe(true);
+ });
+
+ it('renders Not available for the median value', () => {
+ expect(wrapper.find('.stage-median').text()).toBe('Not available');
+ });
+ it('does not render options menu', () => {
+ expect(wrapper.find('.more-actions-toggle').exists()).toBe(false);
+ });
+ });
+
+ describe('User can edit stages', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ canEdit: true, isUserAllowed: true }, false);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ it('renders stage name', () => {
+ hasStageName();
+ });
+
+ it('renders options menu', () => {
+ expect(wrapper.find('.more-actions-toggle').exists()).toBe(true);
+ });
+
+ describe('Default stages', () => {
+ beforeEach(() => {
+ wrapper = createComponent(
+ { canEdit: true, isUserAllowed: true, isDefaultStage: true },
+ false,
+ );
+ });
+ it('can hide the stage', () => {
+ expect(wrapper.text()).toContain('Hide stage');
+ });
+ it('can not edit the stage', () => {
+ expect(wrapper.text()).not.toContain('Edit stage');
+ });
+ it('can not remove the stage', () => {
+ expect(wrapper.text()).not.toContain('Remove stage');
+ });
+ });
+
+ describe('Custom stages', () => {
+ beforeEach(() => {
+ wrapper = createComponent(
+ { canEdit: true, isUserAllowed: true, isDefaultStage: false },
+ false,
+ );
+ });
+ it('can edit the stage', () => {
+ expect(wrapper.text()).toContain('Edit stage');
+ });
+ it('can remove the stage', () => {
+ expect(wrapper.text()).toContain('Remove stage');
+ });
+
+ it('can not hide the stage', () => {
+ expect(wrapper.text()).not.toContain('Hide stage');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/diffs/components/diff_discussion_reply_spec.js b/spec/frontend/diffs/components/diff_discussion_reply_spec.js
new file mode 100644
index 00000000000..28689ab07de
--- /dev/null
+++ b/spec/frontend/diffs/components/diff_discussion_reply_spec.js
@@ -0,0 +1,90 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import DiffDiscussionReply from '~/diffs/components/diff_discussion_reply.vue';
+import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
+import NoteSignedOutWidget from '~/notes/components/note_signed_out_widget.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('DiffDiscussionReply', () => {
+ let wrapper;
+ let getters;
+ let store;
+
+ const createComponent = (props = {}, slots = {}) => {
+ wrapper = shallowMount(DiffDiscussionReply, {
+ store,
+ localVue,
+ sync: false,
+ propsData: {
+ ...props,
+ },
+ slots: {
+ ...slots,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('if user can reply', () => {
+ beforeEach(() => {
+ getters = {
+ userCanReply: () => true,
+ getUserData: () => ({
+ path: 'test-path',
+ avatar_url: 'avatar_url',
+ name: 'John Doe',
+ }),
+ };
+
+ store = new Vuex.Store({
+ getters,
+ });
+ });
+
+ it('should render a form if component has form', () => {
+ createComponent(
+ {
+ renderReplyPlaceholder: false,
+ hasForm: true,
+ },
+ {
+ form: `<div id="test-form"></div>`,
+ },
+ );
+
+ expect(wrapper.find('#test-form').exists()).toBe(true);
+ });
+
+ it('should render a reply placeholder if there is no form', () => {
+ createComponent({
+ renderReplyPlaceholder: true,
+ hasForm: false,
+ });
+
+ expect(wrapper.find(ReplyPlaceholder).exists()).toBe(true);
+ });
+ });
+
+ it('renders a signed out widget when user is not logged in', () => {
+ getters = {
+ userCanReply: () => false,
+ getUserData: () => null,
+ };
+
+ store = new Vuex.Store({
+ getters,
+ });
+
+ createComponent({
+ renderReplyPlaceholder: false,
+ hasForm: false,
+ });
+
+ expect(wrapper.find(NoteSignedOutWidget).exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
new file mode 100644
index 00000000000..48ee5c63f35
--- /dev/null
+++ b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
@@ -0,0 +1,113 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
+import discussionsMockData from '../mock_data/diff_discussions';
+
+const localVue = createLocalVue();
+const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
+
+describe('DiffGutterAvatars', () => {
+ let wrapper;
+
+ const findCollapseButton = () => wrapper.find('.diff-notes-collapse');
+ const findMoreCount = () => wrapper.find('.diff-comments-more-count');
+ const findUserAvatars = () => wrapper.findAll('.diff-comment-avatar');
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(DiffGutterAvatars, {
+ localVue,
+ sync: false,
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when expanded', () => {
+ beforeEach(() => {
+ createComponent({
+ discussions: getDiscussionsMockData(),
+ discussionsExpanded: true,
+ });
+ });
+
+ it('renders a collapse button when discussions are expanded', () => {
+ expect(findCollapseButton().exists()).toBe(true);
+ });
+
+ it('should emit toggleDiscussions event on button click', () => {
+ findCollapseButton().trigger('click');
+
+ expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy();
+ });
+ });
+
+ describe('when collapsed', () => {
+ beforeEach(() => {
+ createComponent({
+ discussions: getDiscussionsMockData(),
+ discussionsExpanded: false,
+ });
+ });
+
+ it('renders user avatars and moreCount text', () => {
+ expect(findUserAvatars().exists()).toBe(true);
+ expect(findMoreCount().exists()).toBe(true);
+ });
+
+ it('renders correct amount of user avatars', () => {
+ expect(findUserAvatars().length).toBe(3);
+ });
+
+ it('renders correct moreCount number', () => {
+ expect(findMoreCount().text()).toBe('+2');
+ });
+
+ it('should emit toggleDiscussions event on avatars click', () => {
+ findUserAvatars()
+ .at(0)
+ .trigger('click');
+
+ expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy();
+ });
+
+ it('should emit toggleDiscussions event on more count text click', () => {
+ findMoreCount().trigger('click');
+
+ expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy();
+ });
+ });
+
+ it('renders an empty more count string if there are no discussions', () => {
+ createComponent({
+ discussions: [],
+ discussionsExpanded: false,
+ });
+
+ expect(findMoreCount().exists()).toBe(false);
+ });
+
+ describe('tooltip text', () => {
+ beforeEach(() => {
+ createComponent({
+ discussions: getDiscussionsMockData(),
+ discussionsExpanded: false,
+ });
+ });
+
+ it('returns original comment if it is shorter than max length', () => {
+ const note = wrapper.vm.discussions[0].notes[0];
+
+ expect(wrapper.vm.getTooltipText(note)).toEqual('Administrator: comment 1');
+ });
+
+ it('returns truncated version of comment if it is longer than max length', () => {
+ const note = wrapper.vm.discussions[0].notes[1];
+
+ expect(wrapper.vm.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is r...');
+ });
+ });
+});
diff --git a/spec/frontend/diffs/mock_data/diff_discussions.js b/spec/frontend/diffs/mock_data/diff_discussions.js
new file mode 100644
index 00000000000..711ab543411
--- /dev/null
+++ b/spec/frontend/diffs/mock_data/diff_discussions.js
@@ -0,0 +1,529 @@
+export default {
+ id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ reply_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ position: {
+ old_line: null,
+ new_line: 2,
+ old_path: 'CHANGELOG',
+ new_path: 'CHANGELOG',
+ base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a',
+ start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962',
+ head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13',
+ },
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2',
+ expanded: true,
+ notes: [
+ {
+ id: '1749',
+ type: 'DiffNote',
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-04-03T21:06:21.521Z',
+ updated_at: '2018-04-08T08:50:41.762Z',
+ system: false,
+ noteable_id: 51,
+ noteable_type: 'MergeRequest',
+ noteable_iid: 20,
+ human_access: 'Owner',
+ note: 'comment 1',
+ note_html: '<p dir="auto">comment 1</p>',
+ last_edited_at: '2018-04-08T08:50:41.762Z',
+ last_edited_by: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ current_user: {
+ can_edit: true,
+ can_award_emoji: true,
+ },
+ resolved: false,
+ resolvable: true,
+ resolved_by: null,
+ discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-test/notes/1749/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1749&user_id=1',
+ path: '/gitlab-org/gitlab-test/notes/1749',
+ noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1749',
+ resolve_path:
+ '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve',
+ resolve_with_issue_path:
+ '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
+ },
+ {
+ id: '1753',
+ type: 'DiffNote',
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Fatih Acet',
+ username: 'fatihacet',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/fatihacevt',
+ },
+ created_at: '2018-04-08T08:49:35.804Z',
+ updated_at: '2018-04-08T08:50:45.915Z',
+ system: false,
+ noteable_id: 51,
+ noteable_type: 'MergeRequest',
+ noteable_iid: 20,
+ human_access: 'Owner',
+ note: 'comment 2 is really long one',
+ note_html: '<p dir="auto">comment 2 is really long one</p>',
+ last_edited_at: '2018-04-08T08:50:45.915Z',
+ last_edited_by: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ current_user: {
+ can_edit: true,
+ can_award_emoji: true,
+ },
+ resolved: false,
+ resolvable: true,
+ resolved_by: null,
+ discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-test/notes/1753/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1753&user_id=1',
+ path: '/gitlab-org/gitlab-test/notes/1753',
+ noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1753',
+ resolve_path:
+ '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve',
+ resolve_with_issue_path:
+ '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
+ },
+ {
+ id: '1754',
+ type: 'DiffNote',
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-04-08T08:50:48.294Z',
+ updated_at: '2018-04-08T08:50:48.294Z',
+ system: false,
+ noteable_id: 51,
+ noteable_type: 'MergeRequest',
+ noteable_iid: 20,
+ human_access: 'Owner',
+ note: 'comment 3',
+ note_html: '<p dir="auto">comment 3</p>',
+ current_user: {
+ can_edit: true,
+ can_award_emoji: true,
+ },
+ resolved: false,
+ resolvable: true,
+ resolved_by: null,
+ discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-test/notes/1754/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1754&user_id=1',
+ path: '/gitlab-org/gitlab-test/notes/1754',
+ noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1754',
+ resolve_path:
+ '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve',
+ resolve_with_issue_path:
+ '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
+ },
+ {
+ id: '1755',
+ type: 'DiffNote',
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-04-08T08:50:50.911Z',
+ updated_at: '2018-04-08T08:50:50.911Z',
+ system: false,
+ noteable_id: 51,
+ noteable_type: 'MergeRequest',
+ noteable_iid: 20,
+ human_access: 'Owner',
+ note: 'comment 4',
+ note_html: '<p dir="auto">comment 4</p>',
+ current_user: {
+ can_edit: true,
+ can_award_emoji: true,
+ },
+ resolved: false,
+ resolvable: true,
+ resolved_by: null,
+ discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-test/notes/1755/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1755&user_id=1',
+ path: '/gitlab-org/gitlab-test/notes/1755',
+ noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1755',
+ resolve_path:
+ '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve',
+ resolve_with_issue_path:
+ '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
+ },
+ {
+ id: '1756',
+ type: 'DiffNote',
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-04-08T08:50:53.895Z',
+ updated_at: '2018-04-08T08:50:53.895Z',
+ system: false,
+ noteable_id: 51,
+ noteable_type: 'MergeRequest',
+ noteable_iid: 20,
+ human_access: 'Owner',
+ note: 'comment 5',
+ note_html: '<p dir="auto">comment 5</p>',
+ current_user: {
+ can_edit: true,
+ can_award_emoji: true,
+ },
+ resolved: false,
+ resolvable: true,
+ resolved_by: null,
+ discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-test/notes/1756/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1756&user_id=1',
+ path: '/gitlab-org/gitlab-test/notes/1756',
+ noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1756',
+ resolve_path:
+ '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve',
+ resolve_with_issue_path:
+ '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
+ },
+ ],
+ individual_note: false,
+ resolvable: true,
+ resolved: false,
+ resolve_path:
+ '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve',
+ resolve_with_issue_path:
+ '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
+ diff_file: {
+ submodule: false,
+ submodule_link: null,
+ blob: {
+ id: '9e10516ca50788acf18c518a231914a21e5f16f7',
+ path: 'CHANGELOG',
+ name: 'CHANGELOG',
+ mode: '100644',
+ readable_text: true,
+ icon: 'file-text-o',
+ },
+ blob_path: 'CHANGELOG',
+ blob_name: 'CHANGELOG',
+ blob_icon: '<i aria-hidden="true" data-hidden="true" class="fa fa-file-text-o fa-fw"></i>',
+ file_hash: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a',
+ file_path: 'CHANGELOG.rb',
+ new_file: false,
+ deleted_file: false,
+ renamed_file: false,
+ old_path: 'CHANGELOG',
+ new_path: 'CHANGELOG',
+ mode_changed: false,
+ a_mode: '100644',
+ b_mode: '100644',
+ text: true,
+ added_lines: 2,
+ removed_lines: 0,
+ diff_refs: {
+ base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a',
+ start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962',
+ head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13',
+ },
+ content_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13',
+ stored_externally: null,
+ external_storage: null,
+ old_path_html: 'CHANGELOG_OLD',
+ new_path_html: 'CHANGELOG',
+ is_fully_expanded: true,
+ context_lines_path:
+ '/gitlab-org/gitlab-test/blob/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG/diff',
+ highlighted_diff_lines: [
+ {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1',
+ type: 'new',
+ old_line: null,
+ new_line: 1,
+ text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ rich_text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ meta_data: null,
+ },
+ {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2',
+ type: 'new',
+ old_line: null,
+ new_line: 2,
+ text: '<span id="LC2" class="line" lang="plaintext"></span>\n',
+ rich_text: '<span id="LC2" class="line" lang="plaintext"></span>\n',
+ meta_data: null,
+ },
+ {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3',
+ type: null,
+ old_line: 1,
+ new_line: 3,
+ text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ meta_data: null,
+ },
+ {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4',
+ type: null,
+ old_line: 2,
+ new_line: 4,
+ text: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ meta_data: null,
+ },
+ {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5',
+ type: null,
+ old_line: 3,
+ new_line: 5,
+ text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ meta_data: null,
+ },
+ {
+ line_code: null,
+ type: 'match',
+ old_line: null,
+ new_line: null,
+ text: '',
+ rich_text: '',
+ meta_data: {
+ old_pos: 3,
+ new_pos: 5,
+ },
+ },
+ {
+ line_code: null,
+ type: 'match',
+ old_line: null,
+ new_line: null,
+ text: '',
+ rich_text: '',
+ meta_data: {
+ old_pos: 3,
+ new_pos: 5,
+ },
+ },
+ {
+ line_code: null,
+ type: 'match',
+ old_line: null,
+ new_line: null,
+ text: '',
+ rich_text: '',
+ meta_data: {
+ old_pos: 3,
+ new_pos: 5,
+ },
+ },
+ ],
+ parallel_diff_lines: [
+ {
+ left: null,
+ right: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1',
+ type: 'new',
+ old_line: null,
+ new_line: 1,
+ text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ rich_text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ meta_data: null,
+ },
+ },
+ {
+ left: null,
+ right: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2',
+ type: 'new',
+ old_line: null,
+ new_line: 2,
+ text: '<span id="LC2" class="line" lang="plaintext"></span>\n',
+ rich_text: '<span id="LC2" class="line" lang="plaintext"></span>\n',
+ meta_data: null,
+ },
+ },
+ {
+ left: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3',
+ type: null,
+ old_line: 1,
+ new_line: 3,
+ text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ meta_data: null,
+ },
+ right: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3',
+ type: null,
+ old_line: 1,
+ new_line: 3,
+ text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ meta_data: null,
+ },
+ },
+ {
+ left: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4',
+ type: null,
+ old_line: 2,
+ new_line: 4,
+ text: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ meta_data: null,
+ },
+ right: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4',
+ type: null,
+ old_line: 2,
+ new_line: 4,
+ text: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ meta_data: null,
+ },
+ },
+ {
+ left: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5',
+ type: null,
+ old_line: 3,
+ new_line: 5,
+ text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ meta_data: null,
+ },
+ right: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5',
+ type: null,
+ old_line: 3,
+ new_line: 5,
+ text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ meta_data: null,
+ },
+ },
+ {
+ left: {
+ line_code: null,
+ type: 'match',
+ old_line: null,
+ new_line: null,
+ text: '',
+ rich_text: '',
+ meta_data: {
+ old_pos: 3,
+ new_pos: 5,
+ },
+ },
+ right: {
+ line_code: null,
+ type: 'match',
+ old_line: null,
+ new_line: null,
+ text: '',
+ rich_text: '',
+ meta_data: {
+ old_pos: 3,
+ new_pos: 5,
+ },
+ },
+ },
+ ],
+ viewer: {
+ name: 'text',
+ error: null,
+ },
+ },
+ diff_discussion: true,
+ truncated_diff_lines: [
+ {
+ text: 'line',
+ rich_text:
+ '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n',
+ can_receive_suggestion: true,
+ line_code: '6f209374f7e565f771b95720abf46024c41d1885_1_1',
+ type: 'new',
+ old_line: null,
+ new_line: 1,
+ meta_data: null,
+ },
+ ],
+};
+
+export const imageDiffDiscussions = [
+ {
+ id: '1',
+ position: {
+ x: 10,
+ y: 10,
+ width: 100,
+ height: 200,
+ },
+ },
+ {
+ id: '2',
+ position: {
+ x: 5,
+ y: 5,
+ width: 100,
+ height: 200,
+ },
+ },
+];
diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js
index a8c8688441d..290c0e797cb 100644
--- a/spec/frontend/environment.js
+++ b/spec/frontend/environment.js
@@ -1,8 +1,11 @@
/* eslint-disable import/no-commonjs */
+const path = require('path');
const { ErrorWithStack } = require('jest-util');
const JSDOMEnvironment = require('jest-environment-jsdom');
+const ROOT_PATH = path.resolve(__dirname, '../..');
+
class CustomEnvironment extends JSDOMEnvironment {
constructor(config, context) {
super(config, context);
@@ -35,9 +38,8 @@ class CustomEnvironment extends JSDOMEnvironment {
this.rejectedPromises.push(error);
};
- this.global.fixturesBasePath = `${process.cwd()}/${
- IS_EE ? 'ee/' : ''
- }spec/javascripts/fixtures`;
+ this.global.fixturesBasePath = `${ROOT_PATH}/tmp/tests/frontend/fixtures${IS_EE ? '-ee' : ''}`;
+ this.global.staticFixturesBasePath = `${ROOT_PATH}/spec/frontend/fixtures`;
// Not yet supported by JSDOM: https://github.com/jsdom/jsdom/issues/317
this.global.document.createRange = () => ({
diff --git a/spec/javascripts/environments/confirm_rollback_modal_spec.js b/spec/frontend/environments/confirm_rollback_modal_spec.js
index 05715bce38f..a1a22274e8f 100644
--- a/spec/javascripts/environments/confirm_rollback_modal_spec.js
+++ b/spec/frontend/environments/confirm_rollback_modal_spec.js
@@ -61,7 +61,7 @@ describe('Confirm Rollback Modal Component', () => {
environment,
},
});
- const eventHubSpy = spyOn(eventHub, '$emit');
+ const eventHubSpy = jest.spyOn(eventHub, '$emit');
const modal = component.find(GlModal);
modal.vm.$emit('ok');
diff --git a/spec/javascripts/environments/environment_rollback_spec.js b/spec/frontend/environments/environment_rollback_spec.js
index 8c47f6a12c0..fb62a096c3d 100644
--- a/spec/javascripts/environments/environment_rollback_spec.js
+++ b/spec/frontend/environments/environment_rollback_spec.js
@@ -1,46 +1,38 @@
-import Vue from 'vue';
-import { shallowMount } from '@vue/test-utils';
+import { shallowMount, mount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import eventHub from '~/environments/event_hub';
-import rollbackComp from '~/environments/components/environment_rollback.vue';
+import RollbackComponent from '~/environments/components/environment_rollback.vue';
describe('Rollback Component', () => {
const retryUrl = 'https://gitlab.com/retry';
- let RollbackComponent;
-
- beforeEach(() => {
- RollbackComponent = Vue.extend(rollbackComp);
- });
it('Should render Re-deploy label when isLastDeployment is true', () => {
- const component = new RollbackComponent({
- el: document.querySelector('.test-dom-element'),
+ const wrapper = mount(RollbackComponent, {
propsData: {
retryUrl,
isLastDeployment: true,
environment: {},
},
- }).$mount();
+ });
- expect(component.$el).toHaveSpriteIcon('repeat');
+ expect(wrapper.element).toHaveSpriteIcon('repeat');
});
it('Should render Rollback label when isLastDeployment is false', () => {
- const component = new RollbackComponent({
- el: document.querySelector('.test-dom-element'),
+ const wrapper = mount(RollbackComponent, {
propsData: {
retryUrl,
isLastDeployment: false,
environment: {},
},
- }).$mount();
+ });
- expect(component.$el).toHaveSpriteIcon('redo');
+ expect(wrapper.element).toHaveSpriteIcon('redo');
});
it('should emit a "rollback" event on button click', () => {
- const eventHubSpy = spyOn(eventHub, '$emit');
- const component = shallowMount(RollbackComponent, {
+ const eventHubSpy = jest.spyOn(eventHub, '$emit');
+ const wrapper = shallowMount(RollbackComponent, {
propsData: {
retryUrl,
environment: {
@@ -48,7 +40,7 @@ describe('Rollback Component', () => {
},
},
});
- const button = component.find(GlButton);
+ const button = wrapper.find(GlButton);
button.vm.$emit('click');
diff --git a/spec/javascripts/error_tracking_settings/components/app_spec.js b/spec/frontend/error_tracking_settings/components/app_spec.js
index 2e52a45fd34..022f12ef191 100644
--- a/spec/javascripts/error_tracking_settings/components/app_spec.js
+++ b/spec/frontend/error_tracking_settings/components/app_spec.js
@@ -4,7 +4,7 @@ import ErrorTrackingSettings from '~/error_tracking_settings/components/app.vue'
import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue';
import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue';
import createStore from '~/error_tracking_settings/store';
-import { TEST_HOST } from 'spec/test_constants';
+import { TEST_HOST } from 'helpers/test_constants';
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js b/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js
index 23e57c4bbf1..23e57c4bbf1 100644
--- a/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js
+++ b/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js
diff --git a/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js b/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js
index 8e5dbe28452..8e5dbe28452 100644
--- a/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js
+++ b/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js
diff --git a/spec/javascripts/error_tracking_settings/mock.js b/spec/frontend/error_tracking_settings/mock.js
index 32cdba33c14..8c5bfd08beb 100644
--- a/spec/javascripts/error_tracking_settings/mock.js
+++ b/spec/frontend/error_tracking_settings/mock.js
@@ -1,5 +1,5 @@
import createStore from '~/error_tracking_settings/store';
-import { TEST_HOST } from 'spec/test_constants';
+import { TEST_HOST } from 'helpers/test_constants';
const defaultStore = createStore();
diff --git a/spec/javascripts/error_tracking_settings/store/actions_spec.js b/spec/frontend/error_tracking_settings/store/actions_spec.js
index 0255b3a7aa4..1eab0f7470b 100644
--- a/spec/javascripts/error_tracking_settings/store/actions_spec.js
+++ b/spec/frontend/error_tracking_settings/store/actions_spec.js
@@ -1,13 +1,16 @@
import MockAdapter from 'axios-mock-adapter';
-import testAction from 'spec/helpers/vuex_action_helper';
-import { TEST_HOST } from 'spec/test_constants';
+import testAction from 'helpers/vuex_action_helper';
+import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import actionsDefaultExport, * as actions from '~/error_tracking_settings/store/actions';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import * as actions from '~/error_tracking_settings/store/actions';
import * as types from '~/error_tracking_settings/store/mutation_types';
import defaultState from '~/error_tracking_settings/store/state';
import { projectList } from '../mock';
+jest.mock('~/lib/utils/url_utility');
+
describe('error tracking settings actions', () => {
let state;
@@ -21,6 +24,7 @@ describe('error tracking settings actions', () => {
afterEach(() => {
mock.restore();
+ refreshCurrentPage.mockClear();
});
it('should request and transform the project list', done => {
@@ -111,7 +115,6 @@ describe('error tracking settings actions', () => {
});
it('should save the page', done => {
- const refreshCurrentPage = spyOnDependency(actionsDefaultExport, 'refreshCurrentPage');
mock.onPatch(TEST_HOST).reply(200);
testAction(actions.updateSettings, null, state, [], [{ type: 'requestSettings' }], () => {
expect(mock.history.patch.length).toBe(1);
diff --git a/spec/javascripts/error_tracking_settings/store/getters_spec.js b/spec/frontend/error_tracking_settings/store/getters_spec.js
index 2c5ff084b8a..2c5ff084b8a 100644
--- a/spec/javascripts/error_tracking_settings/store/getters_spec.js
+++ b/spec/frontend/error_tracking_settings/store/getters_spec.js
diff --git a/spec/javascripts/error_tracking_settings/store/mutation_spec.js b/spec/frontend/error_tracking_settings/store/mutation_spec.js
index bb1f1da784e..fa188462c3f 100644
--- a/spec/javascripts/error_tracking_settings/store/mutation_spec.js
+++ b/spec/frontend/error_tracking_settings/store/mutation_spec.js
@@ -1,4 +1,4 @@
-import { TEST_HOST } from 'spec/test_constants';
+import { TEST_HOST } from 'helpers/test_constants';
import mutations from '~/error_tracking_settings/store/mutations';
import defaultState from '~/error_tracking_settings/store/state';
import * as types from '~/error_tracking_settings/store/mutation_types';
diff --git a/spec/javascripts/error_tracking_settings/utils_spec.js b/spec/frontend/error_tracking_settings/utils_spec.js
index 4b144f7daf1..4b144f7daf1 100644
--- a/spec/javascripts/error_tracking_settings/utils_spec.js
+++ b/spec/frontend/error_tracking_settings/utils_spec.js
diff --git a/spec/frontend/filterable_list_spec.js b/spec/frontend/filterable_list_spec.js
new file mode 100644
index 00000000000..67d18611661
--- /dev/null
+++ b/spec/frontend/filterable_list_spec.js
@@ -0,0 +1,53 @@
+import FilterableList from '~/filterable_list';
+import { getJSONFixture, setHTMLFixture } from './helpers/fixtures';
+
+describe('FilterableList', () => {
+ let List;
+ let form;
+ let filter;
+ let holder;
+
+ beforeEach(() => {
+ setHTMLFixture(`
+ <form id="project-filter-form">
+ <input name="name" class="js-projects-list-filter" />
+ </div>
+ <div class="js-projects-list-holder"></div>
+ `);
+ getJSONFixture('static/projects.json');
+ form = document.querySelector('form#project-filter-form');
+ filter = document.querySelector('.js-projects-list-filter');
+ holder = document.querySelector('.js-projects-list-holder');
+ List = new FilterableList(form, filter, holder);
+ });
+
+ it('processes input parameters', () => {
+ expect(List.filterForm).toEqual(form);
+ expect(List.listFilterElement).toEqual(filter);
+ expect(List.listHolderElement).toEqual(holder);
+ });
+
+ describe('getPagePath', () => {
+ it('returns properly constructed base endpoint', () => {
+ List.filterForm.action = '/foo/bar/';
+ List.listFilterElement.value = 'blah';
+
+ expect(List.getPagePath()).toEqual('/foo/bar/?name=blah');
+ });
+
+ it('properly appends custom parameters to existing URL', () => {
+ List.filterForm.action = '/foo/bar?alpha=beta';
+ List.listFilterElement.value = 'blah';
+
+ expect(List.getPagePath()).toEqual('/foo/bar?alpha=beta&name=blah');
+ });
+ });
+
+ describe('getFilterEndpoint', () => {
+ it('returns getPagePath by default', () => {
+ jest.spyOn(List, 'getPagePath').mockReturnValue('blah/blah/foo');
+
+ expect(List.getFilterEndpoint()).toEqual(List.getPagePath());
+ });
+ });
+});
diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/frontend/fixtures/abuse_reports.rb
index e0aaecf626a..21356390cae 100644
--- a/spec/javascripts/fixtures/abuse_reports.rb
+++ b/spec/frontend/fixtures/abuse_reports.rb
@@ -21,6 +21,6 @@ describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controll
it 'abuse_reports/abuse_reports_list.html' do
get :index
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/frontend/fixtures/admin_users.rb
index 22a5de66577..0209594dadc 100644
--- a/spec/javascripts/fixtures/admin_users.rb
+++ b/spec/frontend/fixtures/admin_users.rb
@@ -23,6 +23,6 @@ describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do
get :new
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/frontend/fixtures/application_settings.rb
index d4651fa6ece..38a060580c1 100644
--- a/spec/javascripts/fixtures/application_settings.rb
+++ b/spec/frontend/fixtures/application_settings.rb
@@ -28,6 +28,6 @@ describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :c
get :show
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/frontend/fixtures/autocomplete_sources.rb
index b20a0159d7d..9e04328e2b9 100644
--- a/spec/javascripts/fixtures/autocomplete_sources.rb
+++ b/spec/frontend/fixtures/autocomplete_sources.rb
@@ -34,6 +34,6 @@ describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type:
type_id: issue.id
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/blob.rb b/spec/frontend/fixtures/blob.rb
index 07670552cd5..ce5030efbf8 100644
--- a/spec/javascripts/fixtures/blob.rb
+++ b/spec/frontend/fixtures/blob.rb
@@ -29,6 +29,6 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
id: 'add-ipython-files/files/ipython/basic.ipynb'
})
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/boards.rb b/spec/frontend/fixtures/boards.rb
index 5835721d3d5..f257d80390f 100644
--- a/spec/javascripts/fixtures/boards.rb
+++ b/spec/frontend/fixtures/boards.rb
@@ -23,6 +23,6 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller
project_id: project
})
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/branches.rb b/spec/frontend/fixtures/branches.rb
index 204aa9b7c7a..197fe42c52a 100644
--- a/spec/javascripts/fixtures/branches.rb
+++ b/spec/frontend/fixtures/branches.rb
@@ -27,6 +27,6 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle
project_id: project
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/frontend/fixtures/clusters.rb
index 1076404e0e3..f15ef010807 100644
--- a/spec/javascripts/fixtures/clusters.rb
+++ b/spec/frontend/fixtures/clusters.rb
@@ -29,6 +29,6 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
id: cluster
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/commit.rb b/spec/frontend/fixtures/commit.rb
index ff9a4bc1adc..a328c455356 100644
--- a/spec/javascripts/fixtures/commit.rb
+++ b/spec/frontend/fixtures/commit.rb
@@ -28,6 +28,6 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller
get :show, params: params
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/frontend/fixtures/deploy_keys.rb
index 38eab853da2..fca233c6f59 100644
--- a/spec/javascripts/fixtures/deploy_keys.rb
+++ b/spec/frontend/fixtures/deploy_keys.rb
@@ -38,6 +38,6 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control
project_id: project
}, format: :json
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/groups.rb b/spec/frontend/fixtures/groups.rb
index 4d0afc3ce1a..c1bb2d43332 100644
--- a/spec/javascripts/fixtures/groups.rb
+++ b/spec/frontend/fixtures/groups.rb
@@ -21,7 +21,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do
it 'groups/edit.html' do
get :edit, params: { id: group }
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -29,7 +29,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do
it 'groups/ci_cd_settings.html' do
get :show, params: { group_id: group }
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/frontend/fixtures/issues.rb
index d8d77f767de..b5eb38e0023 100644
--- a/spec/javascripts/fixtures/issues.rb
+++ b/spec/frontend/fixtures/issues.rb
@@ -48,7 +48,7 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
project_id: project
}
- expect(response).to be_success
+ expect(response).to be_successful
end
private
@@ -60,7 +60,7 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
id: issue.to_param
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -117,6 +117,6 @@ describe API::Issues, '(JavaScript fixtures)', type: :request do
get_related_merge_requests(project.id, issue.iid, user)
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/frontend/fixtures/jobs.rb
index 46ccd6f8c8a..a3a7759c85b 100644
--- a/spec/javascripts/fixtures/jobs.rb
+++ b/spec/frontend/fixtures/jobs.rb
@@ -39,7 +39,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
id: build_with_artifacts.to_param
}
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'jobs/delayed.json' do
@@ -49,6 +49,6 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
id: delayed_job.to_param
}, format: :json
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/labels.rb b/spec/frontend/fixtures/labels.rb
index 4d1b7317274..a312287970f 100644
--- a/spec/javascripts/fixtures/labels.rb
+++ b/spec/frontend/fixtures/labels.rb
@@ -35,7 +35,7 @@ describe 'Labels (JavaScript fixtures)' do
group_id: group
}, format: 'json'
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -52,7 +52,7 @@ describe 'Labels (JavaScript fixtures)' do
project_id: project
}, format: 'json'
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/frontend/fixtures/merge_requests.rb
index 05860be2291..88706e96676 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/frontend/fixtures/merge_requests.rb
@@ -129,6 +129,6 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
id: merge_request.to_param
}, format: :html
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/frontend/fixtures/merge_requests_diffs.rb
index 03b9b713fd8..b633a0495a6 100644
--- a/spec/javascripts/fixtures/merge_requests_diffs.rb
+++ b/spec/frontend/fixtures/merge_requests_diffs.rb
@@ -1,4 +1,3 @@
-
require 'spec_helper'
describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type: :controller do
@@ -65,6 +64,6 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type
**extra_params
}, format: :json
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/frontend/fixtures/pipeline_schedules.rb
index aecd56e6198..a70091a3919 100644
--- a/spec/javascripts/fixtures/pipeline_schedules.rb
+++ b/spec/frontend/fixtures/pipeline_schedules.rb
@@ -28,7 +28,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :
id: pipeline_schedule.id
}
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'pipeline_schedules/edit_with_variables.html' do
@@ -38,6 +38,6 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :
id: pipeline_schedule_populated.id
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/frontend/fixtures/pipelines.rb
index 6b6b0eefab9..ed57eb0aa80 100644
--- a/spec/javascripts/fixtures/pipelines.rb
+++ b/spec/frontend/fixtures/pipelines.rb
@@ -29,6 +29,6 @@ describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controll
project_id: project
}, format: :json
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/frontend/fixtures/projects.rb
index 94c59207898..91e3b65215a 100644
--- a/spec/javascripts/fixtures/projects.rb
+++ b/spec/frontend/fixtures/projects.rb
@@ -34,7 +34,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
id: project
}
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'projects/overview.html' do
@@ -43,7 +43,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
id: project_with_repo
}
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'projects/edit.html' do
@@ -52,7 +52,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
id: project
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -63,7 +63,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
project_id: project
}
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'projects/ci_cd_settings_with_variables.html' do
@@ -75,7 +75,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
project_id: project_variable_populated
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/frontend/fixtures/prometheus_service.rb
index f3171fdd97b..93ee81120d7 100644
--- a/spec/javascripts/fixtures/prometheus_service.rb
+++ b/spec/frontend/fixtures/prometheus_service.rb
@@ -29,6 +29,6 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
id: service.to_param
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/raw.rb b/spec/frontend/fixtures/raw.rb
index 801c80a0112..801c80a0112 100644
--- a/spec/javascripts/fixtures/raw.rb
+++ b/spec/frontend/fixtures/raw.rb
diff --git a/spec/javascripts/fixtures/search.rb b/spec/frontend/fixtures/search.rb
index 22fc546d761..c26c6998ae9 100644
--- a/spec/javascripts/fixtures/search.rb
+++ b/spec/frontend/fixtures/search.rb
@@ -12,6 +12,6 @@ describe SearchController, '(JavaScript fixtures)', type: :controller do
it 'search/show.html' do
get :show
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/services.rb b/spec/frontend/fixtures/services.rb
index 2237702ccca..ee1e088f158 100644
--- a/spec/javascripts/fixtures/services.rb
+++ b/spec/frontend/fixtures/services.rb
@@ -29,6 +29,6 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
id: service.to_param
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/frontend/fixtures/sessions.rb
index 92b74c01c89..18574ea06b5 100644
--- a/spec/javascripts/fixtures/sessions.rb
+++ b/spec/frontend/fixtures/sessions.rb
@@ -19,7 +19,7 @@ describe 'Sessions (JavaScript fixtures)' do
it 'sessions/new.html' do
get :new
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/frontend/fixtures/snippet.rb
index ace84b14eb7..23bcdb47ac6 100644
--- a/spec/javascripts/fixtures/snippet.rb
+++ b/spec/frontend/fixtures/snippet.rb
@@ -28,6 +28,6 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do
get(:show, params: { id: snippet.to_param })
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/frontend/fixtures/static/README.md b/spec/frontend/fixtures/static/README.md
new file mode 100644
index 00000000000..011601d0df8
--- /dev/null
+++ b/spec/frontend/fixtures/static/README.md
@@ -0,0 +1,3 @@
+# Please do not add new files here!
+
+Instead use a Ruby file in the fixtures root directory (`spec/frontend/fixtures/`).
diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html b/spec/frontend/fixtures/static/ajax_loading_spinner.html
index 0e1ebb32b1c..0e1ebb32b1c 100644
--- a/spec/javascripts/fixtures/static/ajax_loading_spinner.html
+++ b/spec/frontend/fixtures/static/ajax_loading_spinner.html
diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html b/spec/frontend/fixtures/static/balsamiq_viewer.html
index cdd723d1a84..cdd723d1a84 100644
--- a/spec/javascripts/fixtures/static/balsamiq_viewer.html
+++ b/spec/frontend/fixtures/static/balsamiq_viewer.html
diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html b/spec/frontend/fixtures/static/create_item_dropdown.html
index d2d38370092..d2d38370092 100644
--- a/spec/javascripts/fixtures/static/create_item_dropdown.html
+++ b/spec/frontend/fixtures/static/create_item_dropdown.html
diff --git a/spec/javascripts/fixtures/static/environments/table.html b/spec/frontend/fixtures/static/environments/table.html
index 417af564ff1..417af564ff1 100644
--- a/spec/javascripts/fixtures/static/environments/table.html
+++ b/spec/frontend/fixtures/static/environments/table.html
diff --git a/spec/frontend/fixtures/static/environments_logs.html b/spec/frontend/fixtures/static/environments_logs.html
new file mode 100644
index 00000000000..6179d3dbc23
--- /dev/null
+++ b/spec/frontend/fixtures/static/environments_logs.html
@@ -0,0 +1,29 @@
+<div class="js-kubernetes-logs" data-logs-path="/root/kubernetes-app/environments/1/logs">
+ <div class="build-page">
+ <div class="build-trace-container prepend-top-default">
+ <div class="top-bar js-top-bar">
+ <div class="truncated-info hidden-xs pull-left"></div>
+ <div class="dropdown prepend-left-10 js-pod-dropdown">
+ <button aria-expanded="false" class="dropdown-menu-toggle" data-toggle="dropdown" type="button">
+ <i class="fa fa-chevron-down"></i>
+ </button>
+ <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up"></div>
+ </div>
+ <div class="controllers pull-right">
+ <div class="has-tooltip controllers-buttons" data-container="body" data-placement="top" title="Scroll to top">
+ <button class="js-scroll-up btn-scroll btn-transparent btn-blank" disabled type="button"></button>
+ </div>
+ <div class="has-tooltip controllers-buttons" data-container="body" data-placement="top" title="Scroll to bottom">
+ <button class="js-scroll-down btn-scroll btn-transparent btn-blank" disabled type="button"></button>
+ </div>
+ <div class="refresh-control pull-right">
+ <div class="has-tooltip controllers-buttons" data-container="body" data-placement="top" title="Refresh">
+ <button class="js-refresh-log btn-default btn-refresh" disabled type="button"></button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <pre class="build-trace" id="build-trace"><code class="bash js-build-output"><div class="build-loader-animation js-build-refresh"></div></code></pre>
+ </div>
+ </div>
+</div>
diff --git a/spec/javascripts/fixtures/static/event_filter.html b/spec/frontend/fixtures/static/event_filter.html
index 8e9b6fb1b5c..8e9b6fb1b5c 100644
--- a/spec/javascripts/fixtures/static/event_filter.html
+++ b/spec/frontend/fixtures/static/event_filter.html
diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html b/spec/frontend/fixtures/static/gl_dropdown.html
index 08f6738414e..08f6738414e 100644
--- a/spec/javascripts/fixtures/static/gl_dropdown.html
+++ b/spec/frontend/fixtures/static/gl_dropdown.html
diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html b/spec/frontend/fixtures/static/gl_field_errors.html
index f8470e02b7c..f8470e02b7c 100644
--- a/spec/javascripts/fixtures/static/gl_field_errors.html
+++ b/spec/frontend/fixtures/static/gl_field_errors.html
diff --git a/spec/javascripts/fixtures/static/images/green_box.png b/spec/frontend/fixtures/static/images/green_box.png
index cd1ff9f9ade..cd1ff9f9ade 100644
--- a/spec/javascripts/fixtures/static/images/green_box.png
+++ b/spec/frontend/fixtures/static/images/green_box.png
Binary files differ
diff --git a/spec/javascripts/fixtures/static/images/one_white_pixel.png b/spec/frontend/fixtures/static/images/one_white_pixel.png
index 073fcf40a18..073fcf40a18 100644
--- a/spec/javascripts/fixtures/static/images/one_white_pixel.png
+++ b/spec/frontend/fixtures/static/images/one_white_pixel.png
Binary files differ
diff --git a/spec/javascripts/fixtures/static/images/red_box.png b/spec/frontend/fixtures/static/images/red_box.png
index 73b2927da0f..73b2927da0f 100644
--- a/spec/javascripts/fixtures/static/images/red_box.png
+++ b/spec/frontend/fixtures/static/images/red_box.png
Binary files differ
diff --git a/spec/javascripts/fixtures/static/issuable_filter.html b/spec/frontend/fixtures/static/issuable_filter.html
index 06b70fb43f1..06b70fb43f1 100644
--- a/spec/javascripts/fixtures/static/issuable_filter.html
+++ b/spec/frontend/fixtures/static/issuable_filter.html
diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html b/spec/frontend/fixtures/static/issue_sidebar_label.html
index ec8fb30f219..ec8fb30f219 100644
--- a/spec/javascripts/fixtures/static/issue_sidebar_label.html
+++ b/spec/frontend/fixtures/static/issue_sidebar_label.html
diff --git a/spec/javascripts/fixtures/static/line_highlighter.html b/spec/frontend/fixtures/static/line_highlighter.html
index 897a25d6760..897a25d6760 100644
--- a/spec/javascripts/fixtures/static/line_highlighter.html
+++ b/spec/frontend/fixtures/static/line_highlighter.html
diff --git a/spec/javascripts/fixtures/static/linked_tabs.html b/spec/frontend/fixtures/static/linked_tabs.html
index c25463bf1db..c25463bf1db 100644
--- a/spec/javascripts/fixtures/static/linked_tabs.html
+++ b/spec/frontend/fixtures/static/linked_tabs.html
diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html b/spec/frontend/fixtures/static/merge_requests_show.html
index 87e36c9f315..87e36c9f315 100644
--- a/spec/javascripts/fixtures/static/merge_requests_show.html
+++ b/spec/frontend/fixtures/static/merge_requests_show.html
diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html b/spec/frontend/fixtures/static/mini_dropdown_graph.html
index cd0b8dec3fc..cd0b8dec3fc 100644
--- a/spec/javascripts/fixtures/static/mini_dropdown_graph.html
+++ b/spec/frontend/fixtures/static/mini_dropdown_graph.html
diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html b/spec/frontend/fixtures/static/notebook_viewer.html
index 4bbb7bf1094..4bbb7bf1094 100644
--- a/spec/javascripts/fixtures/static/notebook_viewer.html
+++ b/spec/frontend/fixtures/static/notebook_viewer.html
diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html b/spec/frontend/fixtures/static/oauth_remember_me.html
index 9ba1ffc72fe..9ba1ffc72fe 100644
--- a/spec/javascripts/fixtures/static/oauth_remember_me.html
+++ b/spec/frontend/fixtures/static/oauth_remember_me.html
diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html b/spec/frontend/fixtures/static/pdf_viewer.html
index 350d35a262f..350d35a262f 100644
--- a/spec/javascripts/fixtures/static/pdf_viewer.html
+++ b/spec/frontend/fixtures/static/pdf_viewer.html
diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html b/spec/frontend/fixtures/static/pipeline_graph.html
index 422372bb7d5..422372bb7d5 100644
--- a/spec/javascripts/fixtures/static/pipeline_graph.html
+++ b/spec/frontend/fixtures/static/pipeline_graph.html
diff --git a/spec/javascripts/fixtures/static/pipelines.html b/spec/frontend/fixtures/static/pipelines.html
index 42333f94f2f..42333f94f2f 100644
--- a/spec/javascripts/fixtures/static/pipelines.html
+++ b/spec/frontend/fixtures/static/pipelines.html
diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html b/spec/frontend/fixtures/static/project_select_combo_button.html
index 50c826051c0..50c826051c0 100644
--- a/spec/javascripts/fixtures/static/project_select_combo_button.html
+++ b/spec/frontend/fixtures/static/project_select_combo_button.html
diff --git a/spec/javascripts/fixtures/static/projects.json b/spec/frontend/fixtures/static/projects.json
index 68a150f602a..68a150f602a 100644
--- a/spec/javascripts/fixtures/static/projects.json
+++ b/spec/frontend/fixtures/static/projects.json
diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html b/spec/frontend/fixtures/static/search_autocomplete.html
index 29db9020424..29db9020424 100644
--- a/spec/javascripts/fixtures/static/search_autocomplete.html
+++ b/spec/frontend/fixtures/static/search_autocomplete.html
diff --git a/spec/javascripts/fixtures/static/signin_tabs.html b/spec/frontend/fixtures/static/signin_tabs.html
index 7e66ab9394b..7e66ab9394b 100644
--- a/spec/javascripts/fixtures/static/signin_tabs.html
+++ b/spec/frontend/fixtures/static/signin_tabs.html
diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html b/spec/frontend/fixtures/static/sketch_viewer.html
index e25e554e568..e25e554e568 100644
--- a/spec/javascripts/fixtures/static/sketch_viewer.html
+++ b/spec/frontend/fixtures/static/sketch_viewer.html
diff --git a/spec/javascripts/fixtures/todos.rb b/spec/frontend/fixtures/todos.rb
index d0c8a6eca01..a7c183d2414 100644
--- a/spec/javascripts/fixtures/todos.rb
+++ b/spec/frontend/fixtures/todos.rb
@@ -29,7 +29,7 @@ describe 'Todos (JavaScript fixtures)' do
it 'todos/todos.html' do
get :index
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -48,7 +48,7 @@ describe 'Todos (JavaScript fixtures)' do
issuable_id: issue_2.id
}, format: 'json'
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/frontend/fixtures/u2f.rb
index f52832b6efb..8ecbc0390cd 100644
--- a/spec/javascripts/fixtures/u2f.rb
+++ b/spec/frontend/fixtures/u2f.rb
@@ -23,7 +23,7 @@ context 'U2F' do
post :create, params: { user: { login: user.username, password: user.password } }
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -38,7 +38,7 @@ context 'U2F' do
it 'u2f/register.html' do
get :show
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/frontend/helpers/fixtures.js b/spec/frontend/helpers/fixtures.js
index b77bcd6266e..778196843db 100644
--- a/spec/frontend/helpers/fixtures.js
+++ b/spec/frontend/helpers/fixtures.js
@@ -4,12 +4,15 @@ import path from 'path';
import { ErrorWithStack } from 'jest-util';
export function getFixture(relativePath) {
- const absolutePath = path.join(global.fixturesBasePath, relativePath);
+ const basePath = relativePath.startsWith('static/')
+ ? global.staticFixturesBasePath
+ : global.fixturesBasePath;
+ const absolutePath = path.join(basePath, relativePath);
if (!fs.existsSync(absolutePath)) {
throw new ErrorWithStack(
`Fixture file ${relativePath} does not exist.
-Did you run bin/rake karma:fixtures?`,
+Did you run bin/rake frontend:fixtures?`,
getFixture,
);
}
diff --git a/spec/frontend/helpers/vue_test_utils_helper.js b/spec/frontend/helpers/vue_test_utils_helper.js
index 121e99c9783..68326e37ae7 100644
--- a/spec/frontend/helpers/vue_test_utils_helper.js
+++ b/spec/frontend/helpers/vue_test_utils_helper.js
@@ -1,5 +1,3 @@
-/* eslint-disable import/prefer-default-export */
-
const vNodeContainsText = (vnode, text) =>
(vnode.text && vnode.text.includes(text)) ||
(vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length);
@@ -19,3 +17,19 @@ export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) =
Boolean(
shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length,
);
+
+/**
+ * Returns a promise that waits for a mutation to be fired before resolving
+ * NOTE: There's no reject action here so it will hang if it waits for a mutation that won't happen.
+ * @param {Object} store - The Vue store that contains the mutations
+ * @param {String} expectedMutationType - The Mutation to wait for
+ */
+export const waitForMutation = (store, expectedMutationType) =>
+ new Promise(resolve => {
+ const unsubscribe = store.subscribe(mutation => {
+ if (mutation.type === expectedMutationType) {
+ unsubscribe();
+ resolve();
+ }
+ });
+ });
diff --git a/spec/javascripts/helpers/vue_test_utils_helper_spec.js b/spec/frontend/helpers/vue_test_utils_helper_spec.js
index 41714066da5..41714066da5 100644
--- a/spec/javascripts/helpers/vue_test_utils_helper_spec.js
+++ b/spec/frontend/helpers/vue_test_utils_helper_spec.js
diff --git a/spec/frontend/ide/lib/files_spec.js b/spec/frontend/ide/lib/files_spec.js
index aa1fa0373db..08a31318544 100644
--- a/spec/frontend/ide/lib/files_spec.js
+++ b/spec/frontend/ide/lib/files_spec.js
@@ -12,6 +12,7 @@ const createEntries = paths => {
const { name, parent } = splitParent(path);
const parentEntry = acc[parent];
+ const previewMode = viewerInformationForPath(name);
acc[path] = {
...decorateData({
@@ -22,7 +23,8 @@ const createEntries = paths => {
path,
url: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}/-/${escapeFileUrl(path)}`),
type,
- previewMode: viewerInformationForPath(path),
+ previewMode,
+ binary: (previewMode && previewMode.binary) || false,
parentPath: parent,
parentTreeUrl: parentEntry
? parentEntry.url
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index 499fa8fc012..3d5ed4b5c0c 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -16,40 +16,16 @@ describe('IDE services', () => {
branch: TEST_BRANCH,
commit_message: 'Hello world',
actions: [],
- start_sha: undefined,
+ start_sha: TEST_COMMIT_SHA,
};
- Api.createBranch.mockReturnValue(Promise.resolve());
Api.commitMultiple.mockReturnValue(Promise.resolve());
});
- describe.each`
- startSha | shouldCreateBranch
- ${undefined} | ${false}
- ${TEST_COMMIT_SHA} | ${true}
- `('when start_sha is $startSha', ({ startSha, shouldCreateBranch }) => {
- beforeEach(() => {
- payload.start_sha = startSha;
+ it('should commit', () => {
+ services.commit(TEST_PROJECT_ID, payload);
- return services.commit(TEST_PROJECT_ID, payload);
- });
-
- if (shouldCreateBranch) {
- it('should create branch', () => {
- expect(Api.createBranch).toHaveBeenCalledWith(TEST_PROJECT_ID, {
- ref: TEST_COMMIT_SHA,
- branch: TEST_BRANCH,
- });
- });
- } else {
- it('should not create branch', () => {
- expect(Api.createBranch).not.toHaveBeenCalled();
- });
- }
-
- it('should commit', () => {
- expect(Api.commitMultiple).toHaveBeenCalledWith(TEST_PROJECT_ID, payload);
- });
+ expect(Api.commitMultiple).toHaveBeenCalledWith(TEST_PROJECT_ID, payload);
});
});
});
diff --git a/spec/frontend/ide/stores/modules/commit/mutations_spec.js b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
index 246500a2f34..45ac1a86ab3 100644
--- a/spec/frontend/ide/stores/modules/commit/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
@@ -62,12 +62,4 @@ describe('IDE commit module mutations', () => {
expect(state.shouldCreateMR).toBe(false);
});
});
-
- describe('INTERACT_WITH_NEW_MR', () => {
- it('sets interactedWithNewMR to true', () => {
- mutations.INTERACT_WITH_NEW_MR(state);
-
- expect(state.interactedWithNewMR).toBe(true);
- });
- });
});
diff --git a/spec/frontend/issue_show/components/pinned_links_spec.js b/spec/frontend/issue_show/components/pinned_links_spec.js
index 50041667a61..77da3390918 100644
--- a/spec/frontend/issue_show/components/pinned_links_spec.js
+++ b/spec/frontend/issue_show/components/pinned_links_spec.js
@@ -5,10 +5,6 @@ import PinnedLinks from '~/issue_show/components/pinned_links.vue';
const localVue = createLocalVue();
const plainZoomUrl = 'https://zoom.us/j/123456789';
-const vanityZoomUrl = 'https://gitlab.zoom.us/j/123456789';
-const startZoomUrl = 'https://zoom.us/s/123456789';
-const personalZoomUrl = 'https://zoom.us/my/hunter-zoloman';
-const randomUrl = 'https://zoom.us.com';
describe('PinnedLinks', () => {
let wrapper;
@@ -27,7 +23,7 @@ describe('PinnedLinks', () => {
localVue,
sync: false,
propsData: {
- descriptionHtml: '',
+ zoomMeetingUrl: null,
...props,
},
});
@@ -35,55 +31,15 @@ describe('PinnedLinks', () => {
it('displays Zoom link', () => {
createComponent({
- descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a>`,
+ zoomMeetingUrl: `<a href="${plainZoomUrl}">Zoom</a>`,
});
expect(link.text).toBe('Join Zoom meeting');
});
- it('detects plain Zoom link', () => {
+ it('does not render if there are no links', () => {
createComponent({
- descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(plainZoomUrl);
- });
-
- it('detects vanity Zoom link', () => {
- createComponent({
- descriptionHtml: `<a href="${vanityZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(vanityZoomUrl);
- });
-
- it('detects Zoom start meeting link', () => {
- createComponent({
- descriptionHtml: `<a href="${startZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(startZoomUrl);
- });
-
- it('detects personal Zoom room link', () => {
- createComponent({
- descriptionHtml: `<a href="${personalZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(personalZoomUrl);
- });
-
- it('only renders final Zoom link in description', () => {
- createComponent({
- descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a><a href="${vanityZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(vanityZoomUrl);
- });
-
- it('does not render for other links', () => {
- createComponent({
- descriptionHtml: `<a href="${randomUrl}">Some other link</a>`,
+ zoomMeetingUrl: null,
});
expect(wrapper.find(GlLink).exists()).toBe(false);
diff --git a/spec/frontend/lib/utils/color_utils_spec.js b/spec/frontend/lib/utils/color_utils_spec.js
new file mode 100644
index 00000000000..433e9d5a85e
--- /dev/null
+++ b/spec/frontend/lib/utils/color_utils_spec.js
@@ -0,0 +1,35 @@
+import { textColorForBackground, hexToRgb } from '~/lib/utils/color_utils';
+
+describe('Color utils', () => {
+ describe('Converting hex code to rgb', () => {
+ it('convert hex code to rgb', () => {
+ expect(hexToRgb('#000000')).toEqual([0, 0, 0]);
+ expect(hexToRgb('#ffffff')).toEqual([255, 255, 255]);
+ });
+
+ it('convert short hex code to rgb', () => {
+ expect(hexToRgb('#000')).toEqual([0, 0, 0]);
+ expect(hexToRgb('#fff')).toEqual([255, 255, 255]);
+ });
+
+ it('handle conversion regardless of the characters case', () => {
+ expect(hexToRgb('#f0F')).toEqual([255, 0, 255]);
+ });
+ });
+
+ describe('Getting text color for given background', () => {
+ // following tests are being ported from `text_color_for_bg` section in labels_helper_spec.rb
+ it('uses light text on dark backgrounds', () => {
+ expect(textColorForBackground('#222E2E')).toEqual('#FFFFFF');
+ });
+
+ it('uses dark text on light backgrounds', () => {
+ expect(textColorForBackground('#EEEEEE')).toEqual('#333333');
+ });
+
+ it('supports RGB triplets', () => {
+ expect(textColorForBackground('#FFF')).toEqual('#333333');
+ expect(textColorForBackground('#000')).toEqual('#FFFFFF');
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/forms_spec.js b/spec/frontend/lib/utils/forms_spec.js
new file mode 100644
index 00000000000..cac17235f0d
--- /dev/null
+++ b/spec/frontend/lib/utils/forms_spec.js
@@ -0,0 +1,74 @@
+import { serializeForm } from '~/lib/utils/forms';
+
+describe('lib/utils/forms', () => {
+ const createDummyForm = inputs => {
+ const form = document.createElement('form');
+
+ form.innerHTML = inputs
+ .map(({ type, name, value }) => {
+ let str = ``;
+ if (type === 'select') {
+ str = `<select name="${name}">`;
+ value.forEach(v => {
+ if (v.length > 0) {
+ str += `<option value="${v}"></option> `;
+ }
+ });
+ str += `</select>`;
+ } else {
+ str = `<input type="${type}" name="${name}" value="${value}" checked/>`;
+ }
+ return str;
+ })
+ .join('');
+
+ return form;
+ };
+
+ describe('serializeForm', () => {
+ it('returns an object of key values from inputs', () => {
+ const form = createDummyForm([
+ { type: 'text', name: 'foo', value: 'foo-value' },
+ { type: 'text', name: 'bar', value: 'bar-value' },
+ ]);
+
+ const data = serializeForm(form);
+
+ expect(data).toEqual({
+ foo: 'foo-value',
+ bar: 'bar-value',
+ });
+ });
+
+ it('works with select', () => {
+ const form = createDummyForm([
+ { type: 'select', name: 'foo', value: ['foo-value1', 'foo-value2'] },
+ { type: 'text', name: 'bar', value: 'bar-value1' },
+ ]);
+
+ const data = serializeForm(form);
+
+ expect(data).toEqual({
+ foo: 'foo-value1',
+ bar: 'bar-value1',
+ });
+ });
+
+ it('works with multiple inputs of the same name', () => {
+ const form = createDummyForm([
+ { type: 'checkbox', name: 'foo', value: 'foo-value3' },
+ { type: 'checkbox', name: 'foo', value: 'foo-value2' },
+ { type: 'checkbox', name: 'foo', value: 'foo-value1' },
+ { type: 'text', name: 'bar', value: 'bar-value2' },
+ { type: 'text', name: 'bar', value: 'bar-value1' },
+ ]);
+
+ const data = serializeForm(form);
+
+ expect(data).toEqual({
+ foo: ['foo-value3', 'foo-value2', 'foo-value1'],
+ bar: ['bar-value2', 'bar-value1'],
+ });
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index 9e920d59093..b6f1aef9ce4 100644
--- a/spec/frontend/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -29,20 +29,6 @@ describe('text_utility', () => {
});
});
- describe('pluralize', () => {
- it('should pluralize given string', () => {
- expect(textUtils.pluralize('test', 2)).toBe('tests');
- });
-
- it('should pluralize when count is 0', () => {
- expect(textUtils.pluralize('test', 0)).toBe('tests');
- });
-
- it('should not pluralize when count is 1', () => {
- expect(textUtils.pluralize('test', 1)).toBe('test');
- });
- });
-
describe('dasherize', () => {
it('should replace underscores with dashes', () => {
expect(textUtils.dasherize('foo_bar_foo')).toEqual('foo-bar-foo');
@@ -55,9 +41,24 @@ describe('text_utility', () => {
});
});
- describe('slugifyWithHyphens', () => {
+ describe('slugify', () => {
+ it('should remove accents and convert to lower case', () => {
+ expect(textUtils.slugify('João')).toEqual('jo-o');
+ });
it('should replaces whitespaces with hyphens and convert to lower case', () => {
- expect(textUtils.slugifyWithHyphens('My Input String')).toEqual('my-input-string');
+ expect(textUtils.slugify('My Input String')).toEqual('my-input-string');
+ });
+ it('should remove trailing whitespace and replace whitespaces within string with a hyphen', () => {
+ expect(textUtils.slugify(' a new project ')).toEqual('a-new-project');
+ });
+ it('should only remove non-allowed special characters', () => {
+ expect(textUtils.slugify('test!_pro-ject~')).toEqual('test-_pro-ject-');
+ });
+ it('should squash multiple hypens', () => {
+ expect(textUtils.slugify('test!!!!_pro-ject~')).toEqual('test-_pro-ject-');
+ });
+ it('should return empty string if only non-allowed characters', () => {
+ expect(textUtils.slugify('здрасти')).toEqual('');
});
});
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index c771984a137..b0bdd924921 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -34,6 +34,41 @@ describe('URL utility', () => {
});
});
+ describe('getParameterValues', () => {
+ beforeEach(() => {
+ setWindowLocation({
+ href: 'https://gitlab.com?test=passing&multiple=1&multiple=2',
+ // make our fake location act like real window.location.toString
+ // URL() (used in getParameterValues) does this if passed an object
+ toString() {
+ return this.href;
+ },
+ });
+ });
+
+ it('returns empty array for no params', () => {
+ expect(urlUtils.getParameterValues()).toEqual([]);
+ });
+
+ it('returns empty array for non-matching params', () => {
+ expect(urlUtils.getParameterValues('notFound')).toEqual([]);
+ });
+
+ it('returns single match', () => {
+ expect(urlUtils.getParameterValues('test')).toEqual(['passing']);
+ });
+
+ it('returns multiple matches', () => {
+ expect(urlUtils.getParameterValues('multiple')).toEqual(['1', '2']);
+ });
+
+ it('accepts url as second arg', () => {
+ const url = 'https://gitlab.com?everything=works';
+ expect(urlUtils.getParameterValues('everything', url)).toEqual(['works']);
+ expect(urlUtils.getParameterValues('test', url)).toEqual([]);
+ });
+ });
+
describe('mergeUrlParams', () => {
it('adds w', () => {
expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag');
@@ -59,6 +94,12 @@ describe('URL utility', () => {
it('adds and updates encoded params', () => {
expect(urlUtils.mergeUrlParams({ a: '&', q: '?' }, '?a=%23#frag')).toBe('?a=%26&q=%3F#frag');
});
+
+ it('treats "+" as "%20"', () => {
+ expect(urlUtils.mergeUrlParams({ ref: 'bogus' }, '?a=lorem+ipsum&ref=charlie')).toBe(
+ '?a=lorem%20ipsum&ref=bogus',
+ );
+ });
});
describe('removeParams', () => {
diff --git a/spec/frontend/matchers.js b/spec/frontend/matchers.js
new file mode 100644
index 00000000000..35c362d0bf5
--- /dev/null
+++ b/spec/frontend/matchers.js
@@ -0,0 +1,38 @@
+export default {
+ toHaveSpriteIcon: (element, iconName) => {
+ if (!iconName) {
+ throw new Error('toHaveSpriteIcon is missing iconName argument!');
+ }
+
+ if (!(element instanceof HTMLElement)) {
+ throw new Error(`${element} is not a DOM element!`);
+ }
+
+ const iconReferences = [].slice.apply(element.querySelectorAll('svg use'));
+ const matchingIcon = iconReferences.find(reference =>
+ reference.getAttribute('xlink:href').endsWith(`#${iconName}`),
+ );
+
+ const pass = Boolean(matchingIcon);
+
+ let message;
+ if (pass) {
+ message = `${element.outerHTML} contains the sprite icon "${iconName}"!`;
+ } else {
+ message = `${element.outerHTML} does not contain the sprite icon "${iconName}"!`;
+
+ const existingIcons = iconReferences.map(reference => {
+ const iconUrl = reference.getAttribute('xlink:href');
+ return `"${iconUrl.replace(/^.+#/, '')}"`;
+ });
+ if (existingIcons.length > 0) {
+ message += ` (only found ${existingIcons.join(',')})`;
+ }
+ }
+
+ return {
+ pass,
+ message: () => message,
+ };
+ },
+};
diff --git a/spec/frontend/mocks/ce/lib/utils/axios_utils.js b/spec/frontend/mocks/ce/lib/utils/axios_utils.js
new file mode 100644
index 00000000000..b4065626b09
--- /dev/null
+++ b/spec/frontend/mocks/ce/lib/utils/axios_utils.js
@@ -0,0 +1,15 @@
+const axios = jest.requireActual('~/lib/utils/axios_utils').default;
+
+axios.isMock = true;
+
+// Fail tests for unmocked requests
+axios.defaults.adapter = config => {
+ const message =
+ `Unexpected unmocked request: ${JSON.stringify(config, null, 2)}\n` +
+ 'Consider using the `axios-mock-adapter` in tests.';
+ const error = new Error(message);
+ error.config = config;
+ throw error;
+};
+
+export default axios;
diff --git a/spec/frontend/mocks/mocks_helper.js b/spec/frontend/mocks/mocks_helper.js
new file mode 100644
index 00000000000..21c032cd3c9
--- /dev/null
+++ b/spec/frontend/mocks/mocks_helper.js
@@ -0,0 +1,60 @@
+/**
+ * @module
+ *
+ * This module implements auto-injected manual mocks that are cleaner than Jest's approach.
+ *
+ * See https://docs.gitlab.com/ee/development/testing_guide/frontend_testing.html
+ */
+
+import fs from 'fs';
+import path from 'path';
+
+import readdir from 'readdir-enhanced';
+
+const MAX_DEPTH = 20;
+const prefixMap = [
+ // E.g. the mock ce/foo/bar maps to require path ~/foo/bar
+ { mocksRoot: 'ce', requirePrefix: '~' },
+ // { mocksRoot: 'ee', requirePrefix: 'ee' }, // We'll deal with EE-specific mocks later
+ { mocksRoot: 'node', requirePrefix: '' },
+ // { mocksRoot: 'virtual', requirePrefix: '' }, // We'll deal with virtual mocks later
+];
+
+const mockFileFilter = stats => stats.isFile() && stats.path.endsWith('.js');
+
+const getMockFiles = root => readdir.sync(root, { deep: MAX_DEPTH, filter: mockFileFilter });
+
+// Function that performs setting a mock. This has to be overridden by the unit test, because
+// jest.setMock can't be overwritten across files.
+// Use require() because jest.setMock expects the CommonJS exports object
+const defaultSetMock = (srcPath, mockPath) =>
+ jest.mock(srcPath, () => jest.requireActual(mockPath));
+
+// eslint-disable-next-line import/prefer-default-export
+export const setupManualMocks = function setupManualMocks(setMock = defaultSetMock) {
+ prefixMap.forEach(({ mocksRoot, requirePrefix }) => {
+ const mocksRootAbsolute = path.join(__dirname, mocksRoot);
+ if (!fs.existsSync(mocksRootAbsolute)) {
+ return;
+ }
+
+ getMockFiles(path.join(__dirname, mocksRoot)).forEach(mockPath => {
+ const mockPathNoExt = mockPath.substring(0, mockPath.length - path.extname(mockPath).length);
+ const sourcePath = path.join(requirePrefix, mockPathNoExt);
+ const mockPathRelative = `./${path.join(mocksRoot, mockPathNoExt)}`;
+
+ try {
+ setMock(sourcePath, mockPathRelative);
+ } catch (e) {
+ if (e.message.includes('Could not locate module')) {
+ // The corresponding mocked module doesn't exist. Raise a better error.
+ // Eventualy, we may support virtual mocks (mocks whose path doesn't directly correspond
+ // to a module, like with the `ee_else_ce` prefix).
+ throw new Error(
+ `A manual mock was defined for module ${sourcePath}, but the module doesn't exist!`,
+ );
+ }
+ }
+ });
+ });
+};
diff --git a/spec/frontend/mocks/mocks_helper_spec.js b/spec/frontend/mocks/mocks_helper_spec.js
new file mode 100644
index 00000000000..b8bb02c2f43
--- /dev/null
+++ b/spec/frontend/mocks/mocks_helper_spec.js
@@ -0,0 +1,149 @@
+/* eslint-disable global-require, promise/catch-or-return */
+
+import path from 'path';
+
+import axios from '~/lib/utils/axios_utils';
+
+const absPath = path.join.bind(null, __dirname);
+
+jest.mock('fs');
+jest.mock('readdir-enhanced');
+
+describe('mocks_helper.js', () => {
+ let setupManualMocks;
+ const setMock = jest.fn().mockName('setMock');
+ let fs;
+ let readdir;
+
+ beforeAll(() => {
+ jest.resetModules();
+ jest.setMock = jest.fn().mockName('jest.setMock');
+ fs = require('fs');
+ readdir = require('readdir-enhanced');
+
+ // We need to provide setupManualMocks with a mock function that pretends to do the setup of
+ // the mock. This is because we can't mock jest.setMock across files.
+ setupManualMocks = () => require('./mocks_helper').setupManualMocks(setMock);
+ });
+
+ afterEach(() => {
+ fs.existsSync.mockReset();
+ readdir.sync.mockReset();
+ setMock.mockReset();
+ });
+
+ it('enumerates through mock file roots', () => {
+ setupManualMocks();
+ expect(fs.existsSync).toHaveBeenCalledTimes(2);
+ expect(fs.existsSync).toHaveBeenNthCalledWith(1, absPath('ce'));
+ expect(fs.existsSync).toHaveBeenNthCalledWith(2, absPath('node'));
+
+ expect(readdir.sync).toHaveBeenCalledTimes(0);
+ });
+
+ it("doesn't traverse the directory tree infinitely", () => {
+ fs.existsSync.mockReturnValue(true);
+ readdir.sync.mockReturnValue([]);
+ setupManualMocks();
+
+ const readdirSpy = readdir.sync;
+ expect(readdirSpy).toHaveBeenCalled();
+ readdirSpy.mock.calls.forEach(call => {
+ expect(call[1].deep).toBeLessThan(100);
+ });
+ });
+
+ it('sets up mocks for CE (the ~/ prefix)', () => {
+ fs.existsSync.mockImplementation(root => root.endsWith('ce'));
+ readdir.sync.mockReturnValue(['root.js', 'lib/utils/util.js']);
+ setupManualMocks();
+
+ expect(readdir.sync).toHaveBeenCalledTimes(1);
+ expect(readdir.sync.mock.calls[0][0]).toBe(absPath('ce'));
+
+ expect(setMock).toHaveBeenCalledTimes(2);
+ expect(setMock).toHaveBeenNthCalledWith(1, '~/root', './ce/root');
+ expect(setMock).toHaveBeenNthCalledWith(2, '~/lib/utils/util', './ce/lib/utils/util');
+ });
+
+ it('sets up mocks for node_modules', () => {
+ fs.existsSync.mockImplementation(root => root.endsWith('node'));
+ readdir.sync.mockReturnValue(['jquery', '@babel/core']);
+ setupManualMocks();
+
+ expect(readdir.sync).toHaveBeenCalledTimes(1);
+ expect(readdir.sync.mock.calls[0][0]).toBe(absPath('node'));
+
+ expect(setMock).toHaveBeenCalledTimes(2);
+ expect(setMock).toHaveBeenNthCalledWith(1, 'jquery', './node/jquery');
+ expect(setMock).toHaveBeenNthCalledWith(2, '@babel/core', './node/@babel/core');
+ });
+
+ it('sets up mocks for all roots', () => {
+ const files = {
+ [absPath('ce')]: ['root', 'lib/utils/util'],
+ [absPath('node')]: ['jquery', '@babel/core'],
+ };
+
+ fs.existsSync.mockReturnValue(true);
+ readdir.sync.mockImplementation(root => files[root]);
+ setupManualMocks();
+
+ expect(readdir.sync).toHaveBeenCalledTimes(2);
+ expect(readdir.sync.mock.calls[0][0]).toBe(absPath('ce'));
+ expect(readdir.sync.mock.calls[1][0]).toBe(absPath('node'));
+
+ expect(setMock).toHaveBeenCalledTimes(4);
+ expect(setMock).toHaveBeenNthCalledWith(1, '~/root', './ce/root');
+ expect(setMock).toHaveBeenNthCalledWith(2, '~/lib/utils/util', './ce/lib/utils/util');
+ expect(setMock).toHaveBeenNthCalledWith(3, 'jquery', './node/jquery');
+ expect(setMock).toHaveBeenNthCalledWith(4, '@babel/core', './node/@babel/core');
+ });
+
+ it('fails when given a virtual mock', () => {
+ fs.existsSync.mockImplementation(p => p.endsWith('ce'));
+ readdir.sync.mockReturnValue(['virtual', 'shouldntBeImported']);
+ setMock.mockImplementation(() => {
+ throw new Error('Could not locate module');
+ });
+
+ expect(setupManualMocks).toThrow(
+ new Error("A manual mock was defined for module ~/virtual, but the module doesn't exist!"),
+ );
+
+ expect(readdir.sync).toHaveBeenCalledTimes(1);
+ expect(readdir.sync.mock.calls[0][0]).toBe(absPath('ce'));
+ });
+
+ describe('auto-injection', () => {
+ it('handles ambiguous paths', () => {
+ jest.isolateModules(() => {
+ const axios2 = require('../../../app/assets/javascripts/lib/utils/axios_utils').default;
+ expect(axios2.isMock).toBe(true);
+ });
+ });
+
+ it('survives jest.isolateModules()', done => {
+ jest.isolateModules(() => {
+ const axios2 = require('~/lib/utils/axios_utils').default;
+ expect(axios2.get('http://gitlab.com'))
+ .rejects.toThrow('Unexpected unmocked request')
+ .then(done);
+ });
+ });
+
+ it('can be unmocked and remocked', () => {
+ jest.dontMock('~/lib/utils/axios_utils');
+ jest.resetModules();
+ const axios2 = require('~/lib/utils/axios_utils').default;
+ expect(axios2).not.toBe(axios);
+ expect(axios2.isMock).toBeUndefined();
+
+ jest.doMock('~/lib/utils/axios_utils');
+ jest.resetModules();
+ const axios3 = require('~/lib/utils/axios_utils').default;
+ expect(axios3).not.toBe(axios2);
+ expect(axios3.isMock).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/mocks/node/jquery.js b/spec/frontend/mocks/node/jquery.js
new file mode 100644
index 00000000000..34a25772f67
--- /dev/null
+++ b/spec/frontend/mocks/node/jquery.js
@@ -0,0 +1,13 @@
+/* eslint-disable import/no-commonjs */
+
+const $ = jest.requireActual('jquery');
+
+// Fail tests for unmocked requests
+$.ajax = () => {
+ throw new Error(
+ 'Unexpected unmocked jQuery.ajax() call! Make sure to mock jQuery.ajax() in tests.',
+ );
+};
+
+// jquery is not an ES6 module
+module.exports = $;
diff --git a/spec/frontend/mocks_spec.js b/spec/frontend/mocks_spec.js
new file mode 100644
index 00000000000..2d2324120fd
--- /dev/null
+++ b/spec/frontend/mocks_spec.js
@@ -0,0 +1,13 @@
+import $ from 'jquery';
+import axios from '~/lib/utils/axios_utils';
+
+describe('Mock auto-injection', () => {
+ describe('mocks', () => {
+ it('~/lib/utils/axios_utils', () =>
+ expect(axios.get('http://gitlab.com')).rejects.toThrow('Unexpected unmocked request'));
+
+ it('jQuery.ajax()', () => {
+ expect($.ajax).toThrow('Unexpected unmocked');
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/__snapshots__/dashboard_state_spec.js.snap b/spec/frontend/monitoring/__snapshots__/dashboard_state_spec.js.snap
new file mode 100644
index 00000000000..5f24bab600c
--- /dev/null
+++ b/spec/frontend/monitoring/__snapshots__/dashboard_state_spec.js.snap
@@ -0,0 +1,37 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EmptyState shows gettingStarted state 1`] = `
+<glemptystate-stub
+ description="Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
+ primarybuttonlink="/clustersPath"
+ primarybuttontext="Install on clusters"
+ secondarybuttonlink="/settingsPath"
+ secondarybuttontext="Configure existing installation"
+ svgpath="/path/to/getting-started.svg"
+ title="Get started with performance monitoring"
+/>
+`;
+
+exports[`EmptyState shows loading state 1`] = `
+<glemptystate-stub
+ description="Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available."
+ primarybuttonlink="/documentationPath"
+ primarybuttontext="View documentation"
+ secondarybuttonlink=""
+ secondarybuttontext=""
+ svgpath="/path/to/loading.svg"
+ title="Waiting for performance data"
+/>
+`;
+
+exports[`EmptyState shows unableToConnect state 1`] = `
+<glemptystate-stub
+ description="Ensure connectivity is available from the GitLab server to the Prometheus server"
+ primarybuttonlink="/documentationPath"
+ primarybuttontext="View documentation"
+ secondarybuttonlink="/settingsPath"
+ secondarybuttontext="Configure Prometheus"
+ svgpath="/path/to/unable-to-connect.svg"
+ title="Unable to connect to Prometheus server"
+/>
+`;
diff --git a/spec/frontend/monitoring/dashboard_state_spec.js b/spec/frontend/monitoring/dashboard_state_spec.js
new file mode 100644
index 00000000000..950422911eb
--- /dev/null
+++ b/spec/frontend/monitoring/dashboard_state_spec.js
@@ -0,0 +1,43 @@
+import { shallowMount } from '@vue/test-utils';
+import EmptyState from '~/monitoring/components/empty_state.vue';
+
+function createComponent(props) {
+ return shallowMount(EmptyState, {
+ propsData: {
+ ...props,
+ settingsPath: '/settingsPath',
+ clustersPath: '/clustersPath',
+ documentationPath: '/documentationPath',
+ emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
+ emptyLoadingSvgPath: '/path/to/loading.svg',
+ emptyNoDataSvgPath: '/path/to/no-data.svg',
+ emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
+ },
+ });
+}
+
+describe('EmptyState', () => {
+ it('shows gettingStarted state', () => {
+ const wrapper = createComponent({
+ selectedState: 'gettingStarted',
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('shows loading state', () => {
+ const wrapper = createComponent({
+ selectedState: 'loading',
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('shows unableToConnect state', () => {
+ const wrapper = createComponent({
+ selectedState: 'unableToConnect',
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+});
diff --git a/spec/frontend/monitoring/embed/embed_spec.js b/spec/frontend/monitoring/embed/embed_spec.js
new file mode 100644
index 00000000000..1ce14e2418a
--- /dev/null
+++ b/spec/frontend/monitoring/embed/embed_spec.js
@@ -0,0 +1,78 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import Embed from '~/monitoring/components/embed.vue';
+import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
+import { TEST_HOST } from 'helpers/test_constants';
+import { groups, initialState, metricsData, metricsWithData } from './mock_data';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('Embed', () => {
+ let wrapper;
+ let store;
+ let actions;
+
+ function mountComponent() {
+ wrapper = shallowMount(Embed, {
+ localVue,
+ store,
+ propsData: {
+ dashboardUrl: TEST_HOST,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ actions = {
+ setFeatureFlags: () => {},
+ setShowErrorBanner: () => {},
+ setEndpoints: () => {},
+ fetchMetricsData: () => {},
+ };
+
+ store = new Vuex.Store({
+ modules: {
+ monitoringDashboard: {
+ namespaced: true,
+ actions,
+ state: initialState,
+ },
+ },
+ });
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('no metrics are available yet', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('shows an empty state when no metrics are present', () => {
+ expect(wrapper.find('.metrics-embed').exists()).toBe(true);
+ expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(false);
+ });
+ });
+
+ describe('metrics are available', () => {
+ beforeEach(() => {
+ store.state.monitoringDashboard.groups = groups;
+ store.state.monitoringDashboard.groups[0].metrics = metricsData;
+ store.state.monitoringDashboard.metricsWithData = metricsWithData;
+
+ mountComponent();
+ });
+
+ it('shows a chart when metrics are present', () => {
+ wrapper.setProps({});
+ expect(wrapper.find('.metrics-embed').exists()).toBe(true);
+ expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(true);
+ expect(wrapper.findAll(MonitorTimeSeriesChart).length).toBe(2);
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/embed/mock_data.js b/spec/frontend/monitoring/embed/mock_data.js
new file mode 100644
index 00000000000..df4acb82e95
--- /dev/null
+++ b/spec/frontend/monitoring/embed/mock_data.js
@@ -0,0 +1,87 @@
+export const metricsWithData = [15, 16];
+
+export const groups = [
+ {
+ panels: [
+ {
+ title: 'Memory Usage (Total)',
+ type: 'area-chart',
+ y_label: 'Total Memory Used',
+ weight: 4,
+ metrics: [
+ {
+ id: 'system_metrics_kubernetes_container_memory_total',
+ metric_id: 15,
+ },
+ ],
+ },
+ {
+ title: 'Core Usage (Total)',
+ type: 'area-chart',
+ y_label: 'Total Cores',
+ weight: 3,
+ metrics: [
+ {
+ id: 'system_metrics_kubernetes_container_cores_total',
+ metric_id: 16,
+ },
+ ],
+ },
+ ],
+ },
+];
+
+export const metrics = [
+ {
+ id: 'system_metrics_kubernetes_container_memory_total',
+ metric_id: 15,
+ },
+ {
+ id: 'system_metrics_kubernetes_container_cores_total',
+ metric_id: 16,
+ },
+];
+
+const queries = [
+ {
+ result: [
+ {
+ values: [
+ ['Mon', 1220],
+ ['Tue', 932],
+ ['Wed', 901],
+ ['Thu', 934],
+ ['Fri', 1290],
+ ['Sat', 1330],
+ ['Sun', 1320],
+ ],
+ },
+ ],
+ },
+];
+
+export const metricsData = [
+ {
+ queries,
+ metrics: [
+ {
+ metric_id: 15,
+ },
+ ],
+ },
+ {
+ queries,
+ metrics: [
+ {
+ metric_id: 16,
+ },
+ ],
+ },
+];
+
+export const initialState = {
+ monitoringDashboard: {},
+ groups: [],
+ metricsWithData: [],
+ useDashboardEndpoint: true,
+};
diff --git a/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js b/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js
index 989b0458481..fd439ba46bd 100644
--- a/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js
+++ b/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js
@@ -23,8 +23,7 @@ describe('JumpToNextDiscussionButton', () => {
button.trigger('click');
- expect(wrapper.emitted()).toEqual({
- onClick: [[]],
- });
+ expect(wrapper.emitted().onClick).toBeTruthy();
+ expect(wrapper.emitted().onClick.length).toBe(1);
});
});
diff --git a/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js b/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js
new file mode 100644
index 00000000000..8881bedf3cc
--- /dev/null
+++ b/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js
@@ -0,0 +1,104 @@
+/* global Mousetrap */
+import 'mousetrap';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import DiscussionKeyboardNavigator from '~/notes/components/discussion_keyboard_navigator.vue';
+import notesModule from '~/notes/stores/modules';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+const NEXT_ID = 'abc123';
+const PREV_ID = 'def456';
+const NEXT_DIFF_ID = 'abc123_diff';
+const PREV_DIFF_ID = 'def456_diff';
+
+describe('notes/components/discussion_keyboard_navigator', () => {
+ let storeOptions;
+ let wrapper;
+ let store;
+
+ const createComponent = (options = {}) => {
+ store = new Vuex.Store(storeOptions);
+
+ wrapper = shallowMount(DiscussionKeyboardNavigator, {
+ localVue,
+ store,
+ ...options,
+ });
+
+ wrapper.vm.jumpToDiscussion = jest.fn();
+ };
+
+ beforeEach(() => {
+ const notes = notesModule();
+
+ notes.getters.nextUnresolvedDiscussionId = () => (currId, isDiff) =>
+ isDiff ? NEXT_DIFF_ID : NEXT_ID;
+ notes.getters.previousUnresolvedDiscussionId = () => (currId, isDiff) =>
+ isDiff ? PREV_DIFF_ID : PREV_ID;
+
+ storeOptions = {
+ modules: {
+ notes,
+ },
+ };
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ storeOptions = null;
+ store = null;
+ });
+
+ describe.each`
+ isDiffView | expectedNextId | expectedPrevId
+ ${true} | ${NEXT_DIFF_ID} | ${PREV_DIFF_ID}
+ ${false} | ${NEXT_ID} | ${PREV_ID}
+ `('when isDiffView is $isDiffView', ({ isDiffView, expectedNextId, expectedPrevId }) => {
+ beforeEach(() => {
+ createComponent({ propsData: { isDiffView } });
+ });
+
+ it('calls jumpToNextDiscussion when pressing `n`', () => {
+ Mousetrap.trigger('n');
+
+ expect(wrapper.vm.jumpToDiscussion).toHaveBeenCalledWith(expectedNextId);
+ expect(wrapper.vm.currentDiscussionId).toEqual(expectedNextId);
+ });
+
+ it('calls jumpToPreviousDiscussion when pressing `p`', () => {
+ Mousetrap.trigger('p');
+
+ expect(wrapper.vm.jumpToDiscussion).toHaveBeenCalledWith(expectedPrevId);
+ expect(wrapper.vm.currentDiscussionId).toEqual(expectedPrevId);
+ });
+ });
+
+ describe('on destroy', () => {
+ beforeEach(() => {
+ jest.spyOn(Mousetrap, 'unbind');
+
+ createComponent();
+
+ wrapper.destroy();
+ });
+
+ it('unbinds keys', () => {
+ expect(Mousetrap.unbind).toHaveBeenCalledWith('n');
+ expect(Mousetrap.unbind).toHaveBeenCalledWith('p');
+ });
+
+ it('does not call jumpToNextDiscussion when pressing `n`', () => {
+ Mousetrap.trigger('n');
+
+ expect(wrapper.vm.jumpToDiscussion).not.toHaveBeenCalled();
+ });
+
+ it('does not call jumpToNextDiscussion when pressing `p`', () => {
+ Mousetrap.trigger('p');
+
+ expect(wrapper.vm.jumpToDiscussion).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/notes/components/discussion_notes_replies_wrapper_spec.js b/spec/frontend/notes/components/discussion_notes_replies_wrapper_spec.js
new file mode 100644
index 00000000000..279ca017b44
--- /dev/null
+++ b/spec/frontend/notes/components/discussion_notes_replies_wrapper_spec.js
@@ -0,0 +1,51 @@
+import { mount } from '@vue/test-utils';
+import DiscussionNotesRepliesWrapper from '~/notes/components/discussion_notes_replies_wrapper.vue';
+
+const TEST_CHILDREN = '<li>Hello!</li><li>World!</li>';
+
+// We have to wrap our SUT with a TestComponent because multiple roots are possible
+// because it's a functional component.
+const TestComponent = {
+ components: { DiscussionNotesRepliesWrapper },
+ template: `<ul><discussion-notes-replies-wrapper v-bind="$attrs">${TEST_CHILDREN}</discussion-notes-replies-wrapper></ul>`,
+};
+
+describe('DiscussionNotesRepliesWrapper', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = mount(TestComponent, {
+ propsData: props,
+ sync: false,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when normal discussion', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders children directly', () => {
+ expect(wrapper.html()).toEqual(`<ul>${TEST_CHILDREN}</ul>`);
+ });
+ });
+
+ describe('when diff discussion', () => {
+ beforeEach(() => {
+ createComponent({
+ isDiffDiscussion: true,
+ });
+ });
+
+ it('wraps children with notes', () => {
+ const notes = wrapper.find('li.discussion-collapsible ul.notes');
+
+ expect(notes.exists()).toBe(true);
+ expect(notes.html()).toEqual(`<ul class="notes">${TEST_CHILDREN}</ul>`);
+ });
+ });
+});
diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js
index 394666403ee..58d367077e8 100644
--- a/spec/frontend/notes/components/discussion_notes_spec.js
+++ b/spec/frontend/notes/components/discussion_notes_spec.js
@@ -87,7 +87,7 @@ describe('DiscussionNotes', () => {
discussion.notes[0],
];
discussion.notes = notesData;
- createComponent({ discussion });
+ createComponent({ discussion, shouldRenderDiffs: true });
const notes = wrapper.findAll('.notes > li');
expect(notes.at(0).is(PlaceholderSystemNote)).toBe(true);
diff --git a/spec/frontend/notes/components/discussion_reply_placeholder_spec.js b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
index 07a366cf339..e008f4ed093 100644
--- a/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
+++ b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
@@ -2,13 +2,19 @@ import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vu
import { shallowMount, createLocalVue } from '@vue/test-utils';
const localVue = createLocalVue();
+const buttonText = 'Test Button Text';
describe('ReplyPlaceholder', () => {
let wrapper;
+ const findButton = () => wrapper.find({ ref: 'button' });
+
beforeEach(() => {
wrapper = shallowMount(ReplyPlaceholder, {
localVue,
+ propsData: {
+ buttonText,
+ },
});
});
@@ -17,9 +23,7 @@ describe('ReplyPlaceholder', () => {
});
it('emits onClick even on button click', () => {
- const button = wrapper.find({ ref: 'button' });
-
- button.trigger('click');
+ findButton().trigger('click');
expect(wrapper.emitted()).toEqual({
onClick: [[]],
@@ -27,8 +31,6 @@ describe('ReplyPlaceholder', () => {
});
it('should render reply button', () => {
- const button = wrapper.find({ ref: 'button' });
-
- expect(button.text()).toEqual('Reply...');
+ expect(findButton().text()).toEqual(buttonText);
});
});
diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js
index a881de8fbfe..39d7c19e731 100644
--- a/spec/frontend/operation_settings/components/external_dashboard_spec.js
+++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js
@@ -7,7 +7,6 @@ import { refreshCurrentPage } from '~/lib/utils/url_utility';
import createFlash from '~/flash';
import { TEST_HOST } from 'helpers/test_constants';
-jest.mock('~/lib/utils/axios_utils');
jest.mock('~/lib/utils/url_utility');
jest.mock('~/flash');
@@ -32,6 +31,10 @@ describe('operation settings external dashboard component', () => {
wrapper = shallow ? shallowMount(...config) : mount(...config);
};
+ beforeEach(() => {
+ jest.spyOn(axios, 'patch').mockImplementation();
+ });
+
afterEach(() => {
if (wrapper.destroy) {
wrapper.destroy();
diff --git a/spec/frontend/pages/search/show/__snapshots__/refresh_counts_spec.js.snap b/spec/frontend/pages/search/show/__snapshots__/refresh_counts_spec.js.snap
new file mode 100644
index 00000000000..ce456d6c899
--- /dev/null
+++ b/spec/frontend/pages/search/show/__snapshots__/refresh_counts_spec.js.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`pages/search/show/refresh_counts fetches and displays search counts 1`] = `
+"<div class=\\"badge\\">22</div>
+<div class=\\"badge js-search-count\\" data-url=\\"http://test.host/search/count?search=lorem+ipsum&amp;project_id=3&amp;scope=issues\\">4</div>
+<div class=\\"badge js-search-count\\" data-url=\\"http://test.host/search/count?search=lorem+ipsum&amp;project_id=3&amp;scope=merge_requests\\">5</div>"
+`;
diff --git a/spec/frontend/pages/search/show/refresh_counts_spec.js b/spec/frontend/pages/search/show/refresh_counts_spec.js
new file mode 100644
index 00000000000..ead268b3971
--- /dev/null
+++ b/spec/frontend/pages/search/show/refresh_counts_spec.js
@@ -0,0 +1,35 @@
+import MockAdapter from 'axios-mock-adapter';
+import { TEST_HOST } from 'helpers/test_constants';
+import axios from '~/lib/utils/axios_utils';
+import refreshCounts from '~/pages/search/show/refresh_counts';
+
+const URL = `${TEST_HOST}/search/count?search=lorem+ipsum&project_id=3`;
+const urlWithScope = scope => `${URL}&scope=${scope}`;
+const counts = [{ scope: 'issues', count: 4 }, { scope: 'merge_requests', count: 5 }];
+const fixture = `<div class="badge">22</div>
+<div class="badge js-search-count hidden" data-url="${urlWithScope('issues')}"></div>
+<div class="badge js-search-count hidden" data-url="${urlWithScope('merge_requests')}"></div>`;
+
+describe('pages/search/show/refresh_counts', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ setFixtures(fixture);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('fetches and displays search counts', () => {
+ counts.forEach(({ scope, count }) => {
+ mock.onGet(urlWithScope(scope)).reply(200, { count });
+ });
+
+ // assert before act behavior
+ return refreshCounts().then(() => {
+ expect(document.body.innerHTML).toMatchSnapshot();
+ });
+ });
+});
diff --git a/spec/frontend/projects/gke_cluster_namespace/gke_cluster_namespace_spec.js b/spec/frontend/projects/gke_cluster_namespace/gke_cluster_namespace_spec.js
new file mode 100644
index 00000000000..7b8df03d3c3
--- /dev/null
+++ b/spec/frontend/projects/gke_cluster_namespace/gke_cluster_namespace_spec.js
@@ -0,0 +1,61 @@
+import initGkeNamespace from '~/projects/gke_cluster_namespace';
+
+describe('GKE cluster namespace', () => {
+ const changeEvent = new Event('change');
+ const isHidden = el => el.classList.contains('hidden');
+ const hasDisabledInput = el => el.querySelector('input').disabled;
+
+ let glManagedCheckbox;
+ let selfManaged;
+ let glManaged;
+
+ beforeEach(() => {
+ setFixtures(`
+ <input class="js-gl-managed" type="checkbox" value="1" checked />
+ <div class="js-namespace">
+ <input type="text" />
+ </div>
+ <div class="js-namespace-prefixed">
+ <input type="text" />
+ </div>
+ `);
+
+ glManagedCheckbox = document.querySelector('.js-gl-managed');
+ selfManaged = document.querySelector('.js-namespace');
+ glManaged = document.querySelector('.js-namespace-prefixed');
+
+ initGkeNamespace();
+ });
+
+ describe('GKE cluster namespace toggles', () => {
+ it('initially displays the GitLab-managed label and input', () => {
+ expect(isHidden(glManaged)).toEqual(false);
+ expect(hasDisabledInput(glManaged)).toEqual(false);
+
+ expect(isHidden(selfManaged)).toEqual(true);
+ expect(hasDisabledInput(selfManaged)).toEqual(true);
+ });
+
+ it('displays the self-managed label and input when the Gitlab-managed checkbox is unchecked', () => {
+ glManagedCheckbox.checked = false;
+ glManagedCheckbox.dispatchEvent(changeEvent);
+
+ expect(isHidden(glManaged)).toEqual(true);
+ expect(hasDisabledInput(glManaged)).toEqual(true);
+
+ expect(isHidden(selfManaged)).toEqual(false);
+ expect(hasDisabledInput(selfManaged)).toEqual(false);
+ });
+
+ it('displays the GitLab-managed label and input when the Gitlab-managed checkbox is checked', () => {
+ glManagedCheckbox.checked = true;
+ glManagedCheckbox.dispatchEvent(changeEvent);
+
+ expect(isHidden(glManaged)).toEqual(false);
+ expect(hasDisabledInput(glManaged)).toEqual(false);
+
+ expect(isHidden(selfManaged)).toEqual(true);
+ expect(hasDisabledInput(selfManaged)).toEqual(true);
+ });
+ });
+});
diff --git a/spec/frontend/projects/projects_filterable_list_spec.js b/spec/frontend/projects/projects_filterable_list_spec.js
new file mode 100644
index 00000000000..e756fb3ab56
--- /dev/null
+++ b/spec/frontend/projects/projects_filterable_list_spec.js
@@ -0,0 +1,31 @@
+import ProjectsFilterableList from '~/projects/projects_filterable_list';
+import { getJSONFixture, setHTMLFixture } from '../helpers/fixtures';
+
+describe('ProjectsFilterableList', () => {
+ let List;
+ let form;
+ let filter;
+ let holder;
+
+ beforeEach(() => {
+ setHTMLFixture(`
+ <form id="project-filter-form">
+ <input name="name" class="js-projects-list-filter" />
+ </div>
+ <div class="js-projects-list-holder"></div>
+ `);
+ getJSONFixture('static/projects.json');
+ form = document.querySelector('form#project-filter-form');
+ filter = document.querySelector('.js-projects-list-filter');
+ holder = document.querySelector('.js-projects-list-holder');
+ List = new ProjectsFilterableList(form, filter, holder);
+ });
+
+ describe('getFilterEndpoint', () => {
+ it('updates converts getPagePath for projects', () => {
+ jest.spyOn(List, 'getPagePath').mockReturnValue('blah/projects?');
+
+ expect(List.getFilterEndpoint()).toEqual('blah/projects.json?');
+ });
+ });
+});
diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js
index 068fa317a87..707eae34793 100644
--- a/spec/frontend/repository/components/breadcrumbs_spec.js
+++ b/spec/frontend/repository/components/breadcrumbs_spec.js
@@ -1,12 +1,14 @@
import { shallowMount, RouterLinkStub } from '@vue/test-utils';
+import { GlDropdown } from '@gitlab/ui';
import Breadcrumbs from '~/repository/components/breadcrumbs.vue';
let vm;
-function factory(currentPath) {
+function factory(currentPath, extraProps = {}) {
vm = shallowMount(Breadcrumbs, {
propsData: {
currentPath,
+ ...extraProps,
},
stubs: {
RouterLink: RouterLinkStub,
@@ -41,4 +43,20 @@ describe('Repository breadcrumbs component', () => {
.attributes('aria-current'),
).toEqual('page');
});
+
+ it('does not render add to tree dropdown when permissions are false', () => {
+ factory('/', { canCollaborate: false });
+
+ vm.setData({ userPermissions: { forkProject: false, createMergeRequestIn: false } });
+
+ expect(vm.find(GlDropdown).exists()).toBe(false);
+ });
+
+ it('renders add to tree dropdown when permissions are true', () => {
+ factory('/', { canCollaborate: true });
+
+ vm.setData({ userPermissions: { forkProject: true, createMergeRequestIn: true } });
+
+ expect(vm.find(GlDropdown).exists()).toBe(true);
+ });
});
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
index c566057ad3f..e539c560975 100644
--- a/spec/frontend/repository/components/table/row_spec.js
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -1,5 +1,5 @@
import { shallowMount, RouterLinkStub } from '@vue/test-utils';
-import { GlBadge } from '@gitlab/ui';
+import { GlBadge, GlLink } from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility';
import TableRow from '~/repository/components/table/row.vue';
@@ -142,4 +142,18 @@ describe('Repository table row component', () => {
expect(vm.find(GlBadge).exists()).toBe(true);
});
+
+ it('renders commit and web links with href for submodule', () => {
+ factory({
+ id: '1',
+ path: 'test',
+ type: 'commit',
+ url: 'https://test.com',
+ submoduleTreeUrl: 'https://test.com/commit',
+ currentPath: '/',
+ });
+
+ expect(vm.find('a').attributes('href')).toEqual('https://test.com');
+ expect(vm.find(GlLink).attributes('href')).toEqual('https://test.com/commit');
+ });
});
diff --git a/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js b/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
new file mode 100644
index 00000000000..452d4cd07cc
--- /dev/null
+++ b/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
@@ -0,0 +1,85 @@
+import { shallowMount } from '@vue/test-utils';
+import { joinPaths } from '~/lib/utils/url_utility';
+import { TEST_HOST } from 'helpers/test_constants';
+import AssigneeAvatarLink from '~/sidebar/components/assignees/assignee_avatar_link.vue';
+import AssigneeAvatar from '~/sidebar/components/assignees/assignee_avatar.vue';
+import userDataMock from '../../user_data_mock';
+
+const TOOLTIP_PLACEMENT = 'bottom';
+const { name: USER_NAME, username: USER_USERNAME } = userDataMock();
+const TEST_ISSUABLE_TYPE = 'merge_request';
+
+describe('AssigneeAvatarLink component', () => {
+ let wrapper;
+
+ function createComponent(props = {}) {
+ const propsData = {
+ user: userDataMock(),
+ showLess: true,
+ rootPath: TEST_HOST,
+ tooltipPlacement: TOOLTIP_PLACEMENT,
+ singleUser: false,
+ issuableType: TEST_ISSUABLE_TYPE,
+ ...props,
+ };
+
+ wrapper = shallowMount(AssigneeAvatarLink, {
+ propsData,
+ sync: false,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findTooltipText = () => wrapper.attributes('data-original-title');
+
+ it('has the root url present in the assigneeUrl method', () => {
+ createComponent();
+ const assigneeUrl = joinPaths(TEST_HOST, USER_USERNAME);
+
+ expect(wrapper.attributes().href).toEqual(assigneeUrl);
+ });
+
+ it('renders assignee avatar', () => {
+ createComponent();
+
+ expect(wrapper.find(AssigneeAvatar).props()).toEqual(
+ expect.objectContaining({
+ issuableType: TEST_ISSUABLE_TYPE,
+ user: userDataMock(),
+ }),
+ );
+ });
+
+ describe.each`
+ issuableType | tooltipHasName | canMerge | expected
+ ${'merge_request'} | ${true} | ${true} | ${USER_NAME}
+ ${'merge_request'} | ${true} | ${false} | ${`${USER_NAME} (cannot merge)`}
+ ${'merge_request'} | ${false} | ${true} | ${''}
+ ${'merge_request'} | ${false} | ${false} | ${'Cannot merge'}
+ ${'issue'} | ${true} | ${true} | ${USER_NAME}
+ ${'issue'} | ${true} | ${false} | ${USER_NAME}
+ ${'issue'} | ${false} | ${true} | ${''}
+ ${'issue'} | ${false} | ${false} | ${''}
+ `(
+ 'with $issuableType and tooltipHasName=$tooltipHasName and canMerge=$canMerge',
+ ({ issuableType, tooltipHasName, canMerge, expected }) => {
+ beforeEach(() => {
+ createComponent({
+ issuableType,
+ tooltipHasName,
+ user: {
+ ...userDataMock(),
+ can_merge: canMerge,
+ },
+ });
+ });
+
+ it('sets tooltip', () => {
+ expect(findTooltipText()).toBe(expected);
+ });
+ },
+ );
+});
diff --git a/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js b/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js
new file mode 100644
index 00000000000..d60ae17733b
--- /dev/null
+++ b/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js
@@ -0,0 +1,78 @@
+import { shallowMount } from '@vue/test-utils';
+import AssigneeAvatar from '~/sidebar/components/assignees/assignee_avatar.vue';
+import { TEST_HOST } from 'helpers/test_constants';
+import userDataMock from '../../user_data_mock';
+
+const TEST_AVATAR = `${TEST_HOST}/avatar.png`;
+const TEST_DEFAULT_AVATAR_URL = `${TEST_HOST}/default/avatar/url.png`;
+
+describe('AssigneeAvatar', () => {
+ let origGon;
+ let wrapper;
+
+ function createComponent(props = {}) {
+ const propsData = {
+ user: userDataMock(),
+ imgSize: 24,
+ issuableType: 'merge_request',
+ ...props,
+ };
+
+ wrapper = shallowMount(AssigneeAvatar, {
+ propsData,
+ sync: false,
+ });
+ }
+
+ beforeEach(() => {
+ origGon = window.gon;
+ window.gon = { default_avatar_url: TEST_DEFAULT_AVATAR_URL };
+ });
+
+ afterEach(() => {
+ window.gon = origGon;
+ wrapper.destroy();
+ });
+
+ const findImg = () => wrapper.find('img');
+
+ it('does not show warning icon if assignee can merge', () => {
+ createComponent();
+
+ expect(wrapper.find('.merge-icon').exists()).toBe(false);
+ });
+
+ it('shows warning icon if assignee cannot merge', () => {
+ createComponent({
+ user: {
+ can_merge: false,
+ },
+ });
+
+ expect(wrapper.find('.merge-icon').exists()).toBe(true);
+ });
+
+ it('does not show warning icon for issuableType = "issue"', () => {
+ createComponent({
+ issuableType: 'issue',
+ });
+
+ expect(wrapper.find('.merge-icon').exists()).toBe(false);
+ });
+
+ it.each`
+ avatar | avatar_url | expected | desc
+ ${TEST_AVATAR} | ${null} | ${TEST_AVATAR} | ${'with avatar'}
+ ${null} | ${TEST_AVATAR} | ${TEST_AVATAR} | ${'with avatar_url'}
+ ${null} | ${null} | ${TEST_DEFAULT_AVATAR_URL} | ${'with no avatar'}
+ `('$desc', ({ avatar, avatar_url, expected }) => {
+ createComponent({
+ user: {
+ avatar,
+ avatar_url,
+ },
+ });
+
+ expect(findImg().attributes('src')).toEqual(expected);
+ });
+});
diff --git a/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js b/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js
new file mode 100644
index 00000000000..ff0c8d181b5
--- /dev/null
+++ b/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js
@@ -0,0 +1,189 @@
+import { shallowMount } from '@vue/test-utils';
+import CollapsedAssigneeList from '~/sidebar/components/assignees/collapsed_assignee_list.vue';
+import CollapsedAssignee from '~/sidebar/components/assignees/collapsed_assignee.vue';
+import UsersMockHelper from 'helpers/user_mock_data_helper';
+
+const DEFAULT_MAX_COUNTER = 99;
+
+describe('CollapsedAssigneeList component', () => {
+ let wrapper;
+
+ function createComponent(props = {}) {
+ const propsData = {
+ users: [],
+ issuableType: 'merge_request',
+ ...props,
+ };
+
+ wrapper = shallowMount(CollapsedAssigneeList, {
+ propsData,
+ sync: false,
+ });
+ }
+
+ const findNoUsersIcon = () => wrapper.find('i[aria-label=None]');
+ const findAvatarCounter = () => wrapper.find('.avatar-counter');
+ const findAssignees = () => wrapper.findAll(CollapsedAssignee);
+ const getTooltipTitle = () => wrapper.attributes('data-original-title');
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('No assignees/users', () => {
+ beforeEach(() => {
+ createComponent({
+ users: [],
+ });
+ });
+
+ it('has no users', () => {
+ expect(findNoUsersIcon().exists()).toBe(true);
+ });
+ });
+
+ describe('One assignee/user', () => {
+ let users;
+
+ beforeEach(() => {
+ users = UsersMockHelper.createNumberRandomUsers(1);
+ });
+
+ it('should not show no users icon', () => {
+ createComponent({ users });
+
+ expect(findNoUsersIcon().exists()).toBe(false);
+ });
+
+ it('has correct "cannot merge" tooltip when user cannot merge', () => {
+ users[0].can_merge = false;
+
+ createComponent({ users });
+
+ expect(getTooltipTitle()).toContain('cannot merge');
+ });
+
+ it('does not have "merge" word in tooltip if user can merge', () => {
+ users[0].can_merge = true;
+
+ createComponent({ users });
+
+ expect(getTooltipTitle()).not.toContain('merge');
+ });
+ });
+
+ describe('More than one assignees/users', () => {
+ let users;
+
+ beforeEach(() => {
+ users = UsersMockHelper.createNumberRandomUsers(2);
+
+ createComponent({ users });
+ });
+
+ it('has multiple-users class', () => {
+ expect(wrapper.classes('multiple-users')).toBe(true);
+ });
+
+ it('does not display an avatar count', () => {
+ expect(findAvatarCounter().exists()).toBe(false);
+ });
+
+ it('returns just two collapsed users', () => {
+ expect(findAssignees().length).toBe(2);
+ });
+ });
+
+ describe('More than two assignees/users', () => {
+ let users;
+ let userNames;
+
+ beforeEach(() => {
+ users = UsersMockHelper.createNumberRandomUsers(3);
+ userNames = users.map(x => x.name).join(', ');
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent({ users });
+ });
+
+ it('does display an avatar count', () => {
+ expect(findAvatarCounter().exists()).toBe(true);
+ expect(findAvatarCounter().text()).toEqual('+2');
+ });
+
+ it('returns one collapsed users', () => {
+ expect(findAssignees().length).toBe(1);
+ });
+ });
+
+ it('has corrent "no one can merge" tooltip when no one can merge', () => {
+ users[0].can_merge = false;
+ users[1].can_merge = false;
+ users[2].can_merge = false;
+
+ createComponent({
+ users,
+ });
+
+ expect(getTooltipTitle()).toEqual(`${userNames} (no one can merge)`);
+ });
+
+ it('has correct "cannot merge" tooltip when one user can merge', () => {
+ users[0].can_merge = true;
+ users[1].can_merge = false;
+ users[2].can_merge = false;
+
+ createComponent({
+ users,
+ });
+
+ expect(getTooltipTitle()).toEqual(`${userNames} (1/3 can merge)`);
+ });
+
+ it('has correct "cannot merge" tooltip when more than one user can merge', () => {
+ users[0].can_merge = false;
+ users[1].can_merge = true;
+ users[2].can_merge = true;
+
+ createComponent({
+ users,
+ });
+
+ expect(getTooltipTitle()).toEqual(`${userNames} (2/3 can merge)`);
+ });
+
+ it('does not have "merge" in tooltip if everyone can merge', () => {
+ users[0].can_merge = true;
+ users[1].can_merge = true;
+ users[2].can_merge = true;
+
+ createComponent({
+ users,
+ });
+
+ expect(getTooltipTitle()).toEqual(userNames);
+ });
+
+ it('displays the correct avatar count', () => {
+ users = UsersMockHelper.createNumberRandomUsers(5);
+
+ createComponent({
+ users,
+ });
+
+ expect(findAvatarCounter().text()).toEqual(`+${users.length - 1}`);
+ });
+
+ it('displays the correct avatar count via a computed property if more than default max counter', () => {
+ users = UsersMockHelper.createNumberRandomUsers(100);
+
+ createComponent({
+ users,
+ });
+
+ expect(findAvatarCounter().text()).toEqual(`${DEFAULT_MAX_COUNTER}+`);
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js b/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js
new file mode 100644
index 00000000000..f9ca7bc1ecb
--- /dev/null
+++ b/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js
@@ -0,0 +1,49 @@
+import { shallowMount } from '@vue/test-utils';
+import CollapsedAssignee from '~/sidebar/components/assignees/collapsed_assignee.vue';
+import AssigneeAvatar from '~/sidebar/components/assignees/assignee_avatar.vue';
+import userDataMock from '../../user_data_mock';
+
+const TEST_USER = userDataMock();
+const TEST_ISSUABLE_TYPE = 'merge_request';
+
+describe('CollapsedAssignee assignee component', () => {
+ let wrapper;
+
+ function createComponent(props = {}) {
+ const propsData = {
+ user: userDataMock(),
+ issuableType: TEST_ISSUABLE_TYPE,
+ ...props,
+ };
+
+ wrapper = shallowMount(CollapsedAssignee, {
+ propsData,
+ sync: false,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has author name', () => {
+ createComponent();
+
+ expect(
+ wrapper
+ .find('.author')
+ .text()
+ .trim(),
+ ).toEqual(TEST_USER.name);
+ });
+
+ it('has assignee avatar', () => {
+ createComponent();
+
+ expect(wrapper.find(AssigneeAvatar).props()).toEqual({
+ imgSize: 24,
+ user: TEST_USER,
+ issuableType: TEST_ISSUABLE_TYPE,
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js b/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
new file mode 100644
index 00000000000..6398351834c
--- /dev/null
+++ b/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
@@ -0,0 +1,103 @@
+import { mount } from '@vue/test-utils';
+import UncollapsedAssigneeList from '~/sidebar/components/assignees/uncollapsed_assignee_list.vue';
+import AssigneeAvatarLink from '~/sidebar/components/assignees/assignee_avatar_link.vue';
+import { TEST_HOST } from 'helpers/test_constants';
+import userDataMock from '../../user_data_mock';
+import UsersMockHelper from '../../../helpers/user_mock_data_helper';
+
+const DEFAULT_RENDER_COUNT = 5;
+
+describe('UncollapsedAssigneeList component', () => {
+ let wrapper;
+
+ function createComponent(props = {}) {
+ const propsData = {
+ users: [],
+ rootPath: TEST_HOST,
+ ...props,
+ };
+
+ wrapper = mount(UncollapsedAssigneeList, {
+ sync: false,
+ propsData,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findMoreButton = () => wrapper.find('.user-list-more button');
+
+ describe('One assignee/user', () => {
+ let user;
+
+ beforeEach(() => {
+ user = userDataMock();
+
+ createComponent({
+ users: [user],
+ });
+ });
+
+ it('only has one user', () => {
+ expect(wrapper.findAll(AssigneeAvatarLink).length).toBe(1);
+ });
+
+ it('calls the AssigneeAvatarLink with the proper props', () => {
+ expect(wrapper.find(AssigneeAvatarLink).exists()).toBe(true);
+ expect(wrapper.find(AssigneeAvatarLink).props().tooltipPlacement).toEqual('left');
+ });
+
+ it('Shows one user with avatar, username and author name', () => {
+ expect(wrapper.text()).toContain(user.name);
+ expect(wrapper.text()).toContain(`@${user.username}`);
+ });
+ });
+
+ describe('n+ more label', () => {
+ describe('when users count is rendered users', () => {
+ beforeEach(() => {
+ createComponent({
+ users: UsersMockHelper.createNumberRandomUsers(DEFAULT_RENDER_COUNT),
+ });
+ });
+
+ it('does not show more label', () => {
+ expect(findMoreButton().exists()).toBe(false);
+ });
+ });
+
+ describe('when more than rendered users', () => {
+ beforeEach(() => {
+ createComponent({
+ users: UsersMockHelper.createNumberRandomUsers(DEFAULT_RENDER_COUNT + 1),
+ });
+ });
+
+ it('shows "+1 more" label', () => {
+ expect(findMoreButton().text()).toBe('+ 1 more');
+ });
+
+ it('shows truncated users', () => {
+ expect(wrapper.findAll(AssigneeAvatarLink).length).toBe(DEFAULT_RENDER_COUNT);
+ });
+
+ describe('when more button is clicked', () => {
+ beforeEach(() => {
+ findMoreButton().trigger('click');
+
+ return wrapper.vm.$nextTick();
+ });
+
+ it('shows "show less" label', () => {
+ expect(findMoreButton().text()).toBe('- show less');
+ });
+
+ it('shows all users', () => {
+ expect(wrapper.findAll(AssigneeAvatarLink).length).toBe(DEFAULT_RENDER_COUNT + 1);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/user_data_mock.js b/spec/frontend/sidebar/user_data_mock.js
new file mode 100644
index 00000000000..8ad70bb3499
--- /dev/null
+++ b/spec/frontend/sidebar/user_data_mock.js
@@ -0,0 +1,9 @@
+export default () => ({
+ avatar_url: 'mock_path',
+ id: 1,
+ name: 'Root',
+ state: 'active',
+ username: 'root',
+ web_url: '',
+ can_merge: true,
+});
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index c17d5253997..d52aeb1fe6b 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -2,9 +2,11 @@ import Vue from 'vue';
import * as jqueryMatchers from 'custom-jquery-matchers';
import $ from 'jquery';
import Translate from '~/vue_shared/translate';
-import axios from '~/lib/utils/axios_utils';
+import { config as testUtilsConfig } from '@vue/test-utils';
import { initializeTestTimeout } from './helpers/timeout';
import { loadHTMLFixture, setHTMLFixture } from './helpers/fixtures';
+import { setupManualMocks } from './mocks/mocks_helper';
+import customMatchers from './matchers';
// Expose jQuery so specs using jQuery plugins can be imported nicely.
// Here is an issue to explore better alternatives:
@@ -13,6 +15,8 @@ window.jQuery = $;
process.on('unhandledRejection', global.promiseRejectionHandler);
+setupManualMocks();
+
afterEach(() =>
// give Promises a bit more time so they fail the right test
new Promise(setImmediate).then(() => {
@@ -23,18 +27,6 @@ afterEach(() =>
initializeTestTimeout(process.env.CI ? 5000 : 500);
-// fail tests for unmocked requests
-beforeEach(done => {
- axios.defaults.adapter = config => {
- const error = new Error(`Unexpected unmocked request: ${JSON.stringify(config, null, 2)}`);
- error.config = config;
- done.fail(error);
- return Promise.reject(error);
- };
-
- done();
-});
-
Vue.config.devtools = false;
Vue.config.productionTip = false;
@@ -60,9 +52,50 @@ Object.assign(global, {
preloadFixtures() {},
});
+Object.assign(global, {
+ MutationObserver() {
+ return {
+ disconnect() {},
+ observe() {},
+ };
+ },
+});
+
// custom-jquery-matchers was written for an old Jest version, we need to make it compatible
Object.entries(jqueryMatchers).forEach(([matcherName, matcherFactory]) => {
expect.extend({
[matcherName]: matcherFactory().compare,
});
});
+
+expect.extend(customMatchers);
+
+// Tech debt issue TBD
+testUtilsConfig.logModifiedComponents = false;
+
+// Basic stub for MutationObserver
+global.MutationObserver = () => ({
+ disconnect: () => {},
+ observe: () => {},
+});
+
+Object.assign(global, {
+ requestIdleCallback(cb) {
+ const start = Date.now();
+ return setTimeout(() => {
+ cb({
+ didTimeout: false,
+ timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),
+ });
+ });
+ },
+ cancelIdleCallback(id) {
+ clearTimeout(id);
+ },
+});
+
+// make sure that each test actually tests something
+// see https://jestjs.io/docs/en/expect#expecthasassertions
+beforeEach(() => {
+ expect.hasAssertions();
+});
diff --git a/spec/frontend/tracking_spec.js b/spec/frontend/tracking_spec.js
new file mode 100644
index 00000000000..0e862c683d3
--- /dev/null
+++ b/spec/frontend/tracking_spec.js
@@ -0,0 +1,143 @@
+import $ from 'jquery';
+import { setHTMLFixture } from './helpers/fixtures';
+
+import Tracking from '~/tracking';
+
+describe('Tracking', () => {
+ beforeEach(() => {
+ window.snowplow = window.snowplow || (() => {});
+ });
+
+ describe('.event', () => {
+ let snowplowSpy = null;
+
+ beforeEach(() => {
+ snowplowSpy = jest.spyOn(window, 'snowplow');
+ });
+
+ afterEach(() => {
+ window.doNotTrack = undefined;
+ navigator.doNotTrack = undefined;
+ navigator.msDoNotTrack = undefined;
+ });
+
+ it('tracks to snowplow (our current tracking system)', () => {
+ Tracking.event('_category_', '_eventName_', { label: '_label_' });
+
+ expect(snowplowSpy).toHaveBeenCalledWith('trackStructEvent', '_category_', '_eventName_', {
+ label: '_label_',
+ property: '',
+ value: '',
+ });
+ });
+
+ it('skips tracking if snowplow is unavailable', () => {
+ window.snowplow = false;
+ Tracking.event('_category_', '_eventName_');
+
+ expect(snowplowSpy).not.toHaveBeenCalled();
+ });
+
+ it('skips tracking if the user does not want to be tracked (general spec)', () => {
+ window.doNotTrack = '1';
+ Tracking.event('_category_', '_eventName_');
+
+ expect(snowplowSpy).not.toHaveBeenCalled();
+ });
+
+ it('skips tracking if the user does not want to be tracked (firefox legacy)', () => {
+ navigator.doNotTrack = 'yes';
+ Tracking.event('_category_', '_eventName_');
+
+ expect(snowplowSpy).not.toHaveBeenCalled();
+ });
+
+ it('skips tracking if the user does not want to be tracked (IE legacy)', () => {
+ navigator.msDoNotTrack = '1';
+ Tracking.event('_category_', '_eventName_');
+
+ expect(snowplowSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('tracking interface events', () => {
+ let eventSpy = null;
+ let subject = null;
+
+ beforeEach(() => {
+ eventSpy = jest.spyOn(Tracking, 'event');
+ subject = new Tracking('_category_');
+ setHTMLFixture(`
+ <input data-track-event="click_input1" data-track-label="_label_" value="_value_"/>
+ <input data-track-event="click_input2" data-track-value="_value_override_" value="_value_"/>
+ <input type="checkbox" data-track-event="toggle_checkbox" value="_value_" checked/>
+ <input class="dropdown" data-track-event="toggle_dropdown"/>
+ <div class="js-projects-list-holder"></div>
+ `);
+ });
+
+ it('binds to clicks on elements matching [data-track-event]', () => {
+ subject.bind(document);
+ $('[data-track-event="click_input1"]').click();
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input1', {
+ label: '_label_',
+ value: '_value_',
+ property: '',
+ });
+ });
+
+ it('allows value override with the data-track-value attribute', () => {
+ subject.bind(document);
+ $('[data-track-event="click_input2"]').click();
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input2', {
+ label: '',
+ value: '_value_override_',
+ property: '',
+ });
+ });
+
+ it('handles checkbox values correctly', () => {
+ subject.bind(document);
+ const $checkbox = $('[data-track-event="toggle_checkbox"]');
+
+ $checkbox.click(); // unchecking
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', {
+ label: '',
+ property: '',
+ value: false,
+ });
+
+ $checkbox.click(); // checking
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', {
+ label: '',
+ property: '',
+ value: '_value_',
+ });
+ });
+
+ it('handles bootstrap dropdowns', () => {
+ new Tracking('_category_').bind(document);
+ const $dropdown = $('[data-track-event="toggle_dropdown"]');
+
+ $dropdown.trigger('show.bs.dropdown'); // showing
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_show', {
+ label: '',
+ property: '',
+ value: '',
+ });
+
+ $dropdown.trigger('hide.bs.dropdown'); // hiding
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_hide', {
+ label: '',
+ property: '',
+ value: '',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/changed_file_icon_spec.js b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
new file mode 100644
index 00000000000..806602877ef
--- /dev/null
+++ b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
@@ -0,0 +1,123 @@
+import { shallowMount } from '@vue/test-utils';
+import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+const changedFile = () => ({ changed: true });
+const stagedFile = () => ({ changed: false, staged: true });
+const changedAndStagedFile = () => ({ changed: true, staged: true });
+const newFile = () => ({ changed: true, tempFile: true });
+const unchangedFile = () => ({ changed: false, tempFile: false, staged: false, deleted: false });
+
+describe('Changed file icon', () => {
+ let wrapper;
+
+ const factory = (props = {}) => {
+ wrapper = shallowMount(ChangedFileIcon, {
+ propsData: {
+ file: changedFile(),
+ showTooltip: true,
+ ...props,
+ },
+ sync: false,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findIcon = () => wrapper.find(Icon);
+ const findIconName = () => findIcon().props('name');
+ const findIconClasses = () =>
+ findIcon()
+ .props('cssClasses')
+ .split(' ');
+ const findTooltipText = () => wrapper.attributes('data-original-title');
+
+ it('with isCentered true, adds center class', () => {
+ factory({
+ isCentered: true,
+ });
+
+ expect(wrapper.classes('ml-auto')).toBe(true);
+ });
+
+ it('with isCentered false, does not center', () => {
+ factory({
+ isCentered: false,
+ });
+
+ expect(wrapper.classes('ml-auto')).toBe(false);
+ });
+
+ it('with showTooltip false, does not show tooltip', () => {
+ factory({
+ showTooltip: false,
+ });
+
+ expect(findTooltipText()).toBeFalsy();
+ });
+
+ describe.each`
+ file | iconName | tooltipText | desc
+ ${changedFile()} | ${'file-modified'} | ${'Unstaged modification'} | ${'with file changed'}
+ ${stagedFile()} | ${'file-modified-solid'} | ${'Staged modification'} | ${'with file staged'}
+ ${changedAndStagedFile()} | ${'file-modified'} | ${'Unstaged and staged modification'} | ${'with file changed and staged'}
+ ${newFile()} | ${'file-addition'} | ${'Unstaged addition'} | ${'with file new'}
+ `('$desc', ({ file, iconName, tooltipText }) => {
+ beforeEach(() => {
+ factory({ file });
+ });
+
+ it('renders icon', () => {
+ expect(findIconName()).toBe(iconName);
+ expect(findIconClasses()).toContain(iconName);
+ });
+
+ it('renders tooltip text', () => {
+ expect(findTooltipText()).toBe(tooltipText);
+ });
+ });
+
+ describe('with file unchanged', () => {
+ beforeEach(() => {
+ factory({
+ file: unchangedFile(),
+ });
+ });
+
+ it('does not show icon', () => {
+ expect(findIcon().exists()).toBe(false);
+ });
+
+ it('does not have tooltip text', () => {
+ expect(findTooltipText()).toBe('');
+ });
+ });
+
+ it('with size set, sets icon size', () => {
+ const size = 8;
+
+ factory({
+ file: changedFile(),
+ size,
+ });
+
+ expect(findIcon().props('size')).toBe(size);
+ });
+
+ // NOTE: It looks like 'showStagedIcon' behavior is backwards to what the name suggests
+ // https://gitlab.com/gitlab-org/gitlab-ce/issues/66071
+ it.each`
+ showStagedIcon | iconName | desc
+ ${false} | ${'file-modified-solid'} | ${'with showStagedIcon false, renders staged icon'}
+ ${true} | ${'file-modified'} | ${'with showStagedIcon true, renders regular icon'}
+ `('$desc', ({ showStagedIcon, iconName }) => {
+ factory({
+ file: stagedFile(),
+ showStagedIcon,
+ });
+
+ expect(findIconName()).toEqual(iconName);
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js
index af92e5f5ae2..48f2ee86619 100644
--- a/spec/javascripts/vue_shared/components/markdown/header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/header_spec.js
@@ -53,7 +53,7 @@ describe('Markdown field header component', () => {
});
it('emits toggle markdown event when clicking preview', () => {
- spyOn(vm, '$emit');
+ jest.spyOn(vm, '$emit').mockImplementation();
vm.$el.querySelector('.js-preview-link').click();
@@ -65,7 +65,7 @@ describe('Markdown field header component', () => {
});
it('does not emit toggle markdown event when triggered from another form', () => {
- spyOn(vm, '$emit');
+ jest.spyOn(vm, '$emit').mockImplementation();
$(document).triggerHandler('markdown-preview:show', [
$(
@@ -76,17 +76,13 @@ describe('Markdown field header component', () => {
expect(vm.$emit).not.toHaveBeenCalled();
});
- it('blurs preview link after click', done => {
+ it('blurs preview link after click', () => {
const link = vm.$el.querySelector('li:nth-child(2) button');
- spyOn(HTMLElement.prototype, 'blur');
+ jest.spyOn(HTMLElement.prototype, 'blur').mockImplementation();
link.click();
- setTimeout(() => {
- expect(link.blur).toHaveBeenCalled();
-
- done();
- });
+ expect(link.blur).toHaveBeenCalled();
});
it('renders markdown table template', () => {
@@ -105,7 +101,7 @@ describe('Markdown field header component', () => {
vm.canSuggest = false;
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.qa-suggestion-btn')).toBe(null);
+ expect(vm.$el.querySelector('.js-suggestion-btn')).toBe(null);
});
});
});
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
index 3b6f67457ad..6716e5cd794 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -28,8 +28,8 @@ describe('Suggestion Diff component', () => {
wrapper.destroy();
});
- const findApplyButton = () => wrapper.find('.qa-apply-btn');
- const findHeader = () => wrapper.find('.qa-suggestion-diff-header');
+ const findApplyButton = () => wrapper.find('.js-apply-btn');
+ const findHeader = () => wrapper.find('.js-suggestion-diff-header');
const findHelpButton = () => wrapper.find('.js-help-btn');
const findLoading = () => wrapper.find(GlLoadingIcon);
@@ -73,7 +73,10 @@ describe('Suggestion Diff component', () => {
});
it('emits apply', () => {
- expect(wrapper.emittedByOrder()).toEqual([{ name: 'apply', args: [expect.any(Function)] }]);
+ expect(wrapper.emittedByOrder()).toContainEqual({
+ name: 'apply',
+ args: [expect.any(Function)],
+ });
});
it('hides apply button', () => {
diff --git a/spec/frontend/wikis_spec.js b/spec/frontend/wikis_spec.js
new file mode 100644
index 00000000000..b2475488d97
--- /dev/null
+++ b/spec/frontend/wikis_spec.js
@@ -0,0 +1,74 @@
+import Wikis from '~/pages/projects/wikis/wikis';
+import { setHTMLFixture } from './helpers/fixtures';
+
+describe('Wikis', () => {
+ describe('setting the commit message when the title changes', () => {
+ const editFormHtmlFixture = args => `<form class="wiki-form ${
+ args.newPage ? 'js-new-wiki-page' : ''
+ }">
+ <input type="text" id="wiki_title" value="My title" />
+ <input type="text" id="wiki_message" />
+ </form>`;
+
+ let wikis;
+ let titleInput;
+ let messageInput;
+
+ describe('when the wiki page is being created', () => {
+ const formHtmlFixture = editFormHtmlFixture({ newPage: true });
+
+ beforeEach(() => {
+ setHTMLFixture(formHtmlFixture);
+
+ titleInput = document.getElementById('wiki_title');
+ messageInput = document.getElementById('wiki_message');
+ wikis = new Wikis();
+ });
+
+ it('binds an event listener to the title input', () => {
+ wikis.handleWikiTitleChange = jest.fn();
+
+ titleInput.dispatchEvent(new Event('keyup'));
+
+ expect(wikis.handleWikiTitleChange).toHaveBeenCalled();
+ });
+
+ it('sets the commit message when title changes', () => {
+ titleInput.value = 'My title';
+ messageInput.value = '';
+
+ titleInput.dispatchEvent(new Event('keyup'));
+
+ expect(messageInput.value).toEqual('Create My title');
+ });
+
+ it('replaces hyphens with spaces', () => {
+ titleInput.value = 'my-hyphenated-title';
+ titleInput.dispatchEvent(new Event('keyup'));
+
+ expect(messageInput.value).toEqual('Create my hyphenated title');
+ });
+ });
+
+ describe('when the wiki page is being updated', () => {
+ const formHtmlFixture = editFormHtmlFixture({ newPage: false });
+
+ beforeEach(() => {
+ setHTMLFixture(formHtmlFixture);
+
+ titleInput = document.getElementById('wiki_title');
+ messageInput = document.getElementById('wiki_message');
+ wikis = new Wikis();
+ });
+
+ it('sets the commit message when title changes, prefixing with "Update"', () => {
+ titleInput.value = 'My title';
+ messageInput.value = '';
+
+ titleInput.dispatchEvent(new Event('keyup'));
+
+ expect(messageInput.value).toEqual('Update My title');
+ });
+ });
+ });
+});
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index d36e428a8ee..dec6b23d72a 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -21,6 +21,10 @@ describe GitlabSchema do
expect(field_instrumenters).to include(instance_of(::Gitlab::Graphql::Present::Instrumentation))
end
+ it 'enables using gitaly call checker' do
+ expect(field_instrumenters).to include(instance_of(::Gitlab::Graphql::CallsGitaly::Instrumentation))
+ end
+
it 'has the base mutation' do
expect(described_class.mutation).to eq(::Types::MutationType.to_graphql)
end
@@ -139,6 +143,26 @@ describe GitlabSchema do
end
end
+ context 'for classes that are not ActiveRecord subclasses and have implemented .lazy_find' do
+ it 'returns the correct record' do
+ note = create(:discussion_note_on_merge_request)
+
+ result = described_class.object_from_id(note.to_global_id)
+
+ expect(result.__sync).to eq(note)
+ end
+
+ it 'batchloads the queries' do
+ note1 = create(:discussion_note_on_merge_request)
+ note2 = create(:discussion_note_on_merge_request)
+
+ expect do
+ [described_class.object_from_id(note1.to_global_id),
+ described_class.object_from_id(note2.to_global_id)].map(&:__sync)
+ end.not_to exceed_query_limit(1)
+ end
+ end
+
context 'for other classes' do
# We cannot use an anonymous class here as `GlobalID` expects `.name` not
# to return `nil`
diff --git a/spec/graphql/resolvers/echo_resolver_spec.rb b/spec/graphql/resolvers/echo_resolver_spec.rb
new file mode 100644
index 00000000000..466501a4227
--- /dev/null
+++ b/spec/graphql/resolvers/echo_resolver_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::EchoResolver do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ let(:text) { 'Message test' }
+
+ describe '#resolve' do
+ it 'echoes text and username' do
+ expect(resolve_echo(text)).to eq %Q("#{current_user.username}" says: #{text})
+ end
+
+ it 'echoes text and nil as username' do
+ expect(resolve_echo(text, { current_user: nil })).to eq "nil says: #{text}"
+ end
+ end
+
+ def resolve_echo(text, context = { current_user: current_user })
+ resolve(described_class, obj: nil, args: { text: text }, ctx: context)
+ end
+end
diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
index 20e197e9f73..47591445fc0 100644
--- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::NamespaceProjectsResolver, :nested_groups do
+describe Resolvers::NamespaceProjectsResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb
index 0d3c3e37daf..77ef8933717 100644
--- a/spec/graphql/types/base_field_spec.rb
+++ b/spec/graphql/types/base_field_spec.rb
@@ -22,6 +22,24 @@ describe Types::BaseField do
expect(field.to_graphql.complexity).to eq 1
end
+ describe '#base_complexity' do
+ context 'with no gitaly calls' do
+ it 'defaults to 1' do
+ field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true)
+
+ expect(field.base_complexity).to eq 1
+ end
+ end
+
+ context 'with a gitaly call' do
+ it 'adds 1 to the default value' do
+ field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true)
+
+ expect(field.base_complexity).to eq 2
+ end
+ end
+ end
+
it 'has specified value' do
field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, complexity: 12)
@@ -52,5 +70,46 @@ describe Types::BaseField do
end
end
end
+
+ context 'calls_gitaly' do
+ context 'for fields with a resolver' do
+ it 'adds 1 if true' do
+ with_gitaly_field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, null: true, calls_gitaly: true)
+ without_gitaly_field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, null: true)
+ base_result = without_gitaly_field.to_graphql.complexity.call({}, {}, 2)
+
+ expect(with_gitaly_field.to_graphql.complexity.call({}, {}, 2)).to eq base_result + 1
+ end
+ end
+
+ context 'for fields without a resolver' do
+ it 'adds 1 if true' do
+ field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true)
+
+ expect(field.to_graphql.complexity).to eq 2
+ end
+ end
+
+ it 'defaults to false' do
+ field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true)
+
+ expect(field.base_complexity).to eq Types::BaseField::DEFAULT_COMPLEXITY
+ end
+
+ context 'with declared constant complexity value' do
+ it 'has complexity set to that constant' do
+ field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, complexity: 12)
+
+ expect(field.to_graphql.complexity).to eq 12
+ end
+
+ it 'does not raise an error even with Gitaly calls' do
+ allow(Gitlab::GitalyClient).to receive(:get_request_count).and_return([0, 1])
+ field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, complexity: 12)
+
+ expect(field.to_graphql.complexity).to eq 12
+ end
+ end
+ end
end
end
diff --git a/spec/graphql/types/diff_refs_type_spec.rb b/spec/graphql/types/diff_refs_type_spec.rb
new file mode 100644
index 00000000000..91017c827ad
--- /dev/null
+++ b/spec/graphql/types/diff_refs_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['DiffRefs'] do
+ it { expect(described_class.graphql_name).to eq('DiffRefs') }
+
+ it { expect(described_class).to have_graphql_fields(:base_sha, :head_sha, :start_sha) }
+end
diff --git a/spec/graphql/types/label_type_spec.rb b/spec/graphql/types/label_type_spec.rb
index f498b32f9ed..8e7b2c69eff 100644
--- a/spec/graphql/types/label_type_spec.rb
+++ b/spec/graphql/types/label_type_spec.rb
@@ -7,4 +7,6 @@ describe GitlabSchema.types['Label'] do
is_expected.to have_graphql_fields(*expected_fields)
end
+
+ it { is_expected.to require_graphql_authorizations(:read_label) }
end
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index f73bd062369..59bd0123d88 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -13,7 +13,7 @@ describe GitlabSchema.types['MergeRequest'] do
description_html state created_at updated_at source_project target_project
project project_id source_project_id target_project_id source_branch
target_branch work_in_progress merge_when_pipeline_succeeds diff_head_sha
- merge_commit_sha user_notes_count should_remove_source_branch
+ merge_commit_sha user_notes_count should_remove_source_branch diff_refs
force_remove_source_branch merge_status in_progress_merge_commit_sha
merge_error allow_collaboration should_be_rebased rebase_commit_sha
rebase_in_progress merge_commit_message default_merge_commit_message
diff --git a/spec/graphql/types/metadata_type_spec.rb b/spec/graphql/types/metadata_type_spec.rb
index 55205bf5b6a..5236380e477 100644
--- a/spec/graphql/types/metadata_type_spec.rb
+++ b/spec/graphql/types/metadata_type_spec.rb
@@ -2,4 +2,5 @@ require 'spec_helper'
describe GitlabSchema.types['Metadata'] do
it { expect(described_class.graphql_name).to eq('Metadata') }
+ it { is_expected.to require_graphql_authorizations(:read_instance_metadata) }
end
diff --git a/spec/graphql/types/namespace_type_spec.rb b/spec/graphql/types/namespace_type_spec.rb
index 77fd590586e..f476dd7286f 100644
--- a/spec/graphql/types/namespace_type_spec.rb
+++ b/spec/graphql/types/namespace_type_spec.rb
@@ -8,9 +8,11 @@ describe GitlabSchema.types['Namespace'] do
it 'has the expected fields' do
expected_fields = %w[
id name path full_name full_path description description_html visibility
- lfs_enabled request_access_enabled projects
+ lfs_enabled request_access_enabled projects root_storage_statistics
]
is_expected.to have_graphql_fields(*expected_fields)
end
+
+ it { is_expected.to require_graphql_authorizations(:read_namespace) }
end
diff --git a/spec/graphql/types/notes/diff_position_type_spec.rb b/spec/graphql/types/notes/diff_position_type_spec.rb
index 2f8724d7f0d..345bca8f702 100644
--- a/spec/graphql/types/notes/diff_position_type_spec.rb
+++ b/spec/graphql/types/notes/diff_position_type_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe GitlabSchema.types['DiffPosition'] do
it 'exposes the expected fields' do
- expected_fields = [:head_sha, :base_sha, :start_sha, :file_path, :old_path,
+ expected_fields = [:diff_refs, :file_path, :old_path,
:new_path, :position_type, :old_line, :new_line, :x, :y,
:width, :height]
diff --git a/spec/graphql/types/notes/discussion_type_spec.rb b/spec/graphql/types/notes/discussion_type_spec.rb
index 2a1eb0efd35..ba7fc961212 100644
--- a/spec/graphql/types/notes/discussion_type_spec.rb
+++ b/spec/graphql/types/notes/discussion_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
describe GitlabSchema.types['Discussion'] do
- it { is_expected.to have_graphql_fields(:id, :created_at, :notes) }
+ it { is_expected.to have_graphql_fields(:id, :created_at, :notes, :reply_id) }
it { is_expected.to require_graphql_authorizations(:read_note) }
end
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index af1972a2513..bc3b8a42392 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -34,9 +34,5 @@ describe GitlabSchema.types['Query'] do
is_expected.to have_graphql_type(Types::MetadataType)
is_expected.to have_graphql_resolver(Resolvers::MetadataResolver)
end
-
- it 'authorizes with read_instance_metadata' do
- is_expected.to require_graphql_authorizations(:read_instance_metadata)
- end
end
end
diff --git a/spec/graphql/types/root_storage_statistics_type_spec.rb b/spec/graphql/types/root_storage_statistics_type_spec.rb
new file mode 100644
index 00000000000..8c69c13aa73
--- /dev/null
+++ b/spec/graphql/types/root_storage_statistics_type_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['RootStorageStatistics'] do
+ it { expect(described_class.graphql_name).to eq('RootStorageStatistics') }
+
+ it 'has all the required fields' do
+ is_expected.to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size,
+ :build_artifacts_size, :packages_size, :wiki_size)
+ end
+
+ it { is_expected.to require_graphql_authorizations(:read_statistics) }
+end
diff --git a/spec/graphql/types/tree/submodule_type_spec.rb b/spec/graphql/types/tree/submodule_type_spec.rb
index bdb3149b41c..768eccba68c 100644
--- a/spec/graphql/types/tree/submodule_type_spec.rb
+++ b/spec/graphql/types/tree/submodule_type_spec.rb
@@ -5,5 +5,5 @@ require 'spec_helper'
describe Types::Tree::SubmoduleType do
it { expect(described_class.graphql_name).to eq('Submodule') }
- it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path) }
+ it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url, :tree_url) }
end
diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb
index d2540696b17..e80388f9ea7 100644
--- a/spec/helpers/auto_devops_helper_spec.rb
+++ b/spec/helpers/auto_devops_helper_spec.rb
@@ -118,7 +118,7 @@ describe AutoDevopsHelper do
it { is_expected.to eq('instance enabled') }
end
- context 'with groups', :nested_groups do
+ context 'with groups' do
before do
receiver.update(parent: parent)
end
@@ -170,7 +170,7 @@ describe AutoDevopsHelper do
it { is_expected.to eq('instance enabled') }
end
- context 'with groups', :nested_groups do
+ context 'with groups' do
let(:receiver) { create(:project, :repository, namespace: group) }
before do
@@ -203,7 +203,7 @@ describe AutoDevopsHelper do
it { is_expected.to be_nil }
end
- context 'with groups', :nested_groups do
+ context 'with groups' do
let(:receiver) { create(:project, :repository, namespace: group) }
context 'when auto devops is disabled on group level' do
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index aa0442ab847..94998d302f9 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -16,7 +16,7 @@ describe AvatarsHelper do
shared_examples 'resource with a custom avatar' do |source_type|
it 'returns a custom avatar image' do
expect(public_send("#{source_type}_icon", *helper_args))
- .to eq "<img src=\"#{resource.avatar.url}\" alt=\"Banana sample\" />"
+ .to eq "<img src=\"#{resource.avatar.url}\" />"
end
end
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 6808ed86c9a..1f236429347 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -29,14 +29,15 @@ describe BlobHelper do
let(:project) { create(:project, :repository, namespace: namespace) }
before do
- allow(self).to receive(:current_user).and_return(nil)
- allow(self).to receive(:can_collaborate_with_project?).and_return(true)
+ allow(helper).to receive(:current_user).and_return(nil)
+ allow(helper).to receive(:can?).and_return(true)
+ allow(helper).to receive(:can_collaborate_with_project?).and_return(true)
end
it 'verifies blob is text' do
expect(helper).not_to receive(:blob_text_viewable?)
- button = edit_blob_button(project, 'refs/heads/master', 'README.md')
+ button = helper.edit_blob_button(project, 'refs/heads/master', 'README.md')
expect(button).to start_with('<button')
end
@@ -46,25 +47,25 @@ describe BlobHelper do
expect(project.repository).not_to receive(:blob_at)
- edit_blob_button(project, 'refs/heads/master', 'README.md', blob: blob)
+ helper.edit_blob_button(project, 'refs/heads/master', 'README.md', blob: blob)
end
it 'returns a link with the proper route' do
stub_feature_flags(web_ide_default: false)
- link = edit_blob_button(project, 'master', 'README.md')
+ link = helper.edit_blob_button(project, 'master', 'README.md')
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md")
end
it 'returns a link with a Web IDE route' do
- link = edit_blob_button(project, 'master', 'README.md')
+ link = helper.edit_blob_button(project, 'master', 'README.md')
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/-/ide/project/#{project.full_path}/edit/master/-/README.md")
end
it 'returns a link with the passed link_opts on the expected route' do
stub_feature_flags(web_ide_default: false)
- link = edit_blob_button(project, 'master', 'README.md', link_opts: { mr_id: 10 })
+ link = helper.edit_blob_button(project, 'master', 'README.md', link_opts: { mr_id: 10 })
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md?mr_id=10")
end
@@ -203,6 +204,13 @@ describe BlobHelper do
describe '#ide_edit_path' do
let(:project) { create(:project) }
+ let(:current_user) { create(:user) }
+ let(:can_push_code) { true }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(current_user)
+ allow(helper).to receive(:can?).and_return(can_push_code)
+ end
around do |example|
old_script_name = Rails.application.routes.default_url_options[:script_name]
@@ -243,5 +251,21 @@ describe BlobHelper do
expect(helper.ide_edit_path(project, "testing/slashes", "readme.md/")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/slashes/-/readme.md/")
end
+
+ context 'when user is not logged in' do
+ let(:current_user) { nil }
+
+ it 'returns IDE path inside the project' do
+ expect(helper.ide_edit_path(project, "master", "")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master")
+ end
+ end
+
+ context 'when user cannot push to the project' do
+ let(:can_push_code) { false }
+
+ it "returns IDE path with the user's fork" do
+ expect(helper.ide_edit_path(project, "master", "")).to eq("/-/ide/project/#{current_user.namespace.full_path}/#{project.path}/edit/master")
+ end
+ end
end
end
diff --git a/spec/helpers/boards_helper_spec.rb b/spec/helpers/boards_helper_spec.rb
index b22947911b9..f014537eb54 100644
--- a/spec/helpers/boards_helper_spec.rb
+++ b/spec/helpers/boards_helper_spec.rb
@@ -1,10 +1,12 @@
require 'spec_helper'
describe BoardsHelper do
+ set(:project) { create(:project) }
+
describe '#build_issue_link_base' do
context 'project board' do
it 'returns correct path for project board' do
- @project = create(:project)
+ @project = project
@board = create(:board, project: @project)
expect(build_issue_link_base).to eq("/#{@project.namespace.path}/#{@project.path}/issues")
@@ -31,7 +33,6 @@ describe BoardsHelper do
describe '#board_data' do
let(:user) { create(:user) }
- let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
before do
@@ -46,4 +47,15 @@ describe BoardsHelper do
expect(helper.board_data[:lists_endpoint]).to eq(board_lists_path(board))
end
end
+
+ describe '#current_board_json' do
+ let(:board_json) { helper.current_board_json }
+
+ it 'can serialise with a basic set of attributes' do
+ board = create(:board, project: project)
+ assign(:board, board)
+
+ expect(board_json).to match_schema('current-board')
+ end
+ end
end
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index bc2422aba90..4f665dc0514 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -53,4 +53,80 @@ describe CiStatusHelper do
expect(helper.pipeline_status_cache_key(pipeline_status)).to eq("pipeline-status/123abc-success")
end
end
+
+ describe "#render_status_with_link" do
+ subject { helper.render_status_with_link("success") }
+
+ it "renders a passed status icon" do
+ is_expected.to include("<span class=\"ci-status-link ci-status-icon-success d-inline-flex")
+ end
+
+ it "has 'Pipeline' as the status type in the title" do
+ is_expected.to include("title=\"Pipeline: passed\"")
+ end
+
+ it "has the success status icon" do
+ is_expected.to include("ci-status-icon-success")
+ end
+
+ context "when pipeline has commit path" do
+ subject { helper.render_status_with_link("success", "/commit-path") }
+
+ it "links to commit" do
+ is_expected.to include("href=\"/commit-path\"")
+ end
+
+ it "does not contain a span element" do
+ is_expected.not_to include("<span")
+ end
+
+ it "has 'Pipeline' as the status type in the title" do
+ is_expected.to include("title=\"Pipeline: passed\"")
+ end
+
+ it "has the correct status icon" do
+ is_expected.to include("ci-status-icon-success")
+ end
+ end
+
+ context "when different type than pipeline is provided" do
+ subject { helper.render_status_with_link("success", type: "commit") }
+
+ it "has the provided type in the title" do
+ is_expected.to include("title=\"Commit: passed\"")
+ end
+ end
+
+ context "when tooltip_placement is provided" do
+ subject { helper.render_status_with_link("success", tooltip_placement: "right") }
+
+ it "has the provided tooltip placement" do
+ is_expected.to include("data-placement=\"right\"")
+ end
+ end
+
+ context "when additional CSS classes are provided" do
+ subject { helper.render_status_with_link("success", cssclass: "extra-class") }
+
+ it "has appended extra class to icon classes" do
+ is_expected.to include("class=\"ci-status-link ci-status-icon-success d-inline-flex extra-class\"")
+ end
+ end
+
+ context "when container is provided" do
+ subject { helper.render_status_with_link("success", container: "my-container") }
+
+ it "has the provided container in data" do
+ is_expected.to include("data-container=\"my-container\"")
+ end
+ end
+
+ context "when icon_size is provided" do
+ subject { helper.render_status_with_link("success", icon_size: 24) }
+
+ it "has the svg class to change size" do
+ is_expected.to include("<svg class=\"s24\">")
+ end
+ end
+ end
end
diff --git a/spec/helpers/dashboard_helper_spec.rb b/spec/helpers/dashboard_helper_spec.rb
index 023238ee0ae..059ae128d93 100644
--- a/spec/helpers/dashboard_helper_spec.rb
+++ b/spec/helpers/dashboard_helper_spec.rb
@@ -12,7 +12,7 @@ describe DashboardHelper do
it 'has all the expected links by default' do
menu_items = [:projects, :groups, :activity, :milestones, :snippets]
- expect(helper.dashboard_nav_links).to contain_exactly(*menu_items)
+ expect(helper.dashboard_nav_links).to include(*menu_items)
end
it 'does not contain cross project elements when the user cannot read cross project' do
@@ -22,6 +22,43 @@ describe DashboardHelper do
end
end
+ describe '#feature_entry' do
+ context 'when implicitly enabled' do
+ it 'considers feature enabled by default' do
+ entry = feature_entry('Demo', href: 'demo.link')
+
+ expect(entry).to include('<p aria-label="Demo: status on">')
+ expect(entry).to include('<a href="demo.link">Demo</a>')
+ end
+ end
+
+ context 'when explicitly enabled' do
+ it 'returns a link' do
+ entry = feature_entry('Demo', href: 'demo.link', enabled: true)
+
+ expect(entry).to include('<p aria-label="Demo: status on">')
+ expect(entry).to include('<a href="demo.link">Demo</a>')
+ end
+
+ it 'returns text if href is not provided' do
+ entry = feature_entry('Demo', enabled: true)
+
+ expect(entry).to include('<p aria-label="Demo: status on">')
+ expect(entry).not_to match(/<a[^>]+>/)
+ end
+ end
+
+ context 'when disabled' do
+ it 'returns text without link' do
+ entry = feature_entry('Demo', href: 'demo.link', enabled: false)
+
+ expect(entry).to include('<p aria-label="Demo: status off">')
+ expect(entry).not_to match(/<a[^>]+>/)
+ expect(entry).to include('Demo')
+ end
+ end
+ end
+
describe '.has_start_trial?' do
subject { helper.has_start_trial? }
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index e6aacb5b92b..d25f0c6de4a 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -103,7 +103,7 @@ describe EmailsHelper do
appearance = create :appearance, header_logo: fixture_file_upload('spec/fixtures/dk.png')
expect(header_logo).to eq(
- %{<img style="height: 50px" src="/uploads/-/system/appearance/header_logo/#{appearance.id}/dk.png" alt="Dk" />}
+ %{<img style="height: 50px" src="/uploads/-/system/appearance/header_logo/#{appearance.id}/dk.png" />}
)
end
end
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 1763c46389a..98719697cea 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -86,7 +86,7 @@ describe GroupsHelper do
end
end
- describe 'group_title', :nested_groups do
+ describe 'group_title' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -99,7 +99,7 @@ describe GroupsHelper do
end
# rubocop:disable Layout/SpaceBeforeComma
- describe '#share_with_group_lock_help_text', :nested_groups do
+ describe '#share_with_group_lock_help_text' do
let!(:root_group) { create(:group) }
let!(:subgroup) { create(:group, parent: root_group) }
let!(:sub_subgroup) { create(:group, parent: subgroup) }
@@ -230,7 +230,7 @@ describe GroupsHelper do
end
end
- describe 'parent_group_options', :nested_groups do
+ describe 'parent_group_options' do
let(:current_user) { create(:user) }
let(:group) { create(:group, name: 'group') }
let(:group2) { create(:group, name: 'group2') }
@@ -262,4 +262,44 @@ describe GroupsHelper do
expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }].to_json)
end
end
+
+ describe '#can_disable_group_emails?' do
+ let(:current_user) { create(:user) }
+ let(:group) { create(:group, name: 'group') }
+ let(:subgroup) { create(:group, name: 'subgroup', parent: group) }
+
+ before do
+ allow(helper).to receive(:current_user) { current_user }
+ end
+
+ it 'returns true for the group owner' do
+ allow(helper).to receive(:can?).with(current_user, :set_emails_disabled, group) { true }
+
+ expect(helper.can_disable_group_emails?(group)).to be_truthy
+ end
+
+ it 'returns false for anyone else' do
+ allow(helper).to receive(:can?).with(current_user, :set_emails_disabled, group) { false }
+
+ expect(helper.can_disable_group_emails?(group)).to be_falsey
+ end
+
+ context 'when subgroups' do
+ before do
+ allow(helper).to receive(:can?).with(current_user, :set_emails_disabled, subgroup) { true }
+ end
+
+ it 'returns false if parent group is disabling emails' do
+ allow(group).to receive(:emails_disabled?).and_return(true)
+
+ expect(helper.can_disable_group_emails?(subgroup)).to be_falsey
+ end
+
+ it 'returns true if parent group is not disabling emails' do
+ allow(group).to receive(:emails_disabled?).and_return(false)
+
+ expect(helper.can_disable_group_emails?(subgroup)).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb
index 37e9ddadb8c..f92b94a9583 100644
--- a/spec/helpers/icons_helper_spec.rb
+++ b/spec/helpers/icons_helper_spec.rb
@@ -125,6 +125,14 @@ describe IconsHelper do
expect(file_type_icon_class('file', 0, 'filename.png')).to eq 'file-image-o'
end
+ it 'returns file-image-o class with .apng' do
+ expect(file_type_icon_class('file', 0, 'filename.apng')).to eq 'file-image-o'
+ end
+
+ it 'returns file-image-o class with .webp' do
+ expect(file_type_icon_class('file', 0, 'filename.webp')).to eq 'file-image-o'
+ end
+
it 'returns file-archive-o class with .tar' do
expect(file_type_icon_class('file', 0, 'filename.tar')).to eq 'file-archive-o'
end
@@ -145,6 +153,10 @@ describe IconsHelper do
expect(file_type_icon_class('file', 0, 'filename.MP3')).to eq 'file-audio-o'
end
+ it 'returns file-audio-o class with .m4a' do
+ expect(file_type_icon_class('file', 0, 'filename.m4a')).to eq 'file-audio-o'
+ end
+
it 'returns file-audio-o class with .wav' do
expect(file_type_icon_class('file', 0, 'filename.wav')).to eq 'file-audio-o'
end
@@ -161,6 +173,10 @@ describe IconsHelper do
expect(file_type_icon_class('file', 0, 'filename.mp4')).to eq 'file-video-o'
end
+ it 'returns file-word-o class with .odt' do
+ expect(file_type_icon_class('file', 0, 'filename.odt')).to eq 'file-word-o'
+ end
+
it 'returns file-word-o class with .doc' do
expect(file_type_icon_class('file', 0, 'filename.doc')).to eq 'file-word-o'
end
@@ -185,6 +201,10 @@ describe IconsHelper do
expect(file_type_icon_class('file', 0, 'filename.xlsx')).to eq 'file-excel-o'
end
+ it 'returns file-excel-o class with .odp' do
+ expect(file_type_icon_class('file', 0, 'filename.odp')).to eq 'file-powerpoint-o'
+ end
+
it 'returns file-excel-o class with .ppt' do
expect(file_type_icon_class('file', 0, 'filename.ppt')).to eq 'file-powerpoint-o'
end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 1d1446eaa30..3c8179460ac 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -202,5 +202,46 @@ describe IssuablesHelper do
}
expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data))
end
+
+ describe '#zoomMeetingUrl in issue' do
+ let(:issue) { create(:issue, author: user, description: description) }
+
+ before do
+ assign(:project, issue.project)
+ end
+
+ context 'no zoom links in the issue description' do
+ let(:description) { 'issue text' }
+
+ it 'does not set zoomMeetingUrl' do
+ expect(helper.issuable_initial_data(issue))
+ .not_to include(:zoomMeetingUrl)
+ end
+ end
+
+ context 'no zoom links in the issue description if it has link but not a zoom link' do
+ let(:description) { 'issue text https://stackoverflow.com/questions/22' }
+
+ it 'does not set zoomMeetingUrl' do
+ expect(helper.issuable_initial_data(issue))
+ .not_to include(:zoomMeetingUrl)
+ end
+ end
+
+ context 'with two zoom links in description' do
+ let(:description) do
+ <<~TEXT
+ issue text and
+ zoom call on https://zoom.us/j/123456789 this url
+ and new zoom url https://zoom.us/s/lastone and some more text
+ TEXT
+ end
+
+ it 'sets zoomMeetingUrl value to the last url' do
+ expect(helper.issuable_initial_data(issue))
+ .to include(zoomMeetingUrl: 'https://zoom.us/s/lastone')
+ end
+ end
+ end
end
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 314305d7a8e..4f1cab38f34 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -3,10 +3,8 @@ require 'spec_helper'
describe LabelsHelper do
describe '#show_label_issuables_link?' do
shared_examples 'a valid response to show_label_issuables_link?' do |issuables_type, when_enabled = true, when_disabled = false|
- let(:context_project) { project }
-
context "when asking for a #{issuables_type} link" do
- subject { show_label_issuables_link?(label.present(issuable_subject: nil), issuables_type, project: context_project) }
+ subject { show_label_issuables_link?(label.present(issuable_subject: nil), issuables_type) }
context "when #{issuables_type} are enabled for the project" do
let(:project) { create(:project, "#{issuables_type}_access_level": ProjectFeature::ENABLED) }
@@ -39,27 +37,11 @@ describe LabelsHelper do
let(:label) { create(:group_label, group: group, title: 'bug') }
context 'when asking for an issue link' do
- context 'in the context of a project' do
- it_behaves_like 'a valid response to show_label_issuables_link?', :issues, true, true
- end
-
- context 'in the context of a group' do
- let(:context_project) { nil }
-
- it_behaves_like 'a valid response to show_label_issuables_link?', :issues, true, true
- end
+ it_behaves_like 'a valid response to show_label_issuables_link?', :issues, true, true
end
context 'when asking for a merge requests link' do
- context 'in the context of a project' do
- it_behaves_like 'a valid response to show_label_issuables_link?', :merge_requests, true, true
- end
-
- context 'in the context of a group' do
- let(:context_project) { nil }
-
- it_behaves_like 'a valid response to show_label_issuables_link?', :merge_requests, true, true
- end
+ it_behaves_like 'a valid response to show_label_issuables_link?', :merge_requests, true, true
end
end
end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 597c8f836a9..f6e1720e113 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -50,6 +50,43 @@ describe MarkupHelper do
expect(markdown(actual, project: second_project)).to match(expected)
end
end
+
+ describe 'uploads' do
+ let(:text) { "![ImageTest](/uploads/test.png)" }
+ let(:group) { create(:group) }
+
+ subject { helper.markdown(text) }
+
+ describe 'inside a project' do
+ it 'renders uploads relative to project' do
+ expect(subject).to include("#{project.full_path}/uploads/test.png")
+ end
+ end
+
+ describe 'inside a group' do
+ before do
+ helper.instance_variable_set(:@group, group)
+ helper.instance_variable_set(:@project, nil)
+ end
+
+ it 'renders uploads relative to the group' do
+ expect(subject).to include("#{group.full_path}/-/uploads/test.png")
+ end
+ end
+
+ describe "with a group in the context" do
+ let(:project_in_group) { create(:project, group: group) }
+
+ before do
+ helper.instance_variable_set(:@group, group)
+ helper.instance_variable_set(:@project, project_in_group)
+ end
+
+ it 'renders uploads relative to project' do
+ expect(subject).to include("#{project_in_group.path_with_namespace}/uploads/test.png")
+ end
+ end
+ end
end
describe '#markdown_field' do
@@ -231,7 +268,7 @@ describe MarkupHelper do
end
end
- describe 'markup' do
+ describe '#markup' do
let(:content) { 'Noël' }
it 'preserves encoding' do
@@ -265,6 +302,77 @@ describe MarkupHelper do
end
end
+ describe '#markup_unsafe' do
+ subject { helper.markup_unsafe(file_name, text, context) }
+
+ let(:file_name) { 'foo.bar' }
+ let(:text) { 'Noël' }
+ let(:project_base) { build(:project, :repository) }
+ let(:context) { { project: project_base } }
+
+ context 'when text is missing' do
+ let(:text) { nil }
+
+ it 'returns an empty string' do
+ is_expected.to eq('')
+ end
+ end
+
+ context 'when file is a markdown file' do
+ let(:file_name) { 'foo.md' }
+
+ it 'returns html (rendered by Banzai)' do
+ expected_html = '<p data-sourcepos="1:1-1:5" dir="auto">Noël</p>'
+
+ expect(Banzai).to receive(:render).with(text, context) { expected_html }
+
+ is_expected.to eq(expected_html)
+ end
+
+ context 'when renderer returns an error' do
+ before do
+ allow(Banzai).to receive(:render).and_raise("An error")
+ end
+
+ it 'returns html (rendered by ActionView:TextHelper)' do
+ is_expected.to eq('<p>Noël</p>')
+ end
+ end
+ end
+
+ context 'when file is asciidoc file' do
+ let(:file_name) { 'foo.adoc' }
+
+ it 'returns html (rendered by Gitlab::Asciidoc)' do
+ expected_html = "<div>\n<p>Noël</p>\n</div>"
+
+ expect(Gitlab::Asciidoc).to receive(:render).with(text, context) { expected_html }
+
+ is_expected.to eq(expected_html)
+ end
+ end
+
+ context 'when file is a regular text file' do
+ let(:file_name) { 'foo.txt' }
+
+ it 'returns html (rendered by ActionView::TagHelper)' do
+ is_expected.to eq('<pre class="plain-readme">Noël</pre>')
+ end
+ end
+
+ context 'when file has an unknown type' do
+ let(:file_name) { 'foo' }
+
+ it 'returns html (rendered by Gitlab::OtherMarkup)' do
+ expected_html = 'Noël'
+
+ expect(Gitlab::OtherMarkup).to receive(:render).with(file_name, text, context) { expected_html }
+
+ is_expected.to eq(expected_html)
+ end
+ end
+ end
+
describe '#first_line_in_markdown' do
shared_examples_for 'common markdown examples' do
let(:project_base) { build(:project, :repository) }
diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb
index 601f864ef36..e38513f6d94 100644
--- a/spec/helpers/namespaces_helper_spec.rb
+++ b/spec/helpers/namespaces_helper_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe NamespacesHelper, :postgresql do
+describe NamespacesHelper do
let!(:admin) { create(:admin) }
let!(:admin_project_creation_level) { nil }
let!(:admin_group) do
@@ -109,7 +109,7 @@ describe NamespacesHelper, :postgresql do
expect(options).to include(user_group.name)
end
- context 'when nested groups are available', :nested_groups do
+ context 'when nested groups are available' do
it 'includes groups nested in groups the user can administer' do
allow(helper).to receive(:current_user).and_return(user)
child_group = create(:group, :private, parent: user_group)
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
index 9ecaabc04ed..5717b15d656 100644
--- a/spec/helpers/notifications_helper_spec.rb
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe NotificationsHelper do
describe 'notification_icon' do
it { expect(notification_icon(:disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
+ it { expect(notification_icon(:owner_disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
it { expect(notification_icon(:participating)).to match('class="fa fa-volume-up fa-fw"') }
it { expect(notification_icon(:mention)).to match('class="fa fa-at fa-fw"') }
it { expect(notification_icon(:global)).to match('class="fa fa-globe fa-fw"') }
@@ -19,4 +20,14 @@ describe NotificationsHelper do
it { expect(notification_event_name(:success_pipeline)).to match('Successful pipeline') }
it { expect(notification_event_name(:failed_pipeline)).to match('Failed pipeline') }
end
+
+ describe '#notification_icon_level' do
+ let(:user) { create(:user) }
+ let(:global_setting) { user.global_notification_setting }
+ let(:notification_setting) { create(:notification_setting, level: :watch) }
+
+ it { expect(notification_icon_level(notification_setting, true)).to eq 'owner_disabled' }
+ it { expect(notification_icon_level(notification_setting)).to eq 'watch' }
+ it { expect(notification_icon_level(global_setting)).to eq 'participating' }
+ end
end
diff --git a/spec/helpers/onboarding_experiment_helper_spec.rb b/spec/helpers/onboarding_experiment_helper_spec.rb
new file mode 100644
index 00000000000..5b7d9b1c2e6
--- /dev/null
+++ b/spec/helpers/onboarding_experiment_helper_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe OnboardingExperimentHelper, type: :helper do
+ describe '.allow_access_to_onboarding?' do
+ context "when we're not gitlab.com" do
+ it 'returns false' do
+ allow(::Gitlab).to receive(:com?).and_return(false)
+
+ expect(helper.allow_access_to_onboarding?).to be(false)
+ end
+ end
+
+ context "when we're gitlab.com" do
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(true)
+ end
+
+ context 'and the :user_onboarding feature is not enabled' do
+ it 'returns false' do
+ stub_feature_flags(user_onboarding: false)
+
+ expect(helper.allow_access_to_onboarding?).to be(false)
+ end
+ end
+
+ context 'and the :user_onboarding feature is enabled' do
+ it 'returns true' do
+ stub_feature_flags(user_onboarding: true)
+ allow(helper).to receive(:current_user).and_return(create(:user))
+
+ expect(helper.allow_access_to_onboarding?).to be(true)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index db0d45c3692..554c08add2d 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -28,7 +28,7 @@ describe PreferencesHelper do
["Your Projects' Activity", 'project_activity'],
["Starred Projects' Activity", 'starred_project_activity'],
["Your Groups", 'groups'],
- ["Your Todos", 'todos'],
+ ["Your To-Do List", 'todos'],
["Assigned Issues", 'issues'],
["Assigned Merge Requests", 'merge_requests']
]
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 3716879c458..a70bfc2adc7 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -107,6 +107,30 @@ describe ProjectsHelper do
end
end
+ describe '#can_disable_emails?' do
+ let(:project) { create(:project) }
+ let(:user) { create(:project_member, :maintainer, user: create(:user), project: project).user }
+
+ it 'returns true for the project owner' do
+ allow(helper).to receive(:can?).with(project.owner, :set_emails_disabled, project) { true }
+
+ expect(helper.can_disable_emails?(project, project.owner)).to be_truthy
+ end
+
+ it 'returns false for anyone else' do
+ allow(helper).to receive(:can?).with(user, :set_emails_disabled, project) { false }
+
+ expect(helper.can_disable_emails?(project, user)).to be_falsey
+ end
+
+ it 'returns false if group emails disabled' do
+ project = create(:project, group: create(:group))
+ allow(project.group).to receive(:emails_disabled?).and_return(true)
+
+ expect(helper.can_disable_emails?(project, project.owner)).to be_falsey
+ end
+ end
+
describe "readme_cache_key" do
let(:project) { create(:project, :repository) }
@@ -477,6 +501,7 @@ describe ProjectsHelper do
it 'returns the command to push to create project over SSH' do
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:enabled_git_access_protocol) { 'ssh' }
+ allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return('git@localhost:')
expect(helper.push_to_create_project_command(user)).to eq('git push --set-upstream git@localhost:john/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)')
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index c69493b579f..2ab72679ee7 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -177,4 +177,48 @@ describe SearchHelper do
end
end
end
+
+ describe 'search_filter_link' do
+ it 'renders a search filter link for the current scope' do
+ @scope = 'projects'
+ @search_results = double
+
+ expect(@search_results).to receive(:formatted_count).with('projects').and_return('23')
+
+ link = search_filter_link('projects', 'Projects')
+
+ expect(link).to have_css('li.active')
+ expect(link).to have_link('Projects', href: search_path(scope: 'projects'))
+ expect(link).to have_css('span.badge.badge-pill:not(.js-search-count):not(.hidden):not([data-url])', text: '23')
+ end
+
+ it 'renders a search filter link for another scope' do
+ link = search_filter_link('projects', 'Projects')
+ count_path = search_count_path(scope: 'projects')
+
+ expect(link).to have_css('li:not([class="active"])')
+ expect(link).to have_link('Projects', href: search_path(scope: 'projects'))
+ expect(link).to have_css("span.badge.badge-pill.js-search-count.hidden[data-url='#{count_path}']", text: '')
+ end
+
+ it 'merges in the current search params and given params' do
+ expect(self).to receive(:params).and_return(
+ ActionController::Parameters.new(
+ search: 'hello',
+ scope: 'ignored',
+ other_param: 'ignored'
+ )
+ )
+
+ link = search_filter_link('projects', 'Projects', search: { project_id: 23 })
+
+ expect(link).to have_link('Projects', href: search_path(scope: 'projects', search: 'hello', project_id: 23))
+ end
+
+ it 'assigns given data attributes on the list container' do
+ link = search_filter_link('projects', 'Projects', data: { foo: 'bar' })
+
+ expect(link).to have_css('li[data-foo="bar"]')
+ end
+ end
end
diff --git a/spec/helpers/sessions_helper_spec.rb b/spec/helpers/sessions_helper_spec.rb
new file mode 100644
index 00000000000..647771ace92
--- /dev/null
+++ b/spec/helpers/sessions_helper_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SessionsHelper do
+ describe '#unconfirmed_email?' do
+ it 'returns true when the flash alert contains a devise failure unconfirmed message' do
+ flash[:alert] = t(:unconfirmed, scope: [:devise, :failure])
+ expect(helper.unconfirmed_email?).to be_truthy
+ end
+
+ it 'returns false when the flash alert does not contain a devise failure unconfirmed message' do
+ flash[:alert] = 'something else'
+ expect(helper.unconfirmed_email?).to be_falsey
+ end
+ end
+end
diff --git a/spec/helpers/sorting_helper_spec.rb b/spec/helpers/sorting_helper_spec.rb
index f405268d198..5397a47b3dd 100644
--- a/spec/helpers/sorting_helper_spec.rb
+++ b/spec/helpers/sorting_helper_spec.rb
@@ -4,6 +4,11 @@ require 'spec_helper'
describe SortingHelper do
include ApplicationHelper
include IconsHelper
+ include ExploreHelper
+
+ def set_sorting_url(option)
+ allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: { label_name: option }))
+ end
describe '#issuable_sort_option_title' do
it 'returns correct title for issuable_sort_option_overrides key' do
@@ -21,7 +26,7 @@ describe SortingHelper do
describe '#issuable_sort_direction_button' do
before do
- allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: { label_name: 'test_label' }))
+ set_sorting_url 'test_label'
end
it 'keeps label filter param' do
@@ -44,4 +49,145 @@ describe SortingHelper do
expect(issuable_sort_direction_button('due_date')).to include('sort-lowest')
end
end
+
+ def stub_controller_path(value)
+ allow(helper.controller).to receive(:controller_path).and_return(value)
+ end
+
+ def project_common_options
+ {
+ sort_value_latest_activity => sort_title_latest_activity,
+ sort_value_recently_created => sort_title_created_date,
+ sort_value_name => sort_title_name,
+ sort_value_stars_desc => sort_title_stars
+ }
+ end
+
+ describe 'with `admin/projects` controller' do
+ before do
+ stub_controller_path 'admin/projects'
+ end
+
+ describe '#projects_sort_options_hash' do
+ it 'returns a hash of available sorting options' do
+ admin_options = project_common_options.merge({
+ sort_value_oldest_activity => sort_title_oldest_activity,
+ sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_recently_created => sort_title_recently_created,
+ sort_value_stars_desc => sort_title_most_stars,
+ sort_value_largest_repo => sort_title_largest_repo
+ })
+
+ expect(projects_sort_options_hash).to eq(admin_options)
+ end
+ end
+ end
+
+ describe 'with `projects` controller' do
+ before do
+ stub_controller_path 'projects'
+ end
+
+ describe '#projects_sort_options_hash' do
+ it 'returns a hash of available sorting options' do
+ expect(projects_sort_options_hash).to include(project_common_options)
+ end
+ end
+
+ describe '#projects_reverse_sort_options_hash' do
+ context 'returns a reversed hash of available sorting options' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:sort_key, :reverse_sort_title) do
+ sort_value_latest_activity | sort_value_oldest_activity
+ sort_value_recently_created | sort_value_oldest_created
+ sort_value_name | sort_value_name_desc
+ sort_value_stars_desc | sort_value_stars_asc
+ sort_value_oldest_activity | sort_value_latest_activity
+ sort_value_oldest_created | sort_value_recently_created
+ sort_value_name_desc | sort_value_name
+ sort_value_stars_asc | sort_value_stars_desc
+ end
+
+ with_them do
+ it do
+ reverse_hash = projects_reverse_sort_options_hash
+
+ expect(reverse_hash).to include(sort_key)
+ expect(reverse_hash[sort_key]).to eq(reverse_sort_title)
+ end
+ end
+ end
+ end
+
+ describe '#project_sort_direction_button' do
+ context 'returns the correct icon for each sort option' do
+ using RSpec::Parameterized::TableSyntax
+
+ sort_lowest_icon = 'sort-lowest'
+ sort_highest_icon = 'sort-highest'
+
+ where(:selected_sort, :icon) do
+ sort_value_latest_activity | sort_highest_icon
+ sort_value_recently_created | sort_highest_icon
+ sort_value_name_desc | sort_highest_icon
+ sort_value_stars_desc | sort_highest_icon
+ sort_value_oldest_activity | sort_lowest_icon
+ sort_value_oldest_created | sort_lowest_icon
+ sort_value_name | sort_lowest_icon
+ sort_value_stars_asc | sort_lowest_icon
+ end
+
+ with_them do
+ it do
+ set_sorting_url selected_sort
+
+ expect(project_sort_direction_button(selected_sort)).to include(icon)
+ end
+ end
+ end
+
+ it 'returns the correct link to reverse the current sort option' do
+ sort_options_links = projects_reverse_sort_options_hash
+
+ sort_options_links.each do |selected_sort, reverse_sort|
+ set_sorting_url selected_sort
+
+ expect(project_sort_direction_button(selected_sort)).to include(reverse_sort)
+ end
+ end
+ end
+
+ describe '#projects_sort_option_titles' do
+ it 'returns a hash of titles for the sorting options' do
+ options = project_common_options.merge({
+ sort_value_oldest_activity => sort_title_latest_activity,
+ sort_value_oldest_created => sort_title_created_date,
+ sort_value_name_desc => sort_title_name,
+ sort_value_stars_asc => sort_title_stars
+ })
+
+ expect(projects_sort_option_titles).to eq(options)
+ end
+ end
+
+ describe 'with project_list_filter_bar off' do
+ before do
+ stub_feature_flags(project_list_filter_bar: false)
+ end
+
+ describe '#projects_sort_options_hash' do
+ it 'returns a hash of available sorting options' do
+ options = project_common_options.merge({
+ sort_value_oldest_activity => sort_title_oldest_activity,
+ sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_recently_created => sort_title_recently_created,
+ sort_value_stars_desc => sort_title_most_stars
+ })
+
+ expect(projects_sort_options_hash).to eq(options)
+ end
+ end
+ end
+ end
end
diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb
index 62c00964524..4bd0fbb76ca 100644
--- a/spec/helpers/storage_helper_spec.rb
+++ b/spec/helpers/storage_helper_spec.rb
@@ -31,7 +31,7 @@ describe StorageHelper do
build_artifacts_size: 30.megabytes))
end
- let(:message) { '10 KB repositories, 10 Bytes wikis, 30 MB build artifacts, 20 GB LFS' }
+ let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB' }
it 'works on ProjectStatistics' do
expect(helper.storage_counters_details(project.statistics)).to eq(message)
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index ea48c69e0ae..ab4ef899119 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -3,15 +3,11 @@ require 'spec_helper'
describe SubmoduleHelper do
include RepoHelpers
- describe 'submodule links' do
- let(:submodule_item) { double(id: 'hash', path: 'rack') }
- let(:config) { Gitlab.config.gitlab }
- let(:repo) { double }
-
- before do
- self.instance_variable_set(:@repository, repo)
- end
+ let(:submodule_item) { double(id: 'hash', path: 'rack') }
+ let(:config) { Gitlab.config.gitlab }
+ let(:repo) { double }
+ shared_examples 'submodule_links' do
context 'submodule on self' do
before do
allow(Gitlab.config.gitlab).to receive(:protocol).and_return('http') # set this just to be sure
@@ -21,28 +17,28 @@ describe SubmoduleHelper do
allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(22) # set this just to be sure
allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
stub_url([config.user, '@', config.host, ':gitlab-org/gitlab-ce.git'].join(''))
- expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
+ expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'detects ssh on non-standard port' do
allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(2222)
allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
stub_url(['ssh://', config.user, '@', config.host, ':2222/gitlab-org/gitlab-ce.git'].join(''))
- expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
+ expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'detects http on standard port' do
allow(Gitlab.config.gitlab).to receive(:port).and_return(80)
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url(['http://', config.host, '/gitlab-org/gitlab-ce.git'].join(''))
- expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
+ expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'detects http on non-standard port' do
allow(Gitlab.config.gitlab).to receive(:port).and_return(3000)
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url(['http://', config.host, ':3000/gitlab-org/gitlab-ce.git'].join(''))
- expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
+ expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'works with relative_url_root' do
@@ -50,7 +46,7 @@ describe SubmoduleHelper do
allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root')
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url(['http://', config.host, '/gitlab/root/gitlab-org/gitlab-ce.git'].join(''))
- expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
+ expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'works with subgroups' do
@@ -58,34 +54,34 @@ describe SubmoduleHelper do
allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root')
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url(['http://', config.host, '/gitlab/root/gitlab-org/sub/gitlab-ce.git'].join(''))
- expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org/sub', 'gitlab-ce'), namespace_project_tree_path('gitlab-org/sub', 'gitlab-ce', 'hash')])
+ expect(subject).to eq([namespace_project_path('gitlab-org/sub', 'gitlab-ce'), namespace_project_tree_path('gitlab-org/sub', 'gitlab-ce', 'hash')])
end
end
context 'submodule on github.com' do
it 'detects ssh' do
stub_url('git@github.com:gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'detects http' do
stub_url('http://github.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'detects https' do
stub_url('https://github.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'handles urls with no .git on the end' do
stub_url('http://github.com/gitlab-org/gitlab-ce')
- expect(submodule_links(submodule_item)).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'returns original with non-standard url' do
stub_url('http://github.com/another/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
+ expect(subject).to eq([repo.submodule_url_for, nil])
end
end
@@ -97,39 +93,39 @@ describe SubmoduleHelper do
allow(repo).to receive(:project).and_return(project)
stub_url('./')
- expect(submodule_links(submodule_item)).to eq(["/master-project/#{project.path}", "/master-project/#{project.path}/tree/hash"])
+ expect(subject).to eq(["/master-project/#{project.path}", "/master-project/#{project.path}/tree/hash"])
end
end
context 'submodule on gitlab.com' do
it 'detects ssh' do
stub_url('git@gitlab.com:gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'detects http' do
stub_url('http://gitlab.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'detects https' do
stub_url('https://gitlab.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'handles urls with no .git on the end' do
stub_url('http://gitlab.com/gitlab-org/gitlab-ce')
- expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'handles urls with trailing whitespace' do
stub_url('http://gitlab.com/gitlab-org/gitlab-ce.git ')
- expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'returns original with non-standard url' do
stub_url('http://gitlab.com/another/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
+ expect(subject).to eq([repo.submodule_url_for, nil])
end
end
@@ -137,27 +133,25 @@ describe SubmoduleHelper do
it 'sanitizes unsupported protocols' do
stub_url('javascript:alert("XSS");')
- expect(helper.submodule_links(submodule_item)).to eq([nil, nil])
+ expect(subject).to eq([nil, nil])
end
it 'sanitizes unsupported protocols disguised as a repository URL' do
stub_url('javascript:alert("XSS");foo/bar.git')
- expect(helper.submodule_links(submodule_item)).to eq([nil, nil])
+ expect(subject).to eq([nil, nil])
end
it 'sanitizes invalid URL with extended ASCII' do
stub_url('é')
- expect(helper.submodule_links(submodule_item)).to eq([nil, nil])
+ expect(subject).to eq([nil, nil])
end
it 'returns original' do
stub_url('http://mygitserver.com/gitlab-org/gitlab-ce')
- expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
- stub_url('http://mygitserver.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
+ expect(subject).to eq([repo.submodule_url_for, nil])
end
end
@@ -168,8 +162,7 @@ describe SubmoduleHelper do
def expect_relative_link_to_resolve_to(relative_path, expected_path)
allow(repo).to receive(:submodule_url_for).and_return(relative_path)
-
- result = submodule_links(submodule_item)
+ result = subject
expect(result).to eq([expected_path, "#{expected_path}/tree/#{submodule_item.id}"])
end
@@ -190,7 +183,7 @@ describe SubmoduleHelper do
it 'returns nil' do
allow(repo).to receive(:submodule_url_for).and_return('../../test.git')
- result = submodule_links(submodule_item)
+ result = subject
expect(result).to eq([nil, nil])
end
@@ -200,7 +193,7 @@ describe SubmoduleHelper do
it 'returns nil because it is not possible to have repo nested under another repo' do
allow(repo).to receive(:submodule_url_for).and_return('./test.git')
- result = submodule_links(submodule_item)
+ result = subject
expect(result).to eq([nil, nil])
end
@@ -236,6 +229,35 @@ describe SubmoduleHelper do
end
end
end
+
+ context 'unknown submodule' do
+ before do
+ # When there is no `.gitmodules` file, or if `.gitmodules` does not
+ # know the submodule at the specified path,
+ # `Repository#submodule_url_for` returns `nil`
+ stub_url(nil)
+ end
+
+ it 'returns no links' do
+ expect(subject).to eq([nil, nil])
+ end
+ end
+ end
+
+ context 'as view helpers in view context' do
+ subject { helper.submodule_links(submodule_item) }
+
+ before do
+ self.instance_variable_set(:@repository, repo)
+ end
+
+ it_behaves_like 'submodule_links'
+ end
+
+ context 'as stand-alone module' do
+ subject { described_class.submodule_links(submodule_item, nil, repo) }
+
+ it_behaves_like 'submodule_links'
end
def stub_url(url)
diff --git a/spec/helpers/tracking_helper_spec.rb b/spec/helpers/tracking_helper_spec.rb
index 71505e8ea69..b0c98be4130 100644
--- a/spec/helpers/tracking_helper_spec.rb
+++ b/spec/helpers/tracking_helper_spec.rb
@@ -4,8 +4,32 @@ require 'spec_helper'
describe TrackingHelper do
describe '#tracking_attrs' do
- it 'returns an empty hash' do
- expect(helper.tracking_attrs('a', 'b', 'c')).to eq({})
+ using RSpec::Parameterized::TableSyntax
+
+ let(:input) { %w(a b c) }
+ let(:results) do
+ {
+ no_data: {},
+ with_data: { data: { track_label: 'a', track_event: 'b', track_property: 'c' } }
+ }
+ end
+
+ where(:snowplow_enabled, :environment, :result) do
+ true | 'production' | :with_data
+ false | 'production' | :no_data
+ true | 'development' | :no_data
+ false | 'development' | :no_data
+ true | 'test' | :no_data
+ false | 'test' | :no_data
+ end
+
+ with_them do
+ it 'returns a hash' do
+ stub_application_setting(snowplow_enabled: snowplow_enabled)
+ allow(Rails).to receive(:env).and_return(environment.inquiry)
+
+ expect(helper.tracking_attrs(*input)).to eq(results[result])
+ end
end
end
end
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index f3649495493..a6623bc7941 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -27,7 +27,7 @@ describe UsersHelper do
context 'with public profile' do
it 'includes all the expected tabs' do
- expect(tabs).to include(:activity, :groups, :contributed, :projects, :snippets)
+ expect(tabs).to include(:activity, :groups, :contributed, :projects, :starred, :snippets)
end
end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index 25a2fcf5a81..2d276696208 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -137,32 +137,6 @@ describe VisibilityLevelHelper do
end
end
- describe "disallowed_visibility_level_description" do
- let(:group) { create(:group, :internal) }
- let!(:subgroup) { create(:group, :internal, parent: group) }
- let!(:project) { create(:project, :internal, group: group) }
-
- describe "project" do
- it "provides correct description for disabled levels" do
- expect(disallowed_visibility_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
- expect(strip_tags disallowed_visibility_level_description(Gitlab::VisibilityLevel::PUBLIC, project))
- .to include "the visibility of #{project.group.name} is internal"
- end
- end
-
- describe "group" do
- it "provides correct description for disabled levels" do
- expect(disallowed_visibility_level?(group, Gitlab::VisibilityLevel::PRIVATE)).to be_truthy
- expect(disallowed_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, group))
- .to include "it contains projects with higher visibility", "it contains sub-groups with higher visibility"
-
- expect(disallowed_visibility_level?(subgroup, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
- expect(strip_tags disallowed_visibility_level_description(Gitlab::VisibilityLevel::PUBLIC, subgroup))
- .to include "the visibility of #{group.name} is internal"
- end
- end
- end
-
describe "selected_visibility_level" do
let(:group) { create(:group, :public) }
let!(:project) { create(:project, :internal, group: group) }
@@ -207,4 +181,50 @@ describe VisibilityLevelHelper do
end
end
end
+
+ describe 'multiple_visibility_levels_restricted?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:user) { create(:user) }
+
+ subject { helper.multiple_visibility_levels_restricted? }
+
+ where(:restricted_visibility_levels, :expected) do
+ [Gitlab::VisibilityLevel::PUBLIC] | false
+ [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL] | true
+ [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE] | true
+ end
+
+ with_them do
+ before do
+ allow(helper).to receive(:current_user) { user }
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:restricted_visibility_levels) { restricted_visibility_levels }
+ end
+
+ it { is_expected.to eq(expected) }
+ end
+ end
+
+ describe 'all_visibility_levels_restricted?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:user) { create(:user) }
+
+ subject { helper.all_visibility_levels_restricted? }
+
+ where(:restricted_visibility_levels, :expected) do
+ [Gitlab::VisibilityLevel::PUBLIC] | false
+ [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL] | false
+ Gitlab::VisibilityLevel.values | true
+ end
+
+ with_them do
+ before do
+ allow(helper).to receive(:current_user) { user }
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:restricted_visibility_levels) { restricted_visibility_levels }
+ end
+
+ it { is_expected.to eq(expected) }
+ end
+ end
end
diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb
index 8eab40aeaf3..ee977e37ec1 100644
--- a/spec/helpers/wiki_helper_spec.rb
+++ b/spec/helpers/wiki_helper_spec.rb
@@ -22,7 +22,7 @@ describe WikiHelper do
describe '#wiki_sort_controls' do
let(:project) { create(:project) }
let(:wiki_link) { helper.wiki_sort_controls(project, sort, direction) }
- let(:classes) { "btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort" }
+ let(:classes) { "btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort" }
def expected_link(sort, direction, icon_class)
path = "/#{project.full_path}/wikis/pages?direction=#{direction}&sort=#{sort}"
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
index f96e5a2133f..73fbd4c7a44 100644
--- a/spec/initializers/6_validations_spec.rb
+++ b/spec/initializers/6_validations_spec.rb
@@ -2,16 +2,6 @@ require 'spec_helper'
require_relative '../../config/initializers/6_validations.rb'
describe '6_validations' do
- before :all do
- FileUtils.mkdir_p('tmp/tests/paths/a/b/c/d')
- FileUtils.mkdir_p('tmp/tests/paths/a/b/c2')
- FileUtils.mkdir_p('tmp/tests/paths/a/b/d')
- end
-
- after :all do
- FileUtils.rm_rf('tmp/tests/paths')
- end
-
describe 'validate_storages_config' do
context 'with correct settings' do
before do
@@ -23,16 +13,6 @@ describe '6_validations' do
end
end
- context 'when one of the settings is incorrect' do
- before do
- mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c', 'failure_count_threshold' => 'not a number'))
- end
-
- it 'throws an error' do
- expect { validate_storages_config }.to raise_error(/failure_count_threshold/)
- end
- end
-
context 'with invalid storage names' do
before do
mock_storages('name with spaces' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c'))
diff --git a/spec/initializers/action_mailer_hooks_spec.rb b/spec/initializers/action_mailer_hooks_spec.rb
new file mode 100644
index 00000000000..3826ed9b00a
--- /dev/null
+++ b/spec/initializers/action_mailer_hooks_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe 'ActionMailer hooks' do
+ describe 'smime signature interceptor' do
+ before do
+ class_spy(ActionMailer::Base).as_stubbed_const
+ end
+
+ it 'is disabled by default' do
+ load Rails.root.join('config/initializers/action_mailer_hooks.rb')
+
+ expect(ActionMailer::Base).not_to(
+ have_received(:register_interceptor).with(Gitlab::Email::Hook::SmimeSignatureInterceptor))
+ end
+
+ describe 'interceptor testbed' do
+ where(:email_enabled, :email_smime_enabled, :smime_interceptor_enabled) do
+ [
+ [false, false, false],
+ [false, true, false],
+ [true, false, false],
+ [true, true, true]
+ ]
+ end
+
+ with_them do
+ before do
+ stub_config_setting(email_enabled: email_enabled)
+ stub_config_setting(email_smime: { enabled: email_smime_enabled })
+ end
+
+ it 'is enabled depending on settings' do
+ load Rails.root.join('config/initializers/action_mailer_hooks.rb')
+
+ if smime_interceptor_enabled
+ expect(ActionMailer::Base).to(
+ have_received(:register_interceptor).with(Gitlab::Email::Hook::SmimeSignatureInterceptor))
+ else
+ expect(ActionMailer::Base).not_to(
+ have_received(:register_interceptor).with(Gitlab::Email::Hook::SmimeSignatureInterceptor))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/javascripts/badges/components/badge_list_spec.js b/spec/javascripts/badges/components/badge_list_spec.js
index 2f72c9ed89d..2fa807657de 100644
--- a/spec/javascripts/badges/components/badge_list_spec.js
+++ b/spec/javascripts/badges/components/badge_list_spec.js
@@ -60,7 +60,7 @@ describe('BadgeList component', () => {
Vue.nextTick()
.then(() => {
- const loadingIcon = vm.$el.querySelector('.spinner');
+ const loadingIcon = vm.$el.querySelector('.gl-spinner');
expect(loadingIcon).toBeVisible();
})
diff --git a/spec/javascripts/badges/components/badge_spec.js b/spec/javascripts/badges/components/badge_spec.js
index 4e4d1ae2e99..c82a03a628a 100644
--- a/spec/javascripts/badges/components/badge_spec.js
+++ b/spec/javascripts/badges/components/badge_spec.js
@@ -15,7 +15,7 @@ describe('Badge component', () => {
const buttons = vm.$el.querySelectorAll('button');
return {
badgeImage: vm.$el.querySelector('img.project-badge'),
- loadingIcon: vm.$el.querySelector('.spinner'),
+ loadingIcon: vm.$el.querySelector('.gl-spinner'),
reloadButton: buttons[buttons.length - 1],
};
};
diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js
index 8e2a947b0dd..b64859ec32a 100644
--- a/spec/javascripts/boards/board_blank_state_spec.js
+++ b/spec/javascripts/boards/board_blank_state_spec.js
@@ -1,7 +1,6 @@
import Vue from 'vue';
import boardsStore from '~/boards/stores/boards_store';
import BoardBlankState from '~/boards/components/board_blank_state.vue';
-import { mockBoardService } from './mock_data';
describe('Boards blank state', () => {
let vm;
@@ -11,9 +10,10 @@ describe('Boards blank state', () => {
const Comp = Vue.extend(BoardBlankState);
boardsStore.create();
- gl.boardService = mockBoardService();
- spyOn(gl.boardService, 'generateDefaultLists').and.callFake(
+ spyOn(boardsStore, 'addList').and.stub();
+ spyOn(boardsStore, 'removeList').and.stub();
+ spyOn(boardsStore, 'generateDefaultLists').and.callFake(
() =>
new Promise((resolve, reject) => {
if (fail) {
@@ -71,9 +71,14 @@ describe('Boards blank state', () => {
vm.$el.querySelector('.btn-success').click();
setTimeout(() => {
- expect(boardsStore.state.lists.length).toBe(2);
- expect(boardsStore.state.lists[0].title).toEqual('To Do');
- expect(boardsStore.state.lists[1].title).toEqual('Doing');
+ expect(boardsStore.addList).toHaveBeenCalledTimes(2);
+ expect(boardsStore.addList).toHaveBeenCalledWith(
+ jasmine.objectContaining({ title: 'To Do' }),
+ );
+
+ expect(boardsStore.addList).toHaveBeenCalledWith(
+ jasmine.objectContaining({ title: 'Doing' }),
+ );
done();
});
@@ -86,7 +91,7 @@ describe('Boards blank state', () => {
setTimeout(() => {
expect(boardsStore.welcomeIsHidden()).toBeFalsy();
- expect(boardsStore.state.lists.length).toBe(1);
+ expect(boardsStore.removeList).toHaveBeenCalledWith(undefined, 'label');
done();
});
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 9c9b435d7fd..6774a46ed58 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -148,7 +148,7 @@ describe('Board list component', () => {
component.list.loadingMore = true;
Vue.nextTick(() => {
- expect(component.$el.querySelector('.board-list-count .spinner')).not.toBeNull();
+ expect(component.$el.querySelector('.board-list-count .gl-spinner')).not.toBeNull();
done();
});
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 5266b1bdbfc..36bd7ada4f0 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -365,4 +365,17 @@ describe('Store', () => {
expect(boardsStore.timeTracking.limitToHours).toEqual(true);
});
});
+
+ describe('setCurrentBoard', () => {
+ const dummyBoard = 'hoverboard';
+
+ it('sets the current board', () => {
+ const { state } = boardsStore;
+ state.currentBoard = null;
+
+ boardsStore.setCurrentBoard(dummyBoard);
+
+ expect(state.currentBoard).toEqual(dummyBoard);
+ });
+ });
});
diff --git a/spec/javascripts/boards/components/board_form_spec.js b/spec/javascripts/boards/components/board_form_spec.js
new file mode 100644
index 00000000000..e9014156a98
--- /dev/null
+++ b/spec/javascripts/boards/components/board_form_spec.js
@@ -0,0 +1,56 @@
+import $ from 'jquery';
+import Vue from 'vue';
+import boardsStore from '~/boards/stores/boards_store';
+import boardForm from '~/boards/components/board_form.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('board_form.vue', () => {
+ const props = {
+ canAdminBoard: false,
+ labelsPath: `${gl.TEST_HOST}/labels/path`,
+ milestonePath: `${gl.TEST_HOST}/milestone/path`,
+ };
+ let vm;
+
+ beforeEach(() => {
+ spyOn($, 'ajax');
+ boardsStore.state.currentPage = 'edit';
+ const Component = Vue.extend(boardForm);
+ vm = mountComponent(Component, props);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('methods', () => {
+ describe('cancel', () => {
+ it('resets currentPage', done => {
+ vm.cancel();
+
+ Vue.nextTick()
+ .then(() => {
+ expect(boardsStore.state.currentPage).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('buttons', () => {
+ it('cancel button triggers cancel()', done => {
+ spyOn(vm, 'cancel');
+
+ Vue.nextTick()
+ .then(() => {
+ const cancelButton = vm.$el.querySelector('button[data-dismiss="modal"]');
+ cancelButton.click();
+
+ expect(vm.cancel).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js
index d08ee41802b..683783334c6 100644
--- a/spec/javascripts/boards/components/board_spec.js
+++ b/spec/javascripts/boards/components/board_spec.js
@@ -1,7 +1,6 @@
import Vue from 'vue';
-import '~/boards/services/board_service';
import Board from '~/boards/components/board';
-import '~/boards/models/list';
+import List from '~/boards/models/list';
import { mockBoardService } from '../mock_data';
describe('Board component', () => {
@@ -27,7 +26,6 @@ describe('Board component', () => {
disabled: false,
issueLinkBase: '/',
rootPath: '/',
- // eslint-disable-next-line no-undef
list: new List({
id: 1,
position: 0,
@@ -53,57 +51,62 @@ describe('Board component', () => {
expect(vm.$el.classList.contains('is-expandable')).toBe(true);
});
- it('board is expandable when list type is closed', done => {
- vm.list.type = 'closed';
-
- Vue.nextTick(() => {
- expect(vm.$el.classList.contains('is-expandable')).toBe(true);
-
- done();
- });
+ it('board is expandable when list type is closed', () => {
+ expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true);
});
- it('board is not expandable when list type is label', done => {
- vm.list.type = 'label';
- vm.list.isExpandable = false;
-
- Vue.nextTick(() => {
- expect(vm.$el.classList.contains('is-expandable')).toBe(false);
+ it('board is expandable when list type is label', () => {
+ expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true);
+ });
- done();
- });
+ it('board is not expandable when list type is blank', () => {
+ expect(new List({ id: 1, list_type: 'blank' }).isExpandable).toBe(false);
});
- it('collapses when clicking header', done => {
+ it('does not collapse when clicking header', done => {
+ vm.list.isExpanded = true;
vm.$el.querySelector('.board-header').click();
Vue.nextTick(() => {
- expect(vm.$el.classList.contains('is-collapsed')).toBe(true);
+ expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
done();
});
});
- it('created sets isExpanded to true from localStorage', done => {
- vm.$el.querySelector('.board-header').click();
+ it('collapses when clicking the collapse icon', done => {
+ vm.list.isExpanded = true;
- return Vue.nextTick()
+ Vue.nextTick()
+ .then(() => {
+ vm.$el.querySelector('.board-title-caret').click();
+ })
.then(() => {
expect(vm.$el.classList.contains('is-collapsed')).toBe(true);
+ done();
+ })
+ .catch(done.fail);
+ });
- // call created manually
- vm.$options.created[0].call(vm);
+ it('expands when clicking the expand icon', done => {
+ vm.list.isExpanded = false;
- return Vue.nextTick();
+ Vue.nextTick()
+ .then(() => {
+ vm.$el.querySelector('.board-title-caret').click();
})
.then(() => {
- expect(vm.$el.classList.contains('is-collapsed')).toBe(true);
-
+ expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
done();
})
.catch(done.fail);
});
+ it('is expanded when created', () => {
+ expect(vm.list.isExpanded).toBe(true);
+ expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
+ });
+
it('does render add issue button', () => {
expect(vm.$el.querySelector('.issue-count-badge-add-button')).not.toBeNull();
});
diff --git a/spec/javascripts/boards/components/boards_selector_spec.js b/spec/javascripts/boards/components/boards_selector_spec.js
new file mode 100644
index 00000000000..473cc0612ea
--- /dev/null
+++ b/spec/javascripts/boards/components/boards_selector_spec.js
@@ -0,0 +1,206 @@
+import Vue from 'vue';
+import BoardService from '~/boards/services/board_service';
+import BoardsSelector from '~/boards/components/boards_selector.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { TEST_HOST } from 'spec/test_constants';
+import boardsStore from '~/boards/stores/boards_store';
+
+const throttleDuration = 1;
+
+function boardGenerator(n) {
+ return new Array(n).fill().map((board, id) => {
+ const name = `board${id}`;
+
+ return {
+ id,
+ name,
+ };
+ });
+}
+
+describe('BoardsSelector', () => {
+ let vm;
+ let allBoardsResponse;
+ let recentBoardsResponse;
+ let fillSearchBox;
+ const boards = boardGenerator(20);
+ const recentBoards = boardGenerator(5);
+
+ beforeEach(done => {
+ setFixtures('<div class="js-boards-selector"></div>');
+ window.gl = window.gl || {};
+
+ boardsStore.setEndpoints({
+ boardsEndpoint: '',
+ recentBoardsEndpoint: '',
+ listsEndpoint: '',
+ bulkUpdatePath: '',
+ boardId: '',
+ });
+ window.gl.boardService = new BoardService();
+
+ allBoardsResponse = Promise.resolve({
+ data: boards,
+ });
+ recentBoardsResponse = Promise.resolve({
+ data: recentBoards,
+ });
+
+ spyOn(BoardService.prototype, 'allBoards').and.returnValue(allBoardsResponse);
+ spyOn(BoardService.prototype, 'recentBoards').and.returnValue(recentBoardsResponse);
+
+ const Component = Vue.extend(BoardsSelector);
+ vm = mountComponent(
+ Component,
+ {
+ throttleDuration,
+ currentBoard: {
+ id: 1,
+ name: 'Development',
+ milestone_id: null,
+ weight: null,
+ assignee_id: null,
+ labels: [],
+ },
+ milestonePath: `${TEST_HOST}/milestone/path`,
+ boardBaseUrl: `${TEST_HOST}/board/base/url`,
+ hasMissingBoards: false,
+ canAdminBoard: true,
+ multipleIssueBoardsAvailable: true,
+ labelsPath: `${TEST_HOST}/labels/path`,
+ projectId: 42,
+ groupId: 19,
+ scopedIssueBoardFeatureEnabled: true,
+ weights: [],
+ },
+ document.querySelector('.js-boards-selector'),
+ );
+
+ // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
+ vm.$children[0].$emit('show');
+
+ Promise.all([allBoardsResponse, recentBoardsResponse])
+ .then(() => vm.$nextTick())
+ .then(done)
+ .catch(done.fail);
+
+ fillSearchBox = filterTerm => {
+ const { searchBox } = vm.$refs;
+ const searchBoxInput = searchBox.$el.querySelector('input');
+ searchBoxInput.value = filterTerm;
+ searchBoxInput.dispatchEvent(new Event('input'));
+ };
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ window.gl.boardService = undefined;
+ });
+
+ describe('filtering', () => {
+ it('shows all boards without filtering', done => {
+ vm.$nextTick()
+ .then(() => {
+ const dropdownItem = vm.$el.querySelectorAll('.js-dropdown-item');
+
+ expect(dropdownItem.length).toBe(boards.length + recentBoards.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('shows only matching boards when filtering', done => {
+ const filterTerm = 'board1';
+ const expectedCount = boards.filter(board => board.name.includes(filterTerm)).length;
+
+ fillSearchBox(filterTerm);
+
+ vm.$nextTick()
+ .then(() => {
+ const dropdownItems = vm.$el.querySelectorAll('.js-dropdown-item');
+
+ expect(dropdownItems.length).toBe(expectedCount);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('shows message if there are no matching boards', done => {
+ fillSearchBox('does not exist');
+
+ vm.$nextTick()
+ .then(() => {
+ const dropdownItems = vm.$el.querySelectorAll('.js-dropdown-item');
+
+ expect(dropdownItems.length).toBe(0);
+ expect(vm.$el).toContainText('No matching boards found');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('recent boards section', () => {
+ it('shows only when boards are greater than 10', done => {
+ vm.$nextTick()
+ .then(() => {
+ const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header');
+
+ const expectedCount = 2; // Recent + All
+
+ expect(expectedCount).toBe(headerEls.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not show when boards are less than 10', done => {
+ spyOn(vm, 'initScrollFade');
+ spyOn(vm, 'setScrollFade');
+
+ vm.$nextTick()
+ .then(() => {
+ vm.boards = vm.boards.slice(0, 5);
+ })
+ .then(vm.$nextTick)
+ .then(() => {
+ const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header');
+ const expectedCount = 0;
+
+ expect(expectedCount).toBe(headerEls.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not show when recentBoards api returns empty array', done => {
+ vm.$nextTick()
+ .then(() => {
+ vm.recentBoards = [];
+ })
+ .then(vm.$nextTick)
+ .then(() => {
+ const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header');
+ const expectedCount = 0;
+
+ expect(expectedCount).toBe(headerEls.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not show when search is active', done => {
+ fillSearchBox('Random string');
+
+ vm.$nextTick()
+ .then(() => {
+ const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header');
+ const expectedCount = 0;
+
+ expect(expectedCount).toBe(headerEls.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index 9854cf49e97..ea22ae5c4e7 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -1,4 +1,5 @@
import BoardService from '~/boards/services/board_service';
+import boardsStore from '~/boards/stores/boards_store';
export const boardObj = {
id: 1,
@@ -76,12 +77,14 @@ export const mockBoardService = (opts = {}) => {
const bulkUpdatePath = opts.bulkUpdatePath || '';
const boardId = opts.boardId || '1';
- return new BoardService({
+ boardsStore.setEndpoints({
boardsEndpoint,
listsEndpoint,
bulkUpdatePath,
boardId,
});
+
+ return new BoardService();
};
export const mockAssigneesList = [
diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
index e6a969bd855..b2fe315f6c6 100644
--- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
@@ -224,7 +224,7 @@ describe('AjaxFormVariableList', () => {
describe('maskableRegex', () => {
it('takes in the regex provided by the data attribute', () => {
- expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
+ expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/@:-]{8,}$');
expect(ajaxVariableList.maskableRegex).toBe(container.dataset.maskableRegex);
});
});
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index 064113e879a..c8d6f789ed0 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -162,7 +162,7 @@ describe('VariableList', () => {
});
it('has a regex provided via a data attribute', () => {
- expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
+ expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/@:-]{8,}$');
});
it('allows values that are 8 characters long', done => {
diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js
index bb90e53e525..f75d63c8f57 100644
--- a/spec/javascripts/collapsed_sidebar_todo_spec.js
+++ b/spec/javascripts/collapsed_sidebar_todo_spec.js
@@ -58,7 +58,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it('sets default tooltip title', () => {
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('title'),
- ).toBe('Add todo');
+ ).toBe('Add a To Do');
});
it('toggle todo state', done => {
@@ -85,7 +85,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
setTimeout(() => {
expect(
document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(),
- ).toBe('Mark todo as done');
+ ).toBe('Mark as done');
done();
});
@@ -99,7 +99,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
.getAttribute('data-original-title'),
- ).toBe('Mark todo as done');
+ ).toBe('Mark as done');
done();
});
@@ -124,13 +124,13 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
expect(
document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(),
- ).toBe('Add todo');
+ ).toBe('Add a To Do');
})
.then(done)
.catch(done.fail);
});
- it('updates aria-label to mark todo as done', done => {
+ it('updates aria-label to Mark as done', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
setTimeout(() => {
@@ -138,7 +138,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
.getAttribute('aria-label'),
- ).toBe('Mark todo as done');
+ ).toBe('Mark as done');
done();
});
@@ -153,7 +153,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
.getAttribute('aria-label'),
- ).toBe('Mark todo as done');
+ ).toBe('Mark as done');
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
})
@@ -163,7 +163,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
.getAttribute('aria-label'),
- ).toBe('Add todo');
+ ).toBe('Add a To Do');
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js b/spec/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js
index fdecb823cd2..7aa7aa9a112 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js
@@ -1,12 +1,12 @@
import Vue from 'vue';
-import GkeMachineTypeDropdown from '~/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue';
-import { createStore } from '~/projects/gke_cluster_dropdowns/store';
+import GkeMachineTypeDropdown from '~/create_cluster/gke_cluster/components/gke_machine_type_dropdown.vue';
+import { createStore } from '~/create_cluster/gke_cluster/store';
import {
SET_PROJECT,
SET_PROJECT_BILLING_STATUS,
SET_ZONE,
SET_MACHINE_TYPES,
-} from '~/projects/gke_cluster_dropdowns/store/mutation_types';
+} from '~/create_cluster/gke_cluster/store/mutation_types';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import {
selectedZoneMock,
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js b/spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js
index 1eb7cb4bd5b..809da3f9088 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
-import GkeProjectIdDropdown from '~/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue';
-import { createStore } from '~/projects/gke_cluster_dropdowns/store';
-import { SET_PROJECTS } from '~/projects/gke_cluster_dropdowns/store/mutation_types';
+import GkeProjectIdDropdown from '~/create_cluster/gke_cluster/components/gke_project_id_dropdown.vue';
+import { createStore } from '~/create_cluster/gke_cluster/store';
+import { SET_PROJECTS } from '~/create_cluster/gke_cluster/store/mutation_types';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { emptyProjectMock, selectedProjectMock } from '../mock_data';
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js b/spec/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js
index 95186e19ca1..9cb9419e433 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js
@@ -1,11 +1,11 @@
import Vue from 'vue';
-import GkeZoneDropdown from '~/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue';
-import { createStore } from '~/projects/gke_cluster_dropdowns/store';
+import GkeZoneDropdown from '~/create_cluster/gke_cluster/components/gke_zone_dropdown.vue';
+import { createStore } from '~/create_cluster/gke_cluster/store';
import {
SET_PROJECT,
SET_ZONES,
SET_PROJECT_BILLING_STATUS,
-} from '~/projects/gke_cluster_dropdowns/store/mutation_types';
+} from '~/create_cluster/gke_cluster/store/mutation_types';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { selectedZoneMock, selectedProjectMock, gapiZonesResponseMock } from '../mock_data';
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/helpers.js b/spec/javascripts/create_cluster/gke_cluster/helpers.js
index 6df511e9157..6df511e9157 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/helpers.js
+++ b/spec/javascripts/create_cluster/gke_cluster/helpers.js
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/mock_data.js b/spec/javascripts/create_cluster/gke_cluster/mock_data.js
index d9f5dbc636f..d9f5dbc636f 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/mock_data.js
+++ b/spec/javascripts/create_cluster/gke_cluster/mock_data.js
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js b/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js
index 9d892b8185b..a7591cc38c7 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js
@@ -1,6 +1,6 @@
import testAction from 'spec/helpers/vuex_action_helper';
-import * as actions from '~/projects/gke_cluster_dropdowns/store/actions';
-import { createStore } from '~/projects/gke_cluster_dropdowns/store';
+import * as actions from '~/create_cluster/gke_cluster/store/actions';
+import { createStore } from '~/create_cluster/gke_cluster/store';
import { gapi } from '../helpers';
import { selectedProjectMock, selectedZoneMock, selectedMachineTypeMock } from '../mock_data';
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/stores/getters_spec.js b/spec/javascripts/create_cluster/gke_cluster/stores/getters_spec.js
index 6f89158f807..ac92716b0ab 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/stores/getters_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/stores/getters_spec.js
@@ -1,4 +1,4 @@
-import * as getters from '~/projects/gke_cluster_dropdowns/store/getters';
+import * as getters from '~/create_cluster/gke_cluster/store/getters';
import { selectedProjectMock, selectedZoneMock, selectedMachineTypeMock } from '../mock_data';
describe('GCP Cluster Dropdown Store Getters', () => {
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/stores/mutations_spec.js b/spec/javascripts/create_cluster/gke_cluster/stores/mutations_spec.js
index 7f8c4f314e4..7ee6ff436e2 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/stores/mutations_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/stores/mutations_spec.js
@@ -1,5 +1,5 @@
-import { createStore } from '~/projects/gke_cluster_dropdowns/store';
-import * as types from '~/projects/gke_cluster_dropdowns/store/mutation_types';
+import { createStore } from '~/create_cluster/gke_cluster/store';
+import * as types from '~/create_cluster/gke_cluster/store/mutation_types';
import {
selectedProjectMock,
selectedZoneMock,
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
index 77f8352047c..ef4bb470734 100644
--- a/spec/javascripts/diffs/components/compare_versions_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import CompareVersionsComponent from '~/diffs/components/compare_versions.vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffsMockData from '../mock_data/merge_request_diffs';
import getDiffWithCommit from '../mock_data/diff_with_commit';
@@ -10,6 +10,8 @@ describe('CompareVersions', () => {
const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
beforeEach(() => {
+ const store = createStore();
+
store.state.diffs.addedLines = 10;
store.state.diffs.removedLines = 20;
store.state.diffs.diffFiles.push('test');
diff --git a/spec/javascripts/diffs/components/diff_expansion_cell_spec.js b/spec/javascripts/diffs/components/diff_expansion_cell_spec.js
new file mode 100644
index 00000000000..63c50c09fce
--- /dev/null
+++ b/spec/javascripts/diffs/components/diff_expansion_cell_spec.js
@@ -0,0 +1,64 @@
+import Vue from 'vue';
+import { createStore } from '~/mr_notes/stores';
+import DiffExpansionCell from '~/diffs/components/diff_expansion_cell.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+
+const EXPAND_UP_CLASS = '.js-unfold';
+const EXPAND_DOWN_CLASS = '.js-unfold-down';
+const EXPAND_ALL_CLASS = '.js-unfold-all';
+
+describe('DiffExpansionCell', () => {
+ const matchLine = diffFileMockData.highlighted_diff_lines[5];
+
+ const createComponent = (options = {}) => {
+ const cmp = Vue.extend(DiffExpansionCell);
+ const defaults = {
+ fileHash: diffFileMockData.file_hash,
+ contextLinesPath: 'contextLinesPath',
+ line: matchLine,
+ isTop: false,
+ isBottom: false,
+ };
+ const props = Object.assign({}, defaults, options);
+
+ return createComponentWithStore(cmp, createStore(), props).$mount();
+ };
+
+ describe('top row', () => {
+ it('should have "expand up" and "show all" option', () => {
+ const vm = createComponent({
+ isTop: true,
+ });
+ const el = vm.$el;
+
+ expect(el.querySelector(EXPAND_UP_CLASS)).not.toBe(null);
+ expect(el.querySelector(EXPAND_DOWN_CLASS)).toBe(null);
+ expect(el.querySelector(EXPAND_ALL_CLASS)).not.toBe(null);
+ });
+ });
+
+ describe('middle row', () => {
+ it('should have "expand down", "show all", "expand up" option', () => {
+ const vm = createComponent();
+ const el = vm.$el;
+
+ expect(el.querySelector(EXPAND_UP_CLASS)).not.toBe(null);
+ expect(el.querySelector(EXPAND_DOWN_CLASS)).not.toBe(null);
+ expect(el.querySelector(EXPAND_ALL_CLASS)).not.toBe(null);
+ });
+ });
+
+ describe('bottom row', () => {
+ it('should have "expand down" and "show all" option', () => {
+ const vm = createComponent({
+ isBottom: true,
+ });
+ const el = vm.$el;
+
+ expect(el.querySelector(EXPAND_UP_CLASS)).toBe(null);
+ expect(el.querySelector(EXPAND_DOWN_CLASS)).not.toBe(null);
+ expect(el.querySelector(EXPAND_ALL_CLASS)).not.toBe(null);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
index 596a1ba5ad2..356e7a8f1fe 100644
--- a/spec/javascripts/diffs/components/diff_file_header_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -372,7 +372,7 @@ describe('diff_file_header', () => {
});
it('displays old and new path if the file was renamed', () => {
- props.diffFile.viewer.name = diffViewerModes.renamed;
+ props.diffFile.renamed_file = true;
vm = mountComponentWithStore(Component, { props, store });
@@ -521,7 +521,7 @@ describe('diff_file_header', () => {
});
describe('with discussions', () => {
- it('dispatches toggleFileDiscussions when user clicks on toggle discussions button', () => {
+ it('dispatches toggleFileDiscussionWrappers when user clicks on toggle discussions button', () => {
const propsCopy = Object.assign({}, props);
propsCopy.diffFile.submodule = false;
propsCopy.diffFile.blob = {
@@ -552,11 +552,11 @@ describe('diff_file_header', () => {
}),
});
- spyOn(vm, 'toggleFileDiscussions');
+ spyOn(vm, 'toggleFileDiscussionWrappers');
vm.$el.querySelector('.js-btn-vue-toggle-comments').click();
- expect(vm.toggleFileDiscussions).toHaveBeenCalled();
+ expect(vm.toggleFileDiscussionWrappers).toHaveBeenCalled();
});
});
});
diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js
index ef4589ada48..3ca2d1dc934 100644
--- a/spec/javascripts/diffs/components/diff_file_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import DiffFileComponent from '~/diffs/components/diff_file.vue';
import { diffViewerModes, diffViewerErrors } from '~/ide/constants';
-import store from 'ee_else_ce/mr_notes/stores';
+import { createStore } from 'ee_else_ce/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
@@ -9,14 +9,18 @@ describe('DiffFile', () => {
let vm;
beforeEach(() => {
- vm = createComponentWithStore(Vue.extend(DiffFileComponent), store, {
+ vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), {
file: JSON.parse(JSON.stringify(diffFileMockData)),
canCurrentUserFork: false,
}).$mount();
});
+ afterEach(() => {
+ vm.$destroy();
+ });
+
describe('template', () => {
- it('should render component with file header, file content components', () => {
+ it('should render component with file header, file content components', done => {
const el = vm.$el;
const { file_hash, file_path } = vm.file;
@@ -30,9 +34,13 @@ describe('DiffFile', () => {
vm.file.renderIt = true;
- vm.$nextTick(() => {
- expect(el.querySelectorAll('.line_content').length).toBeGreaterThan(5);
- });
+ vm.$nextTick()
+ .then(() => {
+ expect(el.querySelectorAll('.line_content').length).toBe(5);
+ expect(el.querySelectorAll('.js-line-expansion-content').length).toBe(1);
+ })
+ .then(done)
+ .catch(done.fail);
});
describe('collapsed', () => {
diff --git a/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js b/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js
deleted file mode 100644
index cdd30919b09..00000000000
--- a/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js
+++ /dev/null
@@ -1,146 +0,0 @@
-import Vue from 'vue';
-import DiffGutterAvatarsComponent from '~/diffs/components/diff_gutter_avatars.vue';
-import { COUNT_OF_AVATARS_IN_GUTTER } from '~/diffs/constants';
-import store from '~/mr_notes/stores';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import discussionsMockData from '../mock_data/diff_discussions';
-
-describe('DiffGutterAvatars', () => {
- let component;
- const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
-
- beforeEach(() => {
- component = createComponentWithStore(Vue.extend(DiffGutterAvatarsComponent), store, {
- discussions: getDiscussionsMockData(),
- }).$mount();
- });
-
- describe('computed', () => {
- describe('discussionsExpanded', () => {
- it('should return true when all discussions are expanded', () => {
- expect(component.discussionsExpanded).toEqual(true);
- });
-
- it('should return false when all discussions are not expanded', () => {
- component.discussions[0].expanded = false;
-
- expect(component.discussionsExpanded).toEqual(false);
- });
- });
-
- describe('allDiscussions', () => {
- it('should return an array of notes', () => {
- expect(component.allDiscussions).toEqual([...component.discussions[0].notes]);
- });
- });
-
- describe('notesInGutter', () => {
- it('should return a subset of discussions to show in gutter', () => {
- expect(component.notesInGutter.length).toEqual(COUNT_OF_AVATARS_IN_GUTTER);
- expect(component.notesInGutter[0]).toEqual({
- note: component.discussions[0].notes[0].note,
- author: component.discussions[0].notes[0].author,
- });
- });
- });
-
- describe('moreCount', () => {
- it('should return count of remaining discussions from gutter', () => {
- expect(component.moreCount).toEqual(2);
- });
- });
-
- describe('moreText', () => {
- it('should return proper text if moreCount > 0', () => {
- expect(component.moreText).toEqual('2 more comments');
- });
-
- it('should return empty string if there is no discussion', () => {
- component.discussions = [];
-
- expect(component.moreText).toEqual('');
- });
- });
- });
-
- describe('methods', () => {
- describe('getTooltipText', () => {
- it('should return original comment if it is shorter than max length', () => {
- const note = component.discussions[0].notes[0];
-
- expect(component.getTooltipText(note)).toEqual('Administrator: comment 1');
- });
-
- it('should return truncated version of comment', () => {
- const note = component.discussions[0].notes[1];
-
- expect(component.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is r...');
- });
- });
-
- describe('toggleDiscussions', () => {
- it('should toggle all discussions', () => {
- expect(component.discussions[0].expanded).toEqual(true);
-
- component.$store.dispatch('setInitialNotes', getDiscussionsMockData());
- component.discussions = component.$store.state.notes.discussions;
- component.toggleDiscussions();
-
- expect(component.discussions[0].expanded).toEqual(false);
- component.$store.dispatch('setInitialNotes', []);
- });
-
- it('forces expansion of all discussions', () => {
- spyOn(component.$store, 'dispatch');
-
- component.discussions[0].expanded = true;
- component.discussions.push({
- ...component.discussions[0],
- id: '123test',
- expanded: false,
- });
-
- component.toggleDiscussions();
-
- expect(component.$store.dispatch.calls.argsFor(0)).toEqual([
- 'toggleDiscussion',
- {
- discussionId: component.discussions[0].id,
- forceExpanded: true,
- },
- ]);
-
- expect(component.$store.dispatch.calls.argsFor(1)).toEqual([
- 'toggleDiscussion',
- {
- discussionId: component.discussions[1].id,
- forceExpanded: true,
- },
- ]);
- });
- });
- });
-
- describe('template', () => {
- const buttonSelector = '.js-diff-comment-button';
- const svgSelector = `${buttonSelector} svg`;
- const avatarSelector = '.js-diff-comment-avatar';
- const plusCountSelector = '.js-diff-comment-plus';
-
- it('should have button to collapse discussions when the discussions expanded', () => {
- expect(component.$el.querySelector(buttonSelector)).toBeDefined();
- expect(component.$el.querySelector(svgSelector)).toBeDefined();
- });
-
- it('should have user avatars when discussions collapsed', () => {
- component.discussions[0].expanded = false;
-
- Vue.nextTick(() => {
- expect(component.$el.querySelector(buttonSelector)).toBeNull();
- expect(component.$el.querySelectorAll(avatarSelector).length).toEqual(4);
- expect(component.$el.querySelector(plusCountSelector)).toBeDefined();
- expect(component.$el.querySelector(plusCountSelector).textContent).toEqual('+2');
- });
- });
- });
-});
diff --git a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
index 038db8eaa7c..6bb704658fb 100644
--- a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
+++ b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import DiffLineGutterContent from '~/diffs/components/diff_line_gutter_content.vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import discussionsMockData from '../mock_data/diff_discussions';
import diffFileMockData from '../mock_data/diff_file';
@@ -23,7 +23,7 @@ describe('DiffLineGutterContent', () => {
props.fileHash = getDiffFileMock().file_hash;
props.contextLinesPath = '/context/lines/path';
- return createComponentWithStore(cmp, store, props).$mount();
+ return createComponentWithStore(cmp, createStore(), props).$mount();
};
describe('computed', () => {
@@ -61,7 +61,7 @@ describe('DiffLineGutterContent', () => {
contextLinesPath: '/context/lines/path',
};
props.line.discussions = [Object.assign({}, discussionsMockData)];
- const component = createComponentWithStore(cmp, store, props).$mount();
+ const component = createComponentWithStore(cmp, createStore(), props).$mount();
expect(component.hasDiscussions).toEqual(true);
expect(component.shouldShowAvatarsOnGutter).toEqual(true);
@@ -70,15 +70,6 @@ describe('DiffLineGutterContent', () => {
});
describe('template', () => {
- it('should render three dots for context lines', () => {
- const component = createComponent({
- isMatchLine: true,
- });
-
- expect(component.$el.querySelector('span').classList.contains('context-cell')).toEqual(true);
- expect(component.$el.innerText).toEqual('...');
- });
-
it('should render comment button', () => {
const component = createComponent({
showCommentButton: true,
diff --git a/spec/javascripts/diffs/components/diff_line_note_form_spec.js b/spec/javascripts/diffs/components/diff_line_note_form_spec.js
index b983dc35a57..237cfccfa29 100644
--- a/spec/javascripts/diffs/components/diff_line_note_form_spec.js
+++ b/spec/javascripts/diffs/components/diff_line_note_form_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import DiffLineNoteForm from '~/diffs/components/diff_line_note_form.vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
import { noteableDataMock } from '../../notes/mock_data';
@@ -15,7 +15,7 @@ describe('DiffLineNoteForm', () => {
diffFile = getDiffFileMock();
diffLines = diffFile.highlighted_diff_lines;
- component = createComponentWithStore(Vue.extend(DiffLineNoteForm), store, {
+ component = createComponentWithStore(Vue.extend(DiffLineNoteForm), createStore(), {
diffFileHash: diffFile.file_hash,
diffLines,
line: diffLines[0],
diff --git a/spec/javascripts/diffs/components/diff_table_cell_spec.js b/spec/javascripts/diffs/components/diff_table_cell_spec.js
index 170e661beea..a5a042c577c 100644
--- a/spec/javascripts/diffs/components/diff_table_cell_spec.js
+++ b/spec/javascripts/diffs/components/diff_table_cell_spec.js
@@ -1,12 +1,12 @@
import Vue from 'vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import DiffTableCell from '~/diffs/components/diff_table_cell.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
describe('DiffTableCell', () => {
const createComponent = options =>
- createComponentWithStore(Vue.extend(DiffTableCell), store, {
+ createComponentWithStore(Vue.extend(DiffTableCell), createStore(), {
line: diffFileMockData.highlighted_diff_lines[0],
fileHash: diffFileMockData.file_hash,
contextLinesPath: 'contextLinesPath',
diff --git a/spec/javascripts/diffs/components/inline_diff_expansion_row_spec.js b/spec/javascripts/diffs/components/inline_diff_expansion_row_spec.js
new file mode 100644
index 00000000000..290b3d7c803
--- /dev/null
+++ b/spec/javascripts/diffs/components/inline_diff_expansion_row_spec.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import { createStore } from '~/mr_notes/stores';
+import InlineDiffExpansionRow from '~/diffs/components/inline_diff_expansion_row.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+
+describe('InlineDiffExpansionRow', () => {
+ const matchLine = diffFileMockData.highlighted_diff_lines[5];
+
+ const createComponent = (options = {}) => {
+ const cmp = Vue.extend(InlineDiffExpansionRow);
+ const defaults = {
+ fileHash: diffFileMockData.file_hash,
+ contextLinesPath: 'contextLinesPath',
+ line: matchLine,
+ isTop: false,
+ isBottom: false,
+ };
+ const props = Object.assign({}, defaults, options);
+
+ return createComponentWithStore(cmp, createStore(), props).$mount();
+ };
+
+ describe('template', () => {
+ it('should render expansion row for match lines', () => {
+ const vm = createComponent();
+
+ expect(vm.$el.classList.contains('line_expansion')).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/inline_diff_table_row_spec.js b/spec/javascripts/diffs/components/inline_diff_table_row_spec.js
index 97926f6625e..0ddffe926d9 100644
--- a/spec/javascripts/diffs/components/inline_diff_table_row_spec.js
+++ b/spec/javascripts/diffs/components/inline_diff_table_row_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
@@ -9,7 +9,7 @@ describe('InlineDiffTableRow', () => {
const thisLine = diffFileMockData.highlighted_diff_lines[0];
beforeEach(() => {
- vm = createComponentWithStore(Vue.extend(InlineDiffTableRow), store, {
+ vm = createComponentWithStore(Vue.extend(InlineDiffTableRow), createStore(), {
line: thisLine,
fileHash: diffFileMockData.file_hash,
contextLinesPath: 'contextLinesPath',
diff --git a/spec/javascripts/diffs/components/inline_diff_view_spec.js b/spec/javascripts/diffs/components/inline_diff_view_spec.js
index 4452106580a..486d9629e26 100644
--- a/spec/javascripts/diffs/components/inline_diff_view_spec.js
+++ b/spec/javascripts/diffs/components/inline_diff_view_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import '~/behaviors/markdown/render_gfm';
import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
-import store from 'ee_else_ce/mr_notes/stores';
+import { createStore } from 'ee_else_ce/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
import discussionsMockData from '../mock_data/diff_discussions';
@@ -10,10 +10,13 @@ describe('InlineDiffView', () => {
let component;
const getDiffFileMock = () => Object.assign({}, diffFileMockData);
const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
+ const notesLength = getDiscussionsMockData()[0].notes.length;
beforeEach(done => {
const diffFile = getDiffFileMock();
+ const store = createStore();
+
store.dispatch('diffs/setInlineDiffViewType');
component = createComponentWithStore(Vue.extend(InlineDiffView), store, {
diffFile,
@@ -27,19 +30,20 @@ describe('InlineDiffView', () => {
it('should have rendered diff lines', () => {
const el = component.$el;
- expect(el.querySelectorAll('tr.line_holder').length).toEqual(6);
+ expect(el.querySelectorAll('tr.line_holder').length).toEqual(5);
expect(el.querySelectorAll('tr.line_holder.new').length).toEqual(2);
- expect(el.querySelectorAll('tr.line_holder.match').length).toEqual(1);
+ expect(el.querySelectorAll('tr.line_expansion.match').length).toEqual(1);
expect(el.textContent.indexOf('Bad dates')).toBeGreaterThan(-1);
});
it('should render discussions', done => {
const el = component.$el;
component.diffLines[1].discussions = getDiscussionsMockData();
+ component.diffLines[1].discussionsExpanded = true;
Vue.nextTick(() => {
expect(el.querySelectorAll('.notes_holder').length).toEqual(1);
- expect(el.querySelectorAll('.notes_holder .note-discussion li').length).toEqual(5);
+ expect(el.querySelectorAll('.notes_holder .note').length).toEqual(notesLength + 1);
expect(el.innerText.indexOf('comment 5')).toBeGreaterThan(-1);
component.$store.dispatch('setInitialNotes', []);
diff --git a/spec/javascripts/diffs/components/parallel_diff_expansion_row_spec.js b/spec/javascripts/diffs/components/parallel_diff_expansion_row_spec.js
new file mode 100644
index 00000000000..a766ebb5efb
--- /dev/null
+++ b/spec/javascripts/diffs/components/parallel_diff_expansion_row_spec.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import { createStore } from '~/mr_notes/stores';
+import ParallelDiffExpansionRow from '~/diffs/components/parallel_diff_expansion_row.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+
+describe('ParallelDiffExpansionRow', () => {
+ const matchLine = diffFileMockData.highlighted_diff_lines[5];
+
+ const createComponent = (options = {}) => {
+ const cmp = Vue.extend(ParallelDiffExpansionRow);
+ const defaults = {
+ fileHash: diffFileMockData.file_hash,
+ contextLinesPath: 'contextLinesPath',
+ line: matchLine,
+ isTop: false,
+ isBottom: false,
+ };
+ const props = Object.assign({}, defaults, options);
+
+ return createComponentWithStore(cmp, createStore(), props).$mount();
+ };
+
+ describe('template', () => {
+ it('should render expansion row for match lines', () => {
+ const vm = createComponent();
+
+ expect(vm.$el.classList.contains('line_expansion')).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/parallel_diff_view_spec.js b/spec/javascripts/diffs/components/parallel_diff_view_spec.js
index 236bda96145..191313bf487 100644
--- a/spec/javascripts/diffs/components/parallel_diff_view_spec.js
+++ b/spec/javascripts/diffs/components/parallel_diff_view_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue';
-import store from 'ee_else_ce/mr_notes/stores';
+import { createStore } from 'ee_else_ce/mr_notes/stores';
import * as constants from '~/diffs/constants';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
@@ -12,7 +12,7 @@ describe('ParallelDiffView', () => {
beforeEach(() => {
const diffFile = getDiffFileMock();
- component = createComponentWithStore(Vue.extend(ParallelDiffView), store, {
+ component = createComponentWithStore(Vue.extend(ParallelDiffView), createStore(), {
diffFile,
diffLines: diffFile.parallel_diff_lines,
}).$mount();
diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js
index 27428197c1c..531686efff1 100644
--- a/spec/javascripts/diffs/mock_data/diff_file.js
+++ b/spec/javascripts/diffs/mock_data/diff_file.js
@@ -1,3 +1,5 @@
+// Copied to ee/spec/frontend/diffs/mock_data/diff_file.js
+
export default {
submodule: false,
submodule_link: null,
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index f973728cfe1..5806cb47034 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -206,6 +206,7 @@ describe('DiffsStoreActions', () => {
position_type: 'text',
},
},
+ hash: 'diff-content-1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a',
},
},
],
@@ -379,7 +380,9 @@ describe('DiffsStoreActions', () => {
const params = { since: 6, to: 26 };
const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
const fileHash = 'ff9200';
- const options = { endpoint, params, lineNumbers, fileHash };
+ const isExpandDown = false;
+ const nextLineNumbers = {};
+ const options = { endpoint, params, lineNumbers, fileHash, isExpandDown, nextLineNumbers };
const mock = new MockAdapter(axios);
const contextLines = { contextLines: [{ lineCode: 6 }] };
mock.onGet(endpoint).reply(200, contextLines);
@@ -391,7 +394,7 @@ describe('DiffsStoreActions', () => {
[
{
type: types.ADD_CONTEXT_LINES,
- payload: { lineNumbers, contextLines, params, fileHash },
+ payload: { lineNumbers, contextLines, params, fileHash, isExpandDown, nextLineNumbers },
},
],
[],
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 9c13c7ceb7a..3e033b6c9dc 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -81,6 +81,8 @@ describe('DiffsStoreMutations', () => {
params: {
bottom: true,
},
+ isExpandDown: false,
+ nextLineNumbers: {},
};
const diffFile = {
file_hash: options.fileHash,
@@ -108,6 +110,8 @@ describe('DiffsStoreMutations', () => {
options.contextLines,
options.lineNumbers,
options.params.bottom,
+ options.isExpandDown,
+ options.nextLineNumbers,
);
expect(addContextLinesSpy).toHaveBeenCalledWith({
@@ -116,6 +120,7 @@ describe('DiffsStoreMutations', () => {
contextLines: options.contextLines,
bottom: options.params.bottom,
lineNumbers: options.lineNumbers,
+ isExpandDown: false,
});
});
});
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index 1f877910125..65eb4c9d2a3 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -260,6 +260,17 @@ describe('DiffsStoreUtils', () => {
expect(linesWithReferences[1].meta_data.old_pos).toEqual(2);
expect(linesWithReferences[1].meta_data.new_pos).toEqual(3);
});
+
+ it('should add correct line references when isExpandDown is true', () => {
+ const lines = [{ type: null }, { type: MATCH_LINE_TYPE }];
+ const linesWithReferences = utils.addLineReferences(lines, lineNumbers, false, true, {
+ old_line: 10,
+ new_line: 11,
+ });
+
+ expect(linesWithReferences[1].meta_data.old_pos).toEqual(10);
+ expect(linesWithReferences[1].meta_data.new_pos).toEqual(11);
+ });
});
describe('trimFirstCharOfLineContent', () => {
diff --git a/spec/javascripts/environments/environment_terminal_button_spec.js b/spec/javascripts/environments/environment_terminal_button_spec.js
index 56e18db59c5..fc98e656efe 100644
--- a/spec/javascripts/environments/environment_terminal_button_spec.js
+++ b/spec/javascripts/environments/environment_terminal_button_spec.js
@@ -12,36 +12,24 @@ describe('Stop Component', () => {
}).$mount();
};
- describe('enabled', () => {
- beforeEach(() => {
- mountWithProps({ terminalPath });
- });
-
- describe('computed', () => {
- it('title', () => {
- expect(component.title).toEqual('Terminal');
- });
- });
-
- it('should render a link to open a web terminal with the provided path', () => {
- expect(component.$el.tagName).toEqual('A');
- expect(component.$el.getAttribute('data-original-title')).toEqual('Terminal');
- expect(component.$el.getAttribute('aria-label')).toEqual('Terminal');
- expect(component.$el.getAttribute('href')).toEqual(terminalPath);
- });
+ beforeEach(() => {
+ mountWithProps({ terminalPath });
+ });
- it('should render a non-disabled button', () => {
- expect(component.$el.classList).not.toContain('disabled');
+ describe('computed', () => {
+ it('title', () => {
+ expect(component.title).toEqual('Terminal');
});
});
- describe('disabled', () => {
- beforeEach(() => {
- mountWithProps({ terminalPath, disabled: true });
- });
+ it('should render a link to open a web terminal with the provided path', () => {
+ expect(component.$el.tagName).toEqual('A');
+ expect(component.$el.getAttribute('data-original-title')).toEqual('Terminal');
+ expect(component.$el.getAttribute('aria-label')).toEqual('Terminal');
+ expect(component.$el.getAttribute('href')).toEqual(terminalPath);
+ });
- it('should render a disabled button', () => {
- expect(component.$el.classList).toContain('disabled');
- });
+ it('should render a non-disabled button', () => {
+ expect(component.$el.classList).not.toContain('disabled');
});
});
diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore
index bed020f5b0a..d6b7ef32c84 100644
--- a/spec/javascripts/fixtures/.gitignore
+++ b/spec/javascripts/fixtures/.gitignore
@@ -1,5 +1,2 @@
-*.html.raw
-*.html
-*.json
-*.pdf
-*.bmpr
+*
+!.gitignore
diff --git a/spec/javascripts/fixtures/static/README.md b/spec/javascripts/fixtures/static/README.md
deleted file mode 100644
index b5c2f8233bf..00000000000
--- a/spec/javascripts/fixtures/static/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Please do not add new files here!
-
-Instead use a Ruby file in the fixtures root directory (`spec/javascripts/fixtures/`).
diff --git a/spec/javascripts/helpers/vue_test_utils_helper.js b/spec/javascripts/helpers/vue_test_utils_helper.js
index 121e99c9783..5b749b11246 100644
--- a/spec/javascripts/helpers/vue_test_utils_helper.js
+++ b/spec/javascripts/helpers/vue_test_utils_helper.js
@@ -1,21 +1,5 @@
-/* eslint-disable import/prefer-default-export */
+// No new code should be added to this file. Instead, modify the
+// file this one re-exports from. For more detail about why, see:
+// https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31349
-const vNodeContainsText = (vnode, text) =>
- (vnode.text && vnode.text.includes(text)) ||
- (vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length);
-
-/**
- * Determines whether a `shallowMount` Wrapper contains text
- * within one of it's slots. This will also work on Wrappers
- * acquired with `find()`, but only if it's parent Wrapper
- * was shallowMounted.
- * NOTE: Prefer checking the rendered output of a component
- * wherever possible using something like `text()` instead.
- * @param {Wrapper} shallowWrapper - Vue test utils wrapper (shallowMounted)
- * @param {String} slotName
- * @param {String} text
- */
-export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) =>
- Boolean(
- shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length,
- );
+export * from '../../frontend/helpers/vue_test_utils_helper';
diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js
index 88652202a8e..c5de31a4138 100644
--- a/spec/javascripts/helpers/vuex_action_helper.js
+++ b/spec/javascripts/helpers/vuex_action_helper.js
@@ -89,9 +89,7 @@ export default (
payload,
);
- return new Promise(resolve => {
- setImmediate(resolve);
- })
+ return new Promise(setImmediate)
.then(() => result)
.catch(error => {
validateResults();
diff --git a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
index b903abe63fc..a3db3ee1b18 100644
--- a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
@@ -1,30 +1,28 @@
import Vue from 'vue';
-import store from '~/ide/stores';
-import consts from '~/ide/stores/modules/commit/constants';
+import { createStore } from '~/ide/stores';
import commitActions from '~/ide/components/commit_sidebar/actions.vue';
+import consts from '~/ide/stores/modules/commit/constants';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { resetStore } from 'spec/ide/helpers';
-import { projectData } from 'spec/ide/mock_data';
+import { projectData, branches } from 'spec/ide/mock_data';
+
+const ACTION_UPDATE_COMMIT_ACTION = 'commit/updateCommitAction';
describe('IDE commit sidebar actions', () => {
+ let store;
let vm;
- const createComponent = ({
- hasMR = false,
- commitAction = consts.COMMIT_TO_NEW_BRANCH,
- mergeRequestsEnabled = true,
- currentBranchId = 'master',
- shouldCreateMR = false,
- } = {}) => {
+
+ const createComponent = ({ hasMR = false, currentBranchId = 'master' } = {}) => {
const Component = Vue.extend(commitActions);
vm = createComponentWithStore(Component, store);
vm.$store.state.currentBranchId = currentBranchId;
vm.$store.state.currentProjectId = 'abcproject';
- vm.$store.state.commit.commitAction = commitAction;
- Vue.set(vm.$store.state.projects, 'abcproject', { ...projectData });
- vm.$store.state.projects.abcproject.merge_requests_enabled = mergeRequestsEnabled;
- vm.$store.state.commit.shouldCreateMR = shouldCreateMR;
+
+ const proj = { ...projectData };
+ proj.branches[currentBranchId] = branches.find(branch => branch.name === currentBranchId);
+
+ Vue.set(vm.$store.state.projects, 'abcproject', proj);
if (hasMR) {
vm.$store.state.currentMergeRequestId = '1';
@@ -33,13 +31,19 @@ describe('IDE commit sidebar actions', () => {
] = { foo: 'bar' };
}
- return vm.$mount();
+ vm.$mount();
+
+ return vm;
};
+ beforeEach(() => {
+ store = createStore();
+ spyOn(store, 'dispatch');
+ });
+
afterEach(() => {
vm.$destroy();
-
- resetStore(vm.$store);
+ vm = null;
});
it('renders 2 groups', () => {
@@ -73,4 +77,152 @@ describe('IDE commit sidebar actions', () => {
expect(vm.commitToCurrentBranchText).not.toContain(injectedSrc);
});
});
+
+ describe('updateSelectedCommitAction', () => {
+ it('does not return anything if currentBranch does not exist', () => {
+ createComponent({ currentBranchId: null });
+
+ expect(vm.$store.dispatch).not.toHaveBeenCalled();
+ });
+
+ it('calls again after staged changes', done => {
+ createComponent({ currentBranchId: null });
+
+ vm.$store.state.currentBranchId = 'master';
+ vm.$store.state.changedFiles.push({});
+ vm.$store.state.stagedFiles.push({});
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ jasmine.anything(),
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ describe('default branch', () => {
+ it('dispatches correct action for default branch', () => {
+ createComponent({
+ currentBranchId: 'master',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledTimes(1);
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_NEW_BRANCH,
+ );
+ });
+ });
+
+ describe('protected branch', () => {
+ describe('with write access', () => {
+ it('dispatches correct action when MR exists', () => {
+ createComponent({
+ hasMR: true,
+ currentBranchId: 'protected/access',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_CURRENT_BRANCH,
+ );
+ });
+
+ it('dispatches correct action when MR does not exists', () => {
+ createComponent({
+ hasMR: false,
+ currentBranchId: 'protected/access',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_CURRENT_BRANCH,
+ );
+ });
+ });
+
+ describe('without write access', () => {
+ it('dispatches correct action when MR exists', () => {
+ createComponent({
+ hasMR: true,
+ currentBranchId: 'protected/no-access',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_NEW_BRANCH,
+ );
+ });
+
+ it('dispatches correct action when MR does not exists', () => {
+ createComponent({
+ hasMR: false,
+ currentBranchId: 'protected/no-access',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_NEW_BRANCH,
+ );
+ });
+ });
+ });
+
+ describe('regular branch', () => {
+ describe('with write access', () => {
+ it('dispatches correct action when MR exists', () => {
+ createComponent({
+ hasMR: true,
+ currentBranchId: 'regular',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_CURRENT_BRANCH,
+ );
+ });
+
+ it('dispatches correct action when MR does not exists', () => {
+ createComponent({
+ hasMR: false,
+ currentBranchId: 'regular',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_CURRENT_BRANCH,
+ );
+ });
+ });
+
+ describe('without write access', () => {
+ it('dispatches correct action when MR exists', () => {
+ createComponent({
+ hasMR: true,
+ currentBranchId: 'regular/no-access',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_NEW_BRANCH,
+ );
+ });
+
+ it('dispatches correct action when MR does not exists', () => {
+ createComponent({
+ hasMR: false,
+ currentBranchId: 'regular/no-access',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_NEW_BRANCH,
+ );
+ });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
index 7017bfcd6a6..5f2db695241 100644
--- a/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
@@ -1,33 +1,36 @@
import Vue from 'vue';
import store from '~/ide/stores';
-import consts from '~/ide/stores/modules/commit/constants';
import NewMergeRequestOption from '~/ide/components/commit_sidebar/new_merge_request_option.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { projectData } from 'spec/ide/mock_data';
+import { projectData, branches } from 'spec/ide/mock_data';
import { resetStore } from 'spec/ide/helpers';
+import consts from '../../../../../app/assets/javascripts/ide/stores/modules/commit/constants';
describe('create new MR checkbox', () => {
let vm;
- const createComponent = ({
- hasMR = false,
- commitAction = consts.COMMIT_TO_NEW_BRANCH,
- currentBranchId = 'master',
- } = {}) => {
+ const setMR = () => {
+ vm.$store.state.currentMergeRequestId = '1';
+ vm.$store.state.projects[store.state.currentProjectId].mergeRequests[
+ store.state.currentMergeRequestId
+ ] = { foo: 'bar' };
+ };
+
+ const createComponent = ({ currentBranchId = 'master', createNewBranch = false } = {}) => {
const Component = Vue.extend(NewMergeRequestOption);
vm = createComponentWithStore(Component, store);
+ vm.$store.state.commit.commitAction = createNewBranch
+ ? consts.COMMIT_TO_NEW_BRANCH
+ : consts.COMMIT_TO_CURRENT_BRANCH;
+
vm.$store.state.currentBranchId = currentBranchId;
vm.$store.state.currentProjectId = 'abcproject';
- vm.$store.state.commit.commitAction = commitAction;
- Vue.set(vm.$store.state.projects, 'abcproject', { ...projectData });
- if (hasMR) {
- vm.$store.state.currentMergeRequestId = '1';
- vm.$store.state.projects[store.state.currentProjectId].mergeRequests[
- store.state.currentMergeRequestId
- ] = { foo: 'bar' };
- }
+ const proj = JSON.parse(JSON.stringify(projectData));
+ proj.branches[currentBranchId] = branches.find(branch => branch.name === currentBranchId);
+
+ Vue.set(vm.$store.state.projects, 'abcproject', proj);
return vm.$mount();
};
@@ -38,30 +41,131 @@ describe('create new MR checkbox', () => {
resetStore(vm.$store);
});
- it('is hidden when an MR already exists and committing to current branch', () => {
- createComponent({
- hasMR: true,
- commitAction: consts.COMMIT_TO_CURRENT_BRANCH,
- currentBranchId: 'feature',
+ describe('for default branch', () => {
+ describe('is rendered when pushing to a new branch', () => {
+ beforeEach(() => {
+ createComponent({
+ currentBranchId: 'master',
+ createNewBranch: true,
+ });
+ });
+
+ it('has NO new MR', () => {
+ expect(vm.$el.textContent).not.toBe('');
+ });
+
+ it('has new MR', done => {
+ setMR();
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el.textContent).not.toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
- expect(vm.$el.textContent).toBe('');
+ describe('is NOT rendered when pushing to the same branch', () => {
+ beforeEach(() => {
+ createComponent({
+ currentBranchId: 'master',
+ createNewBranch: false,
+ });
+ });
+
+ it('has NO new MR', () => {
+ expect(vm.$el.textContent).toBe('');
+ });
+
+ it('has new MR', done => {
+ setMR();
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el.textContent).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
- it('does not hide checkbox if MR does not exist', () => {
- createComponent({ hasMR: false });
+ describe('for protected branch', () => {
+ describe('when user does not have the write access', () => {
+ beforeEach(() => {
+ createComponent({
+ currentBranchId: 'protected/no-access',
+ });
+ });
+
+ it('is rendered if MR does not exists', () => {
+ expect(vm.$el.textContent).not.toBe('');
+ });
+
+ it('is rendered if MR exists', done => {
+ setMR();
- expect(vm.$el.querySelector('input[type="checkbox"]').hidden).toBe(false);
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el.textContent).not.toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('when user has the write access', () => {
+ beforeEach(() => {
+ createComponent({
+ currentBranchId: 'protected/access',
+ });
+ });
+
+ it('is rendered if MR does not exist', () => {
+ expect(vm.$el.textContent).not.toBe('');
+ });
+
+ it('is hidden if MR exists', done => {
+ setMR();
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el.textContent).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
- it('does not hide checkbox when creating a new branch', () => {
- createComponent({ commitAction: consts.COMMIT_TO_NEW_BRANCH });
+ describe('for regular branch', () => {
+ beforeEach(() => {
+ createComponent({
+ currentBranchId: 'regular',
+ });
+ });
- expect(vm.$el.querySelector('input[type="checkbox"]').hidden).toBe(false);
+ it('is rendered if no MR exists', () => {
+ expect(vm.$el.textContent).not.toBe('');
+ });
+
+ it('is hidden if MR exists', done => {
+ setMR();
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el.textContent).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
it('dispatches toggleShouldCreateMR when clicking checkbox', () => {
- createComponent();
+ createComponent({
+ currentBranchId: 'regular',
+ });
const el = vm.$el.querySelector('input[type="checkbox"]');
spyOn(vm.$store, 'dispatch');
el.dispatchEvent(new Event('change'));
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
index 002b5a005b8..0701b773e17 100644
--- a/spec/javascripts/ide/components/repo_editor_spec.js
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -5,7 +5,7 @@ import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
import repoEditor from '~/ide/components/repo_editor.vue';
import Editor from '~/ide/lib/editor';
-import { activityBarViews } from '~/ide/constants';
+import { activityBarViews, FILE_VIEW_MODE_EDITOR, FILE_VIEW_MODE_PREVIEW } from '~/ide/constants';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import setTimeoutPromise from '../../helpers/set_timeout_promise_helper';
import { file, resetStore } from '../helpers';
@@ -14,7 +14,10 @@ describe('RepoEditor', () => {
let vm;
beforeEach(done => {
- const f = file();
+ const f = {
+ ...file(),
+ viewMode: FILE_VIEW_MODE_EDITOR,
+ };
const RepoEditor = Vue.extend(repoEditor);
vm = createComponentWithStore(RepoEditor, store, {
@@ -27,6 +30,7 @@ describe('RepoEditor', () => {
Vue.set(vm.$store.state.entries, f.path, f);
spyOn(vm, 'getFileData').and.returnValue(Promise.resolve());
+ spyOn(vm, 'getRawFileData').and.returnValue(Promise.resolve());
vm.$mount();
@@ -41,12 +45,17 @@ describe('RepoEditor', () => {
Editor.editorInstance.dispose();
});
- it('renders an ide container', done => {
- Vue.nextTick(() => {
- expect(vm.shouldHideEditor).toBeFalsy();
+ const findEditor = () => vm.$el.querySelector('.multi-file-editor-holder');
+ const changeRightPanelCollapsed = () => {
+ const { state } = vm.$store;
- done();
- });
+ state.rightPanelCollapsed = !state.rightPanelCollapsed;
+ };
+
+ it('renders an ide container', () => {
+ expect(vm.shouldHideEditor).toBeFalsy();
+ expect(vm.showEditor).toBe(true);
+ expect(findEditor()).not.toHaveCss({ display: 'none' });
});
it('renders only an edit tab', done => {
@@ -283,7 +292,7 @@ describe('RepoEditor', () => {
});
it('calls updateDimensions when rightPanelCollapsed is changed', done => {
- vm.$store.state.rightPanelCollapsed = true;
+ changeRightPanelCollapsed();
vm.$nextTick(() => {
expect(vm.editor.updateDimensions).toHaveBeenCalled();
@@ -358,6 +367,85 @@ describe('RepoEditor', () => {
});
});
+ describe('when files view mode is preview', () => {
+ beforeEach(done => {
+ spyOn(vm.editor, 'updateDimensions');
+ vm.file.viewMode = FILE_VIEW_MODE_PREVIEW;
+ vm.$nextTick(done);
+ });
+
+ it('should hide editor', () => {
+ expect(vm.showEditor).toBe(false);
+ expect(findEditor()).toHaveCss({ display: 'none' });
+ });
+
+ it('should not update dimensions', done => {
+ changeRightPanelCollapsed();
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.editor.updateDimensions).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ describe('when file view mode changes to editor', () => {
+ beforeEach(done => {
+ vm.file.viewMode = FILE_VIEW_MODE_EDITOR;
+
+ // one tick to trigger watch
+ vm.$nextTick()
+ // another tick needed until we can update dimensions
+ .then(() => vm.$nextTick())
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('should update dimensions', () => {
+ expect(vm.editor.updateDimensions).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('initEditor', () => {
+ beforeEach(() => {
+ spyOn(vm.editor, 'createInstance');
+ spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true);
+ });
+
+ it('is being initialised for files without content even if shouldHideEditor is `true`', done => {
+ vm.file.content = '';
+ vm.file.raw = '';
+
+ vm.initEditor();
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.getFileData).toHaveBeenCalled();
+ expect(vm.getRawFileData).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not initialize editor for files already with content', done => {
+ expect(vm.getFileData.calls.count()).toEqual(1);
+ expect(vm.getRawFileData.calls.count()).toEqual(1);
+
+ vm.file.content = 'foo';
+
+ vm.initEditor();
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.getFileData.calls.count()).toEqual(1);
+ expect(vm.getRawFileData.calls.count()).toEqual(1);
+ expect(vm.editor.createInstance).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
it('calls removePendingTab when old file is pending', done => {
spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true);
spyOn(vm, 'removePendingTab');
@@ -367,6 +455,7 @@ describe('RepoEditor', () => {
vm.$nextTick()
.then(() => {
vm.file = file('testing');
+ vm.file.content = 'foo'; // need to prevent full cycle of initEditor
return vm.$nextTick();
})
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index 570a396c5e3..c02c7e5d45e 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -176,23 +176,51 @@ export const branches = [
committed_date: '2018-08-01T00:20:05Z',
},
can_push: true,
+ protected: true,
+ default: true,
},
{
id: 2,
- name: 'feature/lorem-ipsum',
+ name: 'protected/no-access',
commit: {
message: 'Update some stuff',
committed_date: '2018-08-02T00:00:05Z',
},
- can_push: true,
+ can_push: false,
+ protected: true,
+ default: false,
},
{
id: 3,
- name: 'feature/dolar-amit',
+ name: 'protected/access',
+ commit: {
+ message: 'Update some stuff',
+ committed_date: '2018-08-02T00:00:05Z',
+ },
+ can_push: true,
+ protected: true,
+ default: false,
+ },
+ {
+ id: 4,
+ name: 'regular',
commit: {
message: 'Update some more stuff',
committed_date: '2018-06-30T00:20:05Z',
},
can_push: true,
+ protected: false,
+ default: false,
+ },
+ {
+ id: 5,
+ name: 'regular/no-access',
+ commit: {
+ message: 'Update some more stuff',
+ committed_date: '2018-06-30T00:20:05Z',
+ },
+ can_push: false,
+ protected: false,
+ default: false,
},
];
diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js
index 2d105103c1c..8504fb3f42b 100644
--- a/spec/javascripts/ide/stores/actions_spec.js
+++ b/spec/javascripts/ide/stores/actions_spec.js
@@ -10,6 +10,7 @@ import actions, {
deleteEntry,
renameEntry,
getBranchData,
+ createTempEntry,
} from '~/ide/stores/actions';
import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
@@ -247,18 +248,30 @@ describe('Multi-file store actions', () => {
});
it('sets tmp file as active', done => {
- store
- .dispatch('createTempEntry', {
+ testAction(
+ createTempEntry,
+ {
name: 'test',
branchId: 'mybranch',
type: 'blob',
- })
- .then(f => {
- expect(f.active).toBeTruthy();
-
- done();
- })
- .catch(done.fail);
+ },
+ store.state,
+ [
+ { type: types.CREATE_TMP_ENTRY, payload: jasmine.any(Object) },
+ { type: types.TOGGLE_FILE_OPEN, payload: 'test' },
+ { type: types.ADD_FILE_TO_CHANGED, payload: 'test' },
+ ],
+ [
+ {
+ type: 'setFileActive',
+ payload: 'test',
+ },
+ {
+ type: 'triggerFilesChange',
+ },
+ ],
+ done,
+ );
});
it('creates flash message if file already exists', done => {
@@ -488,7 +501,11 @@ describe('Multi-file store actions', () => {
'path',
store.state,
[{ type: types.DELETE_ENTRY, payload: 'path' }],
- [{ type: 'burstUnusedSeal' }, { type: 'triggerFilesChange' }],
+ [
+ { type: 'burstUnusedSeal' },
+ { type: 'stageChange', payload: 'path' },
+ { type: 'triggerFilesChange' },
+ ],
done,
);
});
@@ -515,7 +532,11 @@ describe('Multi-file store actions', () => {
'testFolder/entry-to-delete',
store.state,
[{ type: types.DELETE_ENTRY, payload: 'testFolder/entry-to-delete' }],
- [{ type: 'burstUnusedSeal' }, { type: 'triggerFilesChange' }],
+ [
+ { type: 'burstUnusedSeal' },
+ { type: 'stageChange', payload: 'testFolder/entry-to-delete' },
+ { type: 'triggerFilesChange' },
+ ],
done,
);
});
diff --git a/spec/javascripts/ide/stores/getters_spec.js b/spec/javascripts/ide/stores/getters_spec.js
index 735bbd47f55..73a8d993a13 100644
--- a/spec/javascripts/ide/stores/getters_spec.js
+++ b/spec/javascripts/ide/stores/getters_spec.js
@@ -221,4 +221,36 @@ describe('IDE store getters', () => {
});
});
});
+
+ describe('canPushToBranch', () => {
+ it('returns false when no currentBranch exists', () => {
+ const localGetters = {
+ currentProject: undefined,
+ };
+
+ expect(getters.canPushToBranch({}, localGetters)).toBeFalsy();
+ });
+
+ it('returns true when can_push to currentBranch', () => {
+ const localGetters = {
+ currentProject: {
+ default_branch: 'master',
+ },
+ currentBranch: { can_push: true },
+ };
+
+ expect(getters.canPushToBranch({}, localGetters)).toBeTruthy();
+ });
+
+ it('returns false when !can_push to currentBranch', () => {
+ const localGetters = {
+ currentProject: {
+ default_branch: 'master',
+ },
+ currentBranch: { can_push: false },
+ };
+
+ expect(getters.canPushToBranch({}, localGetters)).toBeFalsy();
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
index 8a3c132972e..091b454c0d2 100644
--- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -57,6 +57,44 @@ describe('IDE commit module actions', () => {
.then(done)
.catch(done.fail);
});
+
+ it('sets shouldCreateMR to true if "Create new MR" option is visible', done => {
+ store.state.shouldHideNewMrOption = false;
+
+ testAction(
+ actions.updateCommitAction,
+ {},
+ store.state,
+ [
+ {
+ type: mutationTypes.UPDATE_COMMIT_ACTION,
+ payload: { commitAction: jasmine.anything() },
+ },
+ { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR, payload: true },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('sets shouldCreateMR to false if "Create new MR" option is hidden', done => {
+ store.state.shouldHideNewMrOption = true;
+
+ testAction(
+ actions.updateCommitAction,
+ {},
+ store.state,
+ [
+ {
+ type: mutationTypes.UPDATE_COMMIT_ACTION,
+ payload: { commitAction: jasmine.anything() },
+ },
+ { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR, payload: false },
+ ],
+ [],
+ done,
+ );
+ });
});
describe('updateBranchName', () => {
@@ -245,7 +283,7 @@ describe('IDE commit module actions', () => {
master: {
workingReference: '1',
commit: {
- short_id: TEST_COMMIT_SHA,
+ id: TEST_COMMIT_SHA,
},
},
},
@@ -411,7 +449,7 @@ describe('IDE commit module actions', () => {
expect(visitUrl).toHaveBeenCalledWith(
`webUrl/merge_requests/new?merge_request[source_branch]=${
store.getters['commit/placeholderBranchName']
- }&merge_request[target_branch]=master`,
+ }&merge_request[target_branch]=master&nav_source=webide`,
);
done();
@@ -541,147 +579,10 @@ describe('IDE commit module actions', () => {
actions.toggleShouldCreateMR,
{},
store.state,
- [
- { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR },
- { type: mutationTypes.INTERACT_WITH_NEW_MR },
- ],
+ [{ type: mutationTypes.TOGGLE_SHOULD_CREATE_MR }],
[],
done,
);
});
});
-
- describe('setShouldCreateMR', () => {
- beforeEach(() => {
- store.state.projects = {
- project: {
- default_branch: 'master',
- branches: {
- master: {
- name: 'master',
- },
- feature: {
- name: 'feature',
- },
- },
- },
- };
-
- store.state.currentProjectId = 'project';
- });
-
- it('sets to false when the current branch already has an MR', done => {
- store.state.commit.currentMergeRequestId = 1;
- store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- store.state.currentMergeRequestId = '1';
- store.state.currentBranchId = 'feature';
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit.calls.allArgs()[0]).toEqual(
- jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
- );
- done();
- })
- .catch(done.fail);
- });
-
- it('changes to false when current branch is the default branch and user has not interacted', done => {
- store.state.commit.interactedWithNewMR = false;
- store.state.currentBranchId = 'master';
- store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit.calls.allArgs()[0]).toEqual(
- jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
- );
- done();
- })
- .catch(done.fail);
- });
-
- it('changes to true when "create new branch" is selected and user has not interacted', done => {
- store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
- store.state.commit.interactedWithNewMR = false;
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit.calls.allArgs()[0]).toEqual(
- jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, true]),
- );
- done();
- })
- .catch(done.fail);
- });
-
- it('does not change anything if user has interacted and comitting to new branch', done => {
- store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
- store.state.commit.interactedWithNewMR = true;
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit).not.toHaveBeenCalled();
- done();
- })
- .catch(done.fail);
- });
-
- it('does not change anything if user has interacted and comitting to branch without MR', done => {
- store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- store.state.commit.currentMergeRequestId = null;
- store.state.commit.interactedWithNewMR = true;
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit).not.toHaveBeenCalled();
- done();
- })
- .catch(done.fail);
- });
-
- it('still changes to false if hiding the checkbox', done => {
- store.state.currentBranchId = 'feature';
- store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- store.state.currentMergeRequestId = '1';
- store.state.commit.interactedWithNewMR = true;
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit.calls.allArgs()[0]).toEqual(
- jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
- );
- done();
- })
- .catch(done.fail);
- });
-
- it('does not change to false when on master and user has interacted even if MR exists', done => {
- store.state.currentBranchId = 'master';
- store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- store.state.currentMergeRequestId = '1';
- store.state.commit.interactedWithNewMR = true;
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit).not.toHaveBeenCalled();
- done();
- })
- .catch(done.fail);
- });
- });
});
diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
index 6e71a790deb..07445c22917 100644
--- a/spec/javascripts/ide/stores/modules/commit/getters_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
@@ -1,6 +1,6 @@
import commitState from '~/ide/stores/modules/commit/state';
-import consts from '~/ide/stores/modules/commit/constants';
import * as getters from '~/ide/stores/modules/commit/getters';
+import consts from '~/ide/stores/modules/commit/constants';
describe('IDE commit module getters', () => {
let state;
@@ -55,15 +55,15 @@ describe('IDE commit module getters', () => {
});
});
- it('defualts to currentBranchId', () => {
- expect(getters.branchName(state, null, rootState)).toBe('master');
+ it('defaults to currentBranchId when not committing to a new branch', () => {
+ localGetters.isCreatingNewBranch = false;
+
+ expect(getters.branchName(state, localGetters, rootState)).toBe('master');
});
- describe('COMMIT_TO_NEW_BRANCH', () => {
+ describe('commit to a new branch', () => {
beforeEach(() => {
- Object.assign(state, {
- commitAction: consts.COMMIT_TO_NEW_BRANCH,
- });
+ localGetters.isCreatingNewBranch = true;
});
it('uses newBranchName when not empty', () => {
@@ -144,4 +144,152 @@ describe('IDE commit module getters', () => {
});
});
});
+
+ describe('isCreatingNewBranch', () => {
+ it('returns false if NOT creating a new branch', () => {
+ state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+
+ expect(getters.isCreatingNewBranch(state)).toBeFalsy();
+ });
+
+ it('returns true if creating a new branch', () => {
+ state.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+
+ expect(getters.isCreatingNewBranch(state)).toBeTruthy();
+ });
+ });
+
+ describe('shouldHideNewMrOption', () => {
+ let localGetters = {};
+ let rootGetters = {};
+
+ beforeEach(() => {
+ localGetters = {
+ isCreatingNewBranch: null,
+ };
+ rootGetters = {
+ isOnDefaultBranch: null,
+ hasMergeRequest: null,
+ canPushToBranch: null,
+ };
+ });
+
+ describe('NO existing MR for the branch', () => {
+ beforeEach(() => {
+ rootGetters.hasMergeRequest = false;
+ });
+
+ it('should never hide "New MR" option', () => {
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+ });
+ });
+
+ describe('existing MR for the branch', () => {
+ beforeEach(() => {
+ rootGetters.hasMergeRequest = true;
+ });
+
+ it('should NOT hide "New MR" option if user can NOT push to the current branch', () => {
+ rootGetters.canPushToBranch = false;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+ });
+
+ it('should hide "New MR" option if user can push to the current branch', () => {
+ rootGetters.canPushToBranch = true;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeTruthy();
+ });
+ });
+
+ describe('user can NOT push the branch', () => {
+ beforeEach(() => {
+ rootGetters.canPushToBranch = false;
+ });
+
+ it('should never hide "New MR" option', () => {
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+ });
+ });
+
+ describe('user can push to the branch', () => {
+ beforeEach(() => {
+ rootGetters.canPushToBranch = true;
+ });
+
+ it('should NOT hide "New MR" option if there is NO existing MR for the current branch', () => {
+ rootGetters.hasMergeRequest = false;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+ });
+
+ it('should hide "New MR" option if there is existing MR for the current branch', () => {
+ rootGetters.hasMergeRequest = true;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeTruthy();
+ });
+ });
+
+ describe('default branch', () => {
+ beforeEach(() => {
+ rootGetters.isOnDefaultBranch = true;
+ });
+
+ describe('committing to the same branch', () => {
+ beforeEach(() => {
+ localGetters.isCreatingNewBranch = false;
+ rootGetters.canPushToBranch = true;
+ });
+
+ it('should hide "New MR" when there is an existing MR', () => {
+ rootGetters.hasMergeRequest = true;
+
+ expect(
+ getters.shouldHideNewMrOption(state, localGetters, null, rootGetters),
+ ).toBeTruthy();
+ });
+
+ it('should hide "New MR" when there is no existing MR', () => {
+ rootGetters.hasMergeRequest = false;
+
+ expect(
+ getters.shouldHideNewMrOption(state, localGetters, null, rootGetters),
+ ).toBeTruthy();
+ });
+ });
+
+ describe('creating a new branch', () => {
+ beforeEach(() => {
+ localGetters.isCreatingNewBranch = true;
+ });
+
+ it('should NOT hide "New MR" option no matter existence of an MR or write access', () => {
+ rootGetters.hasMergeRequest = false;
+ rootGetters.canPushToBranch = true;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+
+ rootGetters.hasMergeRequest = true;
+ rootGetters.canPushToBranch = true;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+
+ rootGetters.hasMergeRequest = false;
+ rootGetters.canPushToBranch = false;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+ });
+ });
+ });
+
+ it('should never hide "New MR" option when creating a new branch', () => {
+ localGetters.isCreatingNewBranch = true;
+
+ rootGetters.isOnDefaultBranch = false;
+ rootGetters.hasMergeRequest = true;
+ rootGetters.canPushToBranch = true;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/mutations/file_spec.js b/spec/javascripts/ide/stores/mutations/file_spec.js
index efd0d86552b..064e66cef64 100644
--- a/spec/javascripts/ide/stores/mutations/file_spec.js
+++ b/spec/javascripts/ide/stores/mutations/file_spec.js
@@ -1,5 +1,6 @@
import mutations from '~/ide/stores/mutations/file';
import state from '~/ide/stores/state';
+import { FILE_VIEW_MODE_PREVIEW } from '~/ide/constants';
import { file } from '../../helpers';
describe('IDE store file mutations', () => {
@@ -83,6 +84,63 @@ describe('IDE store file mutations', () => {
expect(localFile.raw).toBeNull();
expect(localFile.baseRaw).toBeNull();
});
+
+ it('sets extra file data to all arrays concerned', () => {
+ localState.stagedFiles = [localFile];
+ localState.changedFiles = [localFile];
+ localState.openFiles = [localFile];
+
+ const rawPath = 'foo/bar/blah.md';
+
+ mutations.SET_FILE_DATA(localState, {
+ data: {
+ raw_path: rawPath,
+ },
+ file: localFile,
+ });
+
+ expect(localState.stagedFiles[0].rawPath).toEqual(rawPath);
+ expect(localState.changedFiles[0].rawPath).toEqual(rawPath);
+ expect(localState.openFiles[0].rawPath).toEqual(rawPath);
+ expect(localFile.rawPath).toEqual(rawPath);
+ });
+
+ it('does not mutate certain props on the file', () => {
+ const path = 'New Path';
+ const name = 'New Name';
+ localFile.path = path;
+ localFile.name = name;
+
+ localState.stagedFiles = [localFile];
+ localState.changedFiles = [localFile];
+ localState.openFiles = [localFile];
+
+ mutations.SET_FILE_DATA(localState, {
+ data: {
+ path: 'Old Path',
+ name: 'Old Name',
+ raw: 'Old Raw',
+ base_raw: 'Old Base Raw',
+ },
+ file: localFile,
+ });
+
+ [
+ localState.stagedFiles[0],
+ localState.changedFiles[0],
+ localState.openFiles[0],
+ localFile,
+ ].forEach(f => {
+ expect(f).toEqual(
+ jasmine.objectContaining({
+ path,
+ name,
+ raw: null,
+ baseRaw: null,
+ }),
+ );
+ });
+ });
});
describe('SET_FILE_RAW_DATA', () => {
@@ -315,6 +373,19 @@ describe('IDE store file mutations', () => {
expect(localState.stagedFiles.length).toBe(1);
expect(localState.stagedFiles[0].raw).toEqual('testing 123');
});
+
+ it('adds already-staged file to `replacedFiles`', () => {
+ localFile.raw = 'already-staged';
+
+ mutations.STAGE_CHANGE(localState, localFile.path);
+
+ localFile.raw = 'testing 123';
+
+ mutations.STAGE_CHANGE(localState, localFile.path);
+
+ expect(localState.replacedFiles.length).toBe(1);
+ expect(localState.replacedFiles[0].raw).toEqual('already-staged');
+ });
});
describe('UNSTAGE_CHANGE', () => {
@@ -355,10 +426,10 @@ describe('IDE store file mutations', () => {
it('updates file view mode', () => {
mutations.SET_FILE_VIEWMODE(localState, {
file: localFile,
- viewMode: 'preview',
+ viewMode: FILE_VIEW_MODE_PREVIEW,
});
- expect(localFile.viewMode).toBe('preview');
+ expect(localFile.viewMode).toBe(FILE_VIEW_MODE_PREVIEW);
});
});
diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js
index 460c5b01081..2470c99e300 100644
--- a/spec/javascripts/ide/stores/mutations_spec.js
+++ b/spec/javascripts/ide/stores/mutations_spec.js
@@ -79,6 +79,16 @@ describe('Multi-file store mutations', () => {
});
});
+ describe('CLEAR_REPLACED_FILES', () => {
+ it('clears replacedFiles array', () => {
+ localState.replacedFiles.push('a');
+
+ mutations.CLEAR_REPLACED_FILES(localState);
+
+ expect(localState.replacedFiles.length).toBe(0);
+ });
+ });
+
describe('UPDATE_VIEWER', () => {
it('sets viewer state', () => {
mutations.UPDATE_VIEWER(localState, 'diff');
@@ -109,6 +119,62 @@ describe('Multi-file store mutations', () => {
});
});
+ describe('CREATE_TMP_ENTRY', () => {
+ beforeEach(() => {
+ localState.currentProjectId = 'gitlab-ce';
+ localState.currentBranchId = 'master';
+ localState.trees['gitlab-ce/master'] = {
+ tree: [],
+ };
+ });
+
+ it('creates temp entry in the tree', () => {
+ const tmpFile = file('test');
+ mutations.CREATE_TMP_ENTRY(localState, {
+ data: {
+ entries: {
+ test: {
+ ...tmpFile,
+ tempFile: true,
+ changed: true,
+ },
+ },
+ treeList: [tmpFile],
+ },
+ projectId: 'gitlab-ce',
+ branchId: 'master',
+ });
+
+ expect(localState.trees['gitlab-ce/master'].tree.length).toEqual(1);
+ expect(localState.entries.test.tempFile).toEqual(true);
+ });
+
+ it('marks entry as replacing previous entry if the old one has been deleted', () => {
+ const tmpFile = file('test');
+ localState.entries.test = {
+ ...tmpFile,
+ deleted: true,
+ };
+ mutations.CREATE_TMP_ENTRY(localState, {
+ data: {
+ entries: {
+ test: {
+ ...tmpFile,
+ tempFile: true,
+ changed: true,
+ },
+ },
+ treeList: [tmpFile],
+ },
+ projectId: 'gitlab-ce',
+ branchId: 'master',
+ });
+
+ expect(localState.trees['gitlab-ce/master'].tree.length).toEqual(1);
+ expect(localState.entries.test.replaces).toEqual(true);
+ });
+ });
+
describe('UPDATE_TEMP_FLAG', () => {
beforeEach(() => {
localState.entries.test = {
@@ -252,6 +318,7 @@ describe('Multi-file store mutations', () => {
permalink: `${gl.TEST_HOST}/testing-123`,
commitsPath: `${gl.TEST_HOST}/testing-123`,
blamePath: `${gl.TEST_HOST}/testing-123`,
+ replaces: true,
};
localState.entries.test = f;
localState.changedFiles.push(f);
@@ -262,6 +329,7 @@ describe('Multi-file store mutations', () => {
expect(f.permalink).toBe(`${gl.TEST_HOST}/test`);
expect(f.commitsPath).toBe(`${gl.TEST_HOST}/test`);
expect(f.blamePath).toBe(`${gl.TEST_HOST}/test`);
+ expect(f.replaces).toBe(false);
});
});
diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js
index e3bf6d40245..0fc9519a6bf 100644
--- a/spec/javascripts/ide/stores/utils_spec.js
+++ b/spec/javascripts/ide/stores/utils_spec.js
@@ -92,6 +92,16 @@ describe('Multi-file store utils', () => {
path: 'deletedFile',
deleted: true,
},
+ {
+ ...file('renamedFile'),
+ path: 'renamedFile',
+ prevPath: 'prevPath',
+ },
+ {
+ ...file('replacingFile'),
+ path: 'replacingFile',
+ replaces: true,
+ },
],
currentBranchId: 'master',
};
@@ -131,6 +141,22 @@ describe('Multi-file store utils', () => {
last_commit_id: undefined,
previous_path: undefined,
},
+ {
+ action: commitActionTypes.move,
+ file_path: 'renamedFile',
+ content: null,
+ encoding: 'text',
+ last_commit_id: undefined,
+ previous_path: 'prevPath',
+ },
+ {
+ action: commitActionTypes.update,
+ file_path: 'replacingFile',
+ content: undefined,
+ encoding: 'text',
+ last_commit_id: undefined,
+ previous_path: undefined,
+ },
],
start_sha: undefined,
});
@@ -235,6 +261,41 @@ describe('Multi-file store utils', () => {
},
]);
});
+
+ it('filters out folders from the list', () => {
+ const files = [
+ {
+ path: 'a',
+ type: 'blob',
+ deleted: true,
+ },
+ {
+ path: 'c',
+ type: 'tree',
+ deleted: true,
+ },
+ {
+ path: 'c/d',
+ type: 'blob',
+ deleted: true,
+ },
+ ];
+
+ const flattendFiles = utils.getCommitFiles(files);
+
+ expect(flattendFiles).toEqual([
+ {
+ path: 'a',
+ type: 'blob',
+ deleted: true,
+ },
+ {
+ path: 'c/d',
+ type: 'blob',
+ deleted: true,
+ },
+ ]);
+ });
});
describe('mergeTrees', () => {
diff --git a/spec/javascripts/issuable_spec.js b/spec/javascripts/issuable_spec.js
index 25543053eba..4d57bfb1b33 100644
--- a/spec/javascripts/issuable_spec.js
+++ b/spec/javascripts/issuable_spec.js
@@ -2,14 +2,14 @@ import $ from 'jquery';
import MockAdaptor from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import IssuableIndex from '~/issuable_index';
+import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
describe('Issuable', () => {
- let Issuable;
describe('initBulkUpdate', () => {
it('should not set bulkUpdateSidebar', () => {
- Issuable = new IssuableIndex('issue_');
+ new IssuableIndex('issue_'); // eslint-disable-line no-new
- expect(Issuable.bulkUpdateSidebar).not.toBeDefined();
+ expect(issuableInitBulkUpdateSidebar.bulkUpdateSidebar).toBeNull();
});
it('should set bulkUpdateSidebar', () => {
@@ -17,9 +17,9 @@ describe('Issuable', () => {
element.classList.add('issues-bulk-update');
document.body.appendChild(element);
- Issuable = new IssuableIndex('issue_');
+ new IssuableIndex('issue_'); // eslint-disable-line no-new
- expect(Issuable.bulkUpdateSidebar).toBeDefined();
+ expect(issuableInitBulkUpdateSidebar.bulkUpdateSidebar).toBeDefined();
});
});
@@ -36,7 +36,7 @@ describe('Issuable', () => {
input.setAttribute('id', 'issuable_email');
document.body.appendChild(input);
- Issuable = new IssuableIndex('issue_');
+ new IssuableIndex('issue_'); // eslint-disable-line no-new
mock = new MockAdaptor(axios);
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index 7e00fbf2745..e10426a9858 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import Vue from 'vue';
+import '~/behaviors/markdown/render_gfm';
import Description from '~/issue_show/components/description.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
@@ -91,6 +92,7 @@ describe('Description component', () => {
let TaskList;
beforeEach(() => {
+ vm.$destroy();
vm = mountComponent(
DescriptionComponent,
Object.assign({}, props, {
diff --git a/spec/javascripts/issue_show/components/form_spec.js b/spec/javascripts/issue_show/components/form_spec.js
index b0f4ab2b12d..a111333ac80 100644
--- a/spec/javascripts/issue_show/components/form_spec.js
+++ b/spec/javascripts/issue_show/components/form_spec.js
@@ -1,81 +1,98 @@
import Vue from 'vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
import formComponent from '~/issue_show/components/form.vue';
import eventHub from '~/issue_show/event_hub';
describe('Inline edit form component', () => {
let vm;
- let autosave;
- let autosaveObj;
-
- beforeEach(done => {
- autosaveObj = { reset: jasmine.createSpy() };
-
- autosave = spyOnDependency(formComponent, 'Autosave').and.returnValue(autosaveObj);
+ const defaultProps = {
+ canDestroy: true,
+ formState: {
+ title: 'b',
+ description: 'a',
+ lockedWarningVisible: false,
+ },
+ issuableType: 'issue',
+ markdownPreviewPath: '/',
+ markdownDocsPath: '/',
+ projectPath: '/',
+ projectNamespace: '/',
+ };
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+ const createComponent = props => {
const Component = Vue.extend(formComponent);
- vm = new Component({
- propsData: {
- canDestroy: true,
- formState: {
- title: 'b',
- description: 'a',
- lockedWarningVisible: false,
- },
- issuableType: 'issue',
- markdownPreviewPath: '/',
- markdownDocsPath: '/',
- projectPath: '/',
- projectNamespace: '/',
- },
- }).$mount();
-
- Vue.nextTick(done);
- });
+ vm = mountComponent(Component, {
+ ...defaultProps,
+ ...props,
+ });
+ };
it('does not render template selector if no templates exist', () => {
+ createComponent();
+
expect(vm.$el.querySelector('.js-issuable-selector-wrap')).toBeNull();
});
- it('renders template selector when templates exists', done => {
- vm.issuableTemplates = ['test'];
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.js-issuable-selector-wrap')).not.toBeNull();
+ it('renders template selector when templates exists', () => {
+ createComponent({ issuableTemplates: ['test'] });
- done();
- });
+ expect(vm.$el.querySelector('.js-issuable-selector-wrap')).not.toBeNull();
});
it('hides locked warning by default', () => {
+ createComponent();
+
expect(vm.$el.querySelector('.alert')).toBeNull();
});
- it('shows locked warning if formState is different', done => {
- vm.formState.lockedWarningVisible = true;
+ it('shows locked warning if formState is different', () => {
+ createComponent({ formState: { ...defaultProps.formState, lockedWarningVisible: true } });
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.alert')).not.toBeNull();
+ expect(vm.$el.querySelector('.alert')).not.toBeNull();
+ });
- done();
+ it('hides locked warning when currently saving', () => {
+ createComponent({
+ formState: { ...defaultProps.formState, updateLoading: true, lockedWarningVisible: true },
});
- });
- it('initialized Autosave on mount', () => {
- expect(autosave).toHaveBeenCalledTimes(2);
+ expect(vm.$el.querySelector('.alert')).toBeNull();
});
- it('calls reset on autosave when eventHub emits appropriate events', () => {
- eventHub.$emit('close.form');
+ describe('autosave', () => {
+ let autosaveObj;
+ let autosave;
+
+ beforeEach(() => {
+ autosaveObj = { reset: jasmine.createSpy() };
+ autosave = spyOnDependency(formComponent, 'Autosave').and.returnValue(autosaveObj);
+ });
+
+ it('initialized Autosave on mount', () => {
+ createComponent();
- expect(autosaveObj.reset).toHaveBeenCalledTimes(2);
+ expect(autosave).toHaveBeenCalledTimes(2);
+ });
+
+ it('calls reset on autosave when eventHub emits appropriate events', () => {
+ createComponent();
+
+ eventHub.$emit('close.form');
- eventHub.$emit('delete.issuable');
+ expect(autosaveObj.reset).toHaveBeenCalledTimes(2);
- expect(autosaveObj.reset).toHaveBeenCalledTimes(4);
+ eventHub.$emit('delete.issuable');
- eventHub.$emit('update.issuable');
+ expect(autosaveObj.reset).toHaveBeenCalledTimes(4);
- expect(autosaveObj.reset).toHaveBeenCalledTimes(6);
+ eventHub.$emit('update.issuable');
+
+ expect(autosaveObj.reset).toHaveBeenCalledTimes(6);
+ });
});
});
diff --git a/spec/frontend/jobs/components/empty_state_spec.js b/spec/javascripts/jobs/components/empty_state_spec.js
index a2df79bdda0..c6eac4e27b3 100644
--- a/spec/frontend/jobs/components/empty_state_spec.js
+++ b/spec/javascripts/jobs/components/empty_state_spec.js
@@ -10,6 +10,8 @@ describe('Empty State', () => {
illustrationPath: 'illustrations/pending_job_empty.svg',
illustrationSizeClass: 'svg-430',
title: 'This job has not started yet',
+ playable: false,
+ variablesSettingsUrl: '',
};
const content = 'This job is in pending state and is waiting to be picked by a runner';
@@ -90,4 +92,50 @@ describe('Empty State', () => {
expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull();
});
});
+
+ describe('without playbale action', () => {
+ it('does not render manual variables form', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ });
+
+ expect(vm.$el.querySelector('.js-manual-vars-form')).toBeNull();
+ });
+ });
+
+ describe('with playbale action and not scheduled job', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ playable: true,
+ scheduled: false,
+ action: {
+ path: 'runner',
+ button_title: 'Check runner',
+ method: 'post',
+ },
+ });
+ });
+
+ it('renders manual variables form', () => {
+ expect(vm.$el.querySelector('.js-manual-vars-form')).not.toBeNull();
+ });
+
+ it('does not render the empty state action', () => {
+ expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull();
+ });
+ });
+
+ describe('with playbale action and scheduled job', () => {
+ it('does not render manual variables form', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ });
+
+ expect(vm.$el.querySelector('.js-manual-vars-form')).toBeNull();
+ });
+ });
});
diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
index f28d2c2a882..d3c1cf831bb 100644
--- a/spec/javascripts/jobs/components/job_app_spec.js
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -3,7 +3,9 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import jobApp from '~/jobs/components/job_app.vue';
import createStore from '~/jobs/store';
+import * as types from '~/jobs/store/mutation_types';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { waitForMutation } from 'spec/helpers/vue_test_utils_helper';
import { resetStore } from '../store/helpers';
import job from '../mock_data';
@@ -19,12 +21,24 @@ describe('Job App ', () => {
runnerHelpUrl: 'help/runner',
deploymentHelpUrl: 'help/deployment',
runnerSettingsUrl: 'settings/ci-cd/runners',
+ variablesSettingsUrl: 'settings/ci-cd/variables',
terminalPath: 'jobs/123/terminal',
pagePath: `${gl.TEST_HOST}jobs/123`,
+ projectPath: 'user-name/project-name',
logState:
'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D',
};
+ const waitForJobReceived = () => waitForMutation(store, types.RECEIVE_JOB_SUCCESS);
+ const setupAndMount = ({ jobData = {}, traceData = {} } = {}) => {
+ mock.onGet(props.endpoint).replyOnce(200, { ...job, ...jobData });
+ mock.onGet(`${props.pagePath}/trace.json`).reply(200, traceData);
+
+ vm = mountComponentWithStore(Component, { props, store });
+
+ return waitForJobReceived();
+ };
+
beforeEach(() => {
mock = new MockAdapter(axios);
store = createStore();
@@ -38,103 +52,81 @@ describe('Job App ', () => {
describe('while loading', () => {
beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, job, {});
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {});
- vm = mountComponentWithStore(Component, { props, store });
+ setupAndMount();
});
- it('renders loading icon', done => {
+ it('renders loading icon', () => {
expect(vm.$el.querySelector('.js-job-loading')).not.toBeNull();
expect(vm.$el.querySelector('.js-job-sidebar')).toBeNull();
expect(vm.$el.querySelector('.js-job-content')).toBeNull();
-
- setTimeout(() => {
- done();
- }, 0);
});
});
describe('with successful request', () => {
- beforeEach(() => {
- mock.onGet(`${props.pagePath}/trace.json`).replyOnce(200, {});
- });
-
describe('Header section', () => {
describe('job callout message', () => {
it('should not render the reason when reason is absent', done => {
- mock.onGet(props.endpoint).replyOnce(200, job);
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(vm.shouldRenderCalloutMessage).toBe(false);
-
- done();
- }, 0);
+ setupAndMount()
+ .then(() => {
+ expect(vm.shouldRenderCalloutMessage).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should render the reason when reason is present', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
- callout_message: 'There is an unknown failure, please try again',
- }),
- );
-
- vm = mountComponentWithStore(Component, { props, store });
- setTimeout(() => {
- expect(vm.shouldRenderCalloutMessage).toBe(true);
- done();
- }, 0);
+ setupAndMount({
+ jobData: {
+ callout_message: 'There is an unkown failure, please try again',
+ },
+ })
+ .then(() => {
+ expect(vm.shouldRenderCalloutMessage).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('triggered job', () => {
- beforeEach(() => {
+ beforeEach(done => {
const aYearAgo = new Date();
aYearAgo.setFullYear(aYearAgo.getFullYear() - 1);
- mock
- .onGet(props.endpoint)
- .replyOnce(200, Object.assign({}, job, { started: aYearAgo.toISOString() }));
- vm = mountComponentWithStore(Component, { props, store });
+ setupAndMount({ jobData: { started: aYearAgo.toISOString() } })
+ .then(done)
+ .catch(done.fail);
});
- it('should render provided job information', done => {
- setTimeout(() => {
- expect(
- vm.$el
- .querySelector('.header-main-content')
- .textContent.replace(/\s+/g, ' ')
- .trim(),
- ).toContain('passed Job #4757 triggered 1 year ago by Root');
- done();
- }, 0);
+ it('should render provided job information', () => {
+ expect(
+ vm.$el
+ .querySelector('.header-main-content')
+ .textContent.replace(/\s+/g, ' ')
+ .trim(),
+ ).toContain('passed Job #4757 triggered 1 year ago by Root');
});
- it('should render new issue link', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
- job.new_issue_path,
- );
- done();
- }, 0);
+ it('should render new issue link', () => {
+ expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
+ job.new_issue_path,
+ );
});
});
describe('created job', () => {
it('should render created key', done => {
- mock.onGet(props.endpoint).replyOnce(200, job);
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(
- vm.$el
- .querySelector('.header-main-content')
- .textContent.replace(/\s+/g, ' ')
- .trim(),
- ).toContain('passed Job #4757 created 3 weeks ago by Root');
- done();
- }, 0);
+ setupAndMount()
+ .then(() => {
+ expect(
+ vm.$el
+ .querySelector('.header-main-content')
+ .textContent.replace(/\s+/g, ' ')
+ .trim(),
+ ).toContain('passed Job #4757 created 3 weeks ago by Root');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
@@ -142,9 +134,8 @@ describe('Job App ', () => {
describe('stuck block', () => {
describe('without active runners availabl', () => {
it('renders stuck block when there are no runners', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'pending',
icon: 'status_pending',
@@ -158,23 +149,23 @@ describe('Job App ', () => {
online: false,
},
tags: [],
- }),
- );
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
- expect(vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
+ expect(
+ vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner'),
+ ).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('when available runners can not run specified tag', () => {
it('renders tags in stuck block when there are no runners', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'pending',
icon: 'status_pending',
@@ -187,27 +178,21 @@ describe('Job App ', () => {
available: false,
online: false,
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
- expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('when runners are offline and build has tags', () => {
it('renders message about job being stuck because of no runners with the specified tags', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'pending',
icon: 'status_pending',
@@ -220,48 +205,35 @@ describe('Job App ', () => {
available: true,
online: true,
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
- expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
it('does not renders stuck block when there are no runners', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
runners: { available: true },
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck')).toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('unmet prerequisites block', () => {
it('renders unmet prerequisites block when there is an unmet prerequisites failure', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'failed',
icon: 'status_failed',
@@ -281,104 +253,81 @@ describe('Job App ', () => {
available: true,
},
tags: [],
- }),
- );
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('environments block', () => {
it('renders environment block when job has environment', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
deployment_status: {
environment: {
environment_path: '/path',
name: 'foo',
},
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render environment block when job has environment', done => {
- mock.onGet(props.endpoint).replyOnce(200, job);
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-environment')).toBeNull();
- done();
- }, 0);
+ setupAndMount()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-environment')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('erased block', () => {
it('renders erased block when `erased` is true', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
erased_by: {
username: 'root',
web_url: 'gitlab.com/root',
},
erased_at: '2016-11-07T11:11:16.525Z',
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render erased block when `erased` is false', done => {
- mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { erased_at: null }));
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull();
-
- done();
- }, 0);
+ setupAndMount({
+ jobData: {
+ erased_at: null,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('empty states block', () => {
it('renders empty state when job does not have trace and is not running', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
has_trace: false,
status: {
group: 'pending',
@@ -398,25 +347,18 @@ describe('Job App ', () => {
path: '/path',
},
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render empty state when job does not have trace but it is running', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
has_trace: false,
status: {
group: 'running',
@@ -425,34 +367,23 @@ describe('Job App ', () => {
text: 'running',
details_path: 'path',
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render empty state when job has trace but it is not running', done => {
- mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { has_trace: true }));
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
-
- done();
- }, 0);
+ setupAndMount({ jobData: { has_trace: true } })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
+ done();
});
it('displays remaining time for a delayed job', done => {
@@ -460,120 +391,114 @@ describe('Job App ', () => {
spyOn(Date, 'now').and.callFake(
() => new Date(delayedJobFixture.scheduled_at).getTime() - oneHourInMilliseconds,
);
- mock.onGet(props.endpoint).replyOnce(200, { ...delayedJobFixture });
+ setupAndMount({ jobData: delayedJobFixture })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- store.subscribeAction(action => {
- if (action.type !== 'receiveJobSuccess') {
- return;
- }
+ const title = vm.$el.querySelector('.js-job-empty-state-title');
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
-
- const title = vm.$el.querySelector('.js-job-empty-state-title');
+ expect(title).toContainText('01:00:00');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
- expect(title).toContainText('01:00:00');
- done();
- })
- .catch(done.fail);
- });
+ describe('sidebar', () => {
+ it('has no blank blocks', done => {
+ setupAndMount({
+ jobData: {
+ duration: null,
+ finished_at: null,
+ erased_at: null,
+ queued: null,
+ runner: null,
+ coverage: null,
+ tags: [],
+ cancel_path: null,
+ },
+ })
+ .then(() => {
+ vm.$el.querySelectorAll('.blocks-container > *').forEach(block => {
+ expect(block.textContent.trim()).not.toBe('');
+ });
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
describe('archived job', () => {
- beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, Object.assign({}, job, { archived: true }), {});
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
+ beforeEach(done => {
+ setupAndMount({ jobData: { archived: true } })
+ .then(done)
+ .catch(done.fail);
});
- it('renders warning about job being archived', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull();
- done();
- }, 0);
+ it('renders warning about job being archived', () => {
+ expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull();
});
});
describe('non-archived job', () => {
- beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, job, {});
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
+ beforeEach(done => {
+ setupAndMount()
+ .then(done)
+ .catch(done.fail);
});
- it('does not warning about job being archived', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-archived-job ')).toBeNull();
- done();
- }, 0);
+ it('does not warning about job being archived', () => {
+ expect(vm.$el.querySelector('.js-archived-job ')).toBeNull();
});
});
describe('trace output', () => {
- beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, job, {});
- });
-
describe('with append flag', () => {
it('appends the log content to the existing one', done => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>More<span>',
- status: 'running',
- state: 'newstate',
- append: true,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- vm.$store.state.trace = 'Update';
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update');
-
- done();
- }, 0);
+ setupAndMount({
+ traceData: {
+ html: '<span>More<span>',
+ status: 'running',
+ state: 'newstate',
+ append: true,
+ complete: true,
+ },
+ })
+ .then(() => {
+ vm.$store.state.trace = 'Update';
+
+ return vm.$nextTick();
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('without append flag', () => {
it('replaces the trace', done => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Different<span>',
- status: 'running',
- append: false,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
- vm.$store.state.trace = 'Update';
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain(
- 'Update',
- );
+ setupAndMount({
+ traceData: {
+ html: '<span>Different<span>',
+ status: 'running',
+ append: false,
+ complete: true,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain(
+ 'Update',
+ );
- expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Different');
- done();
- }, 0);
+ expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain(
+ 'Different',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
});
});
@@ -589,83 +514,76 @@ describe('Job App ', () => {
complete: true,
});
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain(
- '50 bytes',
- );
- done();
- }, 0);
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ complete: true,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain(
+ '50 bytes',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('when size is equal than total', () => {
it('does not show the truncated information', done => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 100,
- total: 100,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain(
- '50 bytes',
- );
- done();
- }, 0);
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 100,
+ total: 100,
+ complete: true,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain(
+ '50 bytes',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
describe('trace controls', () => {
- beforeEach(() => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
+ beforeEach(done => {
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ complete: true,
+ },
+ })
+ .then(done)
+ .catch(done.fail);
});
- it('should render scroll buttons', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull();
- expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull();
- done();
- }, 0);
+ it('should render scroll buttons', () => {
+ expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull();
});
- it('should render link to raw ouput', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull();
- done();
- }, 0);
+ it('should render link to raw ouput', () => {
+ expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull();
});
- it('should render link to erase job', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull();
- done();
- }, 0);
+ it('should render link to erase job', () => {
+ expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull();
});
});
});
diff --git a/spec/javascripts/jobs/components/job_log_spec.js b/spec/javascripts/jobs/components/job_log_spec.js
index 7e2ec2ec3f7..24bb6b9a48b 100644
--- a/spec/javascripts/jobs/components/job_log_spec.js
+++ b/spec/javascripts/jobs/components/job_log_spec.js
@@ -11,7 +11,7 @@ describe('Job Log', () => {
let vm;
const trace =
- 'Running with gitlab-runner 11.1.0 (081978aa)<br> on docker-auto-scale-com d5ae8d25<br>Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29 ...<br>';
+ '<span>Running with gitlab-runner 12.1.0 (de7731dd)<br/></span><span> on docker-auto-scale-com d5ae8d25<br/></span><div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="1565502765" data-section="prepare-executor" role="button"></div><span class="section js-section-header section-header js-s-prepare-executor">Using Docker executor with image ruby:2.6 ...<br/></span>';
beforeEach(() => {
store = createStore();
@@ -32,7 +32,7 @@ describe('Job Log', () => {
});
expect(vm.$el.querySelector('code').textContent).toContain(
- 'Running with gitlab-runner 11.1.0 (081978aa)',
+ 'Running with gitlab-runner 12.1.0 (de7731dd)',
);
});
@@ -98,5 +98,25 @@ describe('Job Log', () => {
.then(done)
.catch(done.fail);
});
+
+ it('toggles hidden class to the sibilings rows when header section is clicked', done => {
+ vm.$nextTick()
+ .then(() => {
+ const { section } = vm.$el.querySelector('.js-section-header').dataset;
+ vm.$el.querySelector('.js-section-header').click();
+
+ vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
+ expect(el.classList.contains('hidden')).toEqual(true);
+ });
+
+ vm.$el.querySelector('.js-section-header').click();
+
+ vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
+ expect(el.classList.contains('hidden')).toEqual(false);
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
});
diff --git a/spec/javascripts/jobs/components/manual_variables_form_spec.js b/spec/javascripts/jobs/components/manual_variables_form_spec.js
new file mode 100644
index 00000000000..093aa905185
--- /dev/null
+++ b/spec/javascripts/jobs/components/manual_variables_form_spec.js
@@ -0,0 +1,88 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import Form from '~/jobs/components/manual_variables_form.vue';
+
+describe('Manual Variables Form', () => {
+ let wrapper;
+ const requiredProps = {
+ action: {
+ path: '/play',
+ method: 'post',
+ button_title: 'Trigger this manual action',
+ },
+ variablesSettingsUrl: '/settings',
+ };
+
+ const factory = (props = {}) => {
+ wrapper = shallowMount(Form, {
+ propsData: props,
+ });
+ };
+
+ beforeEach(() => {
+ factory(requiredProps);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders empty form with correct placeholders', () => {
+ expect(wrapper.find({ ref: 'inputKey' }).attributes('placeholder')).toBe('Input variable key');
+ expect(wrapper.find({ ref: 'inputSecretValue' }).attributes('placeholder')).toBe(
+ 'Input variable value',
+ );
+ });
+
+ it('renders help text with provided link', () => {
+ expect(wrapper.find('p').text()).toBe(
+ 'Specify variable values to be used in this run. The values specified in CI/CD settings will be used as default',
+ );
+
+ expect(wrapper.find('a').attributes('href')).toBe(requiredProps.variablesSettingsUrl);
+ });
+
+ describe('when adding a new variable', () => {
+ it('creates a new variable when user types a new key and resets the form', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => wrapper.find({ ref: 'inputKey' }).setValue('new key'))
+ .then(() => {
+ expect(wrapper.vm.variables.length).toBe(1);
+ expect(wrapper.vm.variables[0].key).toBe('new key');
+ expect(wrapper.find({ ref: 'inputKey' }).attributes('value')).toBe(undefined);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('creates a new variable when user types a new value and resets the form', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => wrapper.find({ ref: 'inputSecretValue' }).setValue('new value'))
+ .then(() => {
+ expect(wrapper.vm.variables.length).toBe(1);
+ expect(wrapper.vm.variables[0].secret_value).toBe('new value');
+ expect(wrapper.find({ ref: 'inputSecretValue' }).attributes('value')).toBe(undefined);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('when deleting a variable', () => {
+ it('removes the variable row', () => {
+ wrapper.vm.variables = [
+ {
+ key: 'new key',
+ secret_value: 'value',
+ id: '1',
+ },
+ ];
+
+ wrapper.find(GlButton).vm.$emit('click');
+
+ expect(wrapper.vm.variables.length).toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 296ee85089f..85949f2ae86 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -895,6 +895,45 @@ describe('common_utils', () => {
});
});
+ describe('searchBy', () => {
+ const searchSpace = {
+ iid: 1,
+ reference: '&1',
+ title: 'Error omnis quos consequatur ullam a vitae sed omnis libero cupiditate.',
+ url: '/groups/gitlab-org/-/epics/1',
+ };
+
+ it('returns null when `query` or `searchSpace` params are empty/undefined', () => {
+ expect(commonUtils.searchBy('omnis', null)).toBeNull();
+ expect(commonUtils.searchBy('', searchSpace)).toBeNull();
+ expect(commonUtils.searchBy()).toBeNull();
+ });
+
+ it('returns object with matching props based on `query` & `searchSpace` params', () => {
+ // String `omnis` is found only in `title` prop so return just that
+ expect(commonUtils.searchBy('omnis', searchSpace)).toEqual(
+ jasmine.objectContaining({
+ title: searchSpace.title,
+ }),
+ );
+
+ // String `1` is found in both `iid` and `reference` props so return both
+ expect(commonUtils.searchBy('1', searchSpace)).toEqual(
+ jasmine.objectContaining({
+ iid: searchSpace.iid,
+ reference: searchSpace.reference,
+ }),
+ );
+
+ // String `/epics/1` is found in `url` prop so return just that
+ expect(commonUtils.searchBy('/epics/1', searchSpace)).toEqual(
+ jasmine.objectContaining({
+ url: searchSpace.url,
+ }),
+ );
+ });
+ });
+
describe('isScopedLabel', () => {
it('returns true when `::` is present in title', () => {
expect(commonUtils.isScopedLabel({ title: 'foo::bar' })).toBe(true);
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index d3a76f33679..1e49a955815 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -1,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
import { GlLink } from '@gitlab/ui';
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
import Area from '~/monitoring/components/charts/area.vue';
-import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import { TEST_HOST } from 'spec/test_constants';
import MonitoringMock, { deploymentData } from '../mock_data';
@@ -17,10 +17,10 @@ describe('Area component', () => {
let mockGraphData;
let areaChart;
let spriteSpy;
+ let store;
beforeEach(() => {
- const store = createStore();
-
+ store = createStore();
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
@@ -36,6 +36,7 @@ describe('Area component', () => {
slots: {
default: mockWidgets,
},
+ store,
});
spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake(
@@ -224,6 +225,14 @@ describe('Area component', () => {
});
describe('chartOptions', () => {
+ describe('dataZoom', () => {
+ it('contains an svg object within an array to properly render icon', () => {
+ const dataZoomObject = [{}];
+
+ expect(areaChart.vm.chartOptions.dataZoom).toEqual(dataZoomObject);
+ });
+ });
+
describe('yAxis formatter', () => {
let format;
diff --git a/spec/javascripts/monitoring/charts/empty_chart_spec.js b/spec/javascripts/monitoring/charts/empty_chart_spec.js
new file mode 100644
index 00000000000..bbfca27dc5a
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/empty_chart_spec.js
@@ -0,0 +1,29 @@
+import { shallowMount } from '@vue/test-utils';
+import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
+
+describe('Empty Chart component', () => {
+ let emptyChart;
+ const graphTitle = 'Memory Usage';
+
+ beforeEach(() => {
+ emptyChart = shallowMount(EmptyChart, {
+ propsData: {
+ graphTitle,
+ },
+ });
+ });
+
+ afterEach(() => {
+ emptyChart.destroy();
+ });
+
+ it('render the chart title', () => {
+ expect(emptyChart.find({ ref: 'graphTitle' }).text()).toBe(graphTitle);
+ });
+
+ describe('Computed props', () => {
+ it('sets the height for the svg container', () => {
+ expect(emptyChart.vm.svgContainerStyle.height).toBe('300px');
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/charts/single_stat_spec.js b/spec/javascripts/monitoring/charts/single_stat_spec.js
index 12b73002f97..127a4a7955a 100644
--- a/spec/javascripts/monitoring/charts/single_stat_spec.js
+++ b/spec/javascripts/monitoring/charts/single_stat_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import SingleStatChart from '~/monitoring/components/charts/single_stat.vue';
+import { graphDataPrometheusQuery } from '../mock_data';
describe('Single Stat Chart component', () => {
let singleStatChart;
@@ -7,9 +8,7 @@ describe('Single Stat Chart component', () => {
beforeEach(() => {
singleStatChart = shallowMount(SingleStatChart, {
propsData: {
- title: 'Time to render',
- value: 1,
- unit: 'sec',
+ graphData: graphDataPrometheusQuery,
},
});
});
@@ -19,9 +18,9 @@ describe('Single Stat Chart component', () => {
});
describe('computed', () => {
- describe('valueWithUnit', () => {
+ describe('engineeringNotation', () => {
it('should interpolate the value and unit props', () => {
- expect(singleStatChart.vm.valueWithUnit).toBe('1sec');
+ expect(singleStatChart.vm.engineeringNotation).toBe('91MB');
});
});
});
diff --git a/spec/javascripts/monitoring/charts/time_series_spec.js b/spec/javascripts/monitoring/charts/time_series_spec.js
new file mode 100644
index 00000000000..d145a64e8d0
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/time_series_spec.js
@@ -0,0 +1,335 @@
+import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
+import { GlLink } from '@gitlab/ui';
+import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
+import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
+import TimeSeries from '~/monitoring/components/charts/time_series.vue';
+import * as types from '~/monitoring/stores/mutation_types';
+import { TEST_HOST } from 'spec/test_constants';
+import MonitoringMock, { deploymentData, mockProjectPath } from '../mock_data';
+
+describe('Time series component', () => {
+ const mockSha = 'mockSha';
+ const mockWidgets = 'mockWidgets';
+ const mockSvgPathContent = 'mockSvgPathContent';
+ const projectPath = `${TEST_HOST}${mockProjectPath}`;
+ const commitUrl = `${projectPath}/commit/${mockSha}`;
+ let mockGraphData;
+ let makeTimeSeriesChart;
+ let spriteSpy;
+ let store;
+
+ beforeEach(() => {
+ store = createStore();
+ store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
+ store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
+ store.dispatch('monitoringDashboard/setFeatureFlags', { exportMetricsToCsvEnabled: true });
+ [mockGraphData] = store.state.monitoringDashboard.groups[0].metrics;
+
+ makeTimeSeriesChart = (graphData, type) =>
+ shallowMount(TimeSeries, {
+ propsData: {
+ graphData: { ...graphData, type },
+ containerWidth: 0,
+ deploymentData: store.state.monitoringDashboard.deploymentData,
+ projectPath,
+ },
+ slots: {
+ default: mockWidgets,
+ },
+ sync: false,
+ store,
+ });
+
+ spriteSpy = spyOnDependency(TimeSeries, 'getSvgIconPathContent').and.callFake(
+ () => new Promise(resolve => resolve(mockSvgPathContent)),
+ );
+ });
+
+ describe('general functions', () => {
+ let timeSeriesChart;
+
+ beforeEach(() => {
+ timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart');
+ });
+
+ it('renders chart title', () => {
+ expect(timeSeriesChart.find('.js-graph-title').text()).toBe(mockGraphData.title);
+ });
+
+ it('contains graph widgets from slot', () => {
+ expect(timeSeriesChart.find('.js-graph-widgets').text()).toBe(mockWidgets);
+ });
+
+ describe('when exportMetricsToCsvEnabled is disabled', () => {
+ beforeEach(() => {
+ store.dispatch('monitoringDashboard/setFeatureFlags', { exportMetricsToCsvEnabled: false });
+ });
+
+ it('does not render the Download CSV button', done => {
+ timeSeriesChart.vm.$nextTick(() => {
+ expect(timeSeriesChart.contains('glbutton-stub')).toBe(false);
+ done();
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('formatTooltipText', () => {
+ const mockDate = deploymentData[0].created_at;
+ const mockCommitUrl = deploymentData[0].commitUrl;
+ const generateSeriesData = type => ({
+ seriesData: [
+ {
+ seriesName: timeSeriesChart.vm.chartData[0].name,
+ componentSubType: type,
+ value: [mockDate, 5.55555],
+ seriesIndex: 0,
+ },
+ ],
+ value: mockDate,
+ });
+
+ describe('when series is of line type', () => {
+ beforeEach(done => {
+ timeSeriesChart.vm.formatTooltipText(generateSeriesData('line'));
+ timeSeriesChart.vm.$nextTick(done);
+ });
+
+ it('formats tooltip title', () => {
+ expect(timeSeriesChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM');
+ });
+
+ it('formats tooltip content', () => {
+ const name = 'Core Usage';
+ const value = '5.556';
+ const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel);
+
+ expect(seriesLabel.vm.color).toBe('');
+ expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true);
+ expect(timeSeriesChart.vm.tooltip.content).toEqual([{ name, value, color: undefined }]);
+ expect(
+ shallowWrapperContainsSlotText(
+ timeSeriesChart.find(GlAreaChart),
+ 'tooltipContent',
+ value,
+ ),
+ ).toBe(true);
+ });
+ });
+
+ describe('when series is of scatter type', () => {
+ beforeEach(() => {
+ timeSeriesChart.vm.formatTooltipText(generateSeriesData('scatter'));
+ });
+
+ it('formats tooltip title', () => {
+ expect(timeSeriesChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM');
+ });
+
+ it('formats tooltip sha', () => {
+ expect(timeSeriesChart.vm.tooltip.sha).toBe('f5bcd1d9');
+ });
+
+ it('formats tooltip commit url', () => {
+ expect(timeSeriesChart.vm.tooltip.commitUrl).toBe(mockCommitUrl);
+ });
+ });
+ });
+
+ describe('setSvg', () => {
+ const mockSvgName = 'mockSvgName';
+
+ beforeEach(done => {
+ timeSeriesChart.vm.setSvg(mockSvgName);
+ timeSeriesChart.vm.$nextTick(done);
+ });
+
+ it('gets svg path content', () => {
+ expect(spriteSpy).toHaveBeenCalledWith(mockSvgName);
+ });
+
+ it('sets svg path content', () => {
+ timeSeriesChart.vm.$nextTick(() => {
+ expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`);
+ });
+ });
+ });
+
+ describe('onResize', () => {
+ const mockWidth = 233;
+
+ beforeEach(() => {
+ spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({
+ width: mockWidth,
+ }));
+ timeSeriesChart.vm.onResize();
+ });
+
+ it('sets area chart width', () => {
+ expect(timeSeriesChart.vm.width).toBe(mockWidth);
+ });
+ });
+ });
+
+ describe('computed', () => {
+ describe('chartData', () => {
+ let chartData;
+ const seriesData = () => chartData[0];
+
+ beforeEach(() => {
+ ({ chartData } = timeSeriesChart.vm);
+ });
+
+ it('utilizes all data points', () => {
+ const { values } = mockGraphData.queries[0].result[0];
+
+ expect(chartData.length).toBe(1);
+ expect(seriesData().data.length).toBe(values.length);
+ });
+
+ it('creates valid data', () => {
+ const { data } = seriesData();
+
+ expect(
+ data.filter(
+ ([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number',
+ ).length,
+ ).toBe(data.length);
+ });
+
+ it('formats line width correctly', () => {
+ expect(chartData[0].lineStyle.width).toBe(2);
+ });
+ });
+
+ describe('chartOptions', () => {
+ describe('yAxis formatter', () => {
+ let format;
+
+ beforeEach(() => {
+ format = timeSeriesChart.vm.chartOptions.yAxis.axisLabel.formatter;
+ });
+
+ it('rounds to 3 decimal places', () => {
+ expect(format(0.88888)).toBe('0.889');
+ });
+ });
+ });
+
+ describe('scatterSeries', () => {
+ it('utilizes deployment data', () => {
+ expect(timeSeriesChart.vm.scatterSeries.data).toEqual([
+ ['2017-05-31T21:23:37.881Z', 0],
+ ['2017-05-30T20:08:04.629Z', 0],
+ ['2017-05-30T17:42:38.409Z', 0],
+ ]);
+
+ expect(timeSeriesChart.vm.scatterSeries.symbolSize).toBe(14);
+ });
+ });
+
+ describe('yAxisLabel', () => {
+ it('constructs a label for the chart y-axis', () => {
+ expect(timeSeriesChart.vm.yAxisLabel).toBe('CPU');
+ });
+ });
+
+ describe('csvText', () => {
+ it('converts data from json to csv', () => {
+ const header = `timestamp,${mockGraphData.y_label}`;
+ const data = mockGraphData.queries[0].result[0].values;
+ const firstRow = `${data[0][0]},${data[0][1]}`;
+
+ expect(timeSeriesChart.vm.csvText).toMatch(`^${header}\r\n${firstRow}`);
+ });
+ });
+
+ describe('downloadLink', () => {
+ it('produces a link to download metrics as csv', () => {
+ const link = timeSeriesChart.vm.downloadLink;
+
+ expect(link).toContain('blob:');
+ });
+ });
+ });
+
+ afterEach(() => {
+ timeSeriesChart.destroy();
+ });
+ });
+
+ describe('wrapped components', () => {
+ const glChartComponents = [
+ {
+ chartType: 'area-chart',
+ component: GlAreaChart,
+ },
+ {
+ chartType: 'line-chart',
+ component: GlLineChart,
+ },
+ ];
+
+ glChartComponents.forEach(dynamicComponent => {
+ describe(`GitLab UI: ${dynamicComponent.chartType}`, () => {
+ let timeSeriesAreaChart;
+ let glChart;
+
+ beforeEach(done => {
+ timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType);
+ glChart = timeSeriesAreaChart.find(dynamicComponent.component);
+ timeSeriesAreaChart.vm.$nextTick(done);
+ });
+
+ it('is a Vue instance', () => {
+ expect(glChart.exists()).toBe(true);
+ expect(glChart.isVueInstance()).toBe(true);
+ });
+
+ it('receives data properties needed for proper chart render', () => {
+ const props = glChart.props();
+
+ expect(props.data).toBe(timeSeriesAreaChart.vm.chartData);
+ expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions);
+ expect(props.formatTooltipText).toBe(timeSeriesAreaChart.vm.formatTooltipText);
+ expect(props.thresholds).toBe(timeSeriesAreaChart.vm.thresholds);
+ });
+
+ it('recieves a tooltip title', done => {
+ const mockTitle = 'mockTitle';
+ timeSeriesAreaChart.vm.tooltip.title = mockTitle;
+
+ timeSeriesAreaChart.vm.$nextTick(() => {
+ expect(shallowWrapperContainsSlotText(glChart, 'tooltipTitle', mockTitle)).toBe(true);
+ done();
+ });
+ });
+
+ describe('when tooltip is showing deployment data', () => {
+ beforeEach(done => {
+ timeSeriesAreaChart.vm.tooltip.isDeployment = true;
+ timeSeriesAreaChart.vm.$nextTick(done);
+ });
+
+ it('uses deployment title', () => {
+ expect(shallowWrapperContainsSlotText(glChart, 'tooltipTitle', 'Deployed')).toBe(true);
+ });
+
+ it('renders clickable commit sha in tooltip content', done => {
+ timeSeriesAreaChart.vm.tooltip.sha = mockSha;
+ timeSeriesAreaChart.vm.tooltip.commitUrl = commitUrl;
+
+ timeSeriesAreaChart.vm.$nextTick(() => {
+ const commitLink = timeSeriesAreaChart.find(GlLink);
+
+ expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true);
+ expect(commitLink.attributes('href')).toEqual(commitUrl);
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js
index ab8360193be..f3ec7520c6f 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/components/dashboard_spec.js
@@ -1,18 +1,21 @@
import Vue from 'vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlToast } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
import * as types from '~/monitoring/stores/mutation_types';
import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
-import {
+import MonitoringMock, {
metricsGroupsAPIResponse,
mockApiEndpoint,
environmentData,
singleGroupResponse,
dashboardGitResponse,
-} from './mock_data';
+} from '../mock_data';
+const localVue = createLocalVue();
const propsData = {
hasMetrics: false,
documentationPath: '/path/to/docs',
@@ -40,6 +43,7 @@ describe('Dashboard', () => {
let mock;
let store;
let component;
+ let mockGraphData;
beforeEach(() => {
setFixtures(`
@@ -58,7 +62,9 @@ describe('Dashboard', () => {
});
afterEach(() => {
- component.$destroy();
+ if (component) {
+ component.$destroy();
+ }
mock.restore();
});
@@ -241,7 +247,7 @@ describe('Dashboard', () => {
Vue.nextTick()
.then(() => {
const dropdownItems = component.$el.querySelectorAll(
- '.js-environments-dropdown .dropdown-item.is-active',
+ '.js-environments-dropdown .dropdown-item.active',
);
expect(dropdownItems.length).toEqual(1);
@@ -307,7 +313,7 @@ describe('Dashboard', () => {
});
spyOn(component.$store, 'dispatch').and.stub();
- const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff');
+ const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff').and.callThrough();
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
@@ -319,7 +325,7 @@ describe('Dashboard', () => {
Vue.nextTick()
.then(() => {
expect(component.$store.dispatch).toHaveBeenCalled();
- expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow);
+ expect(getTimeDiffSpy).toHaveBeenCalled();
done();
})
@@ -327,7 +333,17 @@ describe('Dashboard', () => {
});
it('shows a specific time window selected from the url params', done => {
- spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']);
+ const start = 1564439536;
+ const end = 1564441336;
+ spyOnDependency(Dashboard, 'getTimeDiff').and.returnValue({
+ start,
+ end,
+ });
+ spyOnDependency(Dashboard, 'getParameterValues').and.callFake(param => {
+ if (param === 'start') return [start];
+ if (param === 'end') return [end];
+ return [];
+ });
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
@@ -362,6 +378,71 @@ describe('Dashboard', () => {
});
});
+ describe('link to chart', () => {
+ let wrapper;
+ const currentDashboard = 'TEST_DASHBOARD';
+ localVue.use(GlToast);
+ const link = () => wrapper.find('.js-chart-link');
+ const clipboardText = () => link().element.dataset.clipboardText;
+
+ beforeEach(done => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+
+ wrapper = shallowMount(DashboardComponent, {
+ localVue,
+ sync: false,
+ attachToDocument: true,
+ propsData: { ...propsData, hasMetrics: true, currentDashboard },
+ store,
+ });
+
+ setTimeout(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('adds a copy button to the dropdown', () => {
+ expect(link().text()).toContain('Generate link to chart');
+ });
+
+ it('contains a link to the dashboard', () => {
+ expect(clipboardText()).toContain(`dashboard=${currentDashboard}`);
+ expect(clipboardText()).toContain(`group=`);
+ expect(clipboardText()).toContain(`title=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ });
+
+ it('undefined parameter is stripped', done => {
+ wrapper.setProps({ currentDashboard: undefined });
+
+ wrapper.vm.$nextTick(() => {
+ expect(clipboardText()).not.toContain(`dashboard=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ done();
+ });
+ });
+
+ it('null parameter is stripped', done => {
+ wrapper.setProps({ currentDashboard: null });
+
+ wrapper.vm.$nextTick(() => {
+ expect(clipboardText()).not.toContain(`dashboard=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ done();
+ });
+ });
+
+ it('creates a toast when clicked', () => {
+ spyOn(wrapper.vm.$toast, 'show').and.stub();
+
+ link().vm.$emit('click');
+
+ expect(wrapper.vm.$toast.show).toHaveBeenCalled();
+ });
+ });
+
describe('when the window resizes', () => {
beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
@@ -472,4 +553,36 @@ describe('Dashboard', () => {
});
});
});
+
+ describe('when downloading metrics data as CSV', () => {
+ beforeEach(() => {
+ component = new DashboardComponent({
+ propsData: {
+ ...propsData,
+ },
+ store,
+ });
+ store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ MonitoringMock.data,
+ );
+ [mockGraphData] = component.$store.state.monitoringDashboard.groups[0].metrics;
+ });
+
+ describe('csvText', () => {
+ it('converts metrics data from json to csv', () => {
+ const header = `timestamp,${mockGraphData.y_label}`;
+ const data = mockGraphData.queries[0].result[0].values;
+ const firstRow = `${data[0][0]},${data[0][1]}`;
+
+ expect(component.csvText(mockGraphData)).toMatch(`^${header}\r\n${firstRow}`);
+ });
+ });
+
+ describe('downloadCsv', () => {
+ it('produces a link with a Blob', () => {
+ expect(component.downloadCsv(mockGraphData)).toContain(`blob:`);
+ });
+ });
+ });
});
diff --git a/spec/javascripts/monitoring/dashboard_state_spec.js b/spec/javascripts/monitoring/dashboard_state_spec.js
deleted file mode 100644
index 6b2be83aa8c..00000000000
--- a/spec/javascripts/monitoring/dashboard_state_spec.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import Vue from 'vue';
-import EmptyState from '~/monitoring/components/empty_state.vue';
-import { statePaths } from './mock_data';
-
-function createComponent(props) {
- const Component = Vue.extend(EmptyState);
-
- return new Component({
- propsData: {
- ...props,
- settingsPath: statePaths.settingsPath,
- clustersPath: statePaths.clustersPath,
- documentationPath: statePaths.documentationPath,
- emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
- emptyLoadingSvgPath: '/path/to/loading.svg',
- emptyNoDataSvgPath: '/path/to/no-data.svg',
- emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
- },
- }).$mount();
-}
-
-function getTextFromNode(component, selector) {
- return component.$el.querySelector(selector).firstChild.nodeValue.trim();
-}
-
-describe('EmptyState', () => {
- describe('Computed props', () => {
- it('currentState', () => {
- const component = createComponent({
- selectedState: 'gettingStarted',
- });
-
- expect(component.currentState).toBe(component.states.gettingStarted);
- });
-
- it('showButtonDescription returns a description with a link for the unableToConnect state', () => {
- const component = createComponent({
- selectedState: 'unableToConnect',
- });
-
- expect(component.showButtonDescription).toEqual(true);
- });
-
- it('showButtonDescription returns the description without a link for any other state', () => {
- const component = createComponent({
- selectedState: 'loading',
- });
-
- expect(component.showButtonDescription).toEqual(false);
- });
- });
-
- it('should show the gettingStarted state', () => {
- const component = createComponent({
- selectedState: 'gettingStarted',
- });
-
- expect(component.$el.querySelector('svg')).toBeDefined();
- expect(getTextFromNode(component, '.state-title')).toEqual(
- component.states.gettingStarted.title,
- );
-
- expect(getTextFromNode(component, '.state-description')).toEqual(
- component.states.gettingStarted.description,
- );
-
- expect(getTextFromNode(component, '.btn-success')).toEqual(
- component.states.gettingStarted.buttonText,
- );
- });
-
- it('should show the loading state', () => {
- const component = createComponent({
- selectedState: 'loading',
- });
-
- expect(component.$el.querySelector('svg')).toBeDefined();
- expect(getTextFromNode(component, '.state-title')).toEqual(component.states.loading.title);
- expect(getTextFromNode(component, '.state-description')).toEqual(
- component.states.loading.description,
- );
-
- expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.loading.buttonText);
- });
-
- it('should show the unableToConnect state', () => {
- const component = createComponent({
- selectedState: 'unableToConnect',
- });
-
- expect(component.$el.querySelector('svg')).toBeDefined();
- expect(getTextFromNode(component, '.state-title')).toEqual(
- component.states.unableToConnect.title,
- );
-
- expect(component.$el.querySelector('.state-description a')).toBeDefined();
- expect(getTextFromNode(component, '.btn-success')).toEqual(
- component.states.unableToConnect.buttonText,
- );
- });
-});
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index 7bbb215475a..17e7314e214 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -1,5 +1,7 @@
export const mockApiEndpoint = `${gl.TEST_HOST}/monitoring/mock`;
+export const mockProjectPath = '/frontend-fixtures/environments-project';
+
export const metricsGroupsAPIResponse = {
success: true,
data: [
@@ -902,7 +904,7 @@ export const metricsDashboardResponse = {
},
{
title: 'Memory Usage (Pod average)',
- type: 'area-chart',
+ type: 'line-chart',
y_label: 'Memory Used per Pod',
weight: 2,
metrics: [
@@ -935,3 +937,75 @@ export const dashboardGitResponse = [
default: false,
},
];
+
+export const graphDataPrometheusQuery = {
+ title: 'Super Chart A2',
+ type: 'single-stat',
+ weight: 2,
+ metrics: [
+ {
+ id: 'metric_a1',
+ metric_id: 2,
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ },
+ ],
+ queries: [
+ {
+ metricId: null,
+ id: 'metric_a1',
+ metric_id: 2,
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ result: [
+ {
+ metric: { job: 'prometheus' },
+ value: ['2019-06-26T21:03:20.881Z', 91],
+ },
+ ],
+ },
+ ],
+};
+
+export const graphDataPrometheusQueryRange = {
+ title: 'Super Chart A1',
+ type: 'area',
+ weight: 2,
+ metrics: [
+ {
+ id: 'metric_a1',
+ metric_id: 2,
+ query_range:
+ 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ },
+ ],
+ queries: [
+ {
+ metricId: null,
+ id: 'metric_a1',
+ metric_id: 2,
+ query_range:
+ 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ result: [
+ {
+ metric: {},
+ values: [[1495700554.925, '8.0390625'], [1495700614.925, '8.0390625']],
+ },
+ ],
+ },
+ ],
+};
diff --git a/spec/javascripts/monitoring/panel_type_spec.js b/spec/javascripts/monitoring/panel_type_spec.js
new file mode 100644
index 00000000000..a2366e74d43
--- /dev/null
+++ b/spec/javascripts/monitoring/panel_type_spec.js
@@ -0,0 +1,79 @@
+import { shallowMount } from '@vue/test-utils';
+import PanelType from '~/monitoring/components/panel_type.vue';
+import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
+import TimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
+import { graphDataPrometheusQueryRange } from './mock_data';
+import { createStore } from '~/monitoring/stores';
+
+describe('Panel Type component', () => {
+ let store;
+ let panelType;
+ const dashboardWidth = 100;
+
+ describe('When no graphData is available', () => {
+ let glEmptyChart;
+ // Deep clone object before modifying
+ const graphDataNoResult = JSON.parse(JSON.stringify(graphDataPrometheusQueryRange));
+ graphDataNoResult.queries[0].result = [];
+
+ beforeEach(() => {
+ panelType = shallowMount(PanelType, {
+ propsData: {
+ clipboardText: 'dashboard_link',
+ dashboardWidth,
+ graphData: graphDataNoResult,
+ },
+ });
+ });
+
+ afterEach(() => {
+ panelType.destroy();
+ });
+
+ describe('Empty Chart component', () => {
+ beforeEach(() => {
+ glEmptyChart = panelType.find(EmptyChart);
+ });
+
+ it('is a Vue instance', () => {
+ expect(glEmptyChart.isVueInstance()).toBe(true);
+ });
+
+ it('it receives a graph title', () => {
+ const props = glEmptyChart.props();
+
+ expect(props.graphTitle).toBe(panelType.vm.graphData.title);
+ });
+ });
+ });
+
+ describe('when Graph data is available', () => {
+ const exampleText = 'example_text';
+
+ beforeEach(() => {
+ store = createStore();
+ panelType = shallowMount(PanelType, {
+ propsData: {
+ clipboardText: exampleText,
+ dashboardWidth,
+ graphData: graphDataPrometheusQueryRange,
+ },
+ store,
+ });
+ });
+
+ describe('Time Series Chart panel type', () => {
+ it('is rendered', () => {
+ expect(panelType.find(TimeSeriesChart).isVueInstance()).toBe(true);
+ expect(panelType.find(TimeSeriesChart).exists()).toBe(true);
+ });
+
+ it('sets clipboard text on the dropdown', () => {
+ const link = () => panelType.find('.js-chart-link');
+ const clipboardText = () => link().element.dataset.clipboardText;
+
+ expect(clipboardText()).toBe(exampleText);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js
index 677455275de..955a39e03a5 100644
--- a/spec/javascripts/monitoring/store/actions_spec.js
+++ b/spec/javascripts/monitoring/store/actions_spec.js
@@ -313,8 +313,8 @@ describe('Monitoring store actions', () => {
it('commits prometheus query result', done => {
const commit = jasmine.createSpy();
const params = {
- start: '1557216349.469',
- end: '1557218149.469',
+ start: '2019-08-06T12:40:02.184Z',
+ end: '2019-08-06T20:40:02.184Z',
};
const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0];
const state = storeState();
diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js
index 91580366531..43776b1b7f2 100644
--- a/spec/javascripts/monitoring/store/mutations_spec.js
+++ b/spec/javascripts/monitoring/store/mutations_spec.js
@@ -115,12 +115,14 @@ describe('Monitoring mutations', () => {
environmentsEndpoint: 'environments.json',
deploymentsEndpoint: 'deployments.json',
dashboardEndpoint: 'dashboard.json',
+ projectPath: '/gitlab-org/gitlab-ce',
});
expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json');
expect(stateCopy.environmentsEndpoint).toEqual('environments.json');
expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json');
expect(stateCopy.dashboardEndpoint).toEqual('dashboard.json');
+ expect(stateCopy.projectPath).toEqual('/gitlab-org/gitlab-ce');
});
});
diff --git a/spec/javascripts/monitoring/store/utils_spec.js b/spec/javascripts/monitoring/store/utils_spec.js
new file mode 100644
index 00000000000..73dd370ffb3
--- /dev/null
+++ b/spec/javascripts/monitoring/store/utils_spec.js
@@ -0,0 +1,37 @@
+import { groupQueriesByChartInfo } from '~/monitoring/stores/utils';
+
+describe('groupQueriesByChartInfo', () => {
+ let input;
+ let output;
+
+ it('groups metrics with the same chart title and y_axis label', () => {
+ input = [
+ { title: 'title', y_label: 'MB', queries: [{}] },
+ { title: 'title', y_label: 'MB', queries: [{}] },
+ { title: 'new title', y_label: 'MB', queries: [{}] },
+ ];
+
+ output = [
+ { title: 'title', y_label: 'MB', queries: [{ metricId: null }, { metricId: null }] },
+ { title: 'new title', y_label: 'MB', queries: [{ metricId: null }] },
+ ];
+
+ expect(groupQueriesByChartInfo(input)).toEqual(output);
+ });
+
+ // Functionality associated with the /additional_metrics endpoint
+ it("associates a chart's stringified metric_id with the metric", () => {
+ input = [{ id: 3, title: 'new title', y_label: 'MB', queries: [{}] }];
+ output = [{ id: 3, title: 'new title', y_label: 'MB', queries: [{ metricId: '3' }] }];
+
+ expect(groupQueriesByChartInfo(input)).toEqual(output);
+ });
+
+ // Functionality associated with the /metrics_dashboard endpoint
+ it('aliases a stringified metrics_id on the metric to the metricId key', () => {
+ input = [{ title: 'new title', y_label: 'MB', queries: [{ metric_id: 3 }] }];
+ output = [{ title: 'new title', y_label: 'MB', queries: [{ metricId: '3', metric_id: 3 }] }];
+
+ expect(groupQueriesByChartInfo(input)).toEqual(output);
+ });
+});
diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js
index e3c455d1686..e22e8cdc03d 100644
--- a/spec/javascripts/monitoring/utils_spec.js
+++ b/spec/javascripts/monitoring/utils_spec.js
@@ -1,29 +1,64 @@
-import { getTimeDiff } from '~/monitoring/utils';
+import { getTimeDiff, graphDataValidatorForValues } from '~/monitoring/utils';
import { timeWindows } from '~/monitoring/constants';
+import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data';
describe('getTimeDiff', () => {
+ function secondsBetween({ start, end }) {
+ return (new Date(end) - new Date(start)) / 1000;
+ }
+
+ function minutesBetween(timeRange) {
+ return secondsBetween(timeRange) / 60;
+ }
+
+ function hoursBetween(timeRange) {
+ return minutesBetween(timeRange) / 60;
+ }
+
it('defaults to an 8 hour (28800s) difference', () => {
const params = getTimeDiff();
- expect(params.end - params.start).toEqual(28800);
+ expect(hoursBetween(params)).toEqual(8);
});
it('accepts time window as an argument', () => {
- const params = getTimeDiff(timeWindows.thirtyMinutes);
+ const params = getTimeDiff('thirtyMinutes');
- expect(params.end - params.start).not.toEqual(28800);
+ expect(minutesBetween(params)).toEqual(30);
});
it('returns a value for every defined time window', () => {
const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours');
- nonDefaultWindows.forEach(window => {
- const params = getTimeDiff(timeWindows[window]);
- const diff = params.end - params.start;
+ nonDefaultWindows.forEach(timeWindow => {
+ const params = getTimeDiff(timeWindow);
- // Ensure we're not returning the default, 28800 (the # of seconds in 8 hrs)
- expect(diff).not.toEqual(28800);
- expect(typeof diff).toEqual('number');
+ // Ensure we're not returning the default
+ expect(hoursBetween(params)).not.toEqual(8);
});
});
});
+
+describe('graphDataValidatorForValues', () => {
+ /*
+ * When dealing with a metric using the query format, e.g.
+ * query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024'
+ * the validator will look for the `value` key instead of `values`
+ */
+ it('validates data with the query format', () => {
+ const validGraphData = graphDataValidatorForValues(true, graphDataPrometheusQuery);
+
+ expect(validGraphData).toBe(true);
+ });
+
+ /*
+ * When dealing with a metric using the query?range format, e.g.
+ * query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ * the validator will look for the `values` key instead of `value`
+ */
+ it('validates data with the query_range format', () => {
+ const validGraphData = graphDataValidatorForValues(false, graphDataPrometheusQueryRange);
+
+ expect(validGraphData).toBe(true);
+ });
+});
diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js
index 362963ddaf4..88c86746992 100644
--- a/spec/javascripts/notes/components/comment_form_spec.js
+++ b/spec/javascripts/notes/components/comment_form_spec.js
@@ -251,6 +251,21 @@ describe('issue_comment_form component', () => {
});
});
});
+
+ describe('when toggling state', () => {
+ it('should update MR count', done => {
+ spyOn(vm, 'closeIssue').and.returnValue(Promise.resolve());
+
+ const updateMrCountSpy = spyOnDependency(CommentForm, 'refreshUserMergeRequestCounts');
+ vm.toggleIssueState();
+
+ Vue.nextTick(() => {
+ expect(updateMrCountSpy).toHaveBeenCalled();
+
+ done();
+ });
+ });
+ });
});
describe('issue is confidential', () => {
diff --git a/spec/javascripts/notes/components/note_actions/reply_button_spec.js b/spec/javascripts/notes/components/note_actions/reply_button_spec.js
index 11fb89808d9..003773d07ea 100644
--- a/spec/javascripts/notes/components/note_actions/reply_button_spec.js
+++ b/spec/javascripts/notes/components/note_actions/reply_button_spec.js
@@ -25,8 +25,7 @@ describe('ReplyButton', () => {
button.trigger('click');
- expect(wrapper.emitted()).toEqual({
- startReplying: [[]],
- });
+ expect(wrapper.emitted().startReplying).toBeTruthy();
+ expect(wrapper.emitted().startReplying.length).toBe(1);
});
});
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index efa864e7d00..74805ca8c00 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -36,14 +36,20 @@ describe('noteable_discussion component', () => {
});
it('should render user avatar', () => {
+ const discussion = { ...discussionMock };
+ discussion.diff_file = mockDiffFile;
+ discussion.diff_discussion = true;
+
+ wrapper.setProps({ discussion, renderDiffFile: true });
+
expect(wrapper.find('.user-avatar-link').exists()).toBe(true);
});
- it('should not render discussion header for non diff discussions', () => {
+ it('should not render thread header for non diff threads', () => {
expect(wrapper.find('.discussion-header').exists()).toBe(false);
});
- it('should render discussion header', done => {
+ it('should render thread header', done => {
const discussion = { ...discussionMock };
discussion.diff_file = mockDiffFile;
discussion.diff_discussion = true;
@@ -90,16 +96,16 @@ describe('noteable_discussion component', () => {
.catch(done.fail);
});
- it('does not render jump to discussion button', () => {
- expect(
- wrapper.find('*[data-original-title="Jump to next unresolved discussion"]').exists(),
- ).toBe(false);
+ it('does not render jump to thread button', () => {
+ expect(wrapper.find('*[data-original-title="Jump to next unresolved thread"]').exists()).toBe(
+ false,
+ );
});
});
describe('methods', () => {
describe('jumpToNextDiscussion', () => {
- it('expands next unresolved discussion', done => {
+ it('expands next unresolved thread', done => {
const discussion2 = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
discussion2.resolved = false;
discussion2.active = true;
@@ -114,9 +120,7 @@ describe('noteable_discussion component', () => {
const nextDiscussionId = discussion2.id;
- setFixtures(`
- <div class="discussion" data-discussion-id="${nextDiscussionId}"></div>
- `);
+ setFixtures(`<div class="discussion" data-discussion-id="${nextDiscussionId}"></div>`);
wrapper.vm.jumpToNextDiscussion();
@@ -162,20 +166,20 @@ describe('noteable_discussion component', () => {
.catch(done.fail);
});
- describe('for commit discussions', () => {
- it('should display a monospace started a discussion on commit', () => {
- expect(wrapper.text()).toContain(`started a discussion on commit ${truncatedCommitId}`);
+ describe('for commit threads', () => {
+ it('should display a monospace started a thread on commit', () => {
+ expect(wrapper.text()).toContain(`started a thread on commit ${truncatedCommitId}`);
expect(commitElement.exists()).toBe(true);
expect(commitElement.text()).toContain(truncatedCommitId);
});
});
- describe('for diff discussion with a commit id', () => {
- it('should display started discussion on commit header', done => {
+ describe('for diff thread with a commit id', () => {
+ it('should display started thread on commit header', done => {
wrapper.vm.discussion.for_commit = false;
wrapper.vm.$nextTick(() => {
- expect(wrapper.text()).toContain(`started a discussion on commit ${truncatedCommitId}`);
+ expect(wrapper.text()).toContain(`started a thread on commit ${truncatedCommitId}`);
expect(commitElement).not.toBe(null);
@@ -189,7 +193,7 @@ describe('noteable_discussion component', () => {
wrapper.vm.$nextTick(() => {
expect(wrapper.text()).toContain(
- `started a discussion on an outdated change in commit ${truncatedCommitId}`,
+ `started a thread on an outdated change in commit ${truncatedCommitId}`,
);
expect(commitElement).not.toBe(null);
@@ -199,21 +203,21 @@ describe('noteable_discussion component', () => {
});
});
- describe('for diff discussions without a commit id', () => {
- it('should show started a discussion on the diff text', done => {
+ describe('for diff threads without a commit id', () => {
+ it('should show started a thread on the diff text', done => {
Object.assign(wrapper.vm.discussion, {
for_commit: false,
commit_id: null,
});
wrapper.vm.$nextTick(() => {
- expect(wrapper.text()).toContain('started a discussion on the diff');
+ expect(wrapper.text()).toContain('started a thread on the diff');
done();
});
});
- it('should show discussion on older version text', done => {
+ it('should show thread on older version text', done => {
Object.assign(wrapper.vm.discussion, {
for_commit: false,
commit_id: null,
@@ -221,7 +225,7 @@ describe('noteable_discussion component', () => {
});
wrapper.vm.$nextTick(() => {
- expect(wrapper.text()).toContain('started a discussion on an old version of the diff');
+ expect(wrapper.text()).toContain('started a thread on an old version of the diff');
done();
});
@@ -229,7 +233,7 @@ describe('noteable_discussion component', () => {
});
});
- describe('for resolved discussion', () => {
+ describe('for resolved thread', () => {
beforeEach(() => {
const discussion = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
wrapper.setProps({ discussion });
@@ -242,7 +246,7 @@ describe('noteable_discussion component', () => {
});
});
- describe('for unresolved discussion', () => {
+ describe('for unresolved thread', () => {
beforeEach(done => {
const discussion = {
...getJSONFixture(discussionWithTwoUnresolvedNotes)[0],
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 1df5cf9ef68..5f81a168498 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -1,3 +1,5 @@
+// Copied to ee/spec/frontend/notes/mock_data.js
+
export const notesDataMock = {
discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json',
lastFetchedAt: 1501862675,
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index 7a9f32ddcff..1fd4a9a7612 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import $ from 'jquery';
import _ from 'underscore';
+import Api from '~/api';
import { TEST_HOST } from 'spec/test_constants';
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import actionsModule, * as actions from '~/notes/stores/actions';
@@ -8,7 +9,6 @@ import * as mutationTypes from '~/notes/stores/mutation_types';
import * as notesConstants from '~/notes/constants';
import createStore from '~/notes/stores';
import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
-import service from '~/notes/services/notes_service';
import testAction from '../../helpers/vuex_action_helper';
import { resetStore } from '../helpers';
import {
@@ -18,6 +18,8 @@ import {
noteableDataMock,
individualNote,
} from '../mock_data';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
const TEST_ERROR_MESSAGE = 'Test error message';
@@ -334,32 +336,28 @@ describe('Actions Notes Store', () => {
});
});
- describe('deleteNote', () => {
- const interceptor = (request, next) => {
- next(
- request.respondWith(JSON.stringify({}), {
- status: 200,
- }),
- );
- };
+ describe('removeNote', () => {
+ const endpoint = `${TEST_HOST}/note`;
+ let axiosMock;
beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
+ axiosMock = new AxiosMockAdapter(axios);
+ axiosMock.onDelete(endpoint).replyOnce(200, {});
$('body').attr('data-page', '');
});
afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ axiosMock.restore();
$('body').attr('data-page', '');
});
it('commits DELETE_NOTE and dispatches updateMergeRequestWidget', done => {
- const note = { path: `${gl.TEST_HOST}`, id: 1 };
+ const note = { path: endpoint, id: 1 };
testAction(
- actions.deleteNote,
+ actions.removeNote,
note,
store.state,
[
@@ -373,7 +371,7 @@ describe('Actions Notes Store', () => {
type: 'updateMergeRequestWidget',
},
{
- type: 'updateResolvableDiscussonsCounts',
+ type: 'updateResolvableDiscussionsCounts',
},
],
done,
@@ -381,12 +379,12 @@ describe('Actions Notes Store', () => {
});
it('dispatches removeDiscussionsFromDiff on merge request page', done => {
- const note = { path: `${gl.TEST_HOST}`, id: 1 };
+ const note = { path: endpoint, id: 1 };
$('body').attr('data-page', 'projects:merge_requests:show');
testAction(
- actions.deleteNote,
+ actions.removeNote,
note,
store.state,
[
@@ -400,7 +398,7 @@ describe('Actions Notes Store', () => {
type: 'updateMergeRequestWidget',
},
{
- type: 'updateResolvableDiscussonsCounts',
+ type: 'updateResolvableDiscussionsCounts',
},
{
type: 'diffs/removeDiscussionsFromDiff',
@@ -411,6 +409,45 @@ describe('Actions Notes Store', () => {
});
});
+ describe('deleteNote', () => {
+ const endpoint = `${TEST_HOST}/note`;
+ let axiosMock;
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ axiosMock.onDelete(endpoint).replyOnce(200, {});
+
+ $('body').attr('data-page', '');
+ });
+
+ afterEach(() => {
+ axiosMock.restore();
+
+ $('body').attr('data-page', '');
+ });
+
+ it('dispatches removeNote', done => {
+ const note = { path: endpoint, id: 1 };
+
+ testAction(
+ actions.deleteNote,
+ note,
+ {},
+ [],
+ [
+ {
+ type: 'removeNote',
+ payload: {
+ id: 1,
+ path: 'http://test.host/note',
+ },
+ },
+ ],
+ done,
+ );
+ });
+ });
+
describe('createNewNote', () => {
describe('success', () => {
const res = {
@@ -452,7 +489,7 @@ describe('Actions Notes Store', () => {
type: 'startTaskList',
},
{
- type: 'updateResolvableDiscussonsCounts',
+ type: 'updateResolvableDiscussionsCounts',
},
],
done,
@@ -527,7 +564,7 @@ describe('Actions Notes Store', () => {
],
[
{
- type: 'updateResolvableDiscussonsCounts',
+ type: 'updateResolvableDiscussionsCounts',
},
{
type: 'updateMergeRequestWidget',
@@ -552,7 +589,7 @@ describe('Actions Notes Store', () => {
],
[
{
- type: 'updateResolvableDiscussonsCounts',
+ type: 'updateResolvableDiscussionsCounts',
},
{
type: 'updateMergeRequestWidget',
@@ -587,10 +624,10 @@ describe('Actions Notes Store', () => {
});
});
- describe('updateResolvableDiscussonsCounts', () => {
+ describe('updateResolvableDiscussionsCounts', () => {
it('commits UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS', done => {
testAction(
- actions.updateResolvableDiscussonsCounts,
+ actions.updateResolvableDiscussionsCounts,
null,
{},
[{ type: 'UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS' }],
@@ -712,7 +749,7 @@ describe('Actions Notes Store', () => {
[
{ type: 'updateMergeRequestWidget' },
{ type: 'startTaskList' },
- { type: 'updateResolvableDiscussonsCounts' },
+ { type: 'updateResolvableDiscussionsCounts' },
],
done,
);
@@ -846,9 +883,9 @@ describe('Actions Notes Store', () => {
let flashContainer;
beforeEach(() => {
- spyOn(service, 'applySuggestion');
+ spyOn(Api, 'applySuggestion');
dispatch.and.returnValue(Promise.resolve());
- service.applySuggestion.and.returnValue(Promise.resolve());
+ Api.applySuggestion.and.returnValue(Promise.resolve());
flashContainer = {};
});
@@ -877,7 +914,7 @@ describe('Actions Notes Store', () => {
it('when service fails, flashes error message', done => {
const response = { response: { data: { message: TEST_ERROR_MESSAGE } } };
- service.applySuggestion.and.returnValue(Promise.reject(response));
+ Api.applySuggestion.and.returnValue(Promise.reject(response));
testSubmitSuggestion(done, () => {
expect(commit).not.toHaveBeenCalled();
@@ -894,4 +931,31 @@ describe('Actions Notes Store', () => {
});
});
});
+
+ describe('filterDiscussion', () => {
+ const path = 'some-discussion-path';
+ const filter = 0;
+
+ beforeEach(() => {
+ dispatch.and.returnValue(new Promise(() => {}));
+ });
+
+ it('fetches discussions with filter and persistFilter false', () => {
+ actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: false });
+
+ expect(dispatch.calls.allArgs()).toEqual([
+ ['setLoadingState', true],
+ ['fetchDiscussions', { path, filter, persistFilter: false }],
+ ]);
+ });
+
+ it('fetches discussions with filter and persistFilter true', () => {
+ actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: true });
+
+ expect(dispatch.calls.allArgs()).toEqual([
+ ['setLoadingState', true],
+ ['fetchDiscussions', { path, filter, persistFilter: true }],
+ ]);
+ });
+ });
});
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index c3ed079e33b..d69f469c7c7 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -14,6 +14,13 @@ import {
const discussionWithTwoUnresolvedNotes = 'merge_requests/resolved_diff_discussion.json';
+// Helper function to ensure that we're using the same schema across tests.
+const createDiscussionNeighborParams = (discussionId, diffOrder, step) => ({
+ discussionId,
+ diffOrder,
+ step,
+});
+
describe('Getters Notes Store', () => {
let state;
@@ -25,7 +32,6 @@ describe('Getters Notes Store', () => {
targetNoteHash: 'hash',
lastFetchedAt: 'timestamp',
isNotesFetched: false,
-
notesData: notesDataMock,
userData: userDataMock,
noteableData: noteableDataMock,
@@ -244,15 +250,105 @@ describe('Getters Notes Store', () => {
});
});
- describe('nextUnresolvedDiscussionId', () => {
- const localGetters = {
- unresolvedDiscussionsIdsOrdered: () => ['123', '456', '789'],
- };
+ describe('findUnresolvedDiscussionIdNeighbor', () => {
+ let localGetters;
+ beforeEach(() => {
+ localGetters = {
+ unresolvedDiscussionsIdsOrdered: () => ['123', '456', '789'],
+ };
+ });
+
+ [
+ { step: 1, id: '123', expected: '456' },
+ { step: 1, id: '456', expected: '789' },
+ { step: 1, id: '789', expected: '123' },
+ { step: -1, id: '123', expected: '789' },
+ { step: -1, id: '456', expected: '123' },
+ { step: -1, id: '789', expected: '456' },
+ ].forEach(({ step, id, expected }) => {
+ it(`with step ${step} and id ${id}, returns next value`, () => {
+ const params = createDiscussionNeighborParams(id, true, step);
+
+ expect(getters.findUnresolvedDiscussionIdNeighbor(state, localGetters)(params)).toBe(
+ expected,
+ );
+ });
+ });
+
+ describe('with 1 unresolved discussion', () => {
+ beforeEach(() => {
+ localGetters = {
+ unresolvedDiscussionsIdsOrdered: () => ['123'],
+ };
+ });
+
+ [{ step: 1, id: '123', expected: '123' }, { step: -1, id: '123', expected: '123' }].forEach(
+ ({ step, id, expected }) => {
+ it(`with step ${step} and match, returns only value`, () => {
+ const params = createDiscussionNeighborParams(id, true, step);
- it('should return the ID of the discussion after the ID provided', () => {
- expect(getters.nextUnresolvedDiscussionId(state, localGetters)('123')).toBe('456');
- expect(getters.nextUnresolvedDiscussionId(state, localGetters)('456')).toBe('789');
- expect(getters.nextUnresolvedDiscussionId(state, localGetters)('789')).toBe('123');
+ expect(getters.findUnresolvedDiscussionIdNeighbor(state, localGetters)(params)).toBe(
+ expected,
+ );
+ });
+ },
+ );
+
+ it('with no match, returns only value', () => {
+ const params = createDiscussionNeighborParams('bogus', true, 1);
+
+ expect(getters.findUnresolvedDiscussionIdNeighbor(state, localGetters)(params)).toBe('123');
+ });
+ });
+
+ describe('with 0 unresolved discussions', () => {
+ beforeEach(() => {
+ localGetters = {
+ unresolvedDiscussionsIdsOrdered: () => [],
+ };
+ });
+
+ [{ step: 1 }, { step: -1 }].forEach(({ step }) => {
+ it(`with step ${step}, returns undefined`, () => {
+ const params = createDiscussionNeighborParams('bogus', true, step);
+
+ expect(
+ getters.findUnresolvedDiscussionIdNeighbor(state, localGetters)(params),
+ ).toBeUndefined();
+ });
+ });
+ });
+ });
+
+ describe('findUnresolvedDiscussionIdNeighbor aliases', () => {
+ let neighbor;
+ let findUnresolvedDiscussionIdNeighbor;
+ let localGetters;
+
+ beforeEach(() => {
+ neighbor = {};
+ findUnresolvedDiscussionIdNeighbor = jasmine.createSpy().and.returnValue(neighbor);
+ localGetters = { findUnresolvedDiscussionIdNeighbor };
+ });
+
+ describe('nextUnresolvedDiscussionId', () => {
+ it('should return result of find neighbor', () => {
+ const expectedParams = createDiscussionNeighborParams('123', true, 1);
+ const result = getters.nextUnresolvedDiscussionId(state, localGetters)('123', true);
+
+ expect(findUnresolvedDiscussionIdNeighbor).toHaveBeenCalledWith(expectedParams);
+ expect(result).toBe(neighbor);
+ });
+ });
+
+ describe('previosuUnresolvedDiscussionId', () => {
+ it('should return result of find neighbor', () => {
+ const expectedParams = createDiscussionNeighborParams('123', true, -1);
+ const result = getters.previousUnresolvedDiscussionId(state, localGetters)('123', true);
+
+ expect(findUnresolvedDiscussionIdNeighbor).toHaveBeenCalledWith(expectedParams);
+ expect(result).toBe(neighbor);
+ });
});
});
diff --git a/spec/javascripts/pdf/page_spec.js b/spec/javascripts/pdf/page_spec.js
index 6dea570266b..efeb65acf87 100644
--- a/spec/javascripts/pdf/page_spec.js
+++ b/spec/javascripts/pdf/page_spec.js
@@ -17,7 +17,7 @@ describe('Page component', () => {
pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
pdfjsLib
.getDocument(testPDF)
- .then(pdf => pdf.getPage(1))
+ .promise.then(pdf => pdf.getPage(1))
.then(page => {
testPage = page;
})
@@ -31,7 +31,8 @@ describe('Page component', () => {
it('renders the page when mounting', done => {
const promise = Promise.resolve();
- spyOn(testPage, 'render').and.callFake(() => promise);
+ spyOn(testPage, 'render').and.returnValue({ promise });
+
vm = mountComponent(Component, {
page: testPage,
number: 1,
diff --git a/spec/javascripts/performance_bar/components/detailed_metric_spec.js b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
index 8a7aa057186..0486b5fa3db 100644
--- a/spec/javascripts/performance_bar/components/detailed_metric_spec.js
+++ b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
@@ -44,7 +44,6 @@ describe('detailedMetric', () => {
},
metric: 'gitaly',
header: 'Gitaly calls',
- details: 'details',
keys: ['feature', 'request'],
});
});
@@ -79,8 +78,32 @@ describe('detailedMetric', () => {
});
});
- it('displays the metric name', () => {
+ it('displays the metric title', () => {
expect(vm.$el.innerText).toContain('gitaly');
});
+
+ describe('when using a custom metric title', () => {
+ beforeEach(() => {
+ vm = mountComponent(Vue.extend(detailedMetric), {
+ currentRequest: {
+ details: {
+ gitaly: {
+ duration: '123ms',
+ calls: '456',
+ details: requestDetails,
+ },
+ },
+ },
+ metric: 'gitaly',
+ title: 'custom',
+ header: 'Gitaly calls',
+ keys: ['feature', 'request'],
+ });
+ });
+
+ it('displays the custom title', () => {
+ expect(vm.$el.innerText).toContain('custom');
+ });
+ });
});
});
diff --git a/spec/javascripts/performance_bar/components/simple_metric_spec.js b/spec/javascripts/performance_bar/components/simple_metric_spec.js
deleted file mode 100644
index 98b843e9711..00000000000
--- a/spec/javascripts/performance_bar/components/simple_metric_spec.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import Vue from 'vue';
-import simpleMetric from '~/performance_bar/components/simple_metric.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('simpleMetric', () => {
- let vm;
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('when the current request has no details', () => {
- beforeEach(() => {
- vm = mountComponent(Vue.extend(simpleMetric), {
- currentRequest: {},
- metric: 'gitaly',
- });
- });
-
- it('does not display details', () => {
- expect(vm.$el.innerText).not.toContain('/');
- });
-
- it('displays the metric name', () => {
- expect(vm.$el.innerText).toContain('gitaly');
- });
- });
-
- describe('when the current request has details', () => {
- beforeEach(() => {
- vm = mountComponent(Vue.extend(simpleMetric), {
- currentRequest: {
- details: { gitaly: { duration: '123ms', calls: '456' } },
- },
- metric: 'gitaly',
- });
- });
-
- it('diplays details', () => {
- expect(vm.$el.innerText.replace(/\s+/g, ' ')).toContain('123ms / 456');
- });
-
- it('displays the metric name', () => {
- expect(vm.$el.innerText).toContain('gitaly');
- });
- });
-});
diff --git a/spec/javascripts/persistent_user_callout_spec.js b/spec/javascripts/persistent_user_callout_spec.js
index 2fdfff3db03..d15758be5d2 100644
--- a/spec/javascripts/persistent_user_callout_spec.js
+++ b/spec/javascripts/persistent_user_callout_spec.js
@@ -22,6 +22,24 @@ describe('PersistentUserCallout', () => {
return fixture;
}
+ function createDeferredLinkFixture() {
+ const fixture = document.createElement('div');
+ fixture.innerHTML = `
+ <div
+ class="container"
+ data-dismiss-endpoint="${dismissEndpoint}"
+ data-feature-id="${featureName}"
+ data-defer-links="true"
+ >
+ <button type="button" class="js-close"></button>
+ <a href="/somewhere-pleasant" target="_blank" class="deferred-link">A link</a>
+ <a href="/somewhere-else" target="_blank" class="normal-link">Another link</a>
+ </div>
+ `;
+
+ return fixture;
+ }
+
describe('dismiss', () => {
let button;
let mockAxios;
@@ -74,6 +92,75 @@ describe('PersistentUserCallout', () => {
});
});
+ describe('deferred links', () => {
+ let button;
+ let deferredLink;
+ let normalLink;
+ let mockAxios;
+ let persistentUserCallout;
+ let windowSpy;
+
+ beforeEach(() => {
+ const fixture = createDeferredLinkFixture();
+ const container = fixture.querySelector('.container');
+ button = fixture.querySelector('.js-close');
+ deferredLink = fixture.querySelector('.deferred-link');
+ normalLink = fixture.querySelector('.normal-link');
+ mockAxios = new MockAdapter(axios);
+ persistentUserCallout = new PersistentUserCallout(container);
+ spyOn(persistentUserCallout.container, 'remove');
+ windowSpy = spyOn(window, 'open').and.callFake(() => {});
+ });
+
+ afterEach(() => {
+ mockAxios.restore();
+ });
+
+ it('defers loading of a link until callout is dismissed', done => {
+ const { href, target } = deferredLink;
+ mockAxios.onPost(dismissEndpoint).replyOnce(200);
+
+ deferredLink.click();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(windowSpy).toHaveBeenCalledWith(href, target);
+ expect(persistentUserCallout.container.remove).toHaveBeenCalled();
+ expect(mockAxios.history.post[0].data).toBe(
+ JSON.stringify({ feature_name: featureName }),
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not dismiss callout on non-deferred links', done => {
+ normalLink.click();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(windowSpy).not.toHaveBeenCalled();
+ expect(persistentUserCallout.container.remove).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not follow link when notification is closed', done => {
+ mockAxios.onPost(dismissEndpoint).replyOnce(200);
+
+ button.click();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(windowSpy).not.toHaveBeenCalled();
+ expect(persistentUserCallout.container.remove).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('factory', () => {
it('returns an instance of PersistentUserCallout with the provided container property', () => {
const fixture = createFixture();
diff --git a/spec/javascripts/pipelines/pipelines_actions_spec.js b/spec/javascripts/pipelines/pipelines_actions_spec.js
index a7dcd532f4f..953a42b9d15 100644
--- a/spec/javascripts/pipelines/pipelines_actions_spec.js
+++ b/spec/javascripts/pipelines/pipelines_actions_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
-import eventHub from '~/pipelines/event_hub';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import PipelinesActions from '~/pipelines/components/pipelines_actions.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { TEST_HOST } from 'spec/test_constants';
@@ -7,9 +8,15 @@ import { TEST_HOST } from 'spec/test_constants';
describe('Pipelines Actions dropdown', () => {
const Component = Vue.extend(PipelinesActions);
let vm;
+ let mock;
afterEach(() => {
vm.$destroy();
+ mock.restore();
+ });
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
});
describe('manual actions', () => {
@@ -40,6 +47,22 @@ describe('Pipelines Actions dropdown', () => {
expect(dropdownItem).toBeDisabled();
});
+
+ describe('on click', () => {
+ it('makes a request and toggles the loading state', done => {
+ mock.onPost(actions.path).reply(200);
+
+ vm.$el.querySelector('.dropdown-menu li button').click();
+
+ expect(vm.isLoading).toEqual(true);
+
+ setTimeout(() => {
+ expect(vm.isLoading).toEqual(false);
+
+ done();
+ });
+ });
+ });
});
describe('scheduled jobs', () => {
@@ -71,26 +94,27 @@ describe('Pipelines Actions dropdown', () => {
.catch(done.fail);
});
- it('emits postAction event after confirming', () => {
- const emitSpy = jasmine.createSpy('emit');
- eventHub.$on('postAction', emitSpy);
+ it('makes post request after confirming', done => {
+ mock.onPost(scheduledJobAction.path).reply(200);
spyOn(window, 'confirm').and.callFake(() => true);
findDropdownItem(scheduledJobAction).click();
expect(window.confirm).toHaveBeenCalled();
- expect(emitSpy).toHaveBeenCalledWith(scheduledJobAction.path);
+ setTimeout(() => {
+ expect(mock.history.post.length).toBe(1);
+ done();
+ });
});
- it('does not emit postAction event if confirmation is cancelled', () => {
- const emitSpy = jasmine.createSpy('emit');
- eventHub.$on('postAction', emitSpy);
+ it('does not make post request if confirmation is cancelled', () => {
+ mock.onPost(scheduledJobAction.path).reply(200);
spyOn(window, 'confirm').and.callFake(() => false);
findDropdownItem(scheduledJobAction).click();
expect(window.confirm).toHaveBeenCalled();
- expect(emitSpy).not.toHaveBeenCalled();
+ expect(mock.history.post.length).toBe(0);
});
it('displays the remaining time in the dropdown', () => {
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index 78187b69563..daa898ca687 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -736,10 +736,9 @@ describe('Pipelines', () => {
});
describe('when a request is being made', () => {
- it('stops polling, cancels the request, fetches pipelines & restarts polling', done => {
+ it('stops polling, cancels the request, & restarts polling', done => {
spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'restart');
- spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
spyOn(vm.service.cancelationSource, 'cancel').and.callThrough();
setTimeout(() => {
@@ -754,7 +753,6 @@ describe('Pipelines', () => {
expect(vm.poll.stop).toHaveBeenCalled();
setTimeout(() => {
- expect(vm.getPipelines).toHaveBeenCalled();
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
@@ -765,10 +763,9 @@ describe('Pipelines', () => {
});
describe('when no request is being made', () => {
- it('stops polling, fetches pipelines & restarts polling', done => {
+ it('stops polling & restarts polling', done => {
spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'restart');
- spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
setTimeout(() => {
vm.$el.querySelector('.js-builds-dropdown-button').click();
@@ -776,7 +773,6 @@ describe('Pipelines', () => {
expect(vm.poll.stop).toHaveBeenCalled();
setTimeout(() => {
- expect(vm.getPipelines).toHaveBeenCalled();
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index 76a17e6fb31..5ea3f85a247 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -8,6 +8,13 @@ import { reposServerResponse } from '../mock_data';
describe('Registry List', () => {
const Component = Vue.extend(registry);
+ const props = {
+ endpoint: `${TEST_HOST}/foo`,
+ helpPagePath: 'foo',
+ noContainersImage: 'foo',
+ containersErrorImage: 'foo',
+ repositoryUrl: 'foo',
+ };
let vm;
let mock;
@@ -24,7 +31,7 @@ describe('Registry List', () => {
beforeEach(() => {
mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, reposServerResponse);
- vm = mountComponent(Component, { endpoint: `${TEST_HOST}/foo` });
+ vm = mountComponent(Component, { ...props });
});
it('should render a list of repos', done => {
@@ -72,18 +79,13 @@ describe('Registry List', () => {
beforeEach(() => {
mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
- vm = mountComponent(Component, { endpoint: `${TEST_HOST}/foo` });
+ vm = mountComponent(Component, { ...props });
});
it('should render empty message', done => {
setTimeout(() => {
- expect(
- vm.$el
- .querySelector('p')
- .textContent.trim()
- .replace(/[\r\n]+/g, ' '),
- ).toEqual(
- 'No container images stored for this project. Add one by following the instructions above.',
+ expect(vm.$el.querySelector('.js-no-container-images-text').textContent).toEqual(
+ 'With the Container Registry, every project can have its own space to store its Docker images. More Information',
);
done();
}, 0);
@@ -94,12 +96,32 @@ describe('Registry List', () => {
beforeEach(() => {
mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
- vm = mountComponent(Component, { endpoint: `${TEST_HOST}/foo` });
+ vm = mountComponent(Component, { ...props });
});
it('should render a loading spinner', done => {
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.spinner')).not.toBe(null);
+ expect(vm.$el.querySelector('.gl-spinner')).not.toBe(null);
+ done();
+ });
+ });
+ });
+
+ describe('invalid characters in path', () => {
+ beforeEach(() => {
+ mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
+
+ vm = mountComponent(Component, {
+ ...props,
+ characterError: true,
+ });
+ });
+
+ it('should render invalid characters error message', done => {
+ setTimeout(() => {
+ expect(vm.$el.querySelector('p')).not.toContain(
+ 'We are having trouble connecting to Docker, which could be due to an issue with your project name or path. More information',
+ );
done();
});
});
diff --git a/spec/javascripts/registry/components/collapsible_container_spec.js b/spec/javascripts/registry/components/collapsible_container_spec.js
index 9ed4b04324a..2a5d8dd11da 100644
--- a/spec/javascripts/registry/components/collapsible_container_spec.js
+++ b/spec/javascripts/registry/components/collapsible_container_spec.js
@@ -72,21 +72,15 @@ describe('collapsible registry container', () => {
expect(findDeleteBtn()).not.toBeNull();
});
- describe('clicked on delete', () => {
- beforeEach(done => {
- findDeleteBtn().click();
- Vue.nextTick(done);
- });
-
- it('should open confirmation modal', () => {
- expect(vm.$el.querySelector('#confirm-repo-deletion-modal')).not.toBeNull();
- });
+ it('should call deleteItem when confirming deletion', done => {
+ findDeleteBtn().click();
+ spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
- it('should call deleteItem when confirming deletion', () => {
- spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
- vm.$el.querySelector('#confirm-repo-deletion-modal .btn-danger').click();
+ Vue.nextTick(() => {
+ document.querySelector(`#${vm.modalId} .btn-danger`).click();
expect(vm.deleteItem).toHaveBeenCalledWith(vm.repo);
+ done();
});
});
});
diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js
index d366c67a1b9..9c7439206ef 100644
--- a/spec/javascripts/registry/components/table_registry_spec.js
+++ b/spec/javascripts/registry/components/table_registry_spec.js
@@ -1,68 +1,159 @@
import Vue from 'vue';
import tableRegistry from '~/registry/components/table_registry.vue';
import store from '~/registry/stores';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { repoPropsData } from '../mock_data';
-const [firstImage] = repoPropsData.list;
+const [firstImage, secondImage] = repoPropsData.list;
describe('table registry', () => {
let vm;
- let Component;
+ const Component = Vue.extend(tableRegistry);
+ const bulkDeletePath = 'path';
const findDeleteBtn = () => vm.$el.querySelector('.js-delete-registry');
+ const findDeleteBtnRow = () => vm.$el.querySelector('.js-delete-registry-row');
+ const findSelectAllCheckbox = () => vm.$el.querySelector('.js-select-all-checkbox > input');
+ const findAllRowCheckboxes = () =>
+ Array.from(vm.$el.querySelectorAll('.js-select-checkbox input'));
+ const confirmationModal = (child = '') => document.querySelector(`#${vm.modalId} ${child}`);
- beforeEach(() => {
- Component = Vue.extend(tableRegistry);
- vm = new Component({
+ const createComponent = () => {
+ vm = mountComponentWithStore(Component, {
store,
- propsData: {
+ props: {
repo: repoPropsData,
},
- }).$mount();
+ });
+ };
+
+ const selectAllCheckboxes = () => vm.selectAll();
+ const deselectAllCheckboxes = () => vm.deselectAll();
+
+ beforeEach(() => {
+ createComponent();
});
afterEach(() => {
vm.$destroy();
});
- it('should render a table with the registry list', () => {
- expect(vm.$el.querySelectorAll('table tbody tr').length).toEqual(repoPropsData.list.length);
- });
+ describe('rendering', () => {
+ it('should render a table with the registry list', () => {
+ expect(vm.$el.querySelectorAll('table tbody tr').length).toEqual(repoPropsData.list.length);
+ });
- it('should render registry tag', () => {
- const textRendered = vm.$el
- .querySelector('.table tbody tr')
- .textContent.trim()
- .replace(/\s\s+/g, ' ');
+ it('should render registry tag', () => {
+ const textRendered = vm.$el
+ .querySelector('.table tbody tr')
+ .textContent.trim()
+ // replace additional whitespace characters (e.g. new lines) with a single empty space
+ .replace(/\s\s+/g, ' ');
- expect(textRendered).toContain(repoPropsData.list[0].tag);
- expect(textRendered).toContain(repoPropsData.list[0].shortRevision);
- expect(textRendered).toContain(repoPropsData.list[0].layers);
- expect(textRendered).toContain(repoPropsData.list[0].size);
+ expect(textRendered).toContain(repoPropsData.list[0].tag);
+ expect(textRendered).toContain(repoPropsData.list[0].shortRevision);
+ expect(textRendered).toContain(repoPropsData.list[0].layers);
+ expect(textRendered).toContain(repoPropsData.list[0].size);
+ });
});
- describe('delete registry', () => {
- it('should be possible to delete a registry', () => {
- expect(findDeleteBtn()).toBeDefined();
+ describe('multi select', () => {
+ it('should support multiselect and selecting a row should enable delete button', done => {
+ findSelectAllCheckbox().click();
+ selectAllCheckboxes();
+
+ expect(findSelectAllCheckbox().checked).toBe(true);
+
+ Vue.nextTick(() => {
+ expect(findDeleteBtn().disabled).toBe(false);
+ done();
+ });
+ });
+
+ it('selecting all checkbox should select all rows and enable delete button', done => {
+ selectAllCheckboxes();
+
+ Vue.nextTick(() => {
+ const checkedValues = findAllRowCheckboxes().filter(x => x.checked);
+
+ expect(checkedValues.length).toBe(repoPropsData.list.length);
+ done();
+ });
+ });
+
+ it('deselecting select all checkbox should deselect all rows and disable delete button', done => {
+ selectAllCheckboxes();
+ deselectAllCheckboxes();
+
+ Vue.nextTick(() => {
+ const checkedValues = findAllRowCheckboxes().filter(x => x.checked);
+
+ expect(checkedValues.length).toBe(0);
+ done();
+ });
});
- describe('clicked on delete', () => {
- beforeEach(done => {
+ it('should delete multiple items when multiple items are selected', done => {
+ selectAllCheckboxes();
+
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([0, 1]);
+ expect(findDeleteBtn().disabled).toBe(false);
+
findDeleteBtn().click();
- Vue.nextTick(done);
+ spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
+
+ Vue.nextTick(() => {
+ const modal = confirmationModal();
+ confirmationModal('.btn-danger').click();
+
+ expect(modal).toExist();
+
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([]);
+ expect(vm.multiDeleteItems).toHaveBeenCalledWith({
+ path: bulkDeletePath,
+ items: [firstImage.tag, secondImage.tag],
+ });
+ done();
+ });
+ });
});
+ });
+ });
- it('should open confirmation modal and set itemToBeDeleted properly', () => {
- expect(vm.itemToBeDeleted).toEqual(firstImage);
- expect(vm.$el.querySelector('#confirm-image-deletion-modal')).not.toBeNull();
+ describe('delete registry', () => {
+ beforeEach(() => {
+ vm.itemsToBeDeleted = [0];
+ });
+
+ it('should be possible to delete a registry', done => {
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([0]);
+ expect(findDeleteBtn()).toBeDefined();
+ expect(findDeleteBtn().disabled).toBe(false);
+ expect(findDeleteBtnRow()).toBeDefined();
+ done();
});
+ });
- it('should call deleteItem and reset itemToBeDeleted when confirming deletion', () => {
- spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
- vm.$el.querySelector('#confirm-image-deletion-modal .btn-danger').click();
+ it('should call deleteItems and reset itemsToBeDeleted when confirming deletion', done => {
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([0]);
+ expect(findDeleteBtn().disabled).toBe(false);
+ findDeleteBtn().click();
+ spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
+
+ Vue.nextTick(() => {
+ confirmationModal('.btn-danger').click();
- expect(vm.deleteItem).toHaveBeenCalledWith(firstImage);
- expect(vm.itemToBeDeleted).toBeNull();
+ expect(vm.itemsToBeDeleted).toEqual([]);
+ expect(vm.multiDeleteItems).toHaveBeenCalledWith({
+ path: bulkDeletePath,
+ items: [firstImage.tag],
+ });
+ done();
+ });
});
});
});
@@ -72,4 +163,27 @@ describe('table registry', () => {
expect(vm.$el.querySelector('.gl-pagination')).toBeDefined();
});
});
+
+ describe('modal content', () => {
+ it('should show the singular title and image name when deleting a single image', done => {
+ findDeleteBtnRow().click();
+
+ Vue.nextTick(() => {
+ expect(vm.modalTitle).toBe('Remove image');
+ expect(vm.modalDescription).toContain(firstImage.tag);
+ done();
+ });
+ });
+
+ it('should show the plural title and image count when deleting more than one image', done => {
+ selectAllCheckboxes();
+ vm.setModalDescription();
+
+ Vue.nextTick(() => {
+ expect(vm.modalTitle).toBe('Remove images');
+ expect(vm.modalDescription).toContain('<b>2</b> images');
+ done();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/registry/mock_data.js b/spec/javascripts/registry/mock_data.js
index 22db203e77f..130ab298e89 100644
--- a/spec/javascripts/registry/mock_data.js
+++ b/spec/javascripts/registry/mock_data.js
@@ -108,6 +108,17 @@ export const repoPropsData = {
destroyPath: 'path',
canDelete: true,
},
+ {
+ tag: 'test-image',
+ revision: 'b969de599faea2b3d9b6605a8b0897261c571acaa36db1bdc7349b5775b4e0b4',
+ shortRevision: 'b969de599',
+ size: 19,
+ layers: 10,
+ location: 'location-2',
+ createdAt: 1505828744434,
+ destroyPath: 'path-2',
+ canDelete: true,
+ },
],
location: 'location',
name: 'foo',
diff --git a/spec/javascripts/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js
index e98c665f99d..f761a18e326 100644
--- a/spec/javascripts/releases/components/release_block_spec.js
+++ b/spec/javascripts/releases/components/release_block_spec.js
@@ -14,7 +14,7 @@ describe('Release block', () => {
description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>',
author_name: 'Release bot',
author_email: 'release-bot@example.com',
- created_at: '2012-05-28T05:00:00-07:00',
+ released_at: '2012-05-28T05:00:00-07:00',
author: {
avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png',
id: 482476,
@@ -101,7 +101,7 @@ describe('Release block', () => {
});
it('renders release date', () => {
- expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.created_at));
+ expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.released_at));
});
it('renders number of assets provided', () => {
@@ -152,13 +152,13 @@ describe('Release block', () => {
});
});
- describe('with pre_release flag', () => {
+ describe('with upcoming_release flag', () => {
beforeEach(() => {
- vm = factory(Object.assign({}, release, { pre_release: true }));
+ vm = factory(Object.assign({}, release, { upcoming_release: true }));
});
- it('renders pre-release badge', () => {
- expect(vm.$el.textContent).toContain('Pre-release');
+ it('renders upcoming release badge', () => {
+ expect(vm.$el.textContent).toContain('Upcoming Release');
});
});
});
diff --git a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
index a17494966a3..1f1e626ed33 100644
--- a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
@@ -34,7 +34,7 @@ describe('Grouped Test Reports App', () => {
it('renders success summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained no changed test results out of 11 total tests',
);
@@ -61,7 +61,7 @@ describe('Grouped Test Reports App', () => {
it('renders success summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary results are being parsed',
);
@@ -81,7 +81,7 @@ describe('Grouped Test Reports App', () => {
it('renders failed summary text + new badge', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 failed test results out of 11 total tests',
);
@@ -109,7 +109,7 @@ describe('Grouped Test Reports App', () => {
it('renders summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 failed test results and 2 fixed test results out of 11 total tests',
);
@@ -137,7 +137,7 @@ describe('Grouped Test Reports App', () => {
it('renders summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 fixed test results out of 11 total tests',
);
@@ -190,7 +190,7 @@ describe('Grouped Test Reports App', () => {
});
it('renders loading summary text with loading icon', done => {
- expect(vm.$el.querySelector('.spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary results are being parsed',
);
diff --git a/spec/javascripts/sidebar/assignee_title_spec.js b/spec/javascripts/sidebar/assignee_title_spec.js
index 509edba2036..7fff7c075d9 100644
--- a/spec/javascripts/sidebar/assignee_title_spec.js
+++ b/spec/javascripts/sidebar/assignee_title_spec.js
@@ -4,8 +4,10 @@ import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue';
describe('AssigneeTitle component', () => {
let component;
let AssigneeTitleComponent;
+ let statsSpy;
beforeEach(() => {
+ statsSpy = spyOnDependency(AssigneeTitle, 'trackEvent');
AssigneeTitleComponent = Vue.extend(AssigneeTitle);
});
@@ -102,4 +104,16 @@ describe('AssigneeTitle component', () => {
expect(component.$el.querySelector('.edit-link')).not.toBeNull();
});
+
+ it('calls trackEvent when edit is clicked', () => {
+ component = new AssigneeTitleComponent({
+ propsData: {
+ numberOfAssignees: 0,
+ editable: true,
+ },
+ }).$mount();
+ component.$el.querySelector('.js-sidebar-dropdown-toggle').click();
+
+ expect(statsSpy).toHaveBeenCalled();
+ });
});
diff --git a/spec/javascripts/sidebar/assignees_spec.js b/spec/javascripts/sidebar/assignees_spec.js
index 4ae2141d5f0..a1df5389a38 100644
--- a/spec/javascripts/sidebar/assignees_spec.js
+++ b/spec/javascripts/sidebar/assignees_spec.js
@@ -94,115 +94,9 @@ describe('Assignee component', () => {
expect(assignee.querySelector('.author').innerText.trim()).toEqual(UsersMock.user.name);
});
-
- it('Shows one user with avatar, username and author name', () => {
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000/',
- users: [UsersMock.user],
- editable: true,
- },
- }).$mount();
-
- expect(component.$el.querySelector('.author-link')).not.toBeNull();
- // The image
- expect(component.$el.querySelector('.author-link img').getAttribute('src')).toEqual(
- UsersMock.user.avatar,
- );
- // Author name
- expect(component.$el.querySelector('.author-link .author').innerText.trim()).toEqual(
- UsersMock.user.name,
- );
- // Username
- expect(component.$el.querySelector('.author-link .username').innerText.trim()).toEqual(
- `@${UsersMock.user.username}`,
- );
- });
-
- it('has the root url present in the assigneeUrl method', () => {
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000/',
- users: [UsersMock.user],
- editable: true,
- },
- }).$mount();
-
- expect(component.assigneeUrl(UsersMock.user).indexOf('http://localhost:3000/')).not.toEqual(
- -1,
- );
- });
-
- it('has correct "cannot merge" tooltip when user cannot merge', () => {
- const user = Object.assign({}, UsersMock.user, { can_merge: false });
-
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000/',
- users: [user],
- editable: true,
- issuableType: 'merge_request',
- },
- }).$mount();
-
- expect(component.mergeNotAllowedTooltipMessage).toEqual('Cannot merge');
- });
});
describe('Two or more assignees/users', () => {
- it('has correct "cannot merge" tooltip when one user can merge', () => {
- const users = UsersMockHelper.createNumberRandomUsers(3);
- users[0].can_merge = true;
- users[1].can_merge = false;
- users[2].can_merge = false;
-
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000/',
- users,
- editable: true,
- issuableType: 'merge_request',
- },
- }).$mount();
-
- expect(component.mergeNotAllowedTooltipMessage).toEqual('1/3 can merge');
- });
-
- it('has correct "cannot merge" tooltip when no user can merge', () => {
- const users = UsersMockHelper.createNumberRandomUsers(2);
- users[0].can_merge = false;
- users[1].can_merge = false;
-
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000/',
- users,
- editable: true,
- issuableType: 'merge_request',
- },
- }).$mount();
-
- expect(component.mergeNotAllowedTooltipMessage).toEqual('No one can merge');
- });
-
- it('has correct "cannot merge" tooltip when more than one user can merge', () => {
- const users = UsersMockHelper.createNumberRandomUsers(3);
- users[0].can_merge = false;
- users[1].can_merge = true;
- users[2].can_merge = true;
-
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000/',
- users,
- editable: true,
- issuableType: 'merge_request',
- },
- }).$mount();
-
- expect(component.mergeNotAllowedTooltipMessage).toEqual('2/3 can merge');
- });
-
it('has no "cannot merge" tooltip when every user can merge', () => {
const users = UsersMockHelper.createNumberRandomUsers(2);
users[0].can_merge = true;
@@ -217,7 +111,7 @@ describe('Assignee component', () => {
},
}).$mount();
- expect(component.mergeNotAllowedTooltipMessage).toEqual(null);
+ expect(component.collapsedTooltipTitle).not.toContain('cannot merge');
});
it('displays two assignee icons when collapsed', () => {
@@ -295,8 +189,12 @@ describe('Assignee component', () => {
expect(component.$el.querySelector('.user-list-more')).toBe(null);
});
- it('sets tooltip container to body', () => {
- const users = UsersMockHelper.createNumberRandomUsers(2);
+ it('shows sorted assignee where "can merge" users are sorted first', () => {
+ const users = UsersMockHelper.createNumberRandomUsers(3);
+ users[0].can_merge = false;
+ users[1].can_merge = false;
+ users[2].can_merge = true;
+
component = new AssigneeComponent({
propsData: {
rootPath: 'http://localhost:3000',
@@ -305,98 +203,46 @@ describe('Assignee component', () => {
},
}).$mount();
- expect(component.$el.querySelector('.user-link').getAttribute('data-container')).toBe('body');
+ expect(component.sortedAssigness[0].can_merge).toBe(true);
});
- it('Shows the "show-less" assignees label', done => {
- const users = UsersMockHelper.createNumberRandomUsers(6);
+ it('passes the sorted assignees to the uncollapsed-assignee-list', () => {
+ const users = UsersMockHelper.createNumberRandomUsers(3);
+ users[0].can_merge = false;
+ users[1].can_merge = false;
+ users[2].can_merge = true;
+
component = new AssigneeComponent({
propsData: {
rootPath: 'http://localhost:3000',
users,
- editable: true,
+ editable: false,
},
}).$mount();
- expect(component.$el.querySelectorAll('.user-item').length).toEqual(
- component.defaultRenderCount,
- );
-
- expect(component.$el.querySelector('.user-list-more')).not.toBe(null);
- const usersLabelExpectation = users.length - component.defaultRenderCount;
+ const userItems = component.$el.querySelectorAll('.user-list .user-item a');
- expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).not.toBe(
- `+${usersLabelExpectation} more`,
- );
- component.toggleShowLess();
- Vue.nextTick(() => {
- expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).toBe(
- '- show less',
- );
- done();
- });
+ expect(userItems.length).toBe(3);
+ expect(userItems[0].dataset.originalTitle).toBe(users[2].name);
});
- it('Shows the "show-less" when "n+ more " label is clicked', done => {
- const users = UsersMockHelper.createNumberRandomUsers(6);
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000',
- users,
- editable: true,
- },
- }).$mount();
-
- component.$el.querySelector('.user-list-more .btn-link').click();
- Vue.nextTick(() => {
- expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).toBe(
- '- show less',
- );
- done();
- });
- });
+ it('passes the sorted assignees to the collapsed-assignee-list', () => {
+ const users = UsersMockHelper.createNumberRandomUsers(3);
+ users[0].can_merge = false;
+ users[1].can_merge = false;
+ users[2].can_merge = true;
- it('gets the count of avatar via a computed property ', () => {
- const users = UsersMockHelper.createNumberRandomUsers(6);
component = new AssigneeComponent({
propsData: {
rootPath: 'http://localhost:3000',
users,
- editable: true,
+ editable: false,
},
}).$mount();
- expect(component.sidebarAvatarCounter).toEqual(`+${users.length - 1}`);
- });
+ const collapsedButton = component.$el.querySelector('.sidebar-collapsed-user button');
- describe('n+ more label', () => {
- beforeEach(() => {
- const users = UsersMockHelper.createNumberRandomUsers(6);
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000',
- users,
- editable: true,
- },
- }).$mount();
- });
-
- it('shows "+1 more" label', () => {
- expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).toBe(
- '+ 1 more',
- );
- });
-
- it('shows "show less" label', done => {
- component.toggleShowLess();
-
- Vue.nextTick(() => {
- expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).toBe(
- '- show less',
- );
- done();
- });
- });
+ expect(collapsedButton.innerText.trim()).toBe(users[2].name);
});
});
});
diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
index 486a7241e33..ea9e5677bc5 100644
--- a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
@@ -4,8 +4,10 @@ import confidentialIssueSidebar from '~/sidebar/components/confidential/confiden
describe('Confidential Issue Sidebar Block', () => {
let vm1;
let vm2;
+ let statsSpy;
beforeEach(() => {
+ statsSpy = spyOnDependency(confidentialIssueSidebar, 'trackEvent');
const Component = Vue.extend(confidentialIssueSidebar);
const service = {
update: () => Promise.resolve(true),
@@ -67,4 +69,10 @@ describe('Confidential Issue Sidebar Block', () => {
done();
});
});
+
+ it('calls trackEvent when "Edit" is clicked', () => {
+ vm1.$el.querySelector('.confidential-edit').click();
+
+ expect(statsSpy).toHaveBeenCalled();
+ });
});
diff --git a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
index ca882032bdf..2d930428230 100644
--- a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
@@ -4,8 +4,10 @@ import lockIssueSidebar from '~/sidebar/components/lock/lock_issue_sidebar.vue';
describe('LockIssueSidebar', () => {
let vm1;
let vm2;
+ let statsSpy;
beforeEach(() => {
+ statsSpy = spyOnDependency(lockIssueSidebar, 'trackEvent');
const Component = Vue.extend(lockIssueSidebar);
const mediator = {
@@ -59,6 +61,12 @@ describe('LockIssueSidebar', () => {
});
});
+ it('calls trackEvent when "Edit" is clicked', () => {
+ vm1.$el.querySelector('.lock-edit').click();
+
+ expect(statsSpy).toHaveBeenCalled();
+ });
+
it('displays the edit form when opened from collapsed state', done => {
expect(vm1.isLockDialogOpen).toBe(false);
diff --git a/spec/javascripts/sidebar/subscriptions_spec.js b/spec/javascripts/sidebar/subscriptions_spec.js
index 32728e58b06..2efa13f3fe8 100644
--- a/spec/javascripts/sidebar/subscriptions_spec.js
+++ b/spec/javascripts/sidebar/subscriptions_spec.js
@@ -6,8 +6,10 @@ import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Subscriptions', function() {
let vm;
let Subscriptions;
+ let statsSpy;
beforeEach(() => {
+ statsSpy = spyOnDependency(subscriptions, 'trackEvent');
Subscriptions = Vue.extend(subscriptions);
});
@@ -58,6 +60,13 @@ describe('Subscriptions', function() {
expect(vm.$emit).toHaveBeenCalledWith('toggleSubscription', jasmine.any(Object));
});
+ it('calls trackEvent when toggled', () => {
+ vm = mountComponent(Subscriptions, { subscribed: true });
+ vm.toggleSubscription();
+
+ expect(statsSpy).toHaveBeenCalled();
+ });
+
it('onClickCollapsedIcon method emits `toggleSidebar` event on component', () => {
vm = mountComponent(Subscriptions, { subscribed: true });
spyOn(vm, '$emit');
diff --git a/spec/javascripts/sidebar/todo_spec.js b/spec/javascripts/sidebar/todo_spec.js
index f46ea5a0499..e7abd19c865 100644
--- a/spec/javascripts/sidebar/todo_spec.js
+++ b/spec/javascripts/sidebar/todo_spec.js
@@ -53,14 +53,14 @@ describe('SidebarTodo', () => {
describe('buttonLabel', () => {
it('returns todo button text for marking todo as done when `isTodo` prop is `true`', () => {
- expect(vm.buttonLabel).toBe('Mark todo as done');
+ expect(vm.buttonLabel).toBe('Mark as done');
});
it('returns todo button text for add todo when `isTodo` prop is `false`', done => {
vm.isTodo = false;
Vue.nextTick()
.then(() => {
- expect(vm.buttonLabel).toBe('Add todo');
+ expect(vm.buttonLabel).toBe('Add a To Do');
})
.then(done)
.catch(done.fail);
@@ -131,14 +131,14 @@ describe('SidebarTodo', () => {
});
it('check button label computed property', () => {
- expect(vm.buttonLabel).toEqual('Mark todo as done');
+ expect(vm.buttonLabel).toEqual('Mark as done');
});
it('renders button label element when `collapsed` prop is `false`', () => {
const buttonLabelEl = vm.$el.querySelector('span.issuable-todo-inner');
expect(buttonLabelEl).not.toBeNull();
- expect(buttonLabelEl.innerText.trim()).toBe('Mark todo as done');
+ expect(buttonLabelEl.innerText.trim()).toBe('Mark as done');
});
it('renders button icon when `collapsed` prop is `true`', done => {
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 8c80a425581..ce453d7c483 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -3,19 +3,23 @@
*/
import $ from 'jquery';
+import 'core-js/features/set-immediate';
import 'vendor/jasmine-jquery';
import '~/commons';
import Vue from 'vue';
import VueResource from 'vue-resource';
import Translate from '~/vue_shared/translate';
-import CheckEE from '~/vue_shared/mixins/is_ee';
import jasmineDiff from 'jasmine-diff';
+import { config as testUtilsConfig } from '@vue/test-utils';
import { getDefaultAdapter } from '~/lib/utils/axios_utils';
import { FIXTURES_PATH, TEST_HOST } from './test_constants';
import customMatchers from './matchers';
+// Tech debt issue TBD
+testUtilsConfig.logModifiedComponents = false;
+
const isHeadlessChrome = /\bHeadlessChrome\//.test(navigator.userAgent);
Vue.config.devtools = !isHeadlessChrome;
Vue.config.productionTip = false;
@@ -44,7 +48,6 @@ Vue.config.errorHandler = function(err) {
Vue.use(VueResource);
Vue.use(Translate);
-Vue.use(CheckEE);
// enable test fixtures
jasmine.getFixtures().fixturesPath = FIXTURES_PATH;
diff --git a/spec/javascripts/test_constants.js b/spec/javascripts/test_constants.js
index 77c206585fe..c97d47a6406 100644
--- a/spec/javascripts/test_constants.js
+++ b/spec/javascripts/test_constants.js
@@ -1,6 +1,4 @@
-export const FIXTURES_PATH = `/base/${
- process.env.IS_GITLAB_EE ? 'ee/' : ''
-}spec/javascripts/fixtures`;
+export const FIXTURES_PATH = `/fixtures`;
export const TEST_HOST = 'http://test.host';
export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`;
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
index 212519743aa..7216ad00cc1 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
@@ -83,6 +83,24 @@ describe('Merge request widget rebase component', () => {
expect(text).toContain('foo');
expect(text.replace(/\s\s+/g, ' ')).toContain('to allow this merge request to be merged.');
});
+
+ it('should render the correct target branch name', () => {
+ const targetBranch = 'fake-branch-to-test-with';
+ vm = mountComponent(Component, {
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: false,
+ targetBranch,
+ },
+ service: {},
+ });
+
+ const elem = vm.$el.querySelector('.rebase-state-find-class-convention span');
+
+ expect(elem.innerHTML).toContain(
+ `Fast-forward merge is not possible. Rebase the source branch onto <span class="label-branch">${targetBranch}</span> to allow this merge request to be merged.`,
+ );
+ });
});
describe('methods', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
index f622f52a7b9..5aac37d28df 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
@@ -18,7 +18,7 @@ describe('MR widget status icon component', () => {
it('renders loading icon', () => {
vm = mountComponent(Component, { status: 'loading' });
- expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner');
+ expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
index d93badf8cd3..55a11a72551 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
@@ -38,7 +38,9 @@ describe('MRWidgetAutoMergeFailed', () => {
Vue.nextTick(() => {
expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
- expect(vm.$el.querySelector('button .loading-container span').classList).toContain('spinner');
+ expect(vm.$el.querySelector('button .loading-container span').classList).toContain(
+ 'gl-spinner',
+ );
done();
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
index 96e512d222a..70c70eca746 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
@@ -20,7 +20,7 @@ describe('MRWidgetChecking', () => {
});
it('renders loading icon', () => {
- expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner');
+ expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner');
});
it('renders information about merging', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index bb76616be56..53e1f077610 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -58,9 +58,11 @@ const createComponent = (customConfig = {}) => {
describe('ReadyToMerge', () => {
let vm;
+ let updateMrCountSpy;
beforeEach(() => {
vm = createComponent();
+ updateMrCountSpy = spyOnDependency(ReadyToMerge, 'refreshUserMergeRequestCounts');
});
afterEach(() => {
@@ -234,24 +236,26 @@ describe('ReadyToMerge', () => {
});
});
- describe('shouldShowMergeOptionsDropdown', () => {
- it('should return false when no auto merge strategies are available', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
+ describe('shouldShowMergeImmediatelyDropdown', () => {
+ it('should return false if no pipeline is active', () => {
+ Vue.set(vm.mr, 'isPipelineActive', false);
+ Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', false);
- expect(vm.shouldShowMergeOptionsDropdown).toBe(false);
+ expect(vm.shouldShowMergeImmediatelyDropdown).toBe(false);
});
- it('should return true when at least one auto merge strategy is available', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
+ it('should return false if "Pipelines must succeed" is enabled for the current project', () => {
+ Vue.set(vm.mr, 'isPipelineActive', true);
+ Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true);
- expect(vm.shouldShowMergeOptionsDropdown).toBe(true);
+ expect(vm.shouldShowMergeImmediatelyDropdown).toBe(false);
});
- it('should return false when pipeline active but only merge when pipeline succeeds set in project options', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
- Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true);
+ it('should return true if the MR\'s pipeline is active and "Pipelines must succeed" is not enabled for the current project', () => {
+ Vue.set(vm.mr, 'isPipelineActive', true);
+ Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', false);
- expect(vm.shouldShowMergeOptionsDropdown).toBe(false);
+ expect(vm.shouldShowMergeImmediatelyDropdown).toBe(true);
});
});
@@ -461,6 +465,7 @@ describe('ReadyToMerge', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
expect(eventHub.$emit).toHaveBeenCalledWith('FetchActionsContent');
expect(vm.initiateRemoveSourceBranchPolling).toHaveBeenCalled();
+ expect(updateMrCountSpy).toHaveBeenCalled();
expect(cpc).toBeFalsy();
expect(spc).toBeTruthy();
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
index d6d8eecfcb9..cb656525f06 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -21,7 +21,7 @@ describe('Squash before merge component', () => {
});
describe('checkbox', () => {
- const findCheckbox = () => wrapper.find('.qa-squash-checkbox');
+ const findCheckbox = () => wrapper.find('.js-squash-checkbox');
it('is unchecked if passed value prop is false', () => {
createComponent({
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
index bd64d7b2926..5bd1af56bcc 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
@@ -10,7 +10,7 @@ describe('UnresolvedDiscussions', () => {
vm.$destroy();
});
- describe('with discussions path', () => {
+ describe('with threads path', () => {
beforeEach(() => {
vm = mountComponent(Component, {
mr: {
@@ -21,7 +21,7 @@ describe('UnresolvedDiscussions', () => {
it('should have correct elements', () => {
expect(vm.$el.innerText).toContain(
- 'There are unresolved discussions. Please resolve these discussions',
+ 'There are unresolved threads. Please resolve these threads',
);
expect(vm.$el.innerText).toContain('Create an issue to resolve them later');
@@ -29,14 +29,14 @@ describe('UnresolvedDiscussions', () => {
});
});
- describe('without discussions path', () => {
+ describe('without threads path', () => {
beforeEach(() => {
vm = mountComponent(Component, { mr: {} });
});
it('should not show create issue link if user cannot create issue', () => {
expect(vm.$el.innerText).toContain(
- 'There are unresolved discussions. Please resolve these discussions',
+ 'There are unresolved threads. Please resolve these threads',
);
expect(vm.$el.querySelector('.js-create-issue')).toEqual(null);
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 253413ae43e..a55d5537df7 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -233,6 +233,8 @@ export default {
'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775',
troubleshooting_docs_path: 'help',
merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md',
+ merge_train_when_pipeline_succeeds_docs_path:
+ '/help/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/#startadd-to-merge-train-when-pipeline-succeeds',
squash: true,
visual_review_app_available: true,
merge_trains_enabled: true,
diff --git a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
index e27a506f426..e2cd0f084fd 100644
--- a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
+++ b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
@@ -82,47 +82,5 @@ describe('MergeRequestStore', () => {
expect(store.isNothingToMergeState).toEqual(false);
});
});
-
- describe('mergePipelinesEnabled', () => {
- it('should set mergePipelinesEnabled = true when merge_pipelines_enabled is true', () => {
- store.setData({ ...mockData, merge_pipelines_enabled: true });
-
- expect(store.mergePipelinesEnabled).toBe(true);
- });
-
- it('should set mergePipelinesEnabled = false when merge_pipelines_enabled is not provided', () => {
- store.setData({ ...mockData, merge_pipelines_enabled: undefined });
-
- expect(store.mergePipelinesEnabled).toBe(false);
- });
- });
-
- describe('mergeTrainsCount', () => {
- it('should set mergeTrainsCount when merge_trains_count is provided', () => {
- store.setData({ ...mockData, merge_trains_count: 3 });
-
- expect(store.mergeTrainsCount).toBe(3);
- });
-
- it('should set mergeTrainsCount = 0 when merge_trains_count is not provided', () => {
- store.setData({ ...mockData, merge_trains_count: undefined });
-
- expect(store.mergeTrainsCount).toBe(0);
- });
- });
-
- describe('mergeTrainIndex', () => {
- it('should set mergeTrainIndex when merge_train_index is provided', () => {
- store.setData({ ...mockData, merge_train_index: 3 });
-
- expect(store.mergeTrainIndex).toBe(3);
- });
-
- it('should not set mergeTrainIndex when merge_train_index is not provided', () => {
- store.setData({ ...mockData, merge_train_index: undefined });
-
- expect(store.mergeTrainIndex).toBeUndefined();
- });
- });
});
});
diff --git a/spec/javascripts/vue_shared/components/changed_file_icon_spec.js b/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
deleted file mode 100644
index 634ba8403d5..00000000000
--- a/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import Vue from 'vue';
-import changedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
-import createComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('Changed file icon', () => {
- let vm;
-
- function factory(props = {}) {
- const component = Vue.extend(changedFileIcon);
-
- vm = createComponent(component, {
- ...props,
- file: {
- tempFile: false,
- changed: true,
- },
- });
- }
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('centers icon', () => {
- factory({
- isCentered: true,
- });
-
- expect(vm.$el.classList).toContain('ml-auto');
- });
-
- describe('changedIcon', () => {
- it('equals file-modified when not a temp file and has changes', () => {
- factory();
-
- expect(vm.changedIcon).toBe('file-modified');
- });
-
- it('equals file-addition when a temp file', () => {
- factory();
-
- vm.file.tempFile = true;
-
- expect(vm.changedIcon).toBe('file-addition');
- });
- });
-
- describe('changedIconClass', () => {
- it('includes file-modified when not a temp file', () => {
- factory();
-
- expect(vm.changedIconClass).toContain('file-modified');
- });
-
- it('includes file-addition when a temp file', () => {
- factory();
-
- vm.file.tempFile = true;
-
- expect(vm.changedIconClass).toContain('file-addition');
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js
index 5bea8c43da3..1f61e19fa84 100644
--- a/spec/javascripts/vue_shared/components/file_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/file_icon_spec.js
@@ -72,7 +72,7 @@ describe('File Icon component', () => {
const { classList } = vm.$el.querySelector('.loading-container span');
- expect(classList.contains('spinner')).toEqual(true);
+ expect(classList.contains('gl-spinner')).toEqual(true);
});
it('should add a special class and a size class', () => {
diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js
index 7da69e3fa84..6abcac5c0ff 100644
--- a/spec/javascripts/vue_shared/components/file_row_spec.js
+++ b/spec/javascripts/vue_shared/components/file_row_spec.js
@@ -90,6 +90,19 @@ describe('File row component', () => {
expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null);
});
+ it('is not rendered for `moved` entries in subfolders', () => {
+ createComponent({
+ file: {
+ path: 't5',
+ moved: true,
+ tree: [],
+ },
+ level: 2,
+ });
+
+ expect(vm.$el.nodeType).not.toEqual(1);
+ });
+
describe('new dropdown', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
index a9c1a67b39b..2b059e5e9f4 100644
--- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js
+++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
@@ -88,7 +88,7 @@ describe('Header CI Component', () => {
vm.actions[0].isLoading = true;
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn .spinner').getAttribute('style')).toBeFalsy();
+ expect(vm.$el.querySelector('.btn .gl-spinner').getAttribute('style')).toBeFalsy();
done();
});
});
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
index ea74cb9eb21..dc929e83eb7 100644
--- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
@@ -60,7 +60,7 @@ describe('Suggestion Diff component', () => {
describe('init', () => {
it('renders a suggestion header', () => {
- expect(vm.$el.querySelector('.qa-suggestion-diff-header')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-suggestion-diff-header')).not.toBeNull();
});
it('renders a diff table with syntax highlighting', () => {
diff --git a/spec/javascripts/vue_shared/directives/autofocusonshow_spec.js b/spec/javascripts/vue_shared/directives/autofocusonshow_spec.js
new file mode 100644
index 00000000000..f1ca5f61496
--- /dev/null
+++ b/spec/javascripts/vue_shared/directives/autofocusonshow_spec.js
@@ -0,0 +1,38 @@
+import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
+
+/**
+ * We're testing this directive's hooks as pure functions
+ * since behaviour of this directive is highly-dependent
+ * on underlying DOM methods.
+ */
+describe('AutofocusOnShow directive', () => {
+ describe('with input invisible on component render', () => {
+ let el;
+
+ beforeAll(() => {
+ setFixtures('<div id="container" style="display: none;"><input id="inputel"/></div>');
+ el = document.querySelector('#inputel');
+ });
+
+ it('should bind IntersectionObserver on input element', () => {
+ spyOn(el, 'focus');
+
+ autofocusonshow.inserted(el);
+
+ expect(el.visibilityObserver).toBeDefined();
+ expect(el.focus).not.toHaveBeenCalled();
+ });
+
+ it('should stop IntersectionObserver on input element on unbind hook', () => {
+ el.visibilityObserver = {
+ disconnect: () => {},
+ };
+ spyOn(el.visibilityObserver, 'disconnect');
+
+ autofocusonshow.unbind(el);
+
+ expect(el.visibilityObserver).toBeDefined();
+ expect(el.visibilityObserver.disconnect).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/lib/after_commit_queue_spec.rb b/spec/lib/after_commit_queue_spec.rb
index 6e7c2ec2363..8e9dfd90338 100644
--- a/spec/lib/after_commit_queue_spec.rb
+++ b/spec/lib/after_commit_queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AfterCommitQueue do
diff --git a/spec/lib/api/api_spec.rb b/spec/lib/api/api_spec.rb
index ceef0b41e59..c83d068ca50 100644
--- a/spec/lib/api/api_spec.rb
+++ b/spec/lib/api/api_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::API do
diff --git a/spec/lib/api/helpers/custom_validators_spec.rb b/spec/lib/api/helpers/custom_validators_spec.rb
index aed86b21cb7..1ebce2ab5c4 100644
--- a/spec/lib/api/helpers/custom_validators_spec.rb
+++ b/spec/lib/api/helpers/custom_validators_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::Helpers::CustomValidators do
diff --git a/spec/lib/api/helpers/label_helpers_spec.rb b/spec/lib/api/helpers/label_helpers_spec.rb
new file mode 100644
index 00000000000..138e9a22d70
--- /dev/null
+++ b/spec/lib/api/helpers/label_helpers_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Helpers::LabelHelpers do
+ describe 'create_service_params' do
+ let(:label_helper) do
+ Class.new do
+ include API::Helpers::LabelHelpers
+ end.new
+ end
+
+ context 'when a project is given' do
+ it 'returns the expected params' do
+ project = create(:project)
+ expect(label_helper.create_service_params(project)).to eq({ project: project })
+ end
+ end
+
+ context 'when a group is given' do
+ it 'returns the expected params' do
+ group = create(:group)
+ expect(label_helper.create_service_params(group)).to eq({ group: group })
+ end
+ end
+
+ context 'when something else is given' do
+ it 'raises a type error' do
+ expect { label_helper.create_service_params(Class.new) }.to raise_error(TypeError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index c788da55cd2..b57adb46385 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::Helpers::Pagination do
@@ -114,7 +116,7 @@ describe API::Helpers::Pagination do
expect(paginated_relation.order_values).to be_present
expect(paginated_relation.order_values.size).to eq(1)
expect(paginated_relation.order_values.first).to be_descending
- expect(paginated_relation.order_values.first.expr.name).to eq :id
+ expect(paginated_relation.order_values.first.expr.name).to eq 'id'
end
end
@@ -151,9 +153,9 @@ describe API::Helpers::Pagination do
expect(paginated_relation.order_values).to be_present
expect(paginated_relation.order_values.size).to eq(2)
expect(paginated_relation.order_values.first).to be_descending
- expect(paginated_relation.order_values.first.expr.name).to eq :name
+ expect(paginated_relation.order_values.first.expr.name).to eq 'name'
expect(paginated_relation.order_values.second).to be_descending
- expect(paginated_relation.order_values.second.expr.name).to eq :id
+ expect(paginated_relation.order_values.second.expr.name).to eq 'id'
end
it 'returns the right records (first page)' do
@@ -341,7 +343,7 @@ describe API::Helpers::Pagination do
expect(resource.order_values).to be_empty
expect(paginated_relation.order_values).to be_present
expect(paginated_relation.order_values.first).to be_ascending
- expect(paginated_relation.order_values.first.expr.name).to eq :id
+ expect(paginated_relation.order_values.first.expr.name).to eq 'id'
end
it 'is present it does not add anything' do
@@ -349,7 +351,7 @@ describe API::Helpers::Pagination do
expect(paginated_relation.order_values).to be_present
expect(paginated_relation.order_values.first).to be_descending
- expect(paginated_relation.order_values.first.expr.name).to eq :created_at
+ expect(paginated_relation.order_values.first.expr.name).to eq 'created_at'
end
end
end
diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb
index 99fe8795d91..fb26cc417e8 100644
--- a/spec/lib/api/helpers/related_resources_helpers_spec.rb
+++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::Helpers::RelatedResourcesHelpers do
diff --git a/spec/lib/api/helpers/version_spec.rb b/spec/lib/api/helpers/version_spec.rb
index 34006e0930b..a9f33962537 100644
--- a/spec/lib/api/helpers/version_spec.rb
+++ b/spec/lib/api/helpers/version_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::Helpers::Version do
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index 00916f80784..0624c25e734 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::Helpers do
diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb
index 63f2298357f..e903eada62d 100644
--- a/spec/lib/backup/files_spec.rb
+++ b/spec/lib/backup/files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Backup::Files do
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index ae1c881e1f6..fee7ffc60ee 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Backup::Manager do
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index 5ace5c5b1a2..e1d46c25338 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Backup::Repository do
diff --git a/spec/lib/backup/uploads_spec.rb b/spec/lib/backup/uploads_spec.rb
index 544d3754c0f..55b69f29812 100644
--- a/spec/lib/backup/uploads_spec.rb
+++ b/spec/lib/backup/uploads_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Backup::Uploads do
diff --git a/spec/lib/banzai/color_parser_spec.rb b/spec/lib/banzai/color_parser_spec.rb
index af2a8f215c1..d9202ce77db 100644
--- a/spec/lib/banzai/color_parser_spec.rb
+++ b/spec/lib/banzai/color_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ColorParser do
diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb
index 316dbf052c3..e5a16b167be 100644
--- a/spec/lib/banzai/commit_renderer_spec.rb
+++ b/spec/lib/banzai/commit_renderer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::CommitRenderer do
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index ba995e16be7..cf41af7e7a1 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::CrossProjectReference do
diff --git a/spec/lib/banzai/filter/absolute_link_filter_spec.rb b/spec/lib/banzai/filter/absolute_link_filter_spec.rb
index 50be551cd90..b61bd496dba 100644
--- a/spec/lib/banzai/filter/absolute_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/absolute_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::AbsoluteLinkFilter do
diff --git a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
index 1e82d18d056..3e8b0ea113f 100644
--- a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::AbstractReferenceFilter do
diff --git a/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
index 34f1657b6d3..bd06dae26ba 100644
--- a/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
+++ b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::AsciiDocPostProcessingFilter do
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
index 4972c4b4bd2..8fba72a23f6 100644
--- a/spec/lib/banzai/filter/autolink_filter_spec.rb
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::AutolinkFilter do
diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
index 5b3f679084e..807f1b8bbd3 100644
--- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
+++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Filter::BlockquoteFenceFilter do
diff --git a/spec/lib/banzai/filter/color_filter_spec.rb b/spec/lib/banzai/filter/color_filter_spec.rb
index a098b037510..f8931d37b99 100644
--- a/spec/lib/banzai/filter/color_filter_spec.rb
+++ b/spec/lib/banzai/filter/color_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::ColorFilter, lib: true do
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index 4daf6be1bb7..a82b890be42 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::CommitRangeReferenceFilter do
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index d6c9e9e4b19..326703eea05 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::CommitReferenceFilter do
@@ -103,6 +105,17 @@ describe Banzai::Filter::CommitReferenceFilter do
expect(doc.css('a').first[:href]).to eq(url)
end
+
+ context "a doc with many (29) strings that could be SHAs" do
+ let!(:oids) { noteable.commits.collect(&:id) }
+
+ it 'makes only a single request to Gitaly' do
+ expect(Gitlab::GitalyClient).to receive(:allow_n_plus_1_calls).exactly(0).times
+ expect(Gitlab::Git::Commit).to receive(:batch_by_oid).once.and_call_original
+
+ reference_filter("A big list of SHAs #{oids.join(", ")}", noteable: noteable)
+ end
+ end
end
end
diff --git a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
index 068cdc85a07..bcb74be1034 100644
--- a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'ffaker'
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb
index 85a4619e33d..4e163668a28 100644
--- a/spec/lib/banzai/filter/emoji_filter_spec.rb
+++ b/spec/lib/banzai/filter/emoji_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::EmojiFilter do
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index 7c94cf37e32..78795a157f8 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::ExternalIssueReferenceFilter do
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index 2acbe05f082..59fea5766ee 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'an external link with rel attribute' do
diff --git a/spec/lib/banzai/filter/front_matter_filter_spec.rb b/spec/lib/banzai/filter/front_matter_filter_spec.rb
index 3071dc7cf21..90b383dbcff 100644
--- a/spec/lib/banzai/filter/front_matter_filter_spec.rb
+++ b/spec/lib/banzai/filter/front_matter_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Filter::FrontMatterFilter do
diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
index 0e178b859c4..9d179ef2a49 100644
--- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
+++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::GollumTagsFilter do
diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb
index 1d98fc0d5db..6017380725d 100644
--- a/spec/lib/banzai/filter/html_entity_filter_spec.rb
+++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::HtmlEntityFilter do
diff --git a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
index d06c5535309..6475fd14ce4 100644
--- a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::ImageLazyLoadFilter do
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
index c84b98eb225..7b0cb675551 100644
--- a/spec/lib/banzai/filter/image_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::ImageLinkFilter do
diff --git a/spec/lib/banzai/filter/inline_diff_filter_spec.rb b/spec/lib/banzai/filter/inline_diff_filter_spec.rb
index 63c4ab61b86..c09065fb746 100644
--- a/spec/lib/banzai/filter/inline_diff_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_diff_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::InlineDiffFilter do
diff --git a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
new file mode 100644
index 00000000000..66bbcbf7292
--- /dev/null
+++ b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::InlineMetricsFilter do
+ include FilterSpecHelper
+
+ let(:input) { %(<a href="#{url}">example</a>) }
+ let(:doc) { filter(input) }
+
+ context 'when the document has an external link' do
+ let(:url) { 'https://foo.com' }
+
+ it 'leaves regular non-metrics links unchanged' do
+ expect(doc.to_s).to eq(input)
+ end
+ end
+
+ context 'when the document has a metrics dashboard link' do
+ let(:params) { ['foo', 'bar', 12] }
+ let(:url) { urls.metrics_namespace_project_environment_url(*params) }
+
+ it 'leaves the original link unchanged' do
+ expect(doc.at_css('a').to_s).to eq(input)
+ end
+
+ it 'appends a metrics charts placeholder with dashboard url after metrics links' do
+ node = doc.at_css('.js-render-metrics')
+ expect(node).to be_present
+
+ dashboard_url = urls.metrics_dashboard_namespace_project_environment_url(*params, embedded: true)
+ expect(node.attribute('data-dashboard-url').to_s).to eq(dashboard_url)
+ end
+
+ context 'when the metrics dashboard link is part of a paragraph' do
+ let(:paragraph) { %(This is an <a href="#{url}">example</a> of metrics.) }
+ let(:input) { %(<p>#{paragraph}</p>) }
+
+ it 'appends the charts placeholder after the enclosing paragraph' do
+ expect(doc.at_css('p').to_s).to include(paragraph)
+ expect(doc.at_css('.js-render-metrics')).to be_present
+ end
+ end
+
+ context 'with dashboard params specified' do
+ let(:params) do
+ [
+ 'foo',
+ 'bar',
+ 12,
+ {
+ embedded: true,
+ dashboard: 'config/prometheus/common_metrics.yml',
+ group: 'System metrics (Kubernetes)',
+ title: 'Core Usage (Pod Average)',
+ y_label: 'Cores per Pod'
+ }
+ ]
+ end
+
+ it 'appends a metrics charts placeholder with dashboard url after metrics links' do
+ node = doc.at_css('.js-render-metrics')
+ expect(node).to be_present
+
+ dashboard_url = urls.metrics_dashboard_namespace_project_environment_url(*params)
+ expect(node.attribute('data-dashboard-url').to_s).to eq(dashboard_url)
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
new file mode 100644
index 00000000000..a99cd7d6076
--- /dev/null
+++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::InlineMetricsRedactorFilter do
+ include FilterSpecHelper
+
+ set(:project) { create(:project) }
+
+ let(:url) { urls.metrics_dashboard_project_environment_url(project, 1, embedded: true) }
+ let(:input) { %(<a href="#{url}">example</a>) }
+ let(:doc) { filter(input) }
+
+ context 'without a metrics charts placeholder' do
+ it 'leaves regular non-metrics links unchanged' do
+ expect(doc.to_s).to eq input
+ end
+ end
+
+ context 'with a metrics charts placeholder' do
+ let(:input) { %(<div class="js-render-metrics" data-dashboard-url="#{url}"></div>) }
+
+ context 'no user is logged in' do
+ it 'redacts the placeholder' do
+ expect(doc.to_s).to be_empty
+ end
+ end
+
+ context 'the user does not have permission do see charts' do
+ let(:doc) { filter(input, current_user: build(:user)) }
+
+ it 'redacts the placeholder' do
+ expect(doc.to_s).to be_empty
+ end
+ end
+
+ context 'the user has requisite permissions' do
+ let(:user) { create(:user) }
+ let(:doc) { filter(input, current_user: user) }
+
+ it 'leaves the placeholder' do
+ project.add_maintainer(user)
+
+ expect(doc.to_s).to eq input
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
index a5373517ac8..9f6dcded56f 100644
--- a/spec/lib/banzai/filter/issuable_state_filter_spec.rb
+++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::IssuableStateFilter do
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 914c4e2d823..4a412da27a7 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::IssueReferenceFilter do
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 108d7b43a26..213a5459118 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'html/pipeline'
diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb
index 83fcda29680..06df67facf9 100644
--- a/spec/lib/banzai/filter/markdown_filter_spec.rb
+++ b/spec/lib/banzai/filter/markdown_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::MarkdownFilter do
diff --git a/spec/lib/banzai/filter/math_filter_spec.rb b/spec/lib/banzai/filter/math_filter_spec.rb
index cade8cb409e..c8fd92edcdf 100644
--- a/spec/lib/banzai/filter/math_filter_spec.rb
+++ b/spec/lib/banzai/filter/math_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::MathFilter do
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index 72dfd6ff9ea..12ee952b10e 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::MergeRequestReferenceFilter do
diff --git a/spec/lib/banzai/filter/mermaid_filter_spec.rb b/spec/lib/banzai/filter/mermaid_filter_spec.rb
index f6474c8936d..ae6725cc14c 100644
--- a/spec/lib/banzai/filter/mermaid_filter_spec.rb
+++ b/spec/lib/banzai/filter/mermaid_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::MermaidFilter do
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index f0a5dc8d0d7..3f021adc756 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::MilestoneReferenceFilter do
@@ -363,7 +365,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
expect(doc.css('a')).to be_empty
end
- it 'supports parent group references', :nested_groups do
+ it 'supports parent group references' do
milestone.update!(group: parent_group)
doc = reference_filter("See #{reference}")
@@ -396,7 +398,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
context 'when group milestone' do
let(:group_milestone) { create(:milestone, title: 'group_milestone', group: group) }
- context 'for subgroups', :nested_groups do
+ context 'for subgroups' do
let(:sub_group) { create(:group, parent: group) }
let(:sub_group_milestone) { create(:milestone, title: 'sub_group_milestone', group: sub_group) }
diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb
index 6f7acfe7072..713bab4527b 100644
--- a/spec/lib/banzai/filter/plantuml_filter_spec.rb
+++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::PlantumlFilter do
diff --git a/spec/lib/banzai/filter/reference_filter_spec.rb b/spec/lib/banzai/filter/reference_filter_spec.rb
index f96b6c83b0a..d889b0b832d 100644
--- a/spec/lib/banzai/filter/reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::ReferenceFilter do
diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
index 919825a6102..dc888a47988 100644
--- a/spec/lib/banzai/filter/redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Banzai::Filter::RedactorFilter do
+describe Banzai::Filter::ReferenceRedactorFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 8ff971114d6..ecb83b6cb66 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::RelativeLinkFilter do
@@ -83,6 +85,11 @@ describe Banzai::Filter::RelativeLinkFilter do
expect { filter(act) }.not_to raise_error
end
+ it 'does not explode with an escaped null byte' do
+ act = link("/%00")
+ expect { filter(act) }.not_to raise_error
+ end
+
it 'does not raise an exception with a space in the path' do
act = link("/uploads/d18213acd3732630991986120e167e3d/Landscape_8.jpg \nBut here's some more unexpected text :smile:)")
expect { filter(act) }.not_to raise_error
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index f2a5d7b2c9f..8a4b819e4d6 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::SanitizationFilter do
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index 21cf092428d..62ce12406a2 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::SnippetReferenceFilter do
diff --git a/spec/lib/banzai/filter/spaced_link_filter_spec.rb b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
index 76d7644d76c..98c38813144 100644
--- a/spec/lib/banzai/filter/spaced_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::SpacedLinkFilter do
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index 80ca7a63435..f220ccecee1 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::SyntaxHighlightFilter do
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
index 4a9880ac85a..5ca3c722e3e 100644
--- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::TableOfContentsFilter do
@@ -112,11 +114,11 @@ describe Banzai::Filter::TableOfContentsFilter do
context 'table of contents nesting' do
let(:results) do
result(
- header(1, 'Header 1') <<
- header(2, 'Header 1-1') <<
- header(3, 'Header 1-1-1') <<
- header(2, 'Header 1-2') <<
- header(1, 'Header 2') <<
+ header(1, 'Header 1') +
+ header(2, 'Header 1-1') +
+ header(3, 'Header 1-1-1') +
+ header(2, 'Header 1-2') +
+ header(1, 'Header 2') +
header(2, 'Header 2-1')
)
end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 1e8a44b4549..6bc87d245f5 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::UserReferenceFilter do
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
index 81dda0687f3..483e806624c 100644
--- a/spec/lib/banzai/filter/video_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::VideoLinkFilter do
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index cce1cd0b284..d2d539a62fc 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::WikiLinkFilter do
@@ -70,47 +72,5 @@ describe Banzai::Filter::WikiLinkFilter do
expect(filtered_link.attribute('href').value).to eq(invalid_link)
end
end
-
- context "when the slug is deemed unsafe or invalid" do
- let(:link) { "alert(1);" }
-
- invalid_slugs = [
- "javascript:",
- "JaVaScRiPt:",
- "\u0001java\u0003script:",
- "javascript :",
- "javascript: ",
- "javascript : ",
- ":javascript:",
- "javascript&#58;",
- "javascript&#0058;",
- "javascript&#x3A;",
- "javascript&#x003A;",
- "java\0script:",
- " &#14; javascript:"
- ]
-
- invalid_slugs.each do |slug|
- context "with the slug #{slug}" do
- it "doesn't rewrite a (.) relative link" do
- filtered_link = filter(
- "<a href='.#{link}'>Link</a>",
- project_wiki: wiki,
- page_slug: slug).children[0]
-
- expect(filtered_link.attribute('href').value).not_to include(slug)
- end
-
- it "doesn't rewrite a (..) relative link" do
- filtered_link = filter(
- "<a href='..#{link}'>Link</a>",
- project_wiki: wiki,
- page_slug: slug).children[0]
-
- expect(filtered_link.attribute('href').value).not_to include(slug)
- end
- end
- end
- end
end
end
diff --git a/spec/lib/banzai/filter_array_spec.rb b/spec/lib/banzai/filter_array_spec.rb
index ea84005e7f8..bed41a80d29 100644
--- a/spec/lib/banzai/filter_array_spec.rb
+++ b/spec/lib/banzai/filter_array_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::FilterArray do
diff --git a/spec/lib/banzai/issuable_extractor_spec.rb b/spec/lib/banzai/issuable_extractor_spec.rb
index f42951d9781..7fa6048c1c6 100644
--- a/spec/lib/banzai/issuable_extractor_spec.rb
+++ b/spec/lib/banzai/issuable_extractor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::IssuableExtractor do
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index 7b855251a74..a523608fa50 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ObjectRenderer do
@@ -22,8 +24,8 @@ describe Banzai::ObjectRenderer do
expect(object.user_visible_reference_count).to eq 0
end
- it 'calls Banzai::Redactor to perform redaction' do
- expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
+ it 'calls Banzai::ReferenceRedactor to perform redaction' do
+ expect_any_instance_of(Banzai::ReferenceRedactor).to receive(:redact).and_call_original
renderer.render([object], :note)
end
@@ -82,8 +84,8 @@ describe Banzai::ObjectRenderer do
expect(cacheless_thing.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'")
end
- it 'calls Banzai::Redactor to perform redaction' do
- expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
+ it 'calls Banzai::ReferenceRedactor to perform redaction' do
+ expect_any_instance_of(Banzai::ReferenceRedactor).to receive(:redact).and_call_original
renderer.render([cacheless_thing], :title)
end
diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
index 77cb1954ea3..d032ec71e45 100644
--- a/spec/lib/banzai/pipeline/description_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Pipeline::DescriptionPipeline do
diff --git a/spec/lib/banzai/pipeline/email_pipeline_spec.rb b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
index b99161109eb..eea25320f3d 100644
--- a/spec/lib/banzai/pipeline/email_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Pipeline::EmailPipeline do
diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
index 3d3aa64d630..2b4d1b58676 100644
--- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Pipeline::FullPipeline do
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 7119c826bca..0a3e0962452 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Pipeline::GfmPipeline do
@@ -32,7 +34,7 @@ describe Banzai::Pipeline::GfmPipeline do
result = described_class.call(markdown, project: project)[:output]
link = result.css('a').first
- expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ expect(link['href']).to eq 'http://issue-tracker.example.com/issues/12'
end
it 'parses cross-project references to regular issues' do
@@ -61,7 +63,7 @@ describe Banzai::Pipeline::GfmPipeline do
result = described_class.call(markdown, project: project)[:output]
link = result.css('a').first
- expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ expect(link['href']).to eq 'http://issue-tracker.example.com/issues/12'
end
it 'allows to use long external reference syntax for Redmine' do
@@ -70,7 +72,7 @@ describe Banzai::Pipeline::GfmPipeline do
result = described_class.call(markdown, project: project)[:output]
link = result.css('a').first
- expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ expect(link['href']).to eq 'http://issue-tracker.example.com/issues/12'
end
it 'parses cross-project references to regular issues' do
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 64ca3ec345d..015af20f220 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Pipeline::WikiPipeline do
@@ -177,6 +179,85 @@ describe Banzai::Pipeline::WikiPipeline do
end
end
end
+
+ describe "checking slug validity when assembling links" do
+ context "with a valid slug" do
+ let(:valid_slug) { "http://example.com" }
+
+ it "includes the slug in a (.) relative link" do
+ output = described_class.to_html(
+ "[Link](./alert(1);)",
+ project: project,
+ project_wiki: project_wiki,
+ page_slug: valid_slug
+ )
+
+ expect(output).to include(valid_slug)
+ end
+
+ it "includeds the slug in a (..) relative link" do
+ output = described_class.to_html(
+ "[Link](../alert(1);)",
+ project: project,
+ project_wiki: project_wiki,
+ page_slug: valid_slug
+ )
+
+ expect(output).to include(valid_slug)
+ end
+ end
+
+ context "when the slug is deemed unsafe or invalid" do
+ invalid_slugs = [
+ "javascript:",
+ "JaVaScRiPt:",
+ "\u0001java\u0003script:",
+ "javascript :",
+ "javascript: ",
+ "javascript : ",
+ ":javascript:",
+ "javascript&#58;",
+ "javascript&#0058;",
+ "javascript&#x3A;",
+ "javascript&#x003A;",
+ "java\0script:",
+ " &#14; javascript:"
+ ]
+
+ invalid_js_links = [
+ "alert(1);",
+ "alert(document.location);"
+ ]
+
+ invalid_slugs.each do |slug|
+ context "with the invalid slug #{slug}" do
+ invalid_js_links.each do |link|
+ it "doesn't include a prohibited slug in a (.) relative link '#{link}'" do
+ output = described_class.to_html(
+ "[Link](./#{link})",
+ project: project,
+ project_wiki: project_wiki,
+ page_slug: slug
+ )
+
+ expect(output).not_to include(slug)
+ end
+
+ it "doesn't include a prohibited slug in a (..) relative link '#{link}'" do
+ output = described_class.to_html(
+ "[Link](../#{link})",
+ project: project,
+ project_wiki: project_wiki,
+ page_slug: slug
+ )
+
+ expect(output).not_to include(slug)
+ end
+ end
+ end
+ end
+ end
+ end
end
describe 'videos' do
diff --git a/spec/lib/banzai/querying_spec.rb b/spec/lib/banzai/querying_spec.rb
index 27da2a7439c..b7a235b0558 100644
--- a/spec/lib/banzai/querying_spec.rb
+++ b/spec/lib/banzai/querying_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Querying do
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index c6e9fc414a1..7897164d985 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::BaseParser do
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index f558dea209f..b44ae67e430 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::CommitParser do
diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
index ff3b82cc482..da853233018 100644
--- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::CommitRangeParser do
diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
index 1cb31e57114..0f29a95bdcc 100644
--- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::ExternalIssueParser do
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index 77c2064caba..a925d294b1b 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::IssueParser do
diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb
index e4df2533821..cf8adb57ffc 100644
--- a/spec/lib/banzai/reference_parser/label_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::LabelParser do
diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
index 5417b1f00be..1561dabcdbf 100644
--- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::MergeRequestParser do
diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
index 751d042ffde..006f8e37690 100644
--- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::MilestoneParser do
diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
index d410bd4c164..528f79ed020 100644
--- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::SnippetParser do
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index 112447f098e..a5b4e59a3a1 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::UserParser do
diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/reference_redactor_spec.rb
index 718649e0e10..c30a194a0b3 100644
--- a/spec/lib/banzai/redactor_spec.rb
+++ b/spec/lib/banzai/reference_redactor_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Banzai::Redactor do
+describe Banzai::ReferenceRedactor do
let(:user) { create(:user) }
let(:project) { build(:project) }
let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
index aa828e2f0e9..0d329b47aa3 100644
--- a/spec/lib/banzai/renderer_spec.rb
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Renderer do
@@ -19,6 +21,24 @@ describe Banzai::Renderer do
object
end
+ describe '#cache_collection_render' do
+ let(:merge_request) { fake_object(fresh: true) }
+ let(:context) { { cache_key: [merge_request, 'field'], rendered: merge_request.field_html } }
+
+ context 'when an item has a rendered field' do
+ before do
+ allow(merge_request).to receive(:field).and_return('This is the field')
+ allow(merge_request).to receive(:field_html).and_return('This is the field')
+ end
+
+ it 'does not touch redis if the field is in the cache' do
+ expect(Rails).not_to receive(:cache)
+
+ described_class.cache_collection_render([{ text: merge_request.field, context: context }])
+ end
+ end
+ end
+
describe '#render_field' do
let(:renderer) { described_class }
diff --git a/spec/lib/bitbucket/collection_spec.rb b/spec/lib/bitbucket/collection_spec.rb
index 9008cb3e870..5946be71565 100644
--- a/spec/lib/bitbucket/collection_spec.rb
+++ b/spec/lib/bitbucket/collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
# Emulates paginator. It returns 2 pages with results
diff --git a/spec/lib/bitbucket/connection_spec.rb b/spec/lib/bitbucket/connection_spec.rb
index 14faeb231a9..ec8eac232cd 100644
--- a/spec/lib/bitbucket/connection_spec.rb
+++ b/spec/lib/bitbucket/connection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Connection do
diff --git a/spec/lib/bitbucket/page_spec.rb b/spec/lib/bitbucket/page_spec.rb
index 04d5a0470b1..6301dd56faf 100644
--- a/spec/lib/bitbucket/page_spec.rb
+++ b/spec/lib/bitbucket/page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Page do
diff --git a/spec/lib/bitbucket/paginator_spec.rb b/spec/lib/bitbucket/paginator_spec.rb
index bdf10a5e2a2..a1effa14000 100644
--- a/spec/lib/bitbucket/paginator_spec.rb
+++ b/spec/lib/bitbucket/paginator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Paginator do
diff --git a/spec/lib/bitbucket/representation/comment_spec.rb b/spec/lib/bitbucket/representation/comment_spec.rb
index fec243a9f96..1874296df8c 100644
--- a/spec/lib/bitbucket/representation/comment_spec.rb
+++ b/spec/lib/bitbucket/representation/comment_spec.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Representation::Comment do
describe '#author' do
- it { expect(described_class.new('user' => { 'username' => 'Ben' }).author).to eq('Ben') }
+ it { expect(described_class.new('user' => { 'nickname' => 'Ben' }).author).to eq('Ben') }
it { expect(described_class.new({}).author).to be_nil }
end
diff --git a/spec/lib/bitbucket/representation/issue_spec.rb b/spec/lib/bitbucket/representation/issue_spec.rb
index 20f47224aa8..655b9b78b47 100644
--- a/spec/lib/bitbucket/representation/issue_spec.rb
+++ b/spec/lib/bitbucket/representation/issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Representation::Issue do
@@ -15,7 +17,7 @@ describe Bitbucket::Representation::Issue do
end
describe '#author' do
- it { expect(described_class.new({ 'reporter' => { 'username' => 'Ben' } }).author).to eq('Ben') }
+ it { expect(described_class.new({ 'reporter' => { 'nickname' => 'Ben' } }).author).to eq('Ben') }
it { expect(described_class.new({}).author).to be_nil }
end
diff --git a/spec/lib/bitbucket/representation/pull_request_comment_spec.rb b/spec/lib/bitbucket/representation/pull_request_comment_spec.rb
index 673dcf22ce8..151055f510f 100644
--- a/spec/lib/bitbucket/representation/pull_request_comment_spec.rb
+++ b/spec/lib/bitbucket/representation/pull_request_comment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Representation::PullRequestComment do
diff --git a/spec/lib/bitbucket/representation/pull_request_spec.rb b/spec/lib/bitbucket/representation/pull_request_spec.rb
index 30453528be4..70b51b8efec 100644
--- a/spec/lib/bitbucket/representation/pull_request_spec.rb
+++ b/spec/lib/bitbucket/representation/pull_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Representation::PullRequest do
@@ -6,7 +8,7 @@ describe Bitbucket::Representation::PullRequest do
end
describe '#author' do
- it { expect(described_class.new({ 'author' => { 'username' => 'Ben' } }).author).to eq('Ben') }
+ it { expect(described_class.new({ 'author' => { 'nickname' => 'Ben' } }).author).to eq('Ben') }
it { expect(described_class.new({}).author).to be_nil }
end
diff --git a/spec/lib/bitbucket/representation/repo_spec.rb b/spec/lib/bitbucket/representation/repo_spec.rb
index 405265cc669..a272695e681 100644
--- a/spec/lib/bitbucket/representation/repo_spec.rb
+++ b/spec/lib/bitbucket/representation/repo_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Representation::Repo do
diff --git a/spec/lib/bitbucket/representation/user_spec.rb b/spec/lib/bitbucket/representation/user_spec.rb
index f79ff4edb7b..0169887a24c 100644
--- a/spec/lib/bitbucket/representation/user_spec.rb
+++ b/spec/lib/bitbucket/representation/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Representation::User do
diff --git a/spec/lib/bitbucket_server/client_spec.rb b/spec/lib/bitbucket_server/client_spec.rb
index 4f0d57ca8a6..aa0217856ee 100644
--- a/spec/lib/bitbucket_server/client_spec.rb
+++ b/spec/lib/bitbucket_server/client_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Client do
@@ -56,6 +58,17 @@ describe BitbucketServer::Client do
subject.repos(page_offset: 10, limit: 25)
end
+
+ context 'when filter param is passed' do
+ let(:filter) { 'test' }
+ let(:expected_path) { "#{path}?name=#{filter}" }
+
+ it 'requests a collection with filter applied' do
+ expect(BitbucketServer::Paginator).to receive(:new).with(anything, expected_path, :repo, page_offset: 0, limit: nil)
+
+ subject.repos(filter: filter)
+ end
+ end
end
describe '#create_branch' do
diff --git a/spec/lib/bitbucket_server/connection_spec.rb b/spec/lib/bitbucket_server/connection_spec.rb
index ab8a5b89608..3a7fe4e7321 100644
--- a/spec/lib/bitbucket_server/connection_spec.rb
+++ b/spec/lib/bitbucket_server/connection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Connection do
diff --git a/spec/lib/bitbucket_server/page_spec.rb b/spec/lib/bitbucket_server/page_spec.rb
index cf419a9045b..2da1d0995ca 100644
--- a/spec/lib/bitbucket_server/page_spec.rb
+++ b/spec/lib/bitbucket_server/page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Page do
diff --git a/spec/lib/bitbucket_server/paginator_spec.rb b/spec/lib/bitbucket_server/paginator_spec.rb
index eadd7f68bfb..e01cbeb4270 100644
--- a/spec/lib/bitbucket_server/paginator_spec.rb
+++ b/spec/lib/bitbucket_server/paginator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Paginator do
diff --git a/spec/lib/bitbucket_server/representation/activity_spec.rb b/spec/lib/bitbucket_server/representation/activity_spec.rb
index 15c50e40472..b548dedadfb 100644
--- a/spec/lib/bitbucket_server/representation/activity_spec.rb
+++ b/spec/lib/bitbucket_server/representation/activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Representation::Activity do
diff --git a/spec/lib/bitbucket_server/representation/comment_spec.rb b/spec/lib/bitbucket_server/representation/comment_spec.rb
index 53a20a1d80a..f8c73c3da35 100644
--- a/spec/lib/bitbucket_server/representation/comment_spec.rb
+++ b/spec/lib/bitbucket_server/representation/comment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Representation::Comment do
diff --git a/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb b/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb
index bd7e3597486..db43e990812 100644
--- a/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb
+++ b/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Representation::PullRequestComment do
diff --git a/spec/lib/bitbucket_server/representation/pull_request_spec.rb b/spec/lib/bitbucket_server/representation/pull_request_spec.rb
index 4b8afdb006b..e091890041e 100644
--- a/spec/lib/bitbucket_server/representation/pull_request_spec.rb
+++ b/spec/lib/bitbucket_server/representation/pull_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Representation::PullRequest do
diff --git a/spec/lib/bitbucket_server/representation/repo_spec.rb b/spec/lib/bitbucket_server/representation/repo_spec.rb
index 3ac1030fbb0..801de247d73 100644
--- a/spec/lib/bitbucket_server/representation/repo_spec.rb
+++ b/spec/lib/bitbucket_server/representation/repo_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Representation::Repo do
diff --git a/spec/lib/constraints/feature_constrainer_spec.rb b/spec/lib/constraints/feature_constrainer_spec.rb
index 42efc164f81..0739da801a7 100644
--- a/spec/lib/constraints/feature_constrainer_spec.rb
+++ b/spec/lib/constraints/feature_constrainer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Constraints::FeatureConstrainer do
diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb
index ff295068ba9..573de331898 100644
--- a/spec/lib/constraints/group_url_constrainer_spec.rb
+++ b/spec/lib/constraints/group_url_constrainer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Constraints::GroupUrlConstrainer do
diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb
index 3496b01ebcc..ac3221ecab7 100644
--- a/spec/lib/constraints/project_url_constrainer_spec.rb
+++ b/spec/lib/constraints/project_url_constrainer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Constraints::ProjectUrlConstrainer do
diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb
index e2c85bb27bb..15ef930420c 100644
--- a/spec/lib/constraints/user_url_constrainer_spec.rb
+++ b/spec/lib/constraints/user_url_constrainer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Constraints::UserUrlConstrainer do
diff --git a/spec/lib/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb
index d3fff5bad42..be7be2f3719 100644
--- a/spec/lib/container_registry/blob_spec.rb
+++ b/spec/lib/container_registry/blob_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContainerRegistry::Blob do
@@ -110,11 +112,28 @@ describe ContainerRegistry::Blob do
end
end
+ context 'for a relative address' do
+ before do
+ stub_request(:get, 'http://registry.gitlab/relative')
+ .with { |request| !request.headers.include?('Authorization') }
+ .to_return(
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' },
+ body: '{"key":"value"}')
+ end
+
+ let(:location) { '/relative' }
+
+ it 'returns correct data' do
+ expect(blob.data).to eq '{"key":"value"}'
+ end
+ end
+
context 'for invalid file' do
let(:location) { 'file:///etc/passwd' }
it 'raises an error' do
- expect { blob.data }.to raise_error(ArgumentError, 'invalid address')
+ expect { blob.data }.to raise_error(ArgumentError, 'Invalid scheme for file:///etc/passwd')
end
end
end
diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb
index 3df33f48adb..bc5fddd12ba 100644
--- a/spec/lib/container_registry/client_spec.rb
+++ b/spec/lib/container_registry/client_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContainerRegistry::Client do
@@ -6,6 +8,42 @@ describe ContainerRegistry::Client do
let(:options) { { token: token } }
let(:client) { described_class.new("http://container-registry", options) }
+ shared_examples '#repository_manifest' do |manifest_type|
+ let(:manifest) do
+ {
+ "schemaVersion" => 2,
+ "config" => {
+ "mediaType" => manifest_type,
+ "digest" =>
+ "sha256:4a3ef0786dd241be6000311e1503869b320be433b9cba84cfafeb512d1720c95",
+ "size" => 6608
+ },
+ "layers" => [
+ {
+ "mediaType" => manifest_type,
+ "digest" =>
+ "sha256:83ef92b73cf4595aa7fe214ec6747228283d585f373d8f6bc08d66bebab531b7",
+ "size" => 2828661
+ }
+ ]
+ }
+ end
+
+ it 'GET /v2/:name/manifests/mytag' do
+ stub_request(:get, "http://container-registry/v2/group/test/manifests/mytag")
+ .with(headers: {
+ 'Accept' => described_class::ACCEPTED_TYPES.join(', '),
+ 'Authorization' => "bearer #{token}"
+ })
+ .to_return(status: 200, body: manifest.to_json, headers: { content_type: manifest_type })
+
+ expect(client.repository_manifest('group/test', 'mytag')).to eq(manifest)
+ end
+ end
+
+ it_behaves_like '#repository_manifest', described_class::DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE
+ it_behaves_like '#repository_manifest', described_class::OCI_MANIFEST_V1_TYPE
+
describe '#blob' do
it 'GET /v2/:name/blobs/:digest' do
stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345")
diff --git a/spec/lib/container_registry/path_spec.rb b/spec/lib/container_registry/path_spec.rb
index 010deae822c..8c671b4d56d 100644
--- a/spec/lib/container_registry/path_spec.rb
+++ b/spec/lib/container_registry/path_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContainerRegistry::Path do
diff --git a/spec/lib/container_registry/registry_spec.rb b/spec/lib/container_registry/registry_spec.rb
index 4d6eea94bf0..7cf70a1f562 100644
--- a/spec/lib/container_registry/registry_spec.rb
+++ b/spec/lib/container_registry/registry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContainerRegistry::Registry do
diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb
index cb4ae3be525..110f006536b 100644
--- a/spec/lib/container_registry/tag_spec.rb
+++ b/spec/lib/container_registry/tag_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContainerRegistry::Tag do
@@ -9,7 +11,7 @@ describe ContainerRegistry::Tag do
end
let(:headers) do
- { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' }
+ { 'Accept' => ContainerRegistry::Client::ACCEPTED_TYPES.join(', ') }
end
let(:tag) { described_class.new(repository, 'tag') }
diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb
index 6648e141b7a..d6eca8d85ff 100644
--- a/spec/lib/event_filter_spec.rb
+++ b/spec/lib/event_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EventFilter do
diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb
index 7faa0f31b68..394efa85701 100644
--- a/spec/lib/expand_variables_spec.rb
+++ b/spec/lib/expand_variables_spec.rb
@@ -1,63 +1,134 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExpandVariables do
describe '#expand' do
- subject { described_class.expand(value, variables) }
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "no expansion": {
+ value: 'key',
+ result: 'key',
+ variables: []
+ },
+ "missing variable": {
+ value: 'key$variable',
+ result: 'key',
+ variables: []
+ },
+ "simple expansion": {
+ value: 'key$variable',
+ result: 'keyvalue',
+ variables: [
+ { key: 'variable', value: 'value' }
+ ]
+ },
+ "simple with hash of variables": {
+ value: 'key$variable',
+ result: 'keyvalue',
+ variables: {
+ 'variable' => 'value'
+ }
+ },
+ "complex expansion": {
+ value: 'key${variable}',
+ result: 'keyvalue',
+ variables: [
+ { key: 'variable', value: 'value' }
+ ]
+ },
+ "simple expansions": {
+ value: 'key$variable$variable2',
+ result: 'keyvalueresult',
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' }
+ ]
+ },
+ "complex expansions": {
+ value: 'key${variable}${variable2}',
+ result: 'keyvalueresult',
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' }
+ ]
+ },
+ "complex expansions with missing variable": {
+ value: 'key${variable}${variable2}',
+ result: 'keyvalue',
+ variables: [
+ { key: 'variable', value: 'value' }
+ ]
+ },
+ "out-of-order expansion": {
+ value: 'key$variable2$variable',
+ result: 'keyresultvalue',
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' }
+ ]
+ },
+ "out-of-order complex expansion": {
+ value: 'key${variable2}${variable}',
+ result: 'keyresultvalue',
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' }
+ ]
+ },
+ "review-apps expansion": {
+ value: 'review/$CI_COMMIT_REF_NAME',
+ result: 'review/feature/add-review-apps',
+ variables: [
+ { key: 'CI_COMMIT_REF_NAME', value: 'feature/add-review-apps' }
+ ]
+ },
+ "do not lazily access variables when no expansion": {
+ value: 'key',
+ result: 'key',
+ variables: -> { raise NotImplementedError }
+ },
+ "lazily access variables": {
+ value: 'key$variable',
+ result: 'keyvalue',
+ variables: -> { [{ key: 'variable', value: 'value' }] }
+ }
+ }
+ end
+
+ with_them do
+ subject { ExpandVariables.expand(value, variables) } # rubocop:disable RSpec/DescribedClass
+
+ it { is_expected.to eq(result) }
+ end
+ end
+
+ context 'lazily inits variables' do
+ let(:variables) { -> { [{ key: 'variable', value: 'result' }] } }
+
+ subject { described_class.expand(value, variables) }
+
+ context 'when expanding variable' do
+ let(:value) { 'key$variable$variable2' }
+
+ it 'calls block at most once' do
+ expect(variables).to receive(:call).once.and_call_original
+
+ is_expected.to eq('keyresult')
+ end
+ end
- tests = [
- { value: 'key',
- result: 'key',
- variables: [] },
- { value: 'key$variable',
- result: 'key',
- variables: [] },
- { value: 'key$variable',
- result: 'keyvalue',
- variables: [
- { key: 'variable', value: 'value' }
- ] },
- { value: 'key${variable}',
- result: 'keyvalue',
- variables: [
- { key: 'variable', value: 'value' }
- ] },
- { value: 'key$variable$variable2',
- result: 'keyvalueresult',
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' }
- ] },
- { value: 'key${variable}${variable2}',
- result: 'keyvalueresult',
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' }
- ] },
- { value: 'key$variable2$variable',
- result: 'keyresultvalue',
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' }
- ] },
- { value: 'key${variable2}${variable}',
- result: 'keyresultvalue',
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' }
- ] },
- { value: 'review/$CI_COMMIT_REF_NAME',
- result: 'review/feature/add-review-apps',
- variables: [
- { key: 'CI_COMMIT_REF_NAME', value: 'feature/add-review-apps' }
- ] }
- ]
+ context 'when no expansion is needed' do
+ let(:value) { 'key' }
- tests.each do |test|
- context "#{test[:value]} resolves to #{test[:result]}" do
- let(:value) { test[:value] }
- let(:variables) { test[:variables] }
+ it 'does not call block' do
+ expect(variables).not_to receive(:call)
- it { is_expected.to eq(test[:result]) }
+ is_expected.to eq('key')
+ end
end
end
end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 21ba72953fb..ffe7584a019 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExtractsPath do
diff --git a/spec/lib/feature/gitaly_spec.rb b/spec/lib/feature/gitaly_spec.rb
index 0e24a927d4c..4e07acf9c1a 100644
--- a/spec/lib/feature/gitaly_spec.rb
+++ b/spec/lib/feature/gitaly_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Feature::Gitaly do
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 6f05914f915..3d59b1f35a9 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Feature do
@@ -40,7 +42,7 @@ describe Feature do
.once
.and_call_original
- expect(Rails.cache)
+ expect(Gitlab::ThreadMemoryCache.cache_backend)
.to receive(:fetch)
.once
.with('flipper:persisted_names', expires_in: 1.minute)
@@ -144,6 +146,68 @@ describe Feature do
expect(described_class.enabled?(:enabled_feature_flag)).to be_truthy
end
+ it { expect(described_class.l1_cache_backend).to eq(Gitlab::ThreadMemoryCache.cache_backend) }
+ it { expect(described_class.l2_cache_backend).to eq(Rails.cache) }
+
+ it 'caches the status in L1 and L2 caches',
+ :request_store, :use_clean_rails_memory_store_caching do
+ described_class.enable(:enabled_feature_flag)
+ flipper_key = "flipper/v1/feature/enabled_feature_flag"
+
+ expect(described_class.l2_cache_backend)
+ .to receive(:fetch)
+ .once
+ .with(flipper_key, expires_in: 1.hour)
+ .and_call_original
+
+ expect(described_class.l1_cache_backend)
+ .to receive(:fetch)
+ .once
+ .with(flipper_key, expires_in: 1.minute)
+ .and_call_original
+
+ 2.times do
+ expect(described_class.enabled?(:enabled_feature_flag)).to be_truthy
+ end
+ end
+
+ context 'cached feature flag', :request_store do
+ let(:flag) { :some_feature_flag }
+
+ before do
+ described_class.flipper.memoize = false
+ described_class.enabled?(flag)
+ end
+
+ it 'caches the status in L1 cache for the first minute' do
+ expect do
+ expect(described_class.l1_cache_backend).to receive(:fetch).once.and_call_original
+ expect(described_class.l2_cache_backend).not_to receive(:fetch)
+ expect(described_class.enabled?(flag)).to be_truthy
+ end.not_to exceed_query_limit(0)
+ end
+
+ it 'caches the status in L2 cache after 2 minutes' do
+ Timecop.travel 2.minutes do
+ expect do
+ expect(described_class.l1_cache_backend).to receive(:fetch).once.and_call_original
+ expect(described_class.l2_cache_backend).to receive(:fetch).once.and_call_original
+ expect(described_class.enabled?(flag)).to be_truthy
+ end.not_to exceed_query_limit(0)
+ end
+ end
+
+ it 'fetches the status after an hour' do
+ Timecop.travel 61.minutes do
+ expect do
+ expect(described_class.l1_cache_backend).to receive(:fetch).once.and_call_original
+ expect(described_class.l2_cache_backend).to receive(:fetch).once.and_call_original
+ expect(described_class.enabled?(flag)).to be_truthy
+ end.not_to exceed_query_limit(1)
+ end
+ end
+ end
+
context 'with an individual actor' do
CustomActor = Struct.new(:flipper_id)
@@ -190,6 +254,22 @@ describe Feature do
end
end
+ describe '.remove' do
+ context 'for a non-persisted feature' do
+ it 'returns nil' do
+ expect(described_class.remove(:non_persisted_feature_flag)).to be_nil
+ end
+ end
+
+ context 'for a persisted feature' do
+ it 'returns true' do
+ described_class.enable(:persisted_feature_flag)
+
+ expect(described_class.remove(:persisted_feature_flag)).to be_truthy
+ end
+ end
+ end
+
describe Feature::Target do
describe '#targets' do
let(:project) { create(:project) }
diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb
index ebd907ecb7f..87376a98c60 100644
--- a/spec/lib/file_size_validator_spec.rb
+++ b/spec/lib/file_size_validator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe FileSizeValidator do
diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb
index b9ffe895bf0..9f17308241b 100644
--- a/spec/lib/forever_spec.rb
+++ b/spec/lib/forever_spec.rb
@@ -1,22 +1,14 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Forever do
describe '.date' do
subject { described_class.date }
- context 'when using PostgreSQL' do
- it 'returns Postgresql future date' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
-
- expect(subject).to eq(described_class::POSTGRESQL_DATE)
- end
- end
-
- context 'when using MySQL' do
- it 'returns MySQL future date' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(subject).to eq(described_class::MYSQL_DATE)
+ it 'returns Postgresql future date' do
+ Timecop.travel(Date.new(2999, 12, 31)) do
+ is_expected.to be > Date.today
end
end
end
diff --git a/spec/lib/gitaly/server_spec.rb b/spec/lib/gitaly/server_spec.rb
index 34bd43cb3ab..12dfad6698d 100644
--- a/spec/lib/gitaly/server_spec.rb
+++ b/spec/lib/gitaly/server_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitaly::Server do
diff --git a/spec/lib/gitlab/action_rate_limiter_spec.rb b/spec/lib/gitlab/action_rate_limiter_spec.rb
index 542fc03e555..8b510a475d2 100644
--- a/spec/lib/gitlab/action_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/action_rate_limiter_spec.rb
@@ -1,11 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Gitlab::ActionRateLimiter do
+describe Gitlab::ActionRateLimiter, :clean_gitlab_redis_cache do
let(:redis) { double('redis') }
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:key) { [user, project] }
- let(:cache_key) { "action_rate_limiter:test_action:user:#{user.id}:project:#{project.id}" }
subject { described_class.new(action: :test_action, expiry_time: 100) }
@@ -13,17 +13,98 @@ describe Gitlab::ActionRateLimiter do
allow(Gitlab::Redis::Cache).to receive(:with).and_yield(redis)
end
- it 'increases the throttle count and sets the expire time' do
- expect(redis).to receive(:incr).with(cache_key).and_return(1)
- expect(redis).to receive(:expire).with(cache_key, 100)
+ shared_examples 'action rate limiter' do
+ it 'increases the throttle count and sets the expiration time' do
+ expect(redis).to receive(:incr).with(cache_key).and_return(1)
+ expect(redis).to receive(:expire).with(cache_key, 100)
+
+ expect(subject.throttled?(key, 1)).to be_falsy
+ end
+
+ it 'returns true if the key is throttled' do
+ expect(redis).to receive(:incr).with(cache_key).and_return(2)
+ expect(redis).not_to receive(:expire)
- expect(subject.throttled?(key, 1)).to be false
+ expect(subject.throttled?(key, 1)).to be_truthy
+ end
+
+ context 'when throttling is disabled' do
+ it 'returns false and does not set expiration time' do
+ expect(redis).not_to receive(:incr)
+ expect(redis).not_to receive(:expire)
+
+ expect(subject.throttled?(key, 0)).to be_falsy
+ end
+ end
end
- it 'returns true if the key is throttled' do
- expect(redis).to receive(:incr).with(cache_key).and_return(2)
- expect(redis).not_to receive(:expire)
+ context 'when the key is an array of only ActiveRecord models' do
+ let(:key) { [user, project] }
+
+ let(:cache_key) do
+ "action_rate_limiter:test_action:user:#{user.id}:project:#{project.id}"
+ end
+
+ it_behaves_like 'action rate limiter'
+ end
+
+ context 'when they key a combination of ActiveRecord models and strings' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:commit) { project.repository.commit }
+ let(:path) { 'app/controllers/groups_controller.rb' }
+ let(:key) { [project, commit, path] }
+
+ let(:cache_key) do
+ "action_rate_limiter:test_action:project:#{project.id}:commit:#{commit.sha}:#{path}"
+ end
+
+ it_behaves_like 'action rate limiter'
+ end
+
+ describe '#log_request' do
+ let(:file_path) { 'master/README.md' }
+ let(:type) { :raw_blob_request_limit }
+ let(:fullpath) { "/#{project.full_path}/raw/#{file_path}" }
+
+ let(:request) do
+ double('request', ip: '127.0.0.1', request_method: 'GET', fullpath: fullpath)
+ end
+
+ let(:base_attributes) do
+ {
+ message: 'Action_Rate_Limiter_Request',
+ env: type,
+ remote_ip: '127.0.0.1',
+ request_method: 'GET',
+ path: fullpath
+ }
+ end
+
+ context 'without a current user' do
+ let(:current_user) { nil }
+
+ it 'logs information to auth.log' do
+ expect(Gitlab::AuthLogger).to receive(:error).with(base_attributes).once
+
+ subject.log_request(request, type, current_user)
+ end
+ end
+
+ context 'with a current_user' do
+ let(:current_user) { create(:user) }
+
+ let(:attributes) do
+ base_attributes.merge({
+ user_id: current_user.id,
+ username: current_user.username
+ })
+ end
+
+ it 'logs information to auth.log' do
+ expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once
- expect(subject.throttled?(key, 1)).to be true
+ subject.log_request(request, type, current_user)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/allowable_spec.rb b/spec/lib/gitlab/allowable_spec.rb
index 9d80d480b52..4905cc4c3db 100644
--- a/spec/lib/gitlab/allowable_spec.rb
+++ b/spec/lib/gitlab/allowable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Allowable do
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
new file mode 100644
index 00000000000..29f4be76a65
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::StageEvent do
+ it { expect(described_class).to respond_to(:name) }
+ it { expect(described_class).to respond_to(:identifier) }
+
+ it { expect(described_class.new({})).to respond_to(:object_type) }
+end
diff --git a/spec/lib/gitlab/app_logger_spec.rb b/spec/lib/gitlab/app_logger_spec.rb
index c86d30ce6df..3b21104b15d 100644
--- a/spec/lib/gitlab/app_logger_spec.rb
+++ b/spec/lib/gitlab/app_logger_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::AppLogger, :request_store do
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 0f933ac5464..7c65525b8dc 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'nokogiri'
@@ -45,28 +47,302 @@ module Gitlab
end
context "XSS" do
- links = {
- 'links' => {
+ items = {
+ 'link with extra attribute' => {
input: 'link:mylink"onmouseover="alert(1)[Click Here]',
output: "<div>\n<p><a href=\"mylink\">Click Here</a></p>\n</div>"
},
- 'images' => {
+ 'link with unsafe scheme' => {
+ input: 'link:data://danger[Click Here]',
+ output: "<div>\n<p><a>Click Here</a></p>\n</div>"
+ },
+ 'image with onerror' => {
input: 'image:https://localhost.com/image.png[Alt text" onerror="alert(7)]',
output: "<div>\n<p><span><img src=\"https://localhost.com/image.png\" alt='Alt text\" onerror=\"alert(7)'></span></p>\n</div>"
},
- 'pre' => {
+ 'fenced code with inline script' => {
input: '```mypre"><script>alert(3)</script>',
- output: "<div>\n<div>\n<pre lang=\"mypre\">\"&gt;<code></code></pre>\n</div>\n</div>"
+ output: "<div>\n<div>\n<pre class=\"code highlight js-syntax-highlight plaintext\" lang=\"plaintext\" v-pre=\"true\"><code><span id=\"LC1\" class=\"line\" lang=\"plaintext\">\"&gt;</span></code></pre>\n</div>\n</div>"
}
}
- links.each do |name, data|
+ items.each do |name, data|
it "does not convert dangerous #{name} into HTML" do
expect(render(data[:input], context)).to include(data[:output])
end
end
end
+ context 'with admonition' do
+ it 'preserves classes' do
+ input = <<~ADOC
+ NOTE: An admonition paragraph, like this note, grabs the reader’s attention.
+ ADOC
+
+ output = <<~HTML
+ <div class="admonitionblock">
+ <table>
+ <tr>
+ <td class="icon">
+ <i class="fa icon-note" title="Note"></i>
+ </td>
+ <td>
+ An admonition paragraph, like this note, grabs the reader’s attention.
+ </td>
+ </tr>
+ </table>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with passthrough' do
+ it 'removes non heading ids' do
+ input = <<~ADOC
+ ++++
+ <h2 id="foo">Title</h2>
+ ++++
+ ADOC
+
+ output = <<~HTML
+ <h2>Title</h2>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+
+ it 'removes non footnote def ids' do
+ input = <<~ADOC
+ ++++
+ <div id="def">Footnote definition</div>
+ ++++
+ ADOC
+
+ output = <<~HTML
+ <div>Footnote definition</div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+
+ it 'removes non footnote ref ids' do
+ input = <<~ADOC
+ ++++
+ <a id="ref">Footnote reference</a>
+ ++++
+ ADOC
+
+ output = <<~HTML
+ <a>Footnote reference</a>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with footnotes' do
+ it 'preserves ids and links' do
+ input = <<~ADOC
+ This paragraph has a footnote.footnote:[This is the text of the footnote.]
+ ADOC
+
+ output = <<~HTML
+ <div>
+ <p>This paragraph has a footnote.<sup>[<a id="_footnoteref_1" href="#_footnotedef_1" title="View footnote.">1</a>]</sup></p>
+ </div>
+ <div>
+ <hr>
+ <div id="_footnotedef_1">
+ <a href="#_footnoteref_1">1</a>. This is the text of the footnote.
+ </div>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with section anchors' do
+ it 'preserves ids and links' do
+ input = <<~ADOC
+ = Title
+
+ == First section
+
+ This is the first section.
+
+ == Second section
+
+ This is the second section.
+
+ == Thunder ⚡ !
+
+ This is the third section.
+ ADOC
+
+ output = <<~HTML
+ <h1>Title</h1>
+ <div>
+ <h2 id="user-content-first-section">
+ <a class="anchor" href="#user-content-first-section"></a>First section</h2>
+ <div>
+ <div>
+ <p>This is the first section.</p>
+ </div>
+ </div>
+ </div>
+ <div>
+ <h2 id="user-content-second-section">
+ <a class="anchor" href="#user-content-second-section"></a>Second section</h2>
+ <div>
+ <div>
+ <p>This is the second section.</p>
+ </div>
+ </div>
+ </div>
+ <div>
+ <h2 id="user-content-thunder">
+ <a class="anchor" href="#user-content-thunder"></a>Thunder ⚡ !</h2>
+ <div>
+ <div>
+ <p>This is the third section.</p>
+ </div>
+ </div>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with checklist' do
+ it 'preserves classes' do
+ input = <<~ADOC
+ * [x] checked
+ * [ ] not checked
+ ADOC
+
+ output = <<~HTML
+ <div>
+ <ul class="checklist">
+ <li>
+ <p><i class="fa fa-check-square-o"></i> checked</p>
+ </li>
+ <li>
+ <p><i class="fa fa-square-o"></i> not checked</p>
+ </li>
+ </ul>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with marks' do
+ it 'preserves classes' do
+ input = <<~ADOC
+ Werewolves are allergic to #cassia cinnamon#.
+
+ Did the werewolves read the [.small]#small print#?
+
+ Where did all the [.underline.small]#cores# run off to?
+
+ We need [.line-through]#ten# make that twenty VMs.
+
+ [.big]##O##nce upon an infinite loop.
+ ADOC
+
+ output = <<~HTML
+ <div>
+ <p>Werewolves are allergic to <mark>cassia cinnamon</mark>.</p>
+ </div>
+ <div>
+ <p>Did the werewolves read the <span class="small">small print</span>?</p>
+ </div>
+ <div>
+ <p>Where did all the <span class="underline small">cores</span> run off to?</p>
+ </div>
+ <div>
+ <p>We need <span class="line-through">ten</span> make that twenty VMs.</p>
+ </div>
+ <div>
+ <p><span class="big">O</span>nce upon an infinite loop.</p>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with fenced block' do
+ it 'highlights syntax' do
+ input = <<~ADOC
+ ```js
+ console.log('hello world')
+ ```
+ ADOC
+
+ output = <<~HTML
+ <div>
+ <div>
+ <pre class="code highlight js-syntax-highlight javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span></span></code></pre>
+ </div>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with listing block' do
+ it 'highlights syntax' do
+ input = <<~ADOC
+ [source,c++]
+ .class.cpp
+ ----
+ #include <stdio.h>
+
+ for (int i = 0; i < 5; i++) {
+ std::cout<<"*"<<std::endl;
+ }
+ ----
+ ADOC
+
+ output = <<~HTML
+ <div>
+ <div>class.cpp</div>
+ <div>
+ <pre class="code highlight js-syntax-highlight cpp" lang="cpp" v-pre="true"><code><span id="LC1" class="line" lang="cpp"><span class="cp">#include &lt;stdio.h&gt;</span></span>
+ <span id="LC2" class="line" lang="cpp"></span>
+ <span id="LC3" class="line" lang="cpp"><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span></span>
+ <span id="LC4" class="line" lang="cpp"> <span class="n">std</span><span class="o">::</span><span class="n">cout</span><span class="o">&lt;&lt;</span><span class="s">"*"</span><span class="o">&lt;&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span></span>
+ <span id="LC5" class="line" lang="cpp"><span class="p">}</span></span></code></pre>
+ </div>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with stem block' do
+ it 'does not apply syntax highlighting' do
+ input = <<~ADOC
+ [stem]
+ ++++
+ \sqrt{4} = 2
+ ++++
+ ADOC
+
+ output = "<div>\n<div>\n\\$ qrt{4} = 2\\$\n</div>\n</div>"
+
+ expect(render(input, context)).to include(output)
+ end
+ end
+
context 'external links' do
it 'adds the `rel` attribute to the link' do
output = render('link:https://google.com[Google]', context)
diff --git a/spec/lib/gitlab/auth/activity_spec.rb b/spec/lib/gitlab/auth/activity_spec.rb
index 07854cb1eba..e03fafe3826 100644
--- a/spec/lib/gitlab/auth/activity_spec.rb
+++ b/spec/lib/gitlab/auth/activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Auth::Activity do
diff --git a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
index f39863fdda1..52849f8c172 100644
--- a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
+++ b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::BlockedUserTracker do
diff --git a/spec/lib/gitlab/auth/ldap/access_spec.rb b/spec/lib/gitlab/auth/ldap/access_spec.rb
index 662f899180b..ecdd5b29986 100644
--- a/spec/lib/gitlab/auth/ldap/access_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::LDAP::Access do
diff --git a/spec/lib/gitlab/auth/ldap/adapter_spec.rb b/spec/lib/gitlab/auth/ldap/adapter_spec.rb
index 3eeaf3862f6..54486913b72 100644
--- a/spec/lib/gitlab/auth/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/adapter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::LDAP::Adapter do
diff --git a/spec/lib/gitlab/auth/ldap/authentication_spec.rb b/spec/lib/gitlab/auth/ldap/authentication_spec.rb
index 111572d043b..e68e83e4617 100644
--- a/spec/lib/gitlab/auth/ldap/authentication_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/authentication_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::LDAP::Authentication do
diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb
index b91a09e3137..577dfe51949 100644
--- a/spec/lib/gitlab/auth/ldap/config_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/config_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::LDAP::Config do
diff --git a/spec/lib/gitlab/auth/ldap/dn_spec.rb b/spec/lib/gitlab/auth/ldap/dn_spec.rb
index f2983a02602..63656efba29 100644
--- a/spec/lib/gitlab/auth/ldap/dn_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/dn_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::LDAP::DN do
diff --git a/spec/lib/gitlab/auth/ldap/user_spec.rb b/spec/lib/gitlab/auth/ldap/user_spec.rb
index 44bb9d20e47..bc09de7b525 100644
--- a/spec/lib/gitlab/auth/ldap/user_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::LDAP::User do
diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
index 40001cea22e..a2d9e27ea5b 100644
--- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::OAuth::AuthHash do
@@ -13,13 +15,13 @@ describe Gitlab::Auth::OAuth::AuthHash do
end
let(:uid_raw) do
- "CN=Onur K\xC3\xBC\xC3\xA7\xC3\xBCk,OU=Test,DC=example,DC=net"
+ +"CN=Onur K\xC3\xBC\xC3\xA7\xC3\xBCk,OU=Test,DC=example,DC=net"
end
- let(:email_raw) { "onur.k\xC3\xBC\xC3\xA7\xC3\xBCk_ABC-123@example.net" }
- let(:nickname_raw) { "ok\xC3\xBC\xC3\xA7\xC3\xBCk" }
- let(:first_name_raw) { 'Onur' }
- let(:last_name_raw) { "K\xC3\xBC\xC3\xA7\xC3\xBCk" }
- let(:name_raw) { "Onur K\xC3\xBC\xC3\xA7\xC3\xBCk" }
+ let(:email_raw) { +"onur.k\xC3\xBC\xC3\xA7\xC3\xBCk_ABC-123@example.net" }
+ let(:nickname_raw) { +"ok\xC3\xBC\xC3\xA7\xC3\xBCk" }
+ let(:first_name_raw) { +'Onur' }
+ let(:last_name_raw) { +"K\xC3\xBC\xC3\xA7\xC3\xBCk" }
+ let(:name_raw) { +"Onur K\xC3\xBC\xC3\xA7\xC3\xBCk" }
let(:uid_ascii) { uid_raw.force_encoding(Encoding::ASCII_8BIT) }
let(:email_ascii) { email_raw.force_encoding(Encoding::ASCII_8BIT) }
@@ -40,7 +42,11 @@ describe Gitlab::Auth::OAuth::AuthHash do
last_name: last_name_ascii,
name: name_ascii,
nickname: nickname_ascii,
- uid: uid_ascii
+ uid: uid_ascii,
+ address: {
+ locality: 'some locality',
+ country: 'some country'
+ }
}
end
@@ -51,6 +57,7 @@ describe Gitlab::Auth::OAuth::AuthHash do
it { expect(auth_hash.username).to eql nickname_utf8 }
it { expect(auth_hash.name).to eql name_utf8 }
it { expect(auth_hash.password).not_to be_empty }
+ it { expect(auth_hash.location).to eq 'some locality, some country' }
end
context 'email not provided' do
diff --git a/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb b/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
index bf810d72f0e..45c1baa4089 100644
--- a/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::OAuth::IdentityLinker do
diff --git a/spec/lib/gitlab/auth/o_auth/provider_spec.rb b/spec/lib/gitlab/auth/o_auth/provider_spec.rb
index 80d702cf9dc..f46f9d76a1e 100644
--- a/spec/lib/gitlab/auth/o_auth/provider_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/provider_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::OAuth::Provider do
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index b765c265e69..1e3da4f7c2d 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::OAuth::User do
@@ -785,11 +787,25 @@ describe Gitlab::Auth::OAuth::User do
end
end
- describe '#bypass_two_factor?' do
- subject { oauth_user.bypass_two_factor? }
+ describe "#bypass_two_factor?" do
+ it "when with allow_bypass_two_factor disabled (Default)" do
+ stub_omniauth_config(allow_bypass_two_factor: false)
+ expect(oauth_user.bypass_two_factor?).to be_falsey
+ end
+
+ it "when with allow_bypass_two_factor enabled" do
+ stub_omniauth_config(allow_bypass_two_factor: true)
+ expect(oauth_user.bypass_two_factor?).to be_truthy
+ end
+
+ it "when provider in allow_bypass_two_factor array" do
+ stub_omniauth_config(allow_bypass_two_factor: [provider])
+ expect(oauth_user.bypass_two_factor?).to be_truthy
+ end
- it 'returns always false' do
- is_expected.to be_falsey
+ it "when provider not in allow_bypass_two_factor array" do
+ stub_omniauth_config(allow_bypass_two_factor: ["foo"])
+ expect(oauth_user.bypass_two_factor?).to be_falsey
end
end
end
diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb
index 3d979132880..f7fff389d88 100644
--- a/spec/lib/gitlab/auth/request_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::RequestAuthenticator do
diff --git a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
index 3620e1afe25..13636a495d1 100644
--- a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::Saml::AuthHash do
diff --git a/spec/lib/gitlab/auth/saml/identity_linker_spec.rb b/spec/lib/gitlab/auth/saml/identity_linker_spec.rb
index f3305d574cc..89118ff05ba 100644
--- a/spec/lib/gitlab/auth/saml/identity_linker_spec.rb
+++ b/spec/lib/gitlab/auth/saml/identity_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::Saml::IdentityLinker do
diff --git a/spec/lib/gitlab/auth/saml/user_spec.rb b/spec/lib/gitlab/auth/saml/user_spec.rb
index c523f5e177f..5546438b7ee 100644
--- a/spec/lib/gitlab/auth/saml/user_spec.rb
+++ b/spec/lib/gitlab/auth/saml/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::Saml::User do
diff --git a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
index 22708687a56..ebf7de9c701 100644
--- a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
+++ b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::UniqueIpsLimiter, :clean_gitlab_redis_shared_state do
diff --git a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
index 002ce776be9..8ec19c454d8 100644
--- a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
+++ b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::UserAccessDeniedReason do
diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
index 1e2aebdc84b..41265da97a4 100644
--- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::UserAuthFinders do
@@ -138,6 +140,20 @@ describe Gitlab::Auth::UserAuthFinders do
expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
+
+ context 'with OAuth headers' do
+ it 'returns user' do
+ env['HTTP_AUTHORIZATION'] = "Bearer #{personal_access_token.token}"
+
+ expect(find_user_from_access_token).to eq user
+ end
+
+ it 'returns exception if invalid personal_access_token' do
+ env['HTTP_AUTHORIZATION'] = 'Bearer invalid_20byte_token'
+
+ expect { find_personal_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ end
+ end
end
describe '#find_user_from_web_access_token' do
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 3b5ca7c950c..098c33f9cb1 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth do
@@ -84,7 +86,7 @@ describe Gitlab::Auth do
let(:project) { build.project }
before do
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'gitlab-ci-token')
+ expect(gl_auth).not_to receive(:rate_limit!).with('ip', success: true, login: 'gitlab-ci-token')
end
it 'recognises user-less build' do
@@ -104,7 +106,7 @@ describe Gitlab::Auth do
let(:project) { build.project }
before do
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token')
+ expect(gl_auth).not_to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token')
end
it 'denies authentication' do
@@ -297,6 +299,70 @@ describe Gitlab::Auth do
let(:project) { create(:project) }
let(:auth_failure) { Gitlab::Auth::Result.new(nil, nil) }
+ context 'when deploy token and user have the same username' do
+ let(:username) { 'normal_user' }
+ let(:user) { create(:user, username: username, password: 'my-secret') }
+ let(:deploy_token) { create(:deploy_token, username: username, read_registry: false, projects: [project]) }
+
+ before do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: username)
+ end
+
+ it 'succeeds for the token' do
+ auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:download_code])
+
+ expect(gl_auth.find_for_git_client(username, deploy_token.token, project: project, ip: 'ip'))
+ .to eq(auth_success)
+ end
+
+ it 'succeeds for the user' do
+ auth_success = Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
+
+ expect(gl_auth.find_for_git_client(username, 'my-secret', project: project, ip: 'ip'))
+ .to eq(auth_success)
+ end
+ end
+
+ context 'when deploy tokens have the same username' do
+ context 'and belong to the same project' do
+ let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [project]) }
+ let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [project]) }
+
+ it 'succeeds for the right token' do
+ auth_success = Gitlab::Auth::Result.new(read_repository, project, :deploy_token, [:download_code])
+
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'deployer')
+ expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: project, ip: 'ip'))
+ .to eq(auth_success)
+ end
+
+ it 'fails for the wrong token' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deployer')
+ expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+ end
+
+ context 'and belong to different projects' do
+ let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [create(:project)]) }
+ let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [project]) }
+
+ it 'succeeds for the right token' do
+ auth_success = Gitlab::Auth::Result.new(read_repository, project, :deploy_token, [:download_code])
+
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'deployer')
+ expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: project, ip: 'ip'))
+ .to eq(auth_success)
+ end
+
+ it 'fails for the wrong token' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deployer')
+ expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+ end
+ end
+
context 'when the deploy token has read_repository as scope' do
let(:deploy_token) { create(:deploy_token, read_registry: false, projects: [project]) }
let(:login) { deploy_token.username }
@@ -309,6 +375,15 @@ describe Gitlab::Auth do
.to eq(auth_success)
end
+ it 'succeeds when custom login and token are valid' do
+ deploy_token = create(:deploy_token, username: 'deployer', read_registry: false, projects: [project])
+ auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:download_code])
+
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'deployer')
+ expect(gl_auth.find_for_git_client('deployer', deploy_token.token, project: project, ip: 'ip'))
+ .to eq(auth_success)
+ end
+
it 'fails when login is not valid' do
expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'random_login')
expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip'))
diff --git a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
index c43ed72038e..e299e2a366f 100644
--- a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
+++ b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount, :migration, schema: 20180105212544 do
diff --git a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
index 877c061d11b..2a7cffb2f3e 100644
--- a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
+++ b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 20180529152628 do
diff --git a/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb b/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb
deleted file mode 100644
index 5076996474f..00000000000
--- a/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb
+++ /dev/null
@@ -1,125 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange, :migration, schema: 20170929131201 do
- let(:migration) { described_class.new }
- let(:projects) { table(:projects) }
-
- let(:base1) { projects.create }
- let(:base1_fork1) { projects.create }
- let(:base1_fork2) { projects.create }
-
- let(:base2) { projects.create }
- let(:base2_fork1) { projects.create }
- let(:base2_fork2) { projects.create }
-
- let(:fork_of_fork) { projects.create }
- let(:fork_of_fork2) { projects.create }
- let(:second_level_fork) { projects.create }
- let(:third_level_fork) { projects.create }
-
- let(:fork_network1) { fork_networks.find_by(root_project_id: base1.id) }
- let(:fork_network2) { fork_networks.find_by(root_project_id: base2.id) }
-
- let!(:forked_project_links) { table(:forked_project_links) }
- let!(:fork_networks) { table(:fork_networks) }
- let!(:fork_network_members) { table(:fork_network_members) }
-
- before do
- # The fork-network relation created for the forked project
- fork_networks.create(id: 1, root_project_id: base1.id)
- fork_network_members.create(project_id: base1.id, fork_network_id: 1)
- fork_networks.create(id: 2, root_project_id: base2.id)
- fork_network_members.create(project_id: base2.id, fork_network_id: 2)
-
- # Normal fork links
- forked_project_links.create(id: 1, forked_from_project_id: base1.id, forked_to_project_id: base1_fork1.id)
- forked_project_links.create(id: 2, forked_from_project_id: base1.id, forked_to_project_id: base1_fork2.id)
- forked_project_links.create(id: 3, forked_from_project_id: base2.id, forked_to_project_id: base2_fork1.id)
- forked_project_links.create(id: 4, forked_from_project_id: base2.id, forked_to_project_id: base2_fork2.id)
-
- # Fork links
- forked_project_links.create(id: 5, forked_from_project_id: base1_fork1.id, forked_to_project_id: fork_of_fork.id)
- forked_project_links.create(id: 6, forked_from_project_id: base1_fork1.id, forked_to_project_id: fork_of_fork2.id)
-
- # Forks 3 levels down
- forked_project_links.create(id: 7, forked_from_project_id: fork_of_fork.id, forked_to_project_id: second_level_fork.id)
- forked_project_links.create(id: 8, forked_from_project_id: second_level_fork.id, forked_to_project_id: third_level_fork.id)
-
- migration.perform(1, 8)
- end
-
- it 'creates a memberships for the direct forks' do
- base1_fork1_membership = fork_network_members.find_by(fork_network_id: fork_network1.id,
- project_id: base1_fork1.id)
- base1_fork2_membership = fork_network_members.find_by(fork_network_id: fork_network1.id,
- project_id: base1_fork2.id)
- base2_fork1_membership = fork_network_members.find_by(fork_network_id: fork_network2.id,
- project_id: base2_fork1.id)
- base2_fork2_membership = fork_network_members.find_by(fork_network_id: fork_network2.id,
- project_id: base2_fork2.id)
-
- expect(base1_fork1_membership.forked_from_project_id).to eq(base1.id)
- expect(base1_fork2_membership.forked_from_project_id).to eq(base1.id)
- expect(base2_fork1_membership.forked_from_project_id).to eq(base2.id)
- expect(base2_fork2_membership.forked_from_project_id).to eq(base2.id)
- end
-
- it 'adds the fork network members for forks of forks' do
- fork_of_fork_membership = fork_network_members.find_by(project_id: fork_of_fork.id,
- fork_network_id: fork_network1.id)
- fork_of_fork2_membership = fork_network_members.find_by(project_id: fork_of_fork2.id,
- fork_network_id: fork_network1.id)
- second_level_fork_membership = fork_network_members.find_by(project_id: second_level_fork.id,
- fork_network_id: fork_network1.id)
- third_level_fork_membership = fork_network_members.find_by(project_id: third_level_fork.id,
- fork_network_id: fork_network1.id)
-
- expect(fork_of_fork_membership.forked_from_project_id).to eq(base1_fork1.id)
- expect(fork_of_fork2_membership.forked_from_project_id).to eq(base1_fork1.id)
- expect(second_level_fork_membership.forked_from_project_id).to eq(fork_of_fork.id)
- expect(third_level_fork_membership.forked_from_project_id).to eq(second_level_fork.id)
- end
-
- it 'reschedules itself when there are missing members' do
- allow(migration).to receive(:missing_members?).and_return(true)
-
- expect(BackgroundMigrationWorker)
- .to receive(:perform_in).with(described_class::RESCHEDULE_DELAY, "CreateForkNetworkMembershipsRange", [1, 3])
-
- migration.perform(1, 3)
- end
-
- it 'can be repeated without effect' do
- expect { fork_network_members.count }.not_to change { migration.perform(1, 7) }
- end
-
- it 'knows it is finished for this range' do
- expect(migration.missing_members?(1, 8)).to be_falsy
- end
-
- it 'does not miss members for forks of forks for which the root was deleted' do
- forked_project_links.create(id: 9, forked_from_project_id: base1_fork1.id, forked_to_project_id: projects.create.id)
- base1.destroy
-
- expect(migration.missing_members?(7, 10)).to be_falsy
- end
-
- context 'with more forks' do
- before do
- forked_project_links.create(id: 9, forked_from_project_id: fork_of_fork.id, forked_to_project_id: projects.create.id)
- forked_project_links.create(id: 10, forked_from_project_id: fork_of_fork.id, forked_to_project_id: projects.create.id)
- end
-
- it 'only processes a single batch of links at a time' do
- expect(fork_network_members.count).to eq(10)
-
- migration.perform(8, 10)
-
- expect(fork_network_members.count).to eq(12)
- end
-
- it 'knows when not all memberships within a batch have been created' do
- expect(migration.missing_members?(8, 10)).to be_truthy
- end
- end
-end
diff --git a/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb b/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb
deleted file mode 100644
index 9bae7e53b71..00000000000
--- a/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::DeleteConflictingRedirectRoutesRange, :migration, schema: 20170907170235 do
- let!(:redirect_routes) { table(:redirect_routes) }
- let!(:routes) { table(:routes) }
-
- before do
- routes.create!(id: 1, source_id: 1, source_type: 'Namespace', path: 'foo1')
- routes.create!(id: 2, source_id: 2, source_type: 'Namespace', path: 'foo2')
- routes.create!(id: 3, source_id: 3, source_type: 'Namespace', path: 'foo3')
- routes.create!(id: 4, source_id: 4, source_type: 'Namespace', path: 'foo4')
- routes.create!(id: 5, source_id: 5, source_type: 'Namespace', path: 'foo5')
-
- # Valid redirects
- redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar')
- redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar2')
- redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'bar3')
-
- # Conflicting redirects
- redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'foo1')
- redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo2')
- redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo3')
- redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo4')
- redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo5')
- end
-
- # No-op. See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252
- it 'NO-OP: does not delete any redirect_routes' do
- expect(redirect_routes.count).to eq(8)
-
- described_class.new.perform(1, 5)
-
- expect(redirect_routes.count).to eq(8)
- end
-end
diff --git a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
index 1d9bac79dcd..3c2ed6d3a6d 100644
--- a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
+++ b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::EncryptColumns, :migration, schema: 20180910115836 do
diff --git a/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb b/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
index 9d4921968b3..54af9807e7b 100644
--- a/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
+++ b/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::EncryptRunnersTokens, :migration, schema: 20181121111200 do
diff --git a/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb b/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
index 20af63bc6c8..f3127cbf5df 100644
--- a/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks, :migration, schema: 20180702120647 do
@@ -75,7 +77,7 @@ describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks, :migration, sch
create_resource(target_type, 1, 2)
end
- it 'ignores label links referencing ancestor group labels', :nested_groups do
+ it 'ignores label links referencing ancestor group labels' do
labels_table.create(id: 4, title: 'bug', color: 'red', project_id: 2, type: 'ProjectLabel')
label_links_table.create(label_id: 4, target_type: target_type, target_id: 1)
link = label_links_table.create(label_id: 1, target_type: target_type, target_id: 1)
@@ -85,7 +87,7 @@ describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks, :migration, sch
expect(link.reload.label_id).to eq(1)
end
- it 'checks also issues and MRs in subgroups', :nested_groups do
+ it 'checks also issues and MRs in subgroups' do
link = label_links_table.create(label_id: 2, target_type: target_type, target_id: 1)
subject.perform(1, 100)
diff --git a/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
new file mode 100644
index 00000000000..5938ecca459
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::FixUserNamespaceNames, :migration, schema: 20190620112608 do
+ let(:namespaces) { table(:namespaces) }
+ let(:users) { table(:users) }
+ let(:user) { users.create(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') }
+
+ context 'updating the namespace names' do
+ it 'updates a user namespace within range' do
+ user2 = users.create(name: "Other user's full name", projects_limit: 10, username: 'also-not-null', email: '2')
+ user_namespace1 = namespaces.create(
+ id: 2,
+ owner_id: user.id,
+ name: "Should be the user's name",
+ path: user.username
+ )
+ user_namespace2 = namespaces.create(
+ id: 3,
+ owner_id: user2.id,
+ name: "Should also be the user's name",
+ path: user.username
+ )
+
+ described_class.new.perform(1, 5)
+
+ expect(user_namespace1.reload.name).to eq("The user's full name")
+ expect(user_namespace2.reload.name).to eq("Other user's full name")
+ end
+
+ it 'does not update namespaces out of range' do
+ user_namespace = namespaces.create(
+ id: 6,
+ owner_id: user.id,
+ name: "Should be the user's name",
+ path: user.username
+ )
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { user_namespace.reload.name }
+ end
+
+ it 'does not update groups owned by the users' do
+ user_group = namespaces.create(
+ id: 2,
+ owner_id: user.id,
+ name: 'A group name',
+ path: 'the-path',
+ type: 'Group'
+ )
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { user_group.reload.name }
+ end
+ end
+
+ context 'namespace route names' do
+ let(:routes) { table(:routes) }
+ let(:namespace) do
+ namespaces.create(
+ id: 2,
+ owner_id: user.id,
+ name: "Will be updated to the user's name",
+ path: user.username
+ )
+ end
+
+ it "updates the route name if it didn't match the namespace" do
+ route = routes.create(path: namespace.path, name: 'Incorrect name', source_type: 'Namespace', source_id: namespace.id)
+
+ described_class.new.perform(1, 5)
+
+ expect(route.reload.name).to eq("The user's full name")
+ end
+
+ it 'updates the route name if it was nil match the namespace' do
+ route = routes.create(path: namespace.path, name: nil, source_type: 'Namespace', source_id: namespace.id)
+
+ described_class.new.perform(1, 5)
+
+ expect(route.reload.name).to eq("The user's full name")
+ end
+
+ it "doesn't update group routes" do
+ route = routes.create(path: 'group-path', name: 'Group name', source_type: 'Group', source_id: namespace.id)
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { route.reload.name }
+ end
+
+ it "doesn't touch routes for namespaces out of range" do
+ user_namespace = namespaces.create(
+ id: 6,
+ owner_id: user.id,
+ name: "Should be the user's name",
+ path: user.username
+ )
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { user_namespace.reload.name }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
new file mode 100644
index 00000000000..d1d6d8411d1
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, :migration, schema: 20190620112608 do
+ let(:namespaces) { table(:namespaces) }
+ let(:users) { table(:users) }
+ let(:routes) { table(:routes) }
+ let(:projects) { table(:projects) }
+
+ let(:user) { users.create(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') }
+
+ let(:namespace) do
+ namespaces.create(
+ owner_id: user.id,
+ name: "Should eventually be the user's name",
+ path: user.username
+ )
+ end
+
+ let(:project) do
+ projects.create(namespace_id: namespace.id, name: 'Project Name')
+ end
+
+ it "updates the route for a project if it did not match the user's name" do
+ route = routes.create(
+ id: 1,
+ path: "#{user.username}/#{project.path}",
+ source_id: project.id,
+ source_type: 'Project',
+ name: 'Completely wrong'
+ )
+
+ described_class.new.perform(1, 5)
+
+ expect(route.reload.name).to eq("The user's full name / Project Name")
+ end
+
+ it 'updates the route for a project if the name was nil' do
+ route = routes.create(
+ id: 1,
+ path: "#{user.username}/#{project.path}",
+ source_id: project.id,
+ source_type: 'Project',
+ name: nil
+ )
+
+ described_class.new.perform(1, 5)
+
+ expect(route.reload.name).to eq("The user's full name / Project Name")
+ end
+
+ it 'does not update routes that were are out of the range' do
+ route = routes.create(
+ id: 6,
+ path: "#{user.username}/#{project.path}",
+ source_id: project.id,
+ source_type: 'Project',
+ name: 'Completely wrong'
+ )
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { route.reload.name }
+ end
+
+ it 'does not update routes for projects in groups owned by the user' do
+ group = namespaces.create(
+ owner_id: user.id,
+ name: 'A group',
+ path: 'a-path',
+ type: ''
+ )
+ project = projects.create(namespace_id: group.id, name: 'Project Name')
+ route = routes.create(
+ id: 1,
+ path: "#{group.path}/#{project.path}",
+ source_id: project.id,
+ source_type: 'Project',
+ name: 'Completely wrong'
+ )
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { route.reload.name }
+ end
+
+ it 'does not update routes for namespaces' do
+ route = routes.create(
+ id: 1,
+ path: namespace.path,
+ source_id: namespace.id,
+ source_type: 'Namespace',
+ name: 'Completely wrong'
+ )
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { route.reload.name }
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
new file mode 100644
index 00000000000..7d67dc0251d
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
@@ -0,0 +1,296 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+# rubocop: disable RSpec/FactoriesInMigrationSpecs
+describe Gitlab::BackgroundMigration::LegacyUploadMover do
+ let(:test_dir) { FileUploader.options['storage_path'] }
+ let(:filename) { 'image.png' }
+
+ let!(:namespace) { create(:namespace) }
+ let!(:legacy_project) { create(:project, :legacy_storage, namespace: namespace) }
+ let!(:hashed_project) { create(:project, namespace: namespace) }
+ # default project
+ let(:project) { legacy_project }
+
+ let!(:issue) { create(:issue, project: project) }
+ let!(:note) { create(:note, note: 'some note', project: project, noteable: issue) }
+
+ let(:legacy_upload) { create_upload(note, filename) }
+
+ def create_remote_upload(model, filename)
+ create(:upload, :attachment_upload,
+ path: "note/attachment/#{model.id}/#{filename}", secret: nil,
+ store: ObjectStorage::Store::REMOTE, model: model)
+ end
+
+ def create_upload(model, filename, with_file = true)
+ params = {
+ path: "uploads/-/system/note/attachment/#{model.id}/#{filename}",
+ model: model,
+ store: ObjectStorage::Store::LOCAL
+ }
+
+ if with_file
+ upload = create(:upload, :with_file, :attachment_upload, params)
+ model.update(attachment: upload.build_uploader)
+ model.attachment.upload
+ else
+ create(:upload, :attachment_upload, params)
+ end
+ end
+
+ def new_upload
+ Upload.find_by(model_id: project.id, model_type: 'Project')
+ end
+
+ def expect_error_log
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |logger|
+ expect(logger).to receive(:warn)
+ end
+ end
+
+ shared_examples 'legacy upload deletion' do
+ it 'removes the upload record' do
+ described_class.new(legacy_upload).execute
+
+ expect { legacy_upload.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ shared_examples 'move error' do
+ it 'does not remove the upload file' do
+ expect_error_log
+
+ described_class.new(legacy_upload).execute
+
+ expect(legacy_upload.reload).to eq(legacy_upload)
+ end
+ end
+
+ shared_examples 'migrates the file correctly' do
+ before do
+ described_class.new(legacy_upload).execute
+ end
+
+ it 'creates a new uplaod record correctly' do
+ expect(new_upload.secret).not_to be_nil
+ expect(new_upload.path).to end_with("#{new_upload.secret}/image.png")
+ expect(new_upload.model_id).to eq(project.id)
+ expect(new_upload.model_type).to eq('Project')
+ expect(new_upload.uploader).to eq('FileUploader')
+ end
+
+ it 'updates the legacy upload note so that it references the file in the markdown' do
+ expected_path = File.join('/uploads', new_upload.secret, 'image.png')
+ expected_markdown = "some note \n ![image](#{expected_path})"
+ expect(note.reload.note).to eq(expected_markdown)
+ end
+
+ it 'removes the attachment from the note model' do
+ expect(note.reload.attachment.file).to be_nil
+ end
+ end
+
+ context 'when no model found for the upload' do
+ before do
+ legacy_upload.model = nil
+ expect_error_log
+ end
+
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the upload move fails' do
+ before do
+ expect(FileUploader).to receive(:copy_to).and_raise('failed')
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the upload is in local storage' do
+ shared_examples 'legacy local file' do
+ it 'removes the file correctly' do
+ expect(File.exist?(legacy_upload.absolute_path)).to be_truthy
+
+ described_class.new(legacy_upload).execute
+
+ expect(File.exist?(legacy_upload.absolute_path)).to be_falsey
+ end
+
+ it 'moves legacy uploads to the correct location' do
+ described_class.new(legacy_upload).execute
+
+ expected_path = File.join(test_dir, 'uploads', project.disk_path, new_upload.secret, filename)
+ expect(File.exist?(expected_path)).to be_truthy
+ end
+ end
+
+ context 'when the upload file does not exist on the filesystem' do
+ let(:legacy_upload) { create_upload(note, filename, false) }
+
+ before do
+ expect_error_log
+ end
+
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when an upload belongs to a legacy_diff_note' do
+ let!(:merge_request) { create(:merge_request, source_project: project) }
+ let!(:note) do
+ create(:legacy_diff_note_on_merge_request,
+ note: 'some note', project: project, noteable: merge_request)
+ end
+ let(:legacy_upload) do
+ create(:upload, :with_file, :attachment_upload,
+ path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note)
+ end
+
+ context 'when the file does not exist for the upload' do
+ let(:legacy_upload) do
+ create(:upload, :attachment_upload,
+ path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note)
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the file does not exist on expected path' do
+ let(:legacy_upload) do
+ create(:upload, :attachment_upload, :with_file,
+ path: "uploads/-/system/note/attachment/some_part/#{note.id}/#{filename}", model: note)
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the file path does not include system/note/attachment' do
+ let(:legacy_upload) do
+ create(:upload, :attachment_upload, :with_file,
+ path: "uploads/-/system#{note.id}/#{filename}", model: note)
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the file move raises an error' do
+ before do
+ allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES)
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the file can be handled correctly' do
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+ end
+
+ context 'when object storage is disabled for FileUploader' do
+ context 'when the file belongs to a legacy project' do
+ let(:project) { legacy_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the file belongs to a hashed project' do
+ let(:project) { hashed_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+ end
+
+ context 'when object storage is enabled for FileUploader' do
+ # The process of migrating to object storage is a manual one,
+ # so it would go against expectations to automatically migrate these files
+ # to object storage during this migration.
+ # After this migration, these files should be able to successfully migrate to object storage.
+
+ before do
+ stub_uploads_object_storage(FileUploader)
+ end
+
+ context 'when the file belongs to a legacy project' do
+ let(:project) { legacy_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the file belongs to a hashed project' do
+ let(:project) { hashed_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+ end
+ end
+
+ context 'when legacy uploads are stored in object storage' do
+ let(:legacy_upload) { create_remote_upload(note, filename) }
+ let(:remote_file) do
+ { key: "#{legacy_upload.path}" }
+ end
+ let(:connection) { ::Fog::Storage.new(FileUploader.object_store_credentials) }
+ let(:bucket) { connection.directories.create(key: 'uploads') }
+
+ before do
+ stub_uploads_object_storage(FileUploader)
+ end
+
+ shared_examples 'legacy remote file' do
+ it 'removes the file correctly' do
+ # expect(bucket.files.get(remote_file[:key])).to be_nil
+
+ described_class.new(legacy_upload).execute
+
+ expect(bucket.files.get(remote_file[:key])).to be_nil
+ end
+
+ it 'moves legacy uploads to the correct remote location' do
+ described_class.new(legacy_upload).execute
+
+ connection = ::Fog::Storage.new(FileUploader.object_store_credentials)
+ expect(connection.get_object('uploads', new_upload.path)[:status]).to eq(200)
+ end
+ end
+
+ context 'when the upload file does not exist on the filesystem' do
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the file belongs to a legacy project' do
+ before do
+ bucket.files.create(remote_file)
+ end
+
+ let(:project) { legacy_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy remote file'
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the file belongs to a hashed project' do
+ before do
+ bucket.files.create(remote_file)
+ end
+
+ let(:project) { hashed_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy remote file'
+ it_behaves_like 'legacy upload deletion'
+ end
+ end
+end
+# rubocop: enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
new file mode 100644
index 00000000000..ed8cbfeb11f
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+# rubocop: disable RSpec/FactoriesInMigrationSpecs
+describe Gitlab::BackgroundMigration::LegacyUploadsMigrator do
+ let(:test_dir) { FileUploader.options['storage_path'] }
+
+ let!(:hashed_project) { create(:project) }
+ let!(:legacy_project) { create(:project, :legacy_storage) }
+ let!(:issue) { create(:issue, project: hashed_project) }
+ let!(:issue_legacy) { create(:issue, project: legacy_project) }
+
+ let!(:note1) { create(:note, project: hashed_project, noteable: issue) }
+ let!(:note2) { create(:note, project: hashed_project, noteable: issue) }
+ let!(:note_legacy) { create(:note, project: legacy_project, noteable: issue_legacy) }
+
+ def create_upload(model, with_file = true)
+ filename = 'image.png'
+ params = {
+ path: "uploads/-/system/note/attachment/#{model.id}/#{filename}",
+ model: model,
+ store: ObjectStorage::Store::LOCAL
+ }
+
+ if with_file
+ upload = create(:upload, :with_file, :attachment_upload, params)
+ model.update(attachment: upload.build_uploader)
+ model.attachment.upload
+ else
+ create(:upload, :attachment_upload, params)
+ end
+ end
+
+ let!(:legacy_upload) { create_upload(note1) }
+ let!(:legacy_upload_no_file) { create_upload(note2, false) }
+ let!(:legacy_upload_legacy_project) { create_upload(note_legacy) }
+
+ let(:start_id) { 1 }
+ let(:end_id) { 10000 }
+
+ subject { described_class.new.perform(start_id, end_id) }
+
+ it 'removes all legacy files' do
+ expect(File.exist?(legacy_upload.absolute_path)).to be_truthy
+ expect(File.exist?(legacy_upload_no_file.absolute_path)).to be_falsey
+ expect(File.exist?(legacy_upload_legacy_project.absolute_path)).to be_truthy
+
+ subject
+
+ expect(File.exist?(legacy_upload.absolute_path)).to be_falsey
+ expect(File.exist?(legacy_upload_no_file.absolute_path)).to be_falsey
+ expect(File.exist?(legacy_upload_legacy_project.absolute_path)).to be_falsey
+ end
+
+ it 'removes all AttachmentUploader records' do
+ expect { subject }.to change { Upload.where(uploader: 'AttachmentUploader').count }.from(3).to(0)
+ end
+
+ it 'creates new uploads for successfully migrated records' do
+ expect { subject }.to change { Upload.where(uploader: 'FileUploader').count }.from(0).to(2)
+ end
+end
+# rubocop: enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
index 582396275ed..a496f8416bf 100644
--- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 20180212101928 do
diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
deleted file mode 100644
index 188969951a6..00000000000
--- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
+++ /dev/null
@@ -1,433 +0,0 @@
-require 'spec_helper'
-
-# rubocop:disable RSpec/FactoriesInMigrationSpecs
-describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event, :migration, schema: 20170608152748 do
- describe '#commit_title' do
- it 'returns nil when there are no commits' do
- expect(described_class.new.commit_title).to be_nil
- end
-
- it 'returns nil when there are commits without commit messages' do
- event = described_class.new
-
- allow(event).to receive(:commits).and_return([{ id: '123' }])
-
- expect(event.commit_title).to be_nil
- end
-
- it 'returns the commit message when it is less than 70 characters long' do
- event = described_class.new
-
- allow(event).to receive(:commits).and_return([{ message: 'Hello world' }])
-
- expect(event.commit_title).to eq('Hello world')
- end
-
- it 'returns the first line of a commit message if multiple lines are present' do
- event = described_class.new
-
- allow(event).to receive(:commits).and_return([{ message: "Hello\n\nworld" }])
-
- expect(event.commit_title).to eq('Hello')
- end
-
- it 'truncates the commit to 70 characters when it is too long' do
- event = described_class.new
-
- allow(event).to receive(:commits).and_return([{ message: 'a' * 100 }])
-
- expect(event.commit_title).to eq(('a' * 67) + '...')
- end
- end
-
- describe '#commit_from_sha' do
- it 'returns nil when pushing to a new ref' do
- event = described_class.new
-
- allow(event).to receive(:create?).and_return(true)
-
- expect(event.commit_from_sha).to be_nil
- end
-
- it 'returns the ID of the first commit when pushing to an existing ref' do
- event = described_class.new
-
- allow(event).to receive(:create?).and_return(false)
- allow(event).to receive(:data).and_return(before: '123')
-
- expect(event.commit_from_sha).to eq('123')
- end
- end
-
- describe '#commit_to_sha' do
- it 'returns nil when removing an existing ref' do
- event = described_class.new
-
- allow(event).to receive(:remove?).and_return(true)
-
- expect(event.commit_to_sha).to be_nil
- end
-
- it 'returns the ID of the last commit when pushing to an existing ref' do
- event = described_class.new
-
- allow(event).to receive(:remove?).and_return(false)
- allow(event).to receive(:data).and_return(after: '123')
-
- expect(event.commit_to_sha).to eq('123')
- end
- end
-
- describe '#data' do
- it 'returns the deserialized data' do
- event = described_class.new(data: { before: '123' })
-
- expect(event.data).to eq(before: '123')
- end
-
- it 'returns an empty hash when no data is present' do
- event = described_class.new
-
- expect(event.data).to eq({})
- end
- end
-
- describe '#commits' do
- it 'returns an Array of commits' do
- event = described_class.new(data: { commits: [{ id: '123' }] })
-
- expect(event.commits).to eq([{ id: '123' }])
- end
-
- it 'returns an empty array when no data is present' do
- event = described_class.new
-
- expect(event.commits).to eq([])
- end
- end
-
- describe '#commit_count' do
- it 'returns the number of commits' do
- event = described_class.new(data: { total_commits_count: 2 })
-
- expect(event.commit_count).to eq(2)
- end
-
- it 'returns 0 when no data is present' do
- event = described_class.new
-
- expect(event.commit_count).to eq(0)
- end
- end
-
- describe '#ref' do
- it 'returns the name of the ref' do
- event = described_class.new(data: { ref: 'refs/heads/master' })
-
- expect(event.ref).to eq('refs/heads/master')
- end
- end
-
- describe '#trimmed_ref_name' do
- it 'returns the trimmed ref name for a branch' do
- event = described_class.new(data: { ref: 'refs/heads/master' })
-
- expect(event.trimmed_ref_name).to eq('master')
- end
-
- it 'returns the trimmed ref name for a tag' do
- event = described_class.new(data: { ref: 'refs/tags/v1.2' })
-
- expect(event.trimmed_ref_name).to eq('v1.2')
- end
- end
-
- describe '#create?' do
- it 'returns true when creating a new ref' do
- event = described_class.new(data: { before: described_class::BLANK_REF })
-
- expect(event.create?).to eq(true)
- end
-
- it 'returns false when pushing to an existing ref' do
- event = described_class.new(data: { before: '123' })
-
- expect(event.create?).to eq(false)
- end
- end
-
- describe '#remove?' do
- it 'returns true when removing an existing ref' do
- event = described_class.new(data: { after: described_class::BLANK_REF })
-
- expect(event.remove?).to eq(true)
- end
-
- it 'returns false when pushing to an existing ref' do
- event = described_class.new(data: { after: '123' })
-
- expect(event.remove?).to eq(false)
- end
- end
-
- describe '#push_action' do
- let(:event) { described_class.new }
-
- it 'returns :created when creating a new ref' do
- allow(event).to receive(:create?).and_return(true)
-
- expect(event.push_action).to eq(:created)
- end
-
- it 'returns :removed when removing an existing ref' do
- allow(event).to receive(:create?).and_return(false)
- allow(event).to receive(:remove?).and_return(true)
-
- expect(event.push_action).to eq(:removed)
- end
-
- it 'returns :pushed when pushing to an existing ref' do
- allow(event).to receive(:create?).and_return(false)
- allow(event).to receive(:remove?).and_return(false)
-
- expect(event.push_action).to eq(:pushed)
- end
- end
-
- describe '#ref_type' do
- let(:event) { described_class.new }
-
- it 'returns :tag for a tag' do
- allow(event).to receive(:ref).and_return('refs/tags/1.2')
-
- expect(event.ref_type).to eq(:tag)
- end
-
- it 'returns :branch for a branch' do
- allow(event).to receive(:ref).and_return('refs/heads/1.2')
-
- expect(event.ref_type).to eq(:branch)
- end
- end
-end
-
-##
-# The background migration relies on a temporary table, hence we're migrating
-# to a specific version of the database where said table is still present.
-#
-describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migration, schema: 20170825154015 do
- let(:user_class) do
- Class.new(ActiveRecord::Base) do
- self.table_name = 'users'
- end
- end
-
- let(:migration) { described_class.new }
- let(:user_class) { table(:users) }
- let(:author) { build(:user).becomes(user_class).tap(&:save!).becomes(User) }
- let(:namespace) { create(:namespace, owner: author) }
- let(:projects) { table(:projects) }
- let(:project) { projects.create(namespace_id: namespace.id, creator_id: author.id) }
-
- # We can not rely on FactoryBot as the state of Event may change in ways that
- # the background migration does not expect, hence we use the Event class of
- # the migration itself.
- def create_push_event(project, author, data = nil)
- klass = Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event
-
- klass.create!(
- action: klass::PUSHED,
- project_id: project.id,
- author_id: author.id,
- data: data
- )
- end
-
- describe '#perform' do
- it 'returns if data should not be migrated' do
- allow(migration).to receive(:migrate?).and_return(false)
-
- expect(migration).not_to receive(:find_events)
-
- migration.perform(1, 10)
- end
-
- it 'migrates the range of events if data is to be migrated' do
- event1 = create_push_event(project, author, { commits: [] })
- event2 = create_push_event(project, author, { commits: [] })
-
- allow(migration).to receive(:migrate?).and_return(true)
-
- expect(migration).to receive(:process_event).twice
-
- migration.perform(event1.id, event2.id)
- end
- end
-
- describe '#process_event' do
- it 'processes a regular event' do
- event = double(:event, push_event?: false)
-
- expect(migration).to receive(:replicate_event)
- expect(migration).not_to receive(:create_push_event_payload)
-
- migration.process_event(event)
- end
-
- it 'processes a push event' do
- event = double(:event, push_event?: true)
-
- expect(migration).to receive(:replicate_event)
- expect(migration).to receive(:create_push_event_payload)
-
- migration.process_event(event)
- end
-
- it 'handles an error gracefully' do
- event1 = create_push_event(project, author, { commits: [] })
-
- expect(migration).to receive(:replicate_event).and_call_original
- expect(migration).to receive(:create_push_event_payload).and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key')
-
- migration.process_event(event1)
-
- expect(described_class::EventForMigration.all.count).to eq(0)
- end
- end
-
- describe '#replicate_event' do
- it 'replicates the event to the "events_for_migration" table' do
- event = create_push_event(
- project,
- author,
- data: { commits: [] },
- title: 'bla'
- )
-
- attributes = event
- .attributes.with_indifferent_access.except(:title, :data)
-
- expect(described_class::EventForMigration)
- .to receive(:create!)
- .with(attributes)
-
- migration.replicate_event(event)
- end
- end
-
- describe '#create_push_event_payload' do
- let(:push_data) do
- {
- commits: [],
- ref: 'refs/heads/master',
- before: '156e0e9adc587a383a7eeb5b21ddecb9044768a8',
- after: '0' * 40,
- total_commits_count: 1
- }
- end
-
- let(:event) do
- create_push_event(project, author, push_data)
- end
-
- before do
- # The foreign key in push_event_payloads at this point points to the
- # "events_for_migration" table so we need to make sure a row exists in
- # said table.
- migration.replicate_event(event)
- end
-
- it 'creates a push event payload for an event' do
- payload = migration.create_push_event_payload(event)
-
- expect(PushEventPayload.count).to eq(1)
- expect(payload.valid?).to eq(true)
- end
-
- it 'does not create push event payloads for removed events' do
- allow(event).to receive(:id).and_return(-1)
-
- expect { migration.create_push_event_payload(event) }.to raise_error(ActiveRecord::InvalidForeignKey)
-
- expect(PushEventPayload.count).to eq(0)
- end
-
- it 'encodes and decodes the commit IDs from and to binary data' do
- payload = migration.create_push_event_payload(event)
- packed = migration.pack(push_data[:before])
-
- expect(payload.commit_from).to eq(packed)
- expect(payload.commit_to).to be_nil
- end
- end
-
- describe '#find_events' do
- it 'returns the events for the given ID range' do
- event1 = create_push_event(project, author, { commits: [] })
- event2 = create_push_event(project, author, { commits: [] })
- event3 = create_push_event(project, author, { commits: [] })
- events = migration.find_events(event1.id, event2.id)
-
- expect(events.length).to eq(2)
- expect(events.pluck(:id)).not_to include(event3.id)
- end
- end
-
- describe '#migrate?' do
- it 'returns true when data should be migrated' do
- allow(described_class::Event)
- .to receive(:table_exists?).and_return(true)
-
- allow(described_class::PushEventPayload)
- .to receive(:table_exists?).and_return(true)
-
- allow(described_class::EventForMigration)
- .to receive(:table_exists?).and_return(true)
-
- expect(migration.migrate?).to eq(true)
- end
-
- it 'returns false if the "events" table does not exist' do
- allow(described_class::Event)
- .to receive(:table_exists?).and_return(false)
-
- expect(migration.migrate?).to eq(false)
- end
-
- it 'returns false if the "push_event_payloads" table does not exist' do
- allow(described_class::Event)
- .to receive(:table_exists?).and_return(true)
-
- allow(described_class::PushEventPayload)
- .to receive(:table_exists?).and_return(false)
-
- expect(migration.migrate?).to eq(false)
- end
-
- it 'returns false when the "events_for_migration" table does not exist' do
- allow(described_class::Event)
- .to receive(:table_exists?).and_return(true)
-
- allow(described_class::PushEventPayload)
- .to receive(:table_exists?).and_return(true)
-
- allow(described_class::EventForMigration)
- .to receive(:table_exists?).and_return(false)
-
- expect(migration.migrate?).to eq(false)
- end
- end
-
- describe '#pack' do
- it 'packs a SHA1 into a 20 byte binary string' do
- packed = migration.pack('156e0e9adc587a383a7eeb5b21ddecb9044768a8')
-
- expect(packed.bytesize).to eq(20)
- end
-
- it 'returns nil if the input value is nil' do
- expect(migration.pack(nil)).to be_nil
- end
- end
-end
-# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
index 2d1505dacfe..268626d58fd 100644
--- a/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::MigrateLegacyArtifacts, :migration, schema: 20180816161409 do
diff --git a/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb b/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
new file mode 100644
index 00000000000..c45c64f6a23
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::MigrateNullPrivateProfileToFalse, :migration, schema: 20190620105427 do
+ let(:users) { table(:users) }
+
+ it 'correctly migrates nil private_profile to false' do
+ private_profile_true = users.create!(private_profile: true, projects_limit: 1, email: 'a@b.com')
+ private_profile_false = users.create!(private_profile: false, projects_limit: 1, email: 'b@c.com')
+ private_profile_nil = users.create!(private_profile: nil, projects_limit: 1, email: 'c@d.com')
+
+ described_class.new.perform(private_profile_true.id, private_profile_nil.id)
+
+ private_profile_true.reload
+ private_profile_false.reload
+ private_profile_nil.reload
+
+ expect(private_profile_true.private_profile).to eq(true)
+ expect(private_profile_false.private_profile).to eq(false)
+ expect(private_profile_nil.private_profile).to eq(false)
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
index 4db829b1e7b..1a8b0355fd9 100644
--- a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::MigrateStageIndex, :migration, schema: 20180420080616 do
diff --git a/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb
deleted file mode 100644
index 89b56906ed0..00000000000
--- a/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20170711145320 do
- let(:projects) { table(:projects) }
- let(:pipelines) { table(:ci_pipelines) }
- let(:stages) { table(:ci_stages) }
- let(:jobs) { table(:ci_builds) }
-
- let(:statuses) do
- {
- created: 0,
- pending: 1,
- running: 2,
- success: 3,
- failed: 4,
- canceled: 5,
- skipped: 6,
- manual: 7
- }
- end
-
- before do
- projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1')
- pipelines.create!(id: 1, project_id: 1, ref: 'master', sha: 'adf43c3a')
- stages.create!(id: 1, pipeline_id: 1, project_id: 1, name: 'test', status: nil)
- stages.create!(id: 2, pipeline_id: 1, project_id: 1, name: 'deploy', status: nil)
- end
-
- context 'when stage status is known' do
- before do
- create_job(project: 1, pipeline: 1, stage: 'test', status: 'success')
- create_job(project: 1, pipeline: 1, stage: 'test', status: 'running')
- create_job(project: 1, pipeline: 1, stage: 'deploy', status: 'failed')
- end
-
- it 'sets a correct stage status' do
- described_class.new.perform(1, 2)
-
- expect(stages.first.status).to eq statuses[:running]
- expect(stages.second.status).to eq statuses[:failed]
- end
- end
-
- context 'when stage status is not known' do
- it 'sets a skipped stage status' do
- described_class.new.perform(1, 2)
-
- expect(stages.first.status).to eq statuses[:skipped]
- expect(stages.second.status).to eq statuses[:skipped]
- end
- end
-
- context 'when stage status includes status of a retried job' do
- before do
- create_job(project: 1, pipeline: 1, stage: 'test', status: 'canceled')
- create_job(project: 1, pipeline: 1, stage: 'deploy', status: 'failed', retried: true)
- create_job(project: 1, pipeline: 1, stage: 'deploy', status: 'success')
- end
-
- it 'sets a correct stage status' do
- described_class.new.perform(1, 2)
-
- expect(stages.first.status).to eq statuses[:canceled]
- expect(stages.second.status).to eq statuses[:success]
- end
- end
-
- context 'when some job in the stage is blocked / manual' do
- before do
- create_job(project: 1, pipeline: 1, stage: 'test', status: 'failed')
- create_job(project: 1, pipeline: 1, stage: 'test', status: 'manual')
- create_job(project: 1, pipeline: 1, stage: 'deploy', status: 'success', when: 'manual')
- end
-
- it 'sets a correct stage status' do
- described_class.new.perform(1, 2)
-
- expect(stages.first.status).to eq statuses[:manual]
- expect(stages.second.status).to eq statuses[:success]
- end
- end
-
- def create_job(project:, pipeline:, stage:, status:, **opts)
- stages = { test: 1, build: 2, deploy: 3 }
-
- jobs.create!(project_id: project, commit_id: pipeline,
- stage_idx: stages[stage.to_sym], stage: stage,
- status: status, **opts)
- end
-end
diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
deleted file mode 100644
index ea8bdd48e72..00000000000
--- a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'spec_helper'
-
-# rubocop:disable RSpec/FactoriesInMigrationSpecs
-describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder, :delete do
- let(:migration) { described_class.new }
-
- before do
- allow(migration).to receive(:logger).and_return(Logger.new(nil))
- end
-
- describe '#perform' do
- it 'renames the path of system-uploads' do
- upload = create(:upload, model: create(:project), path: 'uploads/system/project/avatar.jpg')
-
- migration.perform('uploads/system/', 'uploads/-/system/')
-
- expect(upload.reload.path).to eq('uploads/-/system/project/avatar.jpg')
- end
- end
-end
-# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb b/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
deleted file mode 100644
index 593486fc56c..00000000000
--- a/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-require 'spec_helper'
-
-# rubocop:disable RSpec/FactoriesInMigrationSpecs
-describe Gitlab::BackgroundMigration::MovePersonalSnippetFiles do
- let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'move_snippet_files_test') }
- let(:old_uploads_dir) { File.join('uploads', 'system', 'personal_snippet') }
- let(:new_uploads_dir) { File.join('uploads', '-', 'system', 'personal_snippet') }
- let(:snippet) do
- snippet = create(:personal_snippet)
- create_upload_for_snippet(snippet)
- snippet.update!(description: markdown_linking_file(snippet))
- snippet
- end
-
- let(:migration) { described_class.new }
-
- before do
- allow(migration).to receive(:base_directory) { test_dir }
- end
-
- describe '#perform' do
- it 'moves the file on the disk' do
- expected_path = File.join(test_dir, new_uploads_dir, snippet.id.to_s, "secret#{snippet.id}", 'upload.txt')
-
- migration.perform(old_uploads_dir, new_uploads_dir)
-
- expect(File.exist?(expected_path)).to be_truthy
- end
-
- it 'updates the markdown of the snippet' do
- expected_path = File.join(new_uploads_dir, snippet.id.to_s, "secret#{snippet.id}", 'upload.txt')
- expected_markdown = "[an upload](#{expected_path})"
-
- migration.perform(old_uploads_dir, new_uploads_dir)
-
- expect(snippet.reload.description).to eq(expected_markdown)
- end
-
- it 'updates the markdown of notes' do
- expected_path = File.join(new_uploads_dir, snippet.id.to_s, "secret#{snippet.id}", 'upload.txt')
- expected_markdown = "with [an upload](#{expected_path})"
-
- note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown_linking_file(snippet)}")
-
- migration.perform(old_uploads_dir, new_uploads_dir)
-
- expect(note.reload.note).to eq(expected_markdown)
- end
- end
-
- def create_upload_for_snippet(snippet)
- snippet_path = path_for_file_in_snippet(snippet)
- path = File.join(old_uploads_dir, snippet.id.to_s, snippet_path)
- absolute_path = File.join(test_dir, path)
-
- FileUtils.mkdir_p(File.dirname(absolute_path))
- FileUtils.touch(absolute_path)
-
- create(:upload, model: snippet, path: snippet_path, uploader: PersonalFileUploader)
- end
-
- def path_for_file_in_snippet(snippet)
- secret = "secret#{snippet.id}"
- filename = 'upload.txt'
-
- File.join(secret, filename)
- end
-
- def markdown_linking_file(snippet)
- path = File.join(old_uploads_dir, snippet.id.to_s, path_for_file_in_snippet(snippet))
- "[an upload](#{path})"
- end
-end
-# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb b/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb
deleted file mode 100644
index dfbf1bb681a..00000000000
--- a/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::NormalizeLdapExternUidsRange, :migration, schema: 20170921101004 do
- let!(:identities) { table(:identities) }
-
- before do
- # LDAP identities
- (1..4).each do |i|
- identities.create!(id: i, provider: 'ldapmain', extern_uid: " uid = foo #{i}, ou = People, dc = example, dc = com ", user_id: i)
- end
-
- # Non-LDAP identity
- identities.create!(id: 5, provider: 'foo', extern_uid: " uid = foo 5, ou = People, dc = example, dc = com ", user_id: 5)
-
- # Another LDAP identity
- identities.create!(id: 6, provider: 'ldapmain', extern_uid: " uid = foo 6, ou = People, dc = example, dc = com ", user_id: 6)
- end
-
- it 'normalizes the LDAP identities in the range' do
- described_class.new.perform(1, 3)
- expect(identities.find(1).extern_uid).to eq("uid=foo 1,ou=people,dc=example,dc=com")
- expect(identities.find(2).extern_uid).to eq("uid=foo 2,ou=people,dc=example,dc=com")
- expect(identities.find(3).extern_uid).to eq("uid=foo 3,ou=people,dc=example,dc=com")
- expect(identities.find(4).extern_uid).to eq(" uid = foo 4, ou = People, dc = example, dc = com ")
- expect(identities.find(5).extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ")
- expect(identities.find(6).extern_uid).to eq(" uid = foo 6, ou = People, dc = example, dc = com ")
-
- described_class.new.perform(4, 6)
- expect(identities.find(1).extern_uid).to eq("uid=foo 1,ou=people,dc=example,dc=com")
- expect(identities.find(2).extern_uid).to eq("uid=foo 2,ou=people,dc=example,dc=com")
- expect(identities.find(3).extern_uid).to eq("uid=foo 3,ou=people,dc=example,dc=com")
- expect(identities.find(4).extern_uid).to eq("uid=foo 4,ou=people,dc=example,dc=com")
- expect(identities.find(5).extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ")
- expect(identities.find(6).extern_uid).to eq("uid=foo 6,ou=people,dc=example,dc=com")
- end
-end
diff --git a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb
deleted file mode 100644
index 0e73c8c59c9..00000000000
--- a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, schema: 20170929131201 do
- let(:migration) { described_class.new }
- let(:projects) { table(:projects) }
- let(:base1) { projects.create }
-
- let(:base2) { projects.create }
- let(:base2_fork1) { projects.create }
-
- let!(:forked_project_links) { table(:forked_project_links) }
- let!(:fork_networks) { table(:fork_networks) }
- let!(:fork_network_members) { table(:fork_network_members) }
-
- let(:fork_network1) { fork_networks.find_by(root_project_id: base1.id) }
- let(:fork_network2) { fork_networks.find_by(root_project_id: base2.id) }
-
- before do
- # A normal fork link
- forked_project_links.create(id: 1,
- forked_from_project_id: base1.id,
- forked_to_project_id: projects.create.id)
- forked_project_links.create(id: 2,
- forked_from_project_id: base1.id,
- forked_to_project_id: projects.create.id)
- forked_project_links.create(id: 3,
- forked_from_project_id: base2.id,
- forked_to_project_id: base2_fork1.id)
-
- # create a fork of a fork
- forked_project_links.create(id: 4,
- forked_from_project_id: base2_fork1.id,
- forked_to_project_id: projects.create.id)
- forked_project_links.create(id: 5,
- forked_from_project_id: projects.create.id,
- forked_to_project_id: projects.create.id)
-
- # Stub out the calls to the other migrations
- allow(BackgroundMigrationWorker).to receive(:perform_in)
-
- migration.perform(1, 3)
- end
-
- it 'creates the fork network' do
- expect(fork_network1).not_to be_nil
- expect(fork_network2).not_to be_nil
- end
-
- it 'does not create a fork network for a fork-of-fork' do
- # perfrom the entire batch
- migration.perform(1, 5)
-
- expect(fork_networks.find_by(root_project_id: base2_fork1.id)).to be_nil
- end
-
- it 'creates memberships for the root of fork networks' do
- base1_membership = fork_network_members.find_by(fork_network_id: fork_network1.id,
- project_id: base1.id)
- base2_membership = fork_network_members.find_by(fork_network_id: fork_network2.id,
- project_id: base2.id)
-
- expect(base1_membership).not_to be_nil
- expect(base2_membership).not_to be_nil
- end
-
- it 'creates a fork network for the fork of which the source was deleted' do
- fork = projects.create
- forked_project_links.create(id: 6, forked_from_project_id: 99999, forked_to_project_id: fork.id)
-
- migration.perform(5, 8)
-
- expect(fork_networks.find_by(root_project_id: 99999)).to be_nil
- expect(fork_networks.find_by(root_project_id: fork.id)).not_to be_nil
- expect(fork_network_members.find_by(project_id: fork.id)).not_to be_nil
- end
-
- it 'schedules a job for inserting memberships for forks-of-forks' do
- delay = Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange::RESCHEDULE_DELAY
-
- expect(BackgroundMigrationWorker)
- .to receive(:perform_in).with(delay, "CreateForkNetworkMembershipsRange", [1, 3])
-
- migration.perform(1, 3)
- end
-
- it 'only processes a single batch of links at a time' do
- expect(fork_networks.count).to eq(2)
-
- migration.perform(3, 5)
-
- expect(fork_networks.count).to eq(3)
- end
-
- it 'can be repeated without effect' do
- expect { migration.perform(1, 3) }.not_to change { fork_network_members.count }
- end
-end
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
index 4a81a37d341..ad4fa4fe03a 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
@@ -27,14 +27,19 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, :migra
merge_requests.create(params)
end
+ before do
+ create_merge_request(2, assignee_id: user.id)
+ create_merge_request(3, assignee_id: user_2.id)
+ create_merge_request(4, assignee_id: user_3.id)
+
+ # Test filtering MRs without assignees
+ create_merge_request(5, assignee_id: nil)
+ # Test filtering already migrated row
+ merge_request_assignees.create!(merge_request_id: 2, user_id: user_3.id)
+ end
+
describe '#perform' do
it 'creates merge_request_assignees rows according to merge_requests' do
- create_merge_request(2, assignee_id: user.id)
- create_merge_request(3, assignee_id: user_2.id)
- create_merge_request(4, assignee_id: user_3.id)
- # Test filtering already migrated row
- merge_request_assignees.create!(merge_request_id: 2, user_id: user_3.id)
-
subject.perform(1, 4)
rows = merge_request_assignees.order(:id).map { |row| row.attributes.slice('merge_request_id', 'user_id') }
@@ -53,4 +58,13 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, :migra
end
end
end
+
+ describe '#perform_all_sync' do
+ it 'executes peform for all merge requests in batches' do
+ expect(subject).to receive(:perform).with(2, 4).ordered
+ expect(subject).to receive(:perform).with(5, 5).ordered
+
+ subject.perform_all_sync(batch_size: 3)
+ end
+ end
end
diff --git a/spec/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id_spec.rb
deleted file mode 100644
index 0cb753c5853..00000000000
--- a/spec/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::BackgroundMigration::PopulateMergeRequestsLatestMergeRequestDiffId, :migration, schema: 20171026082505 do
- let(:projects_table) { table(:projects) }
- let(:merge_requests_table) { table(:merge_requests) }
- let(:merge_request_diffs_table) { table(:merge_request_diffs) }
-
- let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') }
-
- def create_mr!(name, diffs: 0)
- merge_request =
- merge_requests_table.create!(target_project_id: project.id,
- target_branch: 'master',
- source_project_id: project.id,
- source_branch: name,
- title: name)
-
- diffs.times do
- merge_request_diffs_table.create!(merge_request_id: merge_request.id)
- end
-
- merge_request
- end
-
- def diffs_for(merge_request)
- merge_request_diffs_table.where(merge_request_id: merge_request.id)
- end
-
- describe '#perform' do
- it 'ignores MRs without diffs' do
- merge_request_without_diff = create_mr!('without_diff')
- mr_id = merge_request_without_diff.id
-
- expect(merge_request_without_diff.latest_merge_request_diff_id).to be_nil
-
- expect { subject.perform(mr_id, mr_id) }
- .not_to change { merge_request_without_diff.reload.latest_merge_request_diff_id }
- end
-
- it 'ignores MRs that have a diff ID already set' do
- merge_request_with_multiple_diffs = create_mr!('with_multiple_diffs', diffs: 3)
- diff_id = diffs_for(merge_request_with_multiple_diffs).minimum(:id)
- mr_id = merge_request_with_multiple_diffs.id
-
- merge_request_with_multiple_diffs.update!(latest_merge_request_diff_id: diff_id)
-
- expect { subject.perform(mr_id, mr_id) }
- .not_to change { merge_request_with_multiple_diffs.reload.latest_merge_request_diff_id }
- end
-
- it 'migrates multiple MR diffs to the correct values' do
- merge_requests = Array.new(3).map.with_index { |_, i| create_mr!(i, diffs: 3) }
-
- subject.perform(merge_requests.first.id, merge_requests.last.id)
-
- merge_requests.each do |merge_request|
- expect(merge_request.reload.latest_merge_request_diff_id)
- .to eq(diffs_for(merge_request).maximum(:id))
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
index c76adcbe2f5..ea1eaa6417d 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
index 0dee683350f..44f537ca8dd 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
@@ -114,7 +116,7 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :sidekiq, :migra
it 'does not drop the temporary tracking table after processing the batch, if there are still untracked rows' do
subject.perform(1, untracked_files_for_uploads.last.id - 1)
- expect(ActiveRecord::Base.connection.data_source_exists?(:untracked_files_for_uploads)).to be_truthy
+ expect(ActiveRecord::Base.connection.table_exists?(:untracked_files_for_uploads)).to be_truthy
end
it 'drops the temporary tracking table after processing the batch, if there are no untracked rows left' do
diff --git a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
index 35750d89c35..591368ee98e 100644
--- a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
diff --git a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
index d494ce68c5b..f877e8cc1b8 100644
--- a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
+++ b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190527194900_schedule_calculate_wiki_sizes.rb')
diff --git a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
index 6f3fb994f17..3600755ada7 100644
--- a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
+++ b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnServices, :migration, schema: 20180122154930 do
diff --git a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
index 82b484b7d5b..5cd9c02fd3f 100644
--- a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
+++ b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnWebhooks, :migration, schema: 20180104131052 do
diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb
index 1d0ffb5e9df..8960ac706e6 100644
--- a/spec/lib/gitlab/background_migration_spec.rb
+++ b/spec/lib/gitlab/background_migration_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration do
diff --git a/spec/lib/gitlab/badge/coverage/metadata_spec.rb b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
index 74eaf7eaf8b..2b87508bdef 100644
--- a/spec/lib/gitlab/badge/coverage/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/badge/shared/metadata'
diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb
index da789bf3705..eee3f96ab85 100644
--- a/spec/lib/gitlab/badge/coverage/report_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/report_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Badge::Coverage::Report do
diff --git a/spec/lib/gitlab/badge/coverage/template_spec.rb b/spec/lib/gitlab/badge/coverage/template_spec.rb
index d9c21a22590..b51d707a61d 100644
--- a/spec/lib/gitlab/badge/coverage/template_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Badge::Coverage::Template do
diff --git a/spec/lib/gitlab/badge/pipeline/metadata_spec.rb b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
index 9032a8e9016..b096803f921 100644
--- a/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/badge/shared/metadata'
diff --git a/spec/lib/gitlab/badge/pipeline/status_spec.rb b/spec/lib/gitlab/badge/pipeline/status_spec.rb
index dc835375c66..684c6829879 100644
--- a/spec/lib/gitlab/badge/pipeline/status_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Badge::Pipeline::Status do
diff --git a/spec/lib/gitlab/badge/pipeline/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb
index bcef0b7e120..c0aaa3d73e1 100644
--- a/spec/lib/gitlab/badge/pipeline/template_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Badge::Pipeline::Template do
diff --git a/spec/lib/gitlab/badge/shared/metadata.rb b/spec/lib/gitlab/badge/shared/metadata.rb
index 63c7ca5a915..809fa54db02 100644
--- a/spec/lib/gitlab/badge/shared/metadata.rb
+++ b/spec/lib/gitlab/badge/shared/metadata.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'badge metadata' do
describe '#to_html' do
let(:html) { Nokogiri::HTML.parse(metadata.to_html) }
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index f4759b69538..0c1eedad7f4 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
@@ -103,7 +105,7 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
end
end
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let(:project_path) { 'a-group/a-sub-group/a-project' }
let(:existing_group) do
@@ -188,20 +190,6 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
end
end
- context 'when subgroups are not available' do
- let(:project_path) { 'a-group/a-sub-group/a-project' }
-
- before do
- expect(Group).to receive(:supports_nested_objects?) { false }
- end
-
- describe '#create_project_if_needed' do
- it 'raises an error' do
- expect { importer.create_project_if_needed }.to raise_error('Nested groups are not supported on MySQL')
- end
- end
- end
-
def prepare_repository(project_path, source_project)
repo_path = File.join(base_dir, project_path)
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index a07c5371134..0607e2232a1 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ::Gitlab::BareRepositoryImport::Repository do
diff --git a/spec/lib/gitlab/batch_pop_queueing_spec.rb b/spec/lib/gitlab/batch_pop_queueing_spec.rb
new file mode 100644
index 00000000000..28984d52024
--- /dev/null
+++ b/spec/lib/gitlab/batch_pop_queueing_spec.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BatchPopQueueing do
+ include ExclusiveLeaseHelpers
+ using RSpec::Parameterized::TableSyntax
+
+ describe '#initialize' do
+ where(:namespace, :queue_id, :expect_error, :error_type) do
+ 'feature' | '1' | false | nil
+ :feature | '1' | false | nil
+ nil | '1' | true | NoMethodError
+ 'feature' | nil | true | NoMethodError
+ '' | '1' | true | ArgumentError
+ 'feature' | '' | true | ArgumentError
+ 'feature' | 1 | true | NoMethodError
+ end
+
+ with_them do
+ it do
+ if expect_error
+ expect { described_class.new(namespace, queue_id) }.to raise_error(error_type)
+ else
+ expect { described_class.new(namespace, queue_id) }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ describe '#safe_execute', :clean_gitlab_redis_queues do
+ subject { queue.safe_execute(new_items, lock_timeout: lock_timeout) }
+
+ let(:queue) { described_class.new(namespace, queue_id) }
+ let(:namespace) { 'feature' }
+ let(:queue_id) { '1' }
+ let(:lock_timeout) { 10.minutes }
+ let(:new_items) { %w[A B] }
+ let(:lock_key) { queue.send(:lock_key) }
+ let(:queue_key) { queue.send(:queue_key) }
+
+ it 'enqueues new items always' do
+ Gitlab::Redis::Queues.with do |redis|
+ expect(redis).to receive(:sadd).with(queue_key, new_items)
+ expect(redis).to receive(:expire).with(queue_key, (lock_timeout + described_class::EXTRA_QUEUE_EXPIRE_WINDOW).to_i)
+ end
+
+ subject
+ end
+
+ it 'yields the new items with exclusive lease' do
+ uuid = 'test'
+ expect_to_obtain_exclusive_lease(lock_key, uuid, timeout: lock_timeout)
+ expect_to_cancel_exclusive_lease(lock_key, uuid)
+
+ expect { |b| queue.safe_execute(new_items, lock_timeout: lock_timeout, &b) }
+ .to yield_with_args(match_array(new_items))
+ end
+
+ it 'returns the result and no items in the queue' do
+ expect(subject[:status]).to eq(:finished)
+ expect(subject[:new_items]).to be_empty
+
+ Gitlab::Redis::Queues.with do |redis|
+ expect(redis.llen(queue_key)).to be(0)
+ end
+ end
+
+ context 'when new items are enqueued during the process' do
+ it 'returns the result with newly added items' do
+ result = queue.safe_execute(new_items) do
+ queue.safe_execute(['C'])
+ end
+
+ expect(result[:status]).to eq(:finished)
+ expect(result[:new_items]).to eq(['C'])
+
+ Gitlab::Redis::Queues.with do |redis|
+ expect(redis.scard(queue_key)).to be(1)
+ end
+ end
+ end
+
+ context 'when interger items are enqueued' do
+ let(:new_items) { [1, 2, 3] }
+
+ it 'yields as String values' do
+ expect { |b| queue.safe_execute(new_items, lock_timeout: lock_timeout, &b) }
+ .to yield_with_args(%w[1 2 3])
+ end
+ end
+
+ context 'when the queue key does not exist in Redis' do
+ before do
+ allow(queue).to receive(:enqueue) { }
+ end
+
+ it 'yields empty array' do
+ expect { |b| queue.safe_execute(new_items, lock_timeout: lock_timeout, &b) }
+ .to yield_with_args([])
+ end
+ end
+
+ context 'when the other process has already been working on the queue' do
+ before do
+ stub_exclusive_lease_taken(lock_key, timeout: lock_timeout)
+ end
+
+ it 'does not yield the block' do
+ expect { |b| queue.safe_execute(new_items, lock_timeout: lock_timeout, &b) }
+ .not_to yield_control
+ end
+
+ it 'returns the result' do
+ expect(subject[:status]).to eq(:enqueued)
+ end
+ end
+
+ context 'when a duplicate item is enqueued' do
+ it 'returns the poped items to the queue and raise an error' do
+ expect { |b| queue.safe_execute(%w[1 1 2 2], &b) }
+ .to yield_with_args(match_array(%w[1 2]))
+ end
+ end
+
+ context 'when there are two queues' do
+ it 'enqueues items to each queue' do
+ queue_1 = described_class.new(namespace, '1')
+ queue_2 = described_class.new(namespace, '2')
+
+ result_2 = nil
+
+ result_1 = queue_1.safe_execute(['A']) do |_|
+ result_2 = queue_2.safe_execute(['B']) do |_|
+ queue_1.safe_execute(['C'])
+ queue_2.safe_execute(['D'])
+ end
+ end
+
+ expect(result_1[:status]).to eq(:finished)
+ expect(result_1[:new_items]).to eq(['C'])
+ expect(result_2[:status]).to eq(:finished)
+ expect(result_2[:new_items]).to eq(['D'])
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 35700e0b588..3d0d3f91859 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BitbucketImport::Importer do
@@ -23,12 +25,12 @@ describe Gitlab::BitbucketImport::Importer do
let(:reporters) do
[
nil,
- { "username" => "reporter1" },
+ { "nickname" => "reporter1" },
nil,
- { "username" => "reporter2" },
- { "username" => "reporter1" },
+ { "nickname" => "reporter2" },
+ { "nickname" => "reporter1" },
nil,
- { "username" => "reporter3" }
+ { "nickname" => "reporter3" }
]
end
@@ -113,6 +115,7 @@ describe Gitlab::BitbucketImport::Importer do
created_at: Time.now,
updated_at: Time.now)
end
+ let(:author_line) { "*Created by: someuser*\n\n" }
before do
allow(subject).to receive(:import_wiki)
@@ -126,7 +129,7 @@ describe Gitlab::BitbucketImport::Importer do
old_pos: nil,
new_pos: 4,
note: 'Hello world',
- author: 'root',
+ author: 'someuser',
created_at: Time.now,
updated_at: Time.now,
inline?: true,
@@ -137,7 +140,7 @@ describe Gitlab::BitbucketImport::Importer do
iid: 3,
file_path: '.gitmodules',
note: 'Hello world',
- author: 'root',
+ author: 'someuser',
created_at: Time.now,
updated_at: Time.now,
inline?: true,
@@ -161,17 +164,39 @@ describe Gitlab::BitbucketImport::Importer do
notes = merge_request.notes.order(:id).to_a
start_note = notes.first
expect(start_note).to be_a(DiffNote)
- expect(start_note.note).to eq(@inline_note.note)
+ expect(start_note.note).to include(@inline_note.note)
+ expect(start_note.note).to include(author_line)
reply_note = notes.last
expect(reply_note).to be_a(DiffNote)
- expect(reply_note.note).to eq(@reply.note)
+ expect(reply_note.note).to include(@reply.note)
+ expect(reply_note.note).to include(author_line)
+ end
+
+ context 'when user exists in GitLab' do
+ let!(:existing_user) { create(:user, username: 'someuser') }
+ let!(:identity) { create(:identity, provider: 'bitbucket', extern_uid: existing_user.username, user: existing_user) }
+
+ it 'does not add author line to comments' do
+ expect { subject.execute }.to change { MergeRequest.count }.by(1)
+
+ merge_request = MergeRequest.first
+
+ notes = merge_request.notes.order(:id).to_a
+ start_note = notes.first
+ expect(start_note.note).to eq(@inline_note.note)
+ expect(start_note.note).not_to include(author_line)
+
+ reply_note = notes.last
+ expect(reply_note.note).to eq(@reply.note)
+ expect(reply_note.note).not_to include(author_line)
+ end
end
context 'when importing a pull request throws an exception' do
before do
allow(pull_request).to receive(:raw).and_return('hello world')
- allow(subject.client).to receive(:pull_request_comments).and_raise(HTTParty::Error)
+ allow(subject.client).to receive(:pull_request_comments).and_raise(Gitlab::HTTP::Error)
end
it 'logs an error without the backtrace' do
diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
index e2bee22cf1f..0dd8547a925 100644
--- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BitbucketImport::ProjectCreator do
diff --git a/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb b/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
index 795fd069ab2..7b5c7847f2d 100644
--- a/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BitbucketImport::WikiFormatter do
diff --git a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
index cc09804fd53..8ab7b2c5fa7 100644
--- a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BitbucketServerImport::Importer do
diff --git a/spec/lib/gitlab/blame_spec.rb b/spec/lib/gitlab/blame_spec.rb
index 7cab04e9fc9..e1afd5b25bb 100644
--- a/spec/lib/gitlab/blame_spec.rb
+++ b/spec/lib/gitlab/blame_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Blame do
diff --git a/spec/lib/gitlab/build_access_spec.rb b/spec/lib/gitlab/build_access_spec.rb
index 08f50bf4fac..b7af8ace5b5 100644
--- a/spec/lib/gitlab/build_access_spec.rb
+++ b/spec/lib/gitlab/build_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BuildAccess do
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index 483c5ea9cff..91e7edaf704 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
diff --git a/spec/lib/gitlab/cache/request_cache_spec.rb b/spec/lib/gitlab/cache/request_cache_spec.rb
index 5b82c216a13..70a7f090d0a 100644
--- a/spec/lib/gitlab/cache/request_cache_spec.rb
+++ b/spec/lib/gitlab/cache/request_cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Cache::RequestCache do
diff --git a/spec/lib/gitlab/changes_list_spec.rb b/spec/lib/gitlab/changes_list_spec.rb
index 464508fcd73..911450f3a8b 100644
--- a/spec/lib/gitlab/changes_list_spec.rb
+++ b/spec/lib/gitlab/changes_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Gitlab::ChangesList do
diff --git a/spec/lib/gitlab/chat_name_token_spec.rb b/spec/lib/gitlab/chat_name_token_spec.rb
index 1e9fb9077fc..b2d4a466021 100644
--- a/spec/lib/gitlab/chat_name_token_spec.rb
+++ b/spec/lib/gitlab/chat_name_token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ChatNameToken do
diff --git a/spec/lib/gitlab/chat_spec.rb b/spec/lib/gitlab/chat_spec.rb
index d61c4b36668..08cc16314c5 100644
--- a/spec/lib/gitlab/chat_spec.rb
+++ b/spec/lib/gitlab/chat_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Chat, :use_clean_rails_memory_store_caching do
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
index 45fb33e9e4a..3a8e8f67e16 100644
--- a/spec/lib/gitlab/checks/change_access_spec.rb
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Checks::ChangeAccess do
diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb
index 0e0788ce974..9432be083d3 100644
--- a/spec/lib/gitlab/checks/force_push_spec.rb
+++ b/spec/lib/gitlab/checks/force_push_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Checks::ForcePush do
diff --git a/spec/lib/gitlab/checks/lfs_integrity_spec.rb b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
index 887ea8fc1e0..88e8f5d74d1 100644
--- a/spec/lib/gitlab/checks/lfs_integrity_spec.rb
+++ b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Checks::LfsIntegrity do
diff --git a/spec/lib/gitlab/checks/project_created_spec.rb b/spec/lib/gitlab/checks/project_created_spec.rb
index ac02007e111..14cb5e6ec66 100644
--- a/spec/lib/gitlab/checks/project_created_spec.rb
+++ b/spec/lib/gitlab/checks/project_created_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb
index 8e9386b1ba1..3ca977aa48d 100644
--- a/spec/lib/gitlab/checks/project_moved_spec.rb
+++ b/spec/lib/gitlab/checks/project_moved_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb
index 3d57ce431ab..c8afcbd053d 100644
--- a/spec/lib/gitlab/ci/ansi2html_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2html_spec.rb
@@ -1,14 +1,16 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Ansi2html do
subject { described_class }
it "prints non-ansi as-is" do
- expect(convert_html("Hello")).to eq('<span class="">Hello</span>')
+ expect(convert_html("Hello")).to eq('<span>Hello</span>')
end
it "strips non-color-changing control sequences" do
- expect(convert_html("Hello \e[2Kworld")).to eq('<span class="">Hello world</span>')
+ expect(convert_html("Hello \e[2Kworld")).to eq('<span>Hello world</span>')
end
it "prints simply red" do
@@ -32,7 +34,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets colors after red on blue" do
- expect(convert_html("\e[31;44mHello\e[0m world")).to eq('<span class="term-fg-red term-bg-blue">Hello</span><span class=""> world</span>')
+ expect(convert_html("\e[31;44mHello\e[0m world")).to eq('<span class="term-fg-red term-bg-blue">Hello</span><span> world</span>')
end
it "performs color change from red/blue to yellow/blue" do
@@ -44,11 +46,11 @@ describe Gitlab::Ci::Ansi2html do
end
it "performs color change from red/blue to reset to yellow/green" do
- expect(convert_html("\e[31;44mHello\e[0m \e[33;42mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello</span><span class=""> </span><span class="term-fg-yellow term-bg-green">world</span>')
+ expect(convert_html("\e[31;44mHello\e[0m \e[33;42mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello</span><span> </span><span class="term-fg-yellow term-bg-green">world</span>')
end
it "ignores unsupported codes" do
- expect(convert_html("\e[51mHello\e[0m")).to eq('<span class="">Hello</span>')
+ expect(convert_html("\e[51mHello\e[0m")).to eq('<span>Hello</span>')
end
it "prints light red" do
@@ -72,8 +74,8 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets bold text" do
- expect(convert_html("\e[1mHello\e[21m world")).to eq('<span class="term-bold">Hello</span><span class=""> world</span>')
- expect(convert_html("\e[1mHello\e[22m world")).to eq('<span class="term-bold">Hello</span><span class=""> world</span>')
+ expect(convert_html("\e[1mHello\e[21m world")).to eq('<span class="term-bold">Hello</span><span> world</span>')
+ expect(convert_html("\e[1mHello\e[22m world")).to eq('<span class="term-bold">Hello</span><span> world</span>')
end
it "prints italic text" do
@@ -81,7 +83,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets italic text" do
- expect(convert_html("\e[3mHello\e[23m world")).to eq('<span class="term-italic">Hello</span><span class=""> world</span>')
+ expect(convert_html("\e[3mHello\e[23m world")).to eq('<span class="term-italic">Hello</span><span> world</span>')
end
it "prints underlined text" do
@@ -89,7 +91,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets underlined text" do
- expect(convert_html("\e[4mHello\e[24m world")).to eq('<span class="term-underline">Hello</span><span class=""> world</span>')
+ expect(convert_html("\e[4mHello\e[24m world")).to eq('<span class="term-underline">Hello</span><span> world</span>')
end
it "prints concealed text" do
@@ -97,7 +99,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets concealed text" do
- expect(convert_html("\e[8mHello\e[28m world")).to eq('<span class="term-conceal">Hello</span><span class=""> world</span>')
+ expect(convert_html("\e[8mHello\e[28m world")).to eq('<span class="term-conceal">Hello</span><span> world</span>')
end
it "prints crossed-out text" do
@@ -105,7 +107,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets crossed-out text" do
- expect(convert_html("\e[9mHello\e[29m world")).to eq('<span class="term-cross">Hello</span><span class=""> world</span>')
+ expect(convert_html("\e[9mHello\e[29m world")).to eq('<span class="term-cross">Hello</span><span> world</span>')
end
it "can print 256 xterm fg colors" do
@@ -137,15 +139,15 @@ describe Gitlab::Ci::Ansi2html do
end
it "prints &lt;" do
- expect(convert_html("<")).to eq('<span class="">&lt;</span>')
+ expect(convert_html("<")).to eq('<span>&lt;</span>')
end
it "replaces newlines with line break tags" do
- expect(convert_html("\n")).to eq('<span class=""><br/><span class=""></span></span>')
+ expect(convert_html("\n")).to eq('<span><br/></span>')
end
it "groups carriage returns with newlines" do
- expect(convert_html("\r\n")).to eq('<span class=""><br/><span class=""></span></span>')
+ expect(convert_html("\r\n")).to eq('<span><br/></span>')
end
describe "incremental update" do
@@ -182,7 +184,7 @@ describe Gitlab::Ci::Ansi2html do
context "with partial sequence" do
let(:pre_text) { "Hello\e" }
- let(:pre_html) { "<span class=\"\">Hello</span>" }
+ let(:pre_html) { "<span>Hello</span>" }
let(:text) { "[1m World" }
let(:html) { "<span class=\"term-bold\"> World</span>" }
@@ -191,9 +193,9 @@ describe Gitlab::Ci::Ansi2html do
context 'with new line' do
let(:pre_text) { "Hello\r" }
- let(:pre_html) { "<span class=\"\">Hello\r</span>" }
+ let(:pre_html) { "<span>Hello\r</span>" }
let(:text) { "\nWorld" }
- let(:html) { "<span class=\"\"><br/><span class=\"\">World</span></span>" }
+ let(:html) { "<span><br/>World</span>" }
it_behaves_like 'stateable converter'
end
@@ -207,7 +209,7 @@ describe Gitlab::Ci::Ansi2html do
let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"}
let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"}
let(:section_start_html) do
- '<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer"' \
+ '<div class="js-section-start section-start fa fa-caret-down pr-2 cursor-pointer"' \
" data-timestamp=\"#{section_start_time.to_i}\" data-section=\"#{class_name(section_name)}\"" \
' role="button"></div>'
end
@@ -220,7 +222,7 @@ describe Gitlab::Ci::Ansi2html do
text = "#{section_start}Some text#{section_end}"
class_name_start = section_start.gsub("\033[0K", '').gsub('<', '&lt;')
class_name_end = section_end.gsub("\033[0K", '').gsub('<', '&lt;')
- html = %{<span class="">#{class_name_start}Some text#{class_name_end}</span>}
+ html = %{<span>#{class_name_start}Some text#{class_name_end}</span>}
expect(convert_html(text)).to eq(html)
end
@@ -230,12 +232,11 @@ describe Gitlab::Ci::Ansi2html do
let(:text) { "#{section_start}Some text#{section_end}" }
it 'prints light red' do
- text = "#{section_start}\e[91mHello\e[0m\n#{section_end}"
- header = %{<span class="term-fg-l-red section js-section-header section-header js-s-#{class_name(section_name)}">Hello</span>}
- line_break = %{<span class="section js-section-header section-header js-s-#{class_name(section_name)}"><br/></span>}
- line = %{<span class="section line s_#{class_name(section_name)}"></span>}
- empty_line = %{<span class="section js-s-#{class_name(section_name)}"></span>}
- html = "#{section_start_html}#{header}#{line_break}#{line}#{empty_line}#{section_end_html}"
+ text = "#{section_start}\e[91mHello\e[0m\nLine 1\nLine 2\nLine 3\n#{section_end}"
+ header = %{<span class="term-fg-l-red section js-section-header section-header cursor-pointer js-s-#{class_name(section_name)}">Hello</span>}
+ line_break = %{<span class="section js-section-header section-header cursor-pointer js-s-#{class_name(section_name)}"><br/></span>}
+ output_line = %{<span class="section line js-s-#{class_name(section_name)}">Line 1<br/>Line 2<br/>Line 3<br/></span>}
+ html = "#{section_start_html}#{header}#{line_break}#{output_line}#{section_end_html}"
expect(convert_html(text)).to eq(html)
end
diff --git a/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
index 987c6b37aaa..cec3e70bb9f 100644
--- a/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Artifacts::Adapters::GzipStream do
diff --git a/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
index ec2dd724b45..66a234232e1 100644
--- a/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Artifacts::Adapters::RawStream do
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
index 3b905611467..24d17eb0fb3 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
index a9a4af1f455..ff189c4701e 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Artifacts::Metadata do
diff --git a/spec/lib/gitlab/ci/build/artifacts/path_spec.rb b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
index 7bd6a2ead25..7bbef0f5197 100644
--- a/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Artifacts::Path do
diff --git a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
index d53db05e5e6..9148c0d579e 100644
--- a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
+++ b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Credentials::Factory do
diff --git a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb b/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
index c6054138cde..552580dcbbe 100644
--- a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
+++ b/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Credentials::Registry do
diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb
index 6e20e0ef5c3..04bab9c58b8 100644
--- a/spec/lib/gitlab/ci/build/image_spec.rb
+++ b/spec/lib/gitlab/ci/build/image_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Image do
diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
index 92cf0376c02..48ac2e4e657 100644
--- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Policy::Changes do
diff --git a/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb b/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
index 4510b82ca9d..bc2e6fe6b8d 100644
--- a/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Policy::Kubernetes do
diff --git a/spec/lib/gitlab/ci/build/policy/refs_spec.rb b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
index 22ca681cfd3..43c5d3ec980 100644
--- a/spec/lib/gitlab/ci/build/policy/refs_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Policy::Refs do
diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
index 9b016901a20..7140c14facb 100644
--- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Policy::Variables do
@@ -11,7 +13,12 @@ describe Gitlab::Ci::Build::Policy::Variables do
build(:ci_build, pipeline: pipeline, project: project, ref: 'master')
end
- let(:seed) { double('build seed', to_resource: ci_build) }
+ let(:seed) do
+ double('build seed',
+ to_resource: ci_build,
+ scoped_variables_hash: ci_build.scoped_variables_hash
+ )
+ end
before do
pipeline.variables.build(key: 'CI_PROJECT_NAME', value: '')
@@ -81,7 +88,12 @@ describe Gitlab::Ci::Build::Policy::Variables do
build(:ci_bridge, pipeline: pipeline, project: project, ref: 'master')
end
- let(:seed) { double('bridge seed', to_resource: bridge) }
+ let(:seed) do
+ double('bridge seed',
+ to_resource: bridge,
+ scoped_variables_hash: ci_build.scoped_variables_hash
+ )
+ end
it 'is satisfied by a matching expression for a bridge job' do
policy = described_class.new(['$MY_VARIABLE'])
@@ -89,5 +101,38 @@ describe Gitlab::Ci::Build::Policy::Variables do
expect(policy).to be_satisfied_by(pipeline, seed)
end
end
+
+ context 'when using project ci variables in environment scope' do
+ let(:ci_build) do
+ build(:ci_build, pipeline: pipeline,
+ project: project,
+ ref: 'master',
+ stage: 'review',
+ environment: 'test/$CI_JOB_STAGE/1')
+ end
+
+ before do
+ create(:ci_variable, project: project,
+ key: 'SCOPED_VARIABLE',
+ value: 'my-value-1')
+
+ create(:ci_variable, project: project,
+ key: 'SCOPED_VARIABLE',
+ value: 'my-value-2',
+ environment_scope: 'test/review/*')
+ end
+
+ it 'is satisfied by scoped variable match' do
+ policy = described_class.new(['$SCOPED_VARIABLE == "my-value-2"'])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satisfied when matching against overridden variable' do
+ policy = described_class.new(['$SCOPED_VARIABLE == "my-value-1"'])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/build/policy_spec.rb b/spec/lib/gitlab/ci/build/policy_spec.rb
index 20ee3dd3e89..80d7b2e9dc8 100644
--- a/spec/lib/gitlab/ci/build/policy_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Policy do
diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
index 51e16c99688..775550f2acc 100644
--- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
+++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
- let(:build) { create(:ci_build) }
-
describe '#unmet?' do
+ let(:build) { create(:ci_build) }
+
subject { described_class.new(build).unmet? }
context 'build has no deployment' do
@@ -17,15 +17,11 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
end
context 'build has a deployment' do
- let!(:deployment) { create(:deployment, deployable: build) }
+ let!(:deployment) { create(:deployment, deployable: build, cluster: cluster) }
context 'and a cluster to deploy to' do
let(:cluster) { create(:cluster, :group) }
- before do
- allow(build.deployment).to receive(:deployment_platform_cluster).and_return(cluster)
- end
-
it { is_expected.to be_truthy }
context 'and the cluster is not managed' do
@@ -35,12 +31,17 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
end
context 'and a namespace is already created for this project' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster, project: build.project) }
+ let(:kubernetes_namespace) { instance_double(Clusters::KubernetesNamespace, service_account_token: 'token') }
+
+ before do
+ allow(Clusters::KubernetesNamespaceFinder).to receive(:new)
+ .and_return(double(execute: kubernetes_namespace))
+ end
it { is_expected.to be_falsey }
context 'and the service_account_token is blank' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :without_token, cluster: cluster, project: build.project) }
+ let(:kubernetes_namespace) { instance_double(Clusters::KubernetesNamespace, service_account_token: nil) }
it { is_expected.to be_truthy }
end
@@ -48,9 +49,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
end
context 'and no cluster to deploy to' do
- before do
- expect(deployment.deployment_platform_cluster).to be_nil
- end
+ let(:cluster) { nil }
it { is_expected.to be_falsey }
end
@@ -58,33 +57,69 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
end
describe '#complete!' do
- let!(:deployment) { create(:deployment, deployable: build) }
- let(:service) { double(execute: true) }
+ let(:build) { create(:ci_build) }
+ let(:prerequisite) { described_class.new(build) }
- subject { described_class.new(build).complete! }
+ subject { prerequisite.complete! }
context 'completion is required' do
let(:cluster) { create(:cluster, :group) }
+ let(:deployment) { create(:deployment, cluster: cluster) }
+ let(:service) { double(execute: true) }
+ let(:kubernetes_namespace) { double }
before do
- allow(build.deployment).to receive(:deployment_platform_cluster).and_return(cluster)
+ allow(prerequisite).to receive(:unmet?).and_return(true)
+ allow(build).to receive(:deployment).and_return(deployment)
end
- it 'creates a kubernetes namespace' do
- expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService)
- .to receive(:new)
- .with(cluster: cluster, kubernetes_namespace: instance_of(Clusters::KubernetesNamespace))
- .and_return(service)
+ context 'kubernetes namespace does not exist' do
+ let(:namespace_builder) { double(execute: kubernetes_namespace)}
- expect(service).to receive(:execute).once
+ before do
+ allow(Clusters::KubernetesNamespaceFinder).to receive(:new)
+ .and_return(double(execute: nil))
+ end
- subject
+ it 'creates a namespace using a new record' do
+ expect(Clusters::BuildKubernetesNamespaceService)
+ .to receive(:new)
+ .with(cluster, environment: deployment.environment)
+ .and_return(namespace_builder)
+
+ expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService)
+ .to receive(:new)
+ .with(cluster: cluster, kubernetes_namespace: kubernetes_namespace)
+ .and_return(service)
+
+ expect(service).to receive(:execute).once
+
+ subject
+ end
+ end
+
+ context 'kubernetes namespace exists (but has no service_account_token)' do
+ before do
+ allow(Clusters::KubernetesNamespaceFinder).to receive(:new)
+ .and_return(double(execute: kubernetes_namespace))
+ end
+
+ it 'creates a namespace using the tokenless record' do
+ expect(Clusters::BuildKubernetesNamespaceService).not_to receive(:new)
+
+ expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService)
+ .to receive(:new)
+ .with(cluster: cluster, kubernetes_namespace: kubernetes_namespace)
+ .and_return(service)
+
+ subject
+ end
end
end
context 'completion is not required' do
before do
- expect(deployment.deployment_platform_cluster).to be_nil
+ allow(prerequisite).to receive(:unmet?).and_return(false)
end
it 'does not create a namespace' do
diff --git a/spec/lib/gitlab/ci/build/rules/rule_spec.rb b/spec/lib/gitlab/ci/build/rules/rule_spec.rb
new file mode 100644
index 00000000000..99852bd4228
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/rules/rule_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Rules::Rule do
+ let(:seed) do
+ double('build seed',
+ to_resource: ci_build,
+ scoped_variables_hash: ci_build.scoped_variables_hash
+ )
+ end
+
+ let(:pipeline) { create(:ci_pipeline) }
+ let(:ci_build) { build(:ci_build, pipeline: pipeline) }
+ let(:rule) { described_class.new(rule_hash) }
+
+ describe '#matches?' do
+ subject { rule.matches?(pipeline, seed) }
+
+ context 'with one matching clause' do
+ let(:rule_hash) do
+ { if: '$VAR == null', when: 'always' }
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'with two matching clauses' do
+ let(:rule_hash) do
+ { if: '$VAR == null', changes: '**/*', when: 'always' }
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'with a matching and non-matching clause' do
+ let(:rule_hash) do
+ { if: '$VAR != null', changes: '$VAR == null', when: 'always' }
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'with two non-matching clauses' do
+ let(:rule_hash) do
+ { if: '$VAR != null', changes: 'README', when: 'always' }
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/rules_spec.rb b/spec/lib/gitlab/ci/build/rules_spec.rb
new file mode 100644
index 00000000000..d7793ebc806
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/rules_spec.rb
@@ -0,0 +1,168 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Rules do
+ let(:pipeline) { create(:ci_pipeline) }
+ let(:ci_build) { build(:ci_build, pipeline: pipeline) }
+
+ let(:seed) do
+ double('build seed',
+ to_resource: ci_build,
+ scoped_variables_hash: ci_build.scoped_variables_hash
+ )
+ end
+
+ let(:rules) { described_class.new(rule_list) }
+
+ describe '.new' do
+ let(:rules_ivar) { rules.instance_variable_get :@rule_list }
+ let(:default_when) { rules.instance_variable_get :@default_when }
+
+ context 'with no rules' do
+ let(:rule_list) { [] }
+
+ it 'sets @rule_list to an empty array' do
+ expect(rules_ivar).to eq([])
+ end
+
+ it 'sets @default_when to "on_success"' do
+ expect(default_when).to eq('on_success')
+ end
+ end
+
+ context 'with one rule' do
+ let(:rule_list) { [{ if: '$VAR == null', when: 'always' }] }
+
+ it 'sets @rule_list to an array of a single rule' do
+ expect(rules_ivar).to be_an(Array)
+ end
+
+ it 'sets @default_when to "on_success"' do
+ expect(default_when).to eq('on_success')
+ end
+ end
+
+ context 'with multiple rules' do
+ let(:rule_list) do
+ [
+ { if: '$VAR == null', when: 'always' },
+ { if: '$VAR == null', when: 'always' }
+ ]
+ end
+
+ it 'sets @rule_list to an array of a single rule' do
+ expect(rules_ivar).to be_an(Array)
+ end
+
+ it 'sets @default_when to "on_success"' do
+ expect(default_when).to eq('on_success')
+ end
+ end
+
+ context 'with a specified default when:' do
+ let(:rule_list) { [{ if: '$VAR == null', when: 'always' }] }
+ let(:rules) { described_class.new(rule_list, 'manual') }
+
+ it 'sets @rule_list to an array of a single rule' do
+ expect(rules_ivar).to be_an(Array)
+ end
+
+ it 'sets @default_when to "manual"' do
+ expect(default_when).to eq('manual')
+ end
+ end
+ end
+
+ describe '#evaluate' do
+ subject { rules.evaluate(pipeline, seed) }
+
+ context 'with nil rules' do
+ let(:rule_list) { nil }
+
+ it { is_expected.to eq(described_class::Result.new('on_success')) }
+
+ context 'and when:manual set as the default' do
+ let(:rules) { described_class.new(rule_list, 'manual') }
+
+ it { is_expected.to eq(described_class::Result.new('manual')) }
+ end
+ end
+
+ context 'with no rules' do
+ let(:rule_list) { [] }
+
+ it { is_expected.to eq(described_class::Result.new('never')) }
+
+ context 'and when:manual set as the default' do
+ let(:rules) { described_class.new(rule_list, 'manual') }
+
+ it { is_expected.to eq(described_class::Result.new('never')) }
+ end
+ end
+
+ context 'with one rule without any clauses' do
+ let(:rule_list) { [{ when: 'manual' }] }
+
+ it { is_expected.to eq(described_class::Result.new('manual')) }
+ end
+
+ context 'with one matching rule' do
+ let(:rule_list) { [{ if: '$VAR == null', when: 'always' }] }
+
+ it { is_expected.to eq(described_class::Result.new('always')) }
+ end
+
+ context 'with two matching rules' do
+ let(:rule_list) do
+ [
+ { if: '$VAR == null', when: 'delayed', start_in: '1 day' },
+ { if: '$VAR == null', when: 'always' }
+ ]
+ end
+
+ it 'returns the value of the first matched rule in the list' do
+ expect(subject).to eq(described_class::Result.new('delayed', '1 day'))
+ end
+ end
+
+ context 'with a non-matching and matching rule' do
+ let(:rule_list) do
+ [
+ { if: '$VAR =! null', when: 'delayed', start_in: '1 day' },
+ { if: '$VAR == null', when: 'always' }
+ ]
+ end
+
+ it { is_expected.to eq(described_class::Result.new('always')) }
+ end
+
+ context 'with a matching and non-matching rule' do
+ let(:rule_list) do
+ [
+ { if: '$VAR == null', when: 'delayed', start_in: '1 day' },
+ { if: '$VAR != null', when: 'always' }
+ ]
+ end
+
+ it { is_expected.to eq(described_class::Result.new('delayed', '1 day')) }
+ end
+
+ context 'with non-matching rules' do
+ let(:rule_list) do
+ [
+ { if: '$VAR != null', when: 'delayed', start_in: '1 day' },
+ { if: '$VAR != null', when: 'always' }
+ ]
+ end
+
+ it { is_expected.to eq(described_class::Result.new('never')) }
+
+ context 'and when:manual set as the default' do
+ let(:rules) { described_class.new(rule_list, 'manual') }
+
+ it 'does not return the default when:' do
+ expect(subject).to eq(described_class::Result.new('never'))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb
index e3136fc925e..84e6e0e177f 100644
--- a/spec/lib/gitlab/ci/build/step_spec.rb
+++ b/spec/lib/gitlab/ci/build/step_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Step do
diff --git a/spec/lib/gitlab/ci/charts_spec.rb b/spec/lib/gitlab/ci/charts_spec.rb
index 1668d3bbaac..cfb7a3f72fa 100644
--- a/spec/lib/gitlab/ci/charts_spec.rb
+++ b/spec/lib/gitlab/ci/charts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Charts do
diff --git a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
index bd1f2c92844..a7f457e0f5e 100644
--- a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Artifacts do
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 8f711e02f9b..4cb63168ec7 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Cache do
diff --git a/spec/lib/gitlab/ci/config/entry/commands_spec.rb b/spec/lib/gitlab/ci/config/entry/commands_spec.rb
index 8934aeb83db..269a3406913 100644
--- a/spec/lib/gitlab/ci/config/entry/commands_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/commands_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Commands do
diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
index 4c6bd859552..48d0864cfca 100644
--- a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Coverage do
diff --git a/spec/lib/gitlab/ci/config/entry/default_spec.rb b/spec/lib/gitlab/ci/config/entry/default_spec.rb
index a901dd80c2c..27d63dbd407 100644
--- a/spec/lib/gitlab/ci/config/entry/default_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/default_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Default do
diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
index 0bc9e8bd3cd..7b72b45fd8d 100644
--- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Environment do
diff --git a/spec/lib/gitlab/ci/config/entry/hidden_spec.rb b/spec/lib/gitlab/ci/config/entry/hidden_spec.rb
index c88ee10550c..40b73352676 100644
--- a/spec/lib/gitlab/ci/config/entry/hidden_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/hidden_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Hidden do
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index 1ebdda398b9..8de2e5de724 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Image do
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 25766d34c65..1853efde350 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Job do
@@ -9,7 +11,7 @@ describe Gitlab::Ci::Config::Entry::Job do
let(:result) do
%i[before_script script stage type after_script cache
- image services only except variables artifacts
+ image services only except rules variables artifacts
environment coverage retry]
end
@@ -84,6 +86,31 @@ describe Gitlab::Ci::Config::Entry::Job do
it { expect(entry).to be_valid }
end
end
+
+ context 'when has needs' do
+ let(:config) do
+ {
+ stage: 'test',
+ script: 'echo',
+ needs: ['another-job']
+ }
+ end
+
+ it { expect(entry).to be_valid }
+
+ context 'when has dependencies' do
+ let(:config) do
+ {
+ stage: 'test',
+ script: 'echo',
+ dependencies: ['another-job'],
+ needs: ['another-job']
+ }
+ end
+
+ it { expect(entry).to be_valid }
+ end
+ end
end
context 'when entry value is not correct' do
@@ -174,6 +201,21 @@ describe Gitlab::Ci::Config::Entry::Job do
expect(entry.errors).to include 'job parallel must be an integer'
end
end
+
+ context 'when it uses both "when:" and "rules:"' do
+ let(:config) do
+ {
+ script: 'echo',
+ when: 'on_failure',
+ rules: [{ if: '$VARIABLE', when: 'on_success' }]
+ }
+ end
+
+ it 'returns an error about when: being combined with rules' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job config key may not be used with `rules`: when'
+ end
+ end
end
context 'when delayed job' do
@@ -213,6 +255,100 @@ describe Gitlab::Ci::Config::Entry::Job do
end
end
+ context 'when only: is used with rules:' do
+ let(:config) { { only: ['merge_requests'], rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing only: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+
+ context 'and only: is blank' do
+ let(:config) { { only: nil, rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing only: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+
+ context 'and rules: is blank' do
+ let(:config) { { only: ['merge_requests'], rules: nil } }
+
+ it 'returns error about mixing only: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+ end
+
+ context 'when except: is used with rules:' do
+ let(:config) { { except: { refs: %w[master] }, rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+
+ context 'and except: is blank' do
+ let(:config) { { except: nil, rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+
+ context 'and rules: is blank' do
+ let(:config) { { except: { refs: %w[master] }, rules: nil } }
+
+ it 'returns error about mixing except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+ end
+
+ context 'when only: and except: are both used with rules:' do
+ let(:config) do
+ {
+ only: %w[merge_requests],
+ except: { refs: %w[master] },
+ rules: [{ if: '$THIS' }]
+ }
+ end
+
+ it 'returns errors about mixing both only: and except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+
+ context 'when only: and except: as both blank' do
+ let(:config) do
+ { only: nil, except: nil, rules: [{ if: '$THIS' }] }
+ end
+
+ it 'returns errors about mixing both only: and except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+
+ context 'when rules: is blank' do
+ let(:config) do
+ { only: %w[merge_requests], except: { refs: %w[master] }, rules: nil }
+ end
+
+ it 'returns errors about mixing both only: and except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+ end
+
context 'when start_in specified without delayed specification' do
let(:config) { { start_in: '1 day' } }
@@ -221,6 +357,66 @@ describe Gitlab::Ci::Config::Entry::Job do
expect(entry.errors).to include 'job start in must be blank'
end
end
+
+ context 'when has dependencies' do
+ context 'that are not a array of strings' do
+ let(:config) do
+ { script: 'echo', dependencies: 'build-job' }
+ end
+
+ it 'returns error about invalid type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job dependencies should be an array of strings'
+ end
+ end
+ end
+
+ context 'when has needs' do
+ context 'that are not a array of strings' do
+ let(:config) do
+ {
+ stage: 'test',
+ script: 'echo',
+ needs: 'build-job'
+ }
+ end
+
+ it 'returns error about invalid type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job needs should be an array of strings'
+ end
+ end
+
+ context 'when have dependencies that are not subset of needs' do
+ let(:config) do
+ {
+ stage: 'test',
+ script: 'echo',
+ dependencies: ['another-job'],
+ needs: ['build-job']
+ }
+ end
+
+ it 'returns error about invalid data' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job dependencies the another-job should be part of needs'
+ end
+ end
+
+ context 'when stage: is missing' do
+ let(:config) do
+ {
+ script: 'echo',
+ needs: ['build-job']
+ }
+ end
+
+ it 'returns error about invalid data' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job config missing required keys: stage'
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
index 3e4137930dc..61c8956d41f 100644
--- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Jobs do
diff --git a/spec/lib/gitlab/ci/config/entry/key_spec.rb b/spec/lib/gitlab/ci/config/entry/key_spec.rb
index 3cbf19bea8b..a7874447725 100644
--- a/spec/lib/gitlab/ci/config/entry/key_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/key_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Key do
diff --git a/spec/lib/gitlab/ci/config/entry/paths_spec.rb b/spec/lib/gitlab/ci/config/entry/paths_spec.rb
index 1d9c5ddee9b..221d5ae5863 100644
--- a/spec/lib/gitlab/ci/config/entry/paths_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/paths_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Paths do
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index fba5671594d..a606eb303e7 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require 'support/helpers/stub_feature_flags'
require_dependency 'active_model'
@@ -49,8 +51,6 @@ describe Gitlab::Ci::Config::Entry::Policy do
let(:config) { ['/^(?!master).+/'] }
- subject { described_class.new([regexp]) }
-
context 'when allow_unsafe_ruby_regexp is disabled' do
before do
stub_feature_flags(allow_unsafe_ruby_regexp: false)
@@ -111,8 +111,6 @@ describe Gitlab::Ci::Config::Entry::Policy do
let(:config) { { refs: ['/^(?!master).+/'] } }
- subject { described_class.new([regexp]) }
-
context 'when allow_unsafe_ruby_regexp is disabled' do
before do
stub_feature_flags(allow_unsafe_ruby_regexp: false)
@@ -202,6 +200,14 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
context 'when changes policy is invalid' do
+ let(:config) { { changes: 'some/*' } }
+
+ it 'returns errors' do
+ expect(entry.errors).to include /changes should be an array of strings/
+ end
+ end
+
+ context 'when changes policy is invalid' do
let(:config) { { changes: [1, 2] } }
it 'returns errors' do
diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
index 38943138cbf..3c352c30e55 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Reports do
diff --git a/spec/lib/gitlab/ci/config/entry/retry_spec.rb b/spec/lib/gitlab/ci/config/entry/retry_spec.rb
index 164a9ed4c3d..f9efd2e014d 100644
--- a/spec/lib/gitlab/ci/config/entry/retry_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/retry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Retry do
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 7a252ed675a..968dbb9c7f2 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Root do
diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
new file mode 100644
index 00000000000..c25344ec1a4
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
@@ -0,0 +1,208 @@
+require 'fast_spec_helper'
+require 'chronic_duration'
+require 'support/helpers/stub_feature_flags'
+require_dependency 'active_model'
+
+describe Gitlab::Ci::Config::Entry::Rules::Rule do
+ let(:entry) { described_class.new(config) }
+
+ describe '.new' do
+ subject { entry }
+
+ context 'with a when: value but no clauses' do
+ let(:config) { { when: 'manual' } }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when specifying an if: clause' do
+ let(:config) { { if: '$THIS || $THAT', when: 'manual' } }
+
+ it { is_expected.to be_valid }
+
+ describe '#when' do
+ subject { entry.when }
+
+ it { is_expected.to eq('manual') }
+ end
+ end
+
+ context 'using a list of multiple expressions' do
+ let(:config) { { if: ['$MY_VAR == "this"', '$YOUR_VAR == "that"'] } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'reports an error about invalid format' do
+ expect(subject.errors).to include(/invalid expression syntax/)
+ end
+ end
+
+ context 'when specifying an invalid if: clause expression' do
+ let(:config) { { if: ['$MY_VAR =='] } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'reports an error about invalid statement' do
+ expect(subject.errors).to include(/invalid expression syntax/)
+ end
+ end
+
+ context 'when specifying an if: clause expression with an invalid token' do
+ let(:config) { { if: ['$MY_VAR == 123'] } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'reports an error about invalid statement' do
+ expect(subject.errors).to include(/invalid expression syntax/)
+ end
+ end
+
+ context 'when using invalid regex in an if: clause' do
+ let(:config) { { if: ['$MY_VAR =~ /some ( thing/'] } }
+
+ it 'reports an error about invalid expression' do
+ expect(subject.errors).to include(/invalid expression syntax/)
+ end
+ end
+
+ context 'when using an if: clause with lookahead regex character "?"' do
+ let(:config) { { if: '$CI_COMMIT_REF =~ /^(?!master).+/' } }
+
+ context 'when allow_unsafe_ruby_regexp is disabled' do
+ it { is_expected.not_to be_valid }
+
+ it 'reports an error about invalid expression syntax' do
+ expect(subject.errors).to include(/invalid expression syntax/)
+ end
+ end
+ end
+
+ context 'when using a changes: clause' do
+ let(:config) { { changes: %w[app/ lib/ spec/ other/* paths/**/*.rb] } }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when using a string as an invalid changes: clause' do
+ let(:config) { { changes: 'a regular string' } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'reports an error about invalid policy' do
+ expect(subject.errors).to include(/should be an array of strings/)
+ end
+ end
+
+ context 'when using a list as an invalid changes: clause' do
+ let(:config) { { changes: [1, 2] } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns errors' do
+ expect(subject.errors).to include(/changes should be an array of strings/)
+ end
+ end
+
+ context 'specifying a delayed job' do
+ let(:config) { { if: '$THIS || $THAT', when: 'delayed', start_in: '15 minutes' } }
+
+ it { is_expected.to be_valid }
+
+ it 'sets attributes for the job delay' do
+ expect(entry.when).to eq('delayed')
+ expect(entry.start_in).to eq('15 minutes')
+ end
+
+ context 'without a when: key' do
+ let(:config) { { if: '$THIS || $THAT', start_in: '15 minutes' } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns an error about the disallowed key' do
+ expect(entry.errors).to include(/disallowed keys: start_in/)
+ end
+ end
+
+ context 'without a start_in: key' do
+ let(:config) { { if: '$THIS || $THAT', when: 'delayed' } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns an error about tstart_in being blank' do
+ expect(entry.errors).to include(/start in can't be blank/)
+ end
+ end
+ end
+
+ context 'when specifying unknown policy' do
+ let(:config) { { invalid: :something } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns error about invalid key' do
+ expect(entry.errors).to include(/unknown keys: invalid/)
+ end
+ end
+
+ context 'when clause is empty' do
+ let(:config) { {} }
+
+ it { is_expected.not_to be_valid }
+
+ it 'is not a valid configuration' do
+ expect(entry.errors).to include(/can't be blank/)
+ end
+ end
+
+ context 'when policy strategy does not match' do
+ let(:config) { 'string strategy' }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns information about errors' do
+ expect(entry.errors)
+ .to include(/should be a hash/)
+ end
+ end
+ end
+
+ describe '#value' do
+ subject { entry.value }
+
+ context 'when specifying an if: clause' do
+ let(:config) { { if: '$THIS || $THAT', when: 'manual' } }
+
+ it 'stores the expression as "if"' do
+ expect(subject).to eq(if: '$THIS || $THAT', when: 'manual')
+ end
+ end
+
+ context 'when using a changes: clause' do
+ let(:config) { { changes: %w[app/ lib/ spec/ other/* paths/**/*.rb] } }
+
+ it { is_expected.to eq(config) }
+ end
+
+ context 'when default value has been provided' do
+ let(:config) { { changes: %w[app/**/*.rb] } }
+
+ before do
+ entry.default = { changes: %w[**/*] }
+ end
+
+ it 'does not set a default value' do
+ expect(entry.default).to eq(nil)
+ end
+
+ it 'does not add to provided configuration' do
+ expect(entry.value).to eq(config)
+ end
+ end
+ end
+
+ describe '.default' do
+ it 'does not have default value' do
+ expect(described_class.default).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/rules_spec.rb
new file mode 100644
index 00000000000..291e7373daf
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/rules_spec.rb
@@ -0,0 +1,135 @@
+require 'fast_spec_helper'
+require 'support/helpers/stub_feature_flags'
+require_dependency 'active_model'
+
+describe Gitlab::Ci::Config::Entry::Rules do
+ let(:entry) { described_class.new(config) }
+
+ describe '.new' do
+ subject { entry }
+
+ context 'with a list of rule rule' do
+ let(:config) do
+ [{ if: '$THIS == "that"', when: 'never' }]
+ end
+
+ it { is_expected.to be_a(described_class) }
+ it { is_expected.to be_valid }
+
+ context 'after #compose!' do
+ before do
+ subject.compose!
+ end
+
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'with a list of two rules' do
+ let(:config) do
+ [
+ { if: '$THIS == "that"', when: 'always' },
+ { if: '$SKIP', when: 'never' }
+ ]
+ end
+
+ it { is_expected.to be_a(described_class) }
+ it { is_expected.to be_valid }
+
+ context 'after #compose!' do
+ before do
+ subject.compose!
+ end
+
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'with a single rule object' do
+ let(:config) do
+ { if: '$SKIP', when: 'never' }
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'with an invalid boolean when:' do
+ let(:config) do
+ [{ if: '$THIS == "that"', when: false }]
+ end
+
+ it { is_expected.to be_a(described_class) }
+ it { is_expected.to be_valid }
+
+ context 'after #compose!' do
+ before do
+ subject.compose!
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns an error about invalid when:' do
+ expect(subject.errors).to include(/when unknown value: false/)
+ end
+ end
+ end
+
+ context 'with an invalid string when:' do
+ let(:config) do
+ [{ if: '$THIS == "that"', when: 'explode' }]
+ end
+
+ it { is_expected.to be_a(described_class) }
+ it { is_expected.to be_valid }
+
+ context 'after #compose!' do
+ before do
+ subject.compose!
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns an error about invalid when:' do
+ expect(subject.errors).to include(/when unknown value: explode/)
+ end
+ end
+ end
+ end
+
+ describe '#value' do
+ subject { entry.value }
+
+ context 'with a list of rule rule' do
+ let(:config) do
+ [{ if: '$THIS == "that"', when: 'never' }]
+ end
+
+ it { is_expected.to eq(config) }
+ end
+
+ context 'with a list of two rules' do
+ let(:config) do
+ [
+ { if: '$THIS == "that"', when: 'always' },
+ { if: '$SKIP', when: 'never' }
+ ]
+ end
+
+ it { is_expected.to eq(config) }
+ end
+
+ context 'with a single rule object' do
+ let(:config) do
+ { if: '$SKIP', when: 'never' }
+ end
+
+ it { is_expected.to eq(config) }
+ end
+ end
+
+ describe '.default' do
+ it 'does not have default policy' do
+ expect(described_class.default).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/script_spec.rb b/spec/lib/gitlab/ci/config/entry/script_spec.rb
index 069eaa26422..d523243d3b6 100644
--- a/spec/lib/gitlab/ci/config/entry/script_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/script_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Script do
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index d31866a1987..66cca100688 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Service do
diff --git a/spec/lib/gitlab/ci/config/entry/services_spec.rb b/spec/lib/gitlab/ci/config/entry/services_spec.rb
index d5a1316f665..764f783b083 100644
--- a/spec/lib/gitlab/ci/config/entry/services_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/services_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Services do
diff --git a/spec/lib/gitlab/ci/config/entry/stage_spec.rb b/spec/lib/gitlab/ci/config/entry/stage_spec.rb
index 70c8a0a355a..574fa00575a 100644
--- a/spec/lib/gitlab/ci/config/entry/stage_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Stage do
diff --git a/spec/lib/gitlab/ci/config/entry/stages_spec.rb b/spec/lib/gitlab/ci/config/entry/stages_spec.rb
index 182c8d867c7..97970522104 100644
--- a/spec/lib/gitlab/ci/config/entry/stages_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/stages_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Stages do
diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
index 84bfef9e8ad..1320b366367 100644
--- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Variables do
diff --git a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
index d63612053b6..e00104e3c68 100644
--- a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
+++ b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Ci::Config::Extendable::Entry do
diff --git a/spec/lib/gitlab/ci/config/extendable_spec.rb b/spec/lib/gitlab/ci/config/extendable_spec.rb
index 90213f6603d..874b224067b 100644
--- a/spec/lib/gitlab/ci/config/extendable_spec.rb
+++ b/spec/lib/gitlab/ci/config/extendable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Ci::Config::Extendable do
diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb
index cd880177170..6b766cc37bf 100644
--- a/spec/lib/gitlab/ci/config/normalizer_spec.rb
+++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb
@@ -49,37 +49,44 @@ describe Gitlab::Ci::Config::Normalizer do
end
end
- context 'when jobs depend on parallelized jobs' do
- let(:config) { { job_name => job_config, other_job: { script: 'echo 1', dependencies: [job_name.to_s] } } }
-
- it 'parallelizes dependencies' do
- job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"]
-
- expect(subject[:other_job][:dependencies]).to include(*job_names)
+ %i[dependencies needs].each do |context|
+ context "when job has #{context} on parallelized jobs" do
+ let(:config) do
+ {
+ job_name => job_config,
+ other_job: { script: 'echo 1', context => [job_name.to_s] }
+ }
+ end
+
+ it "parallelizes #{context}" do
+ job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"]
+
+ expect(subject[:other_job][context]).to include(*job_names)
+ end
+
+ it "does not include original job name in #{context}" do
+ expect(subject[:other_job][context]).not_to include(job_name)
+ end
end
- it 'does not include original job name in dependencies' do
- expect(subject[:other_job][:dependencies]).not_to include(job_name)
- end
- end
+ context "when there are #{context} which are both parallelized and not" do
+ let(:config) do
+ {
+ job_name => job_config,
+ other_job: { script: 'echo 1' },
+ final_job: { script: 'echo 1', context => [job_name.to_s, "other_job"] }
+ }
+ end
- context 'when there are dependencies which are both parallelized and not' do
- let(:config) do
- {
- job_name => job_config,
- other_job: { script: 'echo 1' },
- final_job: { script: 'echo 1', dependencies: [job_name.to_s, "other_job"] }
- }
- end
-
- it 'parallelizes dependencies' do
- job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"]
+ it "parallelizes #{context}" do
+ job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"]
- expect(subject[:final_job][:dependencies]).to include(*job_names)
- end
+ expect(subject[:final_job][context]).to include(*job_names)
+ end
- it 'includes the regular job in dependencies' do
- expect(subject[:final_job][:dependencies]).to include('other_job')
+ it "includes the regular job in #{context}" do
+ expect(subject[:final_job][context]).to include('other_job')
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 7f336ee853e..986cde3540a 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config do
@@ -90,6 +92,27 @@ describe Gitlab::Ci::Config do
end
end
+ context 'when yml is too big' do
+ let(:yml) do
+ <<~YAML
+ --- &1
+ - hi
+ - *1
+ YAML
+ end
+
+ describe '.new' do
+ it 'raises error' do
+ expect(Gitlab::Sentry).to receive(:track_exception)
+
+ expect { config }.to raise_error(
+ described_class::ConfigError,
+ /The parsed YAML is too big/
+ )
+ end
+ end
+ end
+
context 'when config logic is incorrect' do
let(:yml) { 'before_script: "ls"' }
diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb
index 491e3fba9d9..af4e9d687c4 100644
--- a/spec/lib/gitlab/ci/cron_parser_spec.rb
+++ b/spec/lib/gitlab/ci/cron_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::CronParser do
diff --git a/spec/lib/gitlab/ci/mask_secret_spec.rb b/spec/lib/gitlab/ci/mask_secret_spec.rb
index 3789a142248..6607aaae399 100644
--- a/spec/lib/gitlab/ci/mask_secret_spec.rb
+++ b/spec/lib/gitlab/ci/mask_secret_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::MaskSecret do
diff --git a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
index a49402c7398..8ff60710f67 100644
--- a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Ci::Parsers::Test::Junit do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
index 50cb45c39d1..bf9ff922c05 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Build do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index 5181e9c1583..5775e934cfd 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Command do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
index 0edc3f315bb..650ab193997 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Create do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 0302e4090cf..9bccd5be4fe 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Populate do
@@ -36,8 +38,8 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
it 'populates pipeline with stages' do
expect(pipeline.stages).to be_one
expect(pipeline.stages.first).not_to be_persisted
- expect(pipeline.stages.first.builds).to be_one
- expect(pipeline.stages.first.builds.first).not_to be_persisted
+ expect(pipeline.stages.first.statuses).to be_one
+ expect(pipeline.stages.first.statuses.first).not_to be_persisted
end
it 'correctly assigns user' do
@@ -189,8 +191,8 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
step.perform!
expect(pipeline.stages.size).to eq 1
- expect(pipeline.stages.first.builds.size).to eq 1
- expect(pipeline.stages.first.builds.first.name).to eq 'rspec'
+ expect(pipeline.stages.first.statuses.size).to eq 1
+ expect(pipeline.stages.first.statuses.first.name).to eq 'rspec'
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
index eca23694a2b..9cb59442dfd 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Sequence do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
index c7f4fc98ca3..fe46633ed1b 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Skip do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
index 7d750877d09..ac370433955 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
@@ -10,7 +12,11 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
- project: project, current_user: user, origin_ref: origin_ref, merge_request: merge_request)
+ project: project,
+ current_user: user,
+ origin_ref: origin_ref,
+ merge_request: merge_request,
+ trigger_request: trigger_request)
end
let(:step) { described_class.new(pipeline, command) }
@@ -18,6 +24,7 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
let(:ref) { 'master' }
let(:origin_ref) { ref }
let(:merge_request) { nil }
+ let(:trigger_request) { nil }
shared_context 'detached merge request pipeline' do
let(:merge_request) do
@@ -69,6 +76,43 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
end
end
+ context 'when pipeline triggered by legacy trigger' do
+ let(:user) { nil }
+ let(:trigger_request) do
+ build_stubbed(:ci_trigger_request, trigger: build_stubbed(:ci_trigger, owner: nil))
+ end
+
+ context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: true)
+ step.perform!
+ end
+
+ it 'allows legacy triggers to create a pipeline' do
+ expect(pipeline).to be_valid
+ end
+
+ it 'does not break the chain' do
+ expect(step.break?).to eq false
+ end
+ end
+
+ context 'when :use_legacy_pipeline_triggers feature flag is disabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ step.perform!
+ end
+
+ it 'prevents legacy triggers from creating a pipeline' do
+ expect(pipeline.errors.to_a).to include /Trigger token is invalid/
+ end
+
+ it 'breaks the pipeline builder chain' do
+ expect(step.break?).to eq true
+ end
+ end
+ end
+
describe '#allowed_to_create?' do
subject { step.allowed_to_create? }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
index 00bbc4c817a..79acd3e4f54 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
index 2e8c9d70098..b866355906e 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
diff --git a/spec/lib/gitlab/ci/pipeline/duration_spec.rb b/spec/lib/gitlab/ci/pipeline/duration_spec.rb
index 7c9836e2da6..a4984092f35 100644
--- a/spec/lib/gitlab/ci/pipeline/duration_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/duration_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Duration do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
index 006ce4d8078..847d613dba3 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require 'rspec-parameterized'
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
index fcbd2863289..0e13681a4cf 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
index a6fdec832a3..a527783ffac 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require_dependency 're2'
@@ -67,6 +69,34 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
it { is_expected.to eq(false) }
end
+ context 'when right is nil' do
+ let(:left_value) { 'my-awesome-string' }
+ let(:right_value) { nil }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when left and right are nil' do
+ let(:left_value) { nil }
+ let(:right_value) { nil }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when left is an empty string' do
+ let(:left_value) { '' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when left and right are empty strings' do
+ let(:left_value) { '' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('') }
+
+ it { is_expected.to eq(true) }
+ end
+
context 'when left is a multiline string and matches right' do
let(:left_value) do
<<~TEXT
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
index 38d30c9035a..a3a48f83b27 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
index 99110ff8d88..fb4238ecaf3 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require_dependency 're2'
@@ -67,6 +69,34 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
it { is_expected.to eq(true) }
end
+ context 'when right is nil' do
+ let(:left_value) { 'my-awesome-string' }
+ let(:right_value) { nil }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when left and right are nil' do
+ let(:left_value) { nil }
+ let(:right_value) { nil }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when left is an empty string' do
+ let(:left_value) { '' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when left and right are empty strings' do
+ let(:left_value) { '' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('') }
+
+ it { is_expected.to eq(false) }
+ end
+
context 'when left is a multiline string and matches right' do
let(:left_value) do
<<~TEXT
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
index b5a59929e11..7013c6bacbb 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Null do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
index d542eebc613..15505ebc82b 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require 'rspec-parameterized'
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
index 30ea3f3e28e..2cc25a07417 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
@@ -107,42 +109,6 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
expect(token.build.evaluate)
.to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern')
end
-
- context 'with the ci_variables_complex_expressions feature flag disabled' do
- before do
- stub_feature_flags(ci_variables_complex_expressions: false)
- end
-
- it 'is a greedy scanner for regexp boundaries' do
- scanner = StringScanner.new('/some .* / pattern/')
-
- token = described_class.scan(scanner)
-
- expect(token).not_to be_nil
- expect(token.build.evaluate)
- .to eq Gitlab::UntrustedRegexp.new('some .* / pattern')
- end
-
- it 'does not recognize the \ escape character for /' do
- scanner = StringScanner.new('/some .* \/ pattern/')
-
- token = described_class.scan(scanner)
-
- expect(token).not_to be_nil
- expect(token.build.evaluate)
- .to eq Gitlab::UntrustedRegexp.new('some .* \/ pattern')
- end
-
- it 'does not recognize the \ escape character for $' do
- scanner = StringScanner.new('/some numeric \$ pattern/')
-
- token = described_class.scan(scanner)
-
- expect(token).not_to be_nil
- expect(token.build.evaluate)
- .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern')
- end
- end
end
describe '#evaluate' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
index f54ef492e6d..2a6b90d127f 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
index 599a5411881..29e26930249 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Variable do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
index d8db9c262a1..2b0cee2d6f2 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexer do
@@ -80,34 +82,6 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do
it { is_expected.to eq(tokens) }
end
end
-
- context 'with the ci_variables_complex_expressions feature flag turned off' do
- before do
- stub_feature_flags(ci_variables_complex_expressions: false)
- end
-
- it 'incorrectly tokenizes conjunctive match statements as one match statement' do
- tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/').tokens
-
- expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ && $EMPTY_VARIABLE =~ /nope/'])
- end
-
- it 'incorrectly tokenizes disjunctive match statements as one statement' do
- tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/').tokens
-
- expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ || $EMPTY_VARIABLE =~ /nope/'])
- end
-
- it 'raises an error about && operators' do
- expect { described_class.new('$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE').tokens }
- .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
- end
-
- it 'raises an error about || operators' do
- expect { described_class.new('$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE').tokens }
- .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
- end
- end
end
describe '#lexemes' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
index e88ec5561b6..10adfa18af6 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Parser do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index a2c2e3653d5..79ee4d07e3a 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -1,6 +1,6 @@
-# TODO switch this back after the "ci_variables_complex_expressions" feature flag is removed
-# require 'fast_spec_helper'
-require 'spec_helper'
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
require 'rspec-parameterized'
describe Gitlab::Ci::Pipeline::Expression::Statement do
@@ -118,54 +118,6 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
expect(subject.evaluate).to eq(value)
end
end
-
- context 'with the ci_variables_complex_expressions feature flag disabled' do
- before do
- stub_feature_flags(ci_variables_complex_expressions: false)
- end
-
- where(:expression, :value) do
- '$PRESENT_VARIABLE == "my variable"' | true
- '"my variable" == $PRESENT_VARIABLE' | true
- '$PRESENT_VARIABLE == null' | false
- '$EMPTY_VARIABLE == null' | false
- '"" == $EMPTY_VARIABLE' | true
- '$EMPTY_VARIABLE' | ''
- '$UNDEFINED_VARIABLE == null' | true
- 'null == $UNDEFINED_VARIABLE' | true
- '$PRESENT_VARIABLE' | 'my variable'
- '$UNDEFINED_VARIABLE' | nil
- "$PRESENT_VARIABLE =~ /var.*e$/" | true
- "$PRESENT_VARIABLE =~ /^var.*/" | false
- "$EMPTY_VARIABLE =~ /var.*/" | false
- "$UNDEFINED_VARIABLE =~ /var.*/" | false
- "$PRESENT_VARIABLE =~ /VAR.*/i" | true
- '$PATH_VARIABLE =~ /path/variable/' | true
- '$PATH_VARIABLE =~ /path\/variable/' | true
- '$FULL_PATH_VARIABLE =~ /^/a/full/path/variable/value$/' | true
- '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | true
- '$PRESENT_VARIABLE != "my variable"' | false
- '"my variable" != $PRESENT_VARIABLE' | false
- '$PRESENT_VARIABLE != null' | true
- '$EMPTY_VARIABLE != null' | true
- '"" != $EMPTY_VARIABLE' | false
- '$UNDEFINED_VARIABLE != null' | false
- 'null != $UNDEFINED_VARIABLE' | false
- "$PRESENT_VARIABLE !~ /var.*e$/" | false
- "$PRESENT_VARIABLE !~ /^var.*/" | true
- "$EMPTY_VARIABLE !~ /var.*/" | true
- "$UNDEFINED_VARIABLE !~ /var.*/" | true
- "$PRESENT_VARIABLE !~ /VAR.*/i" | false
- end
-
- with_them do
- let(:text) { expression }
-
- it "evaluates to `#{params[:value].inspect}`" do
- expect(subject.evaluate).to eq value
- end
- end
- end
end
describe '#truthful?' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
index cedfe270f9d..aa807cecb72 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Token do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 7991e2f48b5..89431b80be3 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -1,53 +1,122 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Seed::Build do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+ let(:attributes) { { name: 'rspec', ref: 'master' } }
+ let(:previous_stages) { [] }
- let(:attributes) do
- { name: 'rspec', ref: 'master' }
- end
-
- subject do
- described_class.new(pipeline, attributes)
- end
+ let(:seed_build) { described_class.new(pipeline, attributes, previous_stages) }
describe '#attributes' do
- it 'returns hash attributes of a build' do
- expect(subject.attributes).to be_a Hash
- expect(subject.attributes)
- .to include(:name, :project, :ref)
+ subject { seed_build.attributes }
+
+ it { is_expected.to be_a(Hash) }
+ it { is_expected.to include(:name, :project, :ref) }
+
+ context 'with job:when' do
+ let(:attributes) { { name: 'rspec', ref: 'master', when: 'on_failure' } }
+
+ it { is_expected.to include(when: 'on_failure') }
+ end
+
+ context 'with job:when:delayed' do
+ let(:attributes) { { name: 'rspec', ref: 'master', when: 'delayed', start_in: '3 hours' } }
+
+ it { is_expected.to include(when: 'delayed', start_in: '3 hours') }
+ end
+
+ context 'with job:rules:[when:]' do
+ context 'is matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'always' }] } }
+
+ it { is_expected.to include(when: 'always') }
+ end
+
+ context 'is not matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'always' }] } }
+
+ it { is_expected.to include(when: 'never') }
+ end
+ end
+
+ context 'with job:rules:[when:delayed]' do
+ context 'is matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] } }
+
+ it { is_expected.to include(when: 'delayed', start_in: '3 hours') }
+ end
+
+ context 'is not matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'delayed', start_in: '3 hours' }] } }
+
+ it { is_expected.to include(when: 'never') }
+ end
+ end
+
+ context 'with job:rules but no explicit when:' do
+ context 'is matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null' }] } }
+
+ it { is_expected.to include(when: 'on_success') }
+ end
+
+ context 'is not matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null' }] } }
+
+ it { is_expected.to include(when: 'never') }
+ end
end
end
describe '#bridge?' do
- context 'when job is a bridge' do
+ subject { seed_build.bridge? }
+
+ context 'when job is a downstream bridge' do
let(:attributes) do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
- it { is_expected.to be_bridge }
+ it { is_expected.to be_truthy }
+
+ context 'when trigger definition is empty' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { trigger: '' } }
+ end
+
+ it { is_expected.to be_falsey }
+ end
end
- context 'when trigger definition is empty' do
+ context 'when job is an upstream bridge' do
let(:attributes) do
- { name: 'rspec', ref: 'master', options: { trigger: '' } }
+ { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: 'my/project' } } }
end
- it { is_expected.not_to be_bridge }
+ it { is_expected.to be_truthy }
+
+ context 'when upstream definition is empty' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: '' } } }
+ end
+
+ it { is_expected.to be_falsey }
+ end
end
context 'when job is not a bridge' do
- it { is_expected.not_to be_bridge }
+ it { is_expected.to be_falsey }
end
end
describe '#to_resource' do
+ subject { seed_build.to_resource }
+
context 'when job is not a bridge' do
- it 'returns a valid build resource' do
- expect(subject.to_resource).to be_a(::Ci::Build)
- expect(subject.to_resource).to be_valid
- end
+ it { is_expected.to be_a(::Ci::Build) }
+ it { is_expected.to be_valid }
end
context 'when job is a bridge' do
@@ -55,71 +124,117 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
- it 'returns a valid bridge resource' do
- expect(subject.to_resource).to be_a(::Ci::Bridge)
- expect(subject.to_resource).to be_valid
- end
+ it { is_expected.to be_a(::Ci::Bridge) }
+ it { is_expected.to be_valid }
end
it 'memoizes a resource object' do
- build = subject.to_resource
-
- expect(build.object_id).to eq subject.to_resource.object_id
+ expect(subject.object_id).to eq seed_build.to_resource.object_id
end
it 'can not be persisted without explicit assignment' do
- build = subject.to_resource
-
pipeline.save!
- expect(build).not_to be_persisted
+ expect(subject).not_to be_persisted
end
end
- describe 'applying only/except policies' do
+ describe 'applying job inclusion policies' do
+ subject { seed_build }
+
context 'when no branch policy is specified' do
- let(:attributes) { { name: 'rspec' } }
+ let(:attributes) do
+ { name: 'rspec' }
+ end
it { is_expected.to be_included }
end
context 'when branch policy does not match' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['deploy'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: ['deploy'] } }
+ end
it { is_expected.not_to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['deploy'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: ['deploy'] } }
+ end
it { is_expected.to be_included }
end
+
+ context 'with both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[deploy] },
+ except: { refs: %w[deploy] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'when branch regexp policy does not match' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['/^deploy$/'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[/^deploy$/] } }
+ end
it { is_expected.not_to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['/^deploy$/'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[/^deploy$/] } }
+ end
it { is_expected.to be_included }
end
+
+ context 'with both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[/^deploy$/] },
+ except: { refs: %w[/^deploy$/] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'when branch policy matches' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: %w[deploy master] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[deploy master] } }
+ end
it { is_expected.to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: %w[deploy master] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[deploy master] } }
+ end
+
+ it { is_expected.not_to be_included }
+ end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[deploy master] },
+ except: { refs: %w[deploy master] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -127,13 +242,29 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when keyword policy matches' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['branches'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[branches] } }
+ end
it { is_expected.to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['branches'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[branches] } }
+ end
+
+ it { is_expected.not_to be_included }
+ end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[branches] },
+ except: { refs: %w[branches] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -141,50 +272,78 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when keyword policy does not match' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['tags'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[tags] } }
+ end
it { is_expected.not_to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['tags'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[tags] } }
+ end
it { is_expected.to be_included }
end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[tags] },
+ except: { refs: %w[tags] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'with source-keyword policy' do
using RSpec::Parameterized
- let(:pipeline) { build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) }
+ let(:pipeline) do
+ build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source)
+ end
context 'matches' do
where(:keyword, :source) do
[
- %w(pushes push),
- %w(web web),
- %w(triggers trigger),
- %w(schedules schedule),
- %w(api api),
- %w(external external)
+ %w[pushes push],
+ %w[web web],
+ %w[triggers trigger],
+ %w[schedules schedule],
+ %w[api api],
+ %w[external external]
]
end
with_them do
context 'using an only policy' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: [keyword] } }
+ end
it { is_expected.to be_included }
end
context 'using an except policy' do
- let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: [keyword] } }
+ end
it { is_expected.not_to be_included }
end
context 'using both only and except policies' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } }
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: [keyword] },
+ except: { refs: [keyword] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -193,29 +352,39 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'non-matches' do
where(:keyword, :source) do
- %w(web trigger schedule api external).map { |source| ['pushes', source] } +
- %w(push trigger schedule api external).map { |source| ['web', source] } +
- %w(push web schedule api external).map { |source| ['triggers', source] } +
- %w(push web trigger api external).map { |source| ['schedules', source] } +
- %w(push web trigger schedule external).map { |source| ['api', source] } +
- %w(push web trigger schedule api).map { |source| ['external', source] }
+ %w[web trigger schedule api external].map { |source| ['pushes', source] } +
+ %w[push trigger schedule api external].map { |source| ['web', source] } +
+ %w[push web schedule api external].map { |source| ['triggers', source] } +
+ %w[push web trigger api external].map { |source| ['schedules', source] } +
+ %w[push web trigger schedule external].map { |source| ['api', source] } +
+ %w[push web trigger schedule api].map { |source| ['external', source] }
end
with_them do
context 'using an only policy' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: [keyword] } }
+ end
it { is_expected.not_to be_included }
end
context 'using an except policy' do
- let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: [keyword] } }
+ end
it { is_expected.to be_included }
end
context 'using both only and except policies' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } }
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: [keyword] },
+ except: { refs: [keyword] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -239,12 +408,40 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
it { is_expected.not_to be_included }
end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: ["branches@#{pipeline.project_full_path}"] },
+ except: { refs: ["branches@#{pipeline.project_full_path}"] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: {
+ refs: ["branches@#{pipeline.project_full_path}"]
+ },
+ except: {
+ refs: ["branches@#{pipeline.project_full_path}"]
+ }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
- context 'when repository path does not matches' do
+ context 'when repository path does not match' do
context 'when using only' do
let(:attributes) do
- { name: 'rspec', only: { refs: ['branches@fork'] } }
+ { name: 'rspec', only: { refs: %w[branches@fork] } }
end
it { is_expected.not_to be_included }
@@ -252,11 +449,316 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when using except' do
let(:attributes) do
- { name: 'rspec', except: { refs: ['branches@fork'] } }
+ { name: 'rspec', except: { refs: %w[branches@fork] } }
end
it { is_expected.to be_included }
end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[branches@fork] },
+ except: { refs: %w[branches@fork] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
+
+ context 'using rules:' do
+ using RSpec::Parameterized
+
+ let(:attributes) { { name: 'rspec', rules: rule_set } }
+
+ context 'with a matching if: rule' do
+ context 'with an explicit `when: never`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null', when: 'never' }]],
+ [[{ if: '$VARIABLE == null', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]],
+ [[{ if: '$VARIABLE != "the wrong value"', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
+ end
+ end
+
+ context 'with an explicit `when: always`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null', when: 'always' }]],
+ [[{ if: '$VARIABLE == null', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]],
+ [[{ if: '$VARIABLE != "the wrong value"', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'always')
+ end
+ end
+ end
+
+ context 'with an explicit `when: on_failure`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$CI_JOB_NAME == "rspec" && $VAR == null', when: 'on_failure' }]],
+ [[{ if: '$VARIABLE != null', when: 'delayed', start_in: '1 day' }, { if: '$CI_JOB_NAME == "rspec"', when: 'on_failure' }]],
+ [[{ if: '$VARIABLE == "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$CI_BUILD_NAME == "rspec"', when: 'on_failure' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'on_failure')
+ end
+ end
+ end
+
+ context 'with an explicit `when: delayed`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }]],
+ [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]],
+ [[{ if: '$VARIABLE != "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'delayed', start_in: '1 day')
+ end
+ end
+ end
+
+ context 'without an explicit when: value' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null' }]],
+ [[{ if: '$VARIABLE == null' }, { if: '$VARIABLE == null' }]],
+ [[{ if: '$VARIABLE != "the wrong value"' }, { if: '$VARIABLE == null' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'on_success')
+ end
+ end
+ end
+ end
+
+ context 'with a matching changes: rule' do
+ let(:pipeline) do
+ create(:ci_pipeline, project: project).tap do |pipeline|
+ stub_pipeline_modified_paths(pipeline, %w[app/models/ci/pipeline.rb spec/models/ci/pipeline_spec.rb .gitlab-ci.yml])
+ end
+ end
+
+ context 'with an explicit `when: never`' do
+ where(:rule_set) do
+ [
+ [[{ changes: %w[*/**/*.rb], when: 'never' }, { changes: %w[*/**/*.rb], when: 'always' }]],
+ [[{ changes: %w[app/models/ci/pipeline.rb], when: 'never' }, { changes: %w[app/models/ci/pipeline.rb], when: 'always' }]],
+ [[{ changes: %w[spec/**/*.rb], when: 'never' }, { changes: %w[spec/**/*.rb], when: 'always' }]],
+ [[{ changes: %w[*.yml], when: 'never' }, { changes: %w[*.yml], when: 'always' }]],
+ [[{ changes: %w[.*.yml], when: 'never' }, { changes: %w[.*.yml], when: 'always' }]],
+ [[{ changes: %w[**/*], when: 'never' }, { changes: %w[**/*], when: 'always' }]],
+ [[{ changes: %w[*/**/*.rb *.yml], when: 'never' }, { changes: %w[*/**/*.rb *.yml], when: 'always' }]],
+ [[{ changes: %w[.*.yml **/*], when: 'never' }, { changes: %w[.*.yml **/*], when: 'always' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
+ end
+ end
+
+ context 'with an explicit `when: always`' do
+ where(:rule_set) do
+ [
+ [[{ changes: %w[*/**/*.rb], when: 'always' }, { changes: %w[*/**/*.rb], when: 'never' }]],
+ [[{ changes: %w[app/models/ci/pipeline.rb], when: 'always' }, { changes: %w[app/models/ci/pipeline.rb], when: 'never' }]],
+ [[{ changes: %w[spec/**/*.rb], when: 'always' }, { changes: %w[spec/**/*.rb], when: 'never' }]],
+ [[{ changes: %w[*.yml], when: 'always' }, { changes: %w[*.yml], when: 'never' }]],
+ [[{ changes: %w[.*.yml], when: 'always' }, { changes: %w[.*.yml], when: 'never' }]],
+ [[{ changes: %w[**/*], when: 'always' }, { changes: %w[**/*], when: 'never' }]],
+ [[{ changes: %w[*/**/*.rb *.yml], when: 'always' }, { changes: %w[*/**/*.rb *.yml], when: 'never' }]],
+ [[{ changes: %w[.*.yml **/*], when: 'always' }, { changes: %w[.*.yml **/*], when: 'never' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'always')
+ end
+ end
+ end
+
+ context 'without an explicit when: value' do
+ where(:rule_set) do
+ [
+ [[{ changes: %w[*/**/*.rb] }]],
+ [[{ changes: %w[app/models/ci/pipeline.rb] }]],
+ [[{ changes: %w[spec/**/*.rb] }]],
+ [[{ changes: %w[*.yml] }]],
+ [[{ changes: %w[.*.yml] }]],
+ [[{ changes: %w[**/*] }]],
+ [[{ changes: %w[*/**/*.rb *.yml] }]],
+ [[{ changes: %w[.*.yml **/*] }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'on_success')
+ end
+ end
+ end
+ end
+
+ context 'with no matching rule' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE != null', when: 'never' }]],
+ [[{ if: '$VARIABLE != null', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]],
+ [[{ if: '$VARIABLE == "the wrong value"', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]],
+ [[{ if: '$VARIABLE != null', when: 'always' }]],
+ [[{ if: '$VARIABLE != null', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]],
+ [[{ if: '$VARIABLE == "the wrong value"', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]],
+ [[{ if: '$VARIABLE != null' }]],
+ [[{ if: '$VARIABLE != null' }, { if: '$VARIABLE != null' }]],
+ [[{ if: '$VARIABLE == "the wrong value"' }, { if: '$VARIABLE != null' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
+ end
+ end
+
+ context 'with no rules' do
+ let(:rule_set) { [] }
+
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
+ end
+ end
+ end
+
+ describe 'applying needs: dependency' do
+ subject { seed_build }
+
+ let(:needs_count) { 1 }
+
+ let(:needs_attributes) do
+ Array.new(needs_count, name: 'build')
+ end
+
+ let(:attributes) do
+ {
+ name: 'rspec',
+ needs_attributes: needs_attributes
+ }
+ end
+
+ context 'when build job is not present in prior stages' do
+ it "is included" do
+ is_expected.to be_included
+ end
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: needs 'build'")
+ end
+ end
+
+ context 'when build job is part of prior stages' do
+ let(:stage_attributes) do
+ {
+ name: 'build',
+ index: 0,
+ builds: [{ name: 'build' }]
+ }
+ end
+
+ let(:stage_seed) do
+ Gitlab::Ci::Pipeline::Seed::Stage.new(pipeline, stage_attributes, [])
+ end
+
+ let(:previous_stages) { [stage_seed] }
+
+ it "is included" do
+ is_expected.to be_included
+ end
+
+ it "does not have errors" do
+ expect(subject.errors).to be_empty
+ end
+ end
+
+ context 'when lower limit of needs is reached' do
+ before do
+ stub_feature_flags(ci_dag_limit_needs: true)
+ end
+
+ let(:needs_count) { described_class::LOW_NEEDS_LIMIT + 1 }
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: one job can only need 5 others, but you have listed 6. See needs keyword documentation for more details")
+ end
+ end
+
+ context 'when upper limit of needs is reached' do
+ before do
+ stub_feature_flags(ci_dag_limit_needs: false)
+ end
+
+ let(:needs_count) { described_class::HARD_NEEDS_LIMIT + 1 }
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: one job can only need 50 others, but you have listed 51. See needs keyword documentation for more details")
+ end
+ end
+ end
+
+ describe '#scoped_variables_hash' do
+ subject { seed_build.scoped_variables_hash }
+
+ it { is_expected.to eq(seed_build.to_resource.scoped_variables_hash) }
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
index 493ca3cd7b5..a13335f63d5 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Seed::Stage do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+ let(:previous_stages) { [] }
let(:attributes) do
{ name: 'test',
@@ -13,7 +16,7 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do
end
subject do
- described_class.new(pipeline, attributes)
+ described_class.new(pipeline, attributes, previous_stages)
end
describe '#size' do
@@ -107,6 +110,27 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do
end
end
+ describe '#seeds_names' do
+ it 'returns all job names' do
+ expect(subject.seeds_names).to contain_exactly(
+ 'rspec', 'spinach')
+ end
+
+ it 'returns a set' do
+ expect(subject.seeds_names).to be_a(Set)
+ end
+ end
+
+ describe '#seeds_errors' do
+ it 'returns all errors from seeds' do
+ expect(subject.seeds.first)
+ .to receive(:errors) { ["build error"] }
+
+ expect(subject.errors).to contain_exactly(
+ "build error")
+ end
+ end
+
describe '#to_resource' do
it 'builds a valid stage object with all builds' do
subject.to_resource.save!
diff --git a/spec/lib/gitlab/ci/reports/test_case_spec.rb b/spec/lib/gitlab/ci/reports/test_case_spec.rb
index 6932f79f0ce..20c489ee94c 100644
--- a/spec/lib/gitlab/ci/reports/test_case_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_case_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Reports::TestCase do
diff --git a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
index 71c61e0345f..48eef0643b2 100644
--- a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Reports::TestReportsComparer do
@@ -74,17 +76,11 @@ describe Gitlab::Ci::Reports::TestReportsComparer do
subject { comparer.resolved_count }
context 'when there is a resolved test case in head suites' do
- let(:create_test_case_java_resolved) do
- create_test_case_java_failed.tap do |test_case|
- test_case.instance_variable_set("@status", Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS)
- end
- end
-
before do
base_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
base_reports.get_suite('junit').add_test_case(create_test_case_java_failed)
head_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
- head_reports.get_suite('junit').add_test_case(create_test_case_java_resolved)
+ head_reports.get_suite('junit').add_test_case(create_test_case_java_success)
end
it 'returns the correct count' do
diff --git a/spec/lib/gitlab/ci/reports/test_reports_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_spec.rb
index 74ff134b239..0b5d05bada3 100644
--- a/spec/lib/gitlab/ci/reports/test_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_reports_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Reports::TestReports do
diff --git a/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
index 6ab16e5518d..cf4690bb334 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Reports::TestSuiteComparer do
@@ -10,12 +12,6 @@ describe Gitlab::Ci::Reports::TestSuiteComparer do
let(:test_case_success) { create_test_case_rspec_success }
let(:test_case_failed) { create_test_case_rspec_failed }
- let(:test_case_resolved) do
- create_test_case_rspec_failed.tap do |test_case|
- test_case.instance_variable_set("@status", Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS)
- end
- end
-
describe '#new_failures' do
subject { comparer.new_failures }
@@ -44,7 +40,7 @@ describe Gitlab::Ci::Reports::TestSuiteComparer do
context 'when head sutie has a success test case which failed in base' do
before do
base_suite.add_test_case(test_case_failed)
- head_suite.add_test_case(test_case_resolved)
+ head_suite.add_test_case(test_case_success)
end
it 'does not return the failed test case' do
@@ -81,7 +77,7 @@ describe Gitlab::Ci::Reports::TestSuiteComparer do
context 'when head sutie has a success test case which failed in base' do
before do
base_suite.add_test_case(test_case_failed)
- head_suite.add_test_case(test_case_resolved)
+ head_suite.add_test_case(test_case_success)
end
it 'does not return the failed test case' do
@@ -126,11 +122,11 @@ describe Gitlab::Ci::Reports::TestSuiteComparer do
context 'when head sutie has a success test case which failed in base' do
before do
base_suite.add_test_case(test_case_failed)
- head_suite.add_test_case(test_case_resolved)
+ head_suite.add_test_case(test_case_success)
end
it 'does not return the resolved test case' do
- is_expected.to eq([test_case_resolved])
+ is_expected.to eq([test_case_success])
end
it 'returns the correct resolved count' do
@@ -156,13 +152,8 @@ describe Gitlab::Ci::Reports::TestSuiteComparer do
context 'when there are a new failure and an existing failure' do
let(:test_case_1_success) { create_test_case_rspec_success }
- let(:test_case_2_failed) { create_test_case_rspec_failed }
-
- let(:test_case_1_failed) do
- create_test_case_rspec_success.tap do |test_case|
- test_case.instance_variable_set("@status", Gitlab::Ci::Reports::TestCase::STATUS_FAILED)
- end
- end
+ let(:test_case_1_failed) { create_test_case_rspec_failed }
+ let(:test_case_2_failed) { create_test_case_rspec_failed('case2') }
before do
base_suite.add_test_case(test_case_1_success)
diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
index cd34dbaf62f..8646db43bc8 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Reports::TestSuite do
diff --git a/spec/lib/gitlab/ci/status/build/action_spec.rb b/spec/lib/gitlab/ci/status/build/action_spec.rb
index bdec582b57b..3aae7e18d6d 100644
--- a/spec/lib/gitlab/ci/status/build/action_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/action_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Action do
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
index 78d6fa65b5a..3841dae91c7 100644
--- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Cancelable do
diff --git a/spec/lib/gitlab/ci/status/build/canceled_spec.rb b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
index c6b5cc68770..4b43c78f1a7 100644
--- a/spec/lib/gitlab/ci/status/build/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Canceled do
diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb
index ca3c66f0152..5114540708f 100644
--- a/spec/lib/gitlab/ci/status/build/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/common_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Common do
diff --git a/spec/lib/gitlab/ci/status/build/created_spec.rb b/spec/lib/gitlab/ci/status/build/created_spec.rb
index 8bdfe6ef7a2..6e3aa442810 100644
--- a/spec/lib/gitlab/ci/status/build/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/created_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Created do
diff --git a/spec/lib/gitlab/ci/status/build/erased_spec.rb b/spec/lib/gitlab/ci/status/build/erased_spec.rb
index 0acd271e375..af9c296da0c 100644
--- a/spec/lib/gitlab/ci/status/build/erased_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/erased_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Erased do
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index b6231510b91..de489fa4664 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Factory do
diff --git a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
index 2a5915d75d0..01500689619 100644
--- a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::FailedAllowed do
diff --git a/spec/lib/gitlab/ci/status/build/failed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_spec.rb
index e424270f7c5..78f5214ca81 100644
--- a/spec/lib/gitlab/ci/status/build/failed_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Failed do
diff --git a/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb b/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb
index a4854bdc6b9..19a3c82eac7 100644
--- a/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe Gitlab::Ci::Status::Build::FailedUnmetPrerequisites do
diff --git a/spec/lib/gitlab/ci/status/build/manual_spec.rb b/spec/lib/gitlab/ci/status/build/manual_spec.rb
index 6386296f992..bffe2c10d12 100644
--- a/spec/lib/gitlab/ci/status/build/manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/manual_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Manual do
diff --git a/spec/lib/gitlab/ci/status/build/pending_spec.rb b/spec/lib/gitlab/ci/status/build/pending_spec.rb
index 4cf70828e53..64d57954c15 100644
--- a/spec/lib/gitlab/ci/status/build/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/pending_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Pending do
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index 02f8c4c114b..bb12a900b55 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Play do
diff --git a/spec/lib/gitlab/ci/status/build/retried_spec.rb b/spec/lib/gitlab/ci/status/build/retried_spec.rb
index 76c2fb01e3f..fce497d40a1 100644
--- a/spec/lib/gitlab/ci/status/build/retried_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retried_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Retried do
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
index 84d98588f2d..5b0ae315927 100644
--- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Retryable do
diff --git a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
index 68b87fea75d..8f87da10815 100644
--- a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Scheduled do
diff --git a/spec/lib/gitlab/ci/status/build/skipped_spec.rb b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
index 46f6933025a..7ce5142da78 100644
--- a/spec/lib/gitlab/ci/status/build/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Skipped do
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
index 5b7534c96c1..d3e98400a53 100644
--- a/spec/lib/gitlab/ci/status/build/stop_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Stop do
diff --git a/spec/lib/gitlab/ci/status/build/unschedule_spec.rb b/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
index ed046d66ca5..c18fc3252b4 100644
--- a/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Unschedule do
diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb
index dc74d7e28c5..6cfcea4fdde 100644
--- a/spec/lib/gitlab/ci/status/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/canceled_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Canceled do
diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb
index ce4333f2aca..aeb41e9cfc3 100644
--- a/spec/lib/gitlab/ci/status/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/created_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Created do
diff --git a/spec/lib/gitlab/ci/status/extended_spec.rb b/spec/lib/gitlab/ci/status/extended_spec.rb
index 6eacb07078b..8accfc4a2f9 100644
--- a/spec/lib/gitlab/ci/status/extended_spec.rb
+++ b/spec/lib/gitlab/ci/status/extended_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Extended do
diff --git a/spec/lib/gitlab/ci/status/external/common_spec.rb b/spec/lib/gitlab/ci/status/external/common_spec.rb
index 0d02c371a92..983522fa2d6 100644
--- a/spec/lib/gitlab/ci/status/external/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/external/common_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::External::Common do
diff --git a/spec/lib/gitlab/ci/status/external/factory_spec.rb b/spec/lib/gitlab/ci/status/external/factory_spec.rb
index 529d02a3e39..3b90fb60cca 100644
--- a/spec/lib/gitlab/ci/status/external/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/external/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::External::Factory do
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
index bbf9c7c83a3..b51c0bec47e 100644
--- a/spec/lib/gitlab/ci/status/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Factory do
diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb
index a4a92117c7f..5c7393fc8cf 100644
--- a/spec/lib/gitlab/ci/status/failed_spec.rb
+++ b/spec/lib/gitlab/ci/status/failed_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Failed do
diff --git a/spec/lib/gitlab/ci/status/group/common_spec.rb b/spec/lib/gitlab/ci/status/group/common_spec.rb
index c0ca05881f5..35fff30ea9d 100644
--- a/spec/lib/gitlab/ci/status/group/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/group/common_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Group::Common do
diff --git a/spec/lib/gitlab/ci/status/group/factory_spec.rb b/spec/lib/gitlab/ci/status/group/factory_spec.rb
index 0cd83123938..be76a1d5a65 100644
--- a/spec/lib/gitlab/ci/status/group/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/group/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Group::Factory do
diff --git a/spec/lib/gitlab/ci/status/manual_spec.rb b/spec/lib/gitlab/ci/status/manual_spec.rb
index 0463f2e1aff..0839452ec22 100644
--- a/spec/lib/gitlab/ci/status/manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/manual_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Manual do
diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb
index 0e25358dd8a..5f830e5bb56 100644
--- a/spec/lib/gitlab/ci/status/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/pending_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Pending do
diff --git a/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb b/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
index 1a2b952d374..876ba712d05 100644
--- a/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Blocked do
diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
index 57df8325635..d3251d138b8 100644
--- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Common do
diff --git a/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
index f89712d2b03..90b797965b3 100644
--- a/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Delayed do
diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
index 466087a0e31..8a36cd1b658 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Factory do
diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb
index 9c9d431bb5d..75ff58c5c98 100644
--- a/spec/lib/gitlab/ci/status/running_spec.rb
+++ b/spec/lib/gitlab/ci/status/running_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Running do
diff --git a/spec/lib/gitlab/ci/status/scheduled_spec.rb b/spec/lib/gitlab/ci/status/scheduled_spec.rb
index b8ca3caa1f7..a0374e1a87b 100644
--- a/spec/lib/gitlab/ci/status/scheduled_spec.rb
+++ b/spec/lib/gitlab/ci/status/scheduled_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Scheduled do
diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb
index 63694ca0ea6..7f68d4a2fa9 100644
--- a/spec/lib/gitlab/ci/status/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/skipped_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Skipped do
diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb
index bb2d0a2c75c..26ff0e901fd 100644
--- a/spec/lib/gitlab/ci/status/stage/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Stage::Common do
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index 4b299170c87..8f5b1ff62a5 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Stage::Factory do
diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb
index 2f67df71c4f..d4b3a9f12cc 100644
--- a/spec/lib/gitlab/ci/status/success_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Success do
diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb
index 9493b1d89f2..af952011e21 100644
--- a/spec/lib/gitlab/ci/status/success_warning_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::SuccessWarning do
diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
index 546a9e7d0cc..e0077a5280a 100644
--- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
+++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
@@ -312,7 +314,7 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
end
context 'when utf-8 is being used' do
- let(:sample_trace_raw) { sample_trace_raw_utf8.force_encoding(Encoding::BINARY) }
+ let(:sample_trace_raw) { sample_trace_raw_utf8.dup.force_encoding(Encoding::BINARY) }
let(:sample_trace_raw_utf8) { "😺\n😺\n😺\n😺" }
before do
@@ -442,5 +444,15 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
expect(Ci::BuildTraceChunk.where(build: build).count).to eq(0)
end
+
+ context 'when the job does not have archived trace' do
+ it 'leaves a message in sidekiq log' do
+ expect(Sidekiq.logger).to receive(:warn).with(
+ message: 'The job does not have archived trace but going to be destroyed.',
+ job_id: build.id).and_call_original
+
+ subject
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/trace/section_parser_spec.rb b/spec/lib/gitlab/ci/trace/section_parser_spec.rb
index ca53ff87c6f..5e2efe083be 100644
--- a/spec/lib/gitlab/ci/trace/section_parser_spec.rb
+++ b/spec/lib/gitlab/ci/trace/section_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Trace::SectionParser do
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 35250632e86..af519f4bae6 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -65,9 +65,9 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
result = stream.html
expect(result).to eq(
- "<span class=\"\">ヾ(´༎ຶД༎ຶ`)ノ<br/><span class=\"\"></span></span>"\
- "<span class=\"term-fg-green\">許功蓋</span><span class=\"\"><br/>"\
- "<span class=\"\"></span></span>")
+ "<span>ヾ(´༎ຶД༎ຶ`)ノ<br/></span>"\
+ "<span class=\"term-fg-green\">許功蓋</span>"\
+ "<span><br/></span>")
expect(result.encoding).to eq(Encoding.default_external)
end
end
@@ -253,7 +253,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
it 'returns html content with state' do
result = stream.html_with_state
- expect(result.html).to eq("<span class=\"\">1234</span>")
+ expect(result.html).to eq("<span>1234</span>")
end
context 'follow-up state' do
@@ -269,7 +269,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
result = stream.html_with_state(last_result.state)
expect(result.append).to be_truthy
- expect(result.html).to eq("<span class=\"\">5678</span>")
+ expect(result.html).to eq("<span>5678</span>")
end
end
end
@@ -305,13 +305,11 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
describe '#html' do
shared_examples_for 'htmls' do
it "returns html" do
- expect(stream.html).to eq(
- "<span class=\"\">12<br/><span class=\"\">34<br/>"\
- "<span class=\"\">56</span></span></span>")
+ expect(stream.html).to eq("<span>12<br/>34<br/>56</span>")
end
it "returns html for last line only" do
- expect(stream.html(last_lines: 1)).to eq("<span class=\"\">56</span>")
+ expect(stream.html(last_lines: 1)).to eq("<span>56</span>")
end
end
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index d6510649dba..f7bc5686b68 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index 613814df23f..1bdca753cd3 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Variables::Collection::Item do
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
index 8e732d44d5d..59b9f7d4fb9 100644
--- a/spec/lib/gitlab/ci/variables/collection_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Variables::Collection do
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 789d6813d5b..91c559dcd9b 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
module Gitlab
@@ -123,9 +125,11 @@ module Gitlab
describe 'delayed job entry' do
context 'when delayed is defined' do
let(:config) do
- YAML.dump(rspec: { script: 'rollout 10%',
- when: 'delayed',
- start_in: '1 day' })
+ YAML.dump(rspec: {
+ script: 'rollout 10%',
+ when: 'delayed',
+ start_in: '1 day'
+ })
end
it 'has the attributes' do
@@ -724,12 +728,12 @@ module Gitlab
end
end
- describe "When" do
- %w(on_success on_failure always).each do |when_state|
- it "returns #{when_state} when defined" do
+ describe 'when:' do
+ (Gitlab::Ci::Config::Entry::Job::ALLOWED_WHEN - %w[delayed]).each do |when_state|
+ it "#{when_state} creates one build and sets when:" do
config = YAML.dump({
- rspec: { script: "rspec", when: when_state }
- })
+ rspec: { script: 'rspec', when: when_state }
+ })
config_processor = Gitlab::Ci::YamlProcessor.new(config)
builds = config_processor.stage_builds_attributes("test")
@@ -738,6 +742,35 @@ module Gitlab
expect(builds.first[:when]).to eq(when_state)
end
end
+
+ context 'delayed' do
+ context 'with start_in' do
+ it 'creates one build and sets when:' do
+ config = YAML.dump({
+ rspec: { script: 'rspec', when: 'delayed', start_in: '1 hour' }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config)
+ builds = config_processor.stage_builds_attributes("test")
+
+ expect(builds.size).to eq(1)
+ expect(builds.first[:when]).to eq('delayed')
+ expect(builds.first[:options][:start_in]).to eq('1 hour')
+ end
+ end
+
+ context 'without start_in' do
+ it 'raises an error' do
+ config = YAML.dump({
+ rspec: { script: 'rspec', when: 'delayed' }
+ })
+
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(YamlProcessor::ValidationError, /start in should be a duration/)
+ end
+ end
+ end
end
describe 'Parallel' do
@@ -1083,6 +1116,175 @@ module Gitlab
it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'test1 job: dependency deploy is not defined in prior stages') }
end
+
+ context 'when a job depends on another job that references a not-yet defined stage' do
+ let(:config) do
+ {
+ "stages" => [
+ "version"
+ ],
+ "version" => {
+ "stage" => "version",
+ "dependencies" => ["release:components:versioning"],
+ "script" => ["./versioning/versioning"]
+ },
+ ".release_go" => {
+ "stage" => "build",
+ "script" => ["cd versioning"]
+ },
+ "release:components:versioning" => {
+ "stage" => "build",
+ "script" => ["cd versioning"]
+ }
+ }
+ end
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, /is not defined in prior stages/) }
+ end
+ end
+
+ describe "Needs" do
+ let(:needs) { }
+ let(:dependencies) { }
+
+ let(:config) do
+ {
+ build1: { stage: 'build', script: 'test' },
+ build2: { stage: 'build', script: 'test' },
+ test1: { stage: 'test', script: 'test', needs: needs, dependencies: dependencies },
+ test2: { stage: 'test', script: 'test' },
+ deploy: { stage: 'test', script: 'test' }
+ }
+ end
+
+ subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
+
+ context 'no needs' do
+ it { expect { subject }.not_to raise_error }
+ end
+
+ context 'needs two builds' do
+ let(:needs) { %w(build1 build2) }
+
+ it "does create jobs with valid specification" do
+ expect(subject.builds.size).to eq(5)
+ expect(subject.builds[0]).to eq(
+ stage: "build",
+ stage_idx: 0,
+ name: "build1",
+ options: {
+ script: ["test"]
+ },
+ when: "on_success",
+ allow_failure: false,
+ yaml_variables: []
+ )
+ expect(subject.builds[2]).to eq(
+ stage: "test",
+ stage_idx: 1,
+ name: "test1",
+ options: {
+ script: ["test"],
+ # This does not make sense, there is a follow-up:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/65569
+ bridge_needs: %w[build1 build2]
+ },
+ needs_attributes: [
+ { name: "build1" },
+ { name: "build2" }
+ ],
+ when: "on_success",
+ allow_failure: false,
+ yaml_variables: []
+ )
+ end
+ end
+
+ context 'needs two builds defined as symbols' do
+ let(:needs) { [:build1, :build2] }
+
+ it { expect { subject }.not_to raise_error }
+ end
+
+ context 'undefined need' do
+ let(:needs) { ['undefined'] }
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'test1 job: undefined need: undefined') }
+ end
+
+ context 'needs to deploy' do
+ let(:needs) { ['deploy'] }
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'test1 job: need deploy is not defined in prior stages') }
+ end
+
+ context 'needs and dependencies that are mismatching' do
+ let(:needs) { %w(build1) }
+ let(:dependencies) { %w(build2) }
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:test1 dependencies the build2 should be part of needs') }
+ end
+ end
+
+ describe 'rules' do
+ subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
+
+ let(:config) do
+ {
+ var_default: { stage: 'build', script: 'test', rules: [{ if: '$VAR == null' }] },
+ var_when: { stage: 'build', script: 'test', rules: [{ if: '$VAR == null', when: 'always' }] },
+ var_and_changes: { stage: 'build', script: 'test', rules: [{ if: '$VAR == null', changes: %w[README], when: 'always' }] },
+ changes_not_var: { stage: 'test', script: 'test', rules: [{ if: '$VAR != null', changes: %w[README] }] },
+ var_not_changes: { stage: 'test', script: 'test', rules: [{ if: '$VAR == null', changes: %w[other/file.rb], when: 'always' }] },
+ nothing: { stage: 'test', script: 'test', rules: [{ when: 'manual' }] },
+ var_never: { stage: 'deploy', script: 'test', rules: [{ if: '$VAR == null', when: 'never' }] },
+ var_delayed: { stage: 'deploy', script: 'test', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] },
+ two_rules: { stage: 'deploy', script: 'test', rules: [{ if: '$VAR == null', when: 'on_success' }, { changes: %w[README], when: 'manual' }] }
+ }
+ end
+
+ it 'raises no exceptions' do
+ expect { subject }.not_to raise_error
+ end
+
+ it 'returns all jobs regardless of their inclusion' do
+ expect(subject.builds.count).to eq(config.keys.count)
+ end
+
+ context 'used with job-level when' do
+ let(:config) do
+ {
+ var_default: {
+ stage: 'build',
+ script: 'test',
+ when: 'always',
+ rules: [{ if: '$VAR == null' }]
+ }
+ }
+ end
+
+ it 'raises a ValidationError' do
+ expect { subject }.to raise_error(YamlProcessor::ValidationError, /may not be used with `rules`: when/)
+ end
+ end
+
+ context 'used with job-level when:delayed' do
+ let(:config) do
+ {
+ var_default: {
+ stage: 'build',
+ script: 'test',
+ when: 'delayed',
+ start_in: '10 minutes',
+ rules: [{ if: '$VAR == null' }]
+ }
+ }
+ end
+
+ it 'raises a ValidationError' do
+ expect { subject }.to raise_error(YamlProcessor::ValidationError, /may not be used with `rules`: when, start_in/)
+ end
+ end
end
describe "Hidden jobs" do
@@ -1403,7 +1605,7 @@ module Gitlab
config = YAML.dump({ rspec: { script: "test", when: 1 } })
expect do
Gitlab::Ci::YamlProcessor.new(config)
- end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec when should be on_success, on_failure, always, manual or delayed")
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec when should be one of: #{Gitlab::Ci::Config::Entry::Job::ALLOWED_WHEN.join(', ')}")
end
it "returns errors if job artifacts:name is not an a string" do
diff --git a/spec/lib/gitlab/ci_access_spec.rb b/spec/lib/gitlab/ci_access_spec.rb
index 75b90e76083..3c68d209eb6 100644
--- a/spec/lib/gitlab/ci_access_spec.rb
+++ b/spec/lib/gitlab/ci_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CiAccess do
diff --git a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
new file mode 100644
index 00000000000..4d8edfeac80
--- /dev/null
+++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Cleanup::OrphanJobArtifactFilesBatch do
+ let(:batch_size) { 10 }
+ let(:dry_run) { true }
+
+ subject(:batch) { described_class.new(batch_size: batch_size, dry_run: dry_run) }
+
+ context 'no dry run' do
+ let(:dry_run) { false }
+
+ it 'deletes only orphan job artifacts from disk' do
+ job_artifact = create(:ci_job_artifact, :archive)
+ orphan_artifact = create(:ci_job_artifact, :archive)
+ batch << artifact_path(job_artifact)
+ batch << artifact_path(orphan_artifact)
+ orphan_artifact.delete
+
+ batch.clean!
+
+ expect(batch.artifact_files.count).to eq(2)
+ expect(batch.lost_and_found.count).to eq(1)
+ expect(batch.lost_and_found.first.artifact_id).to eq(orphan_artifact.id)
+ end
+
+ it 'does not mix up job ID and artifact ID' do
+ # take maximum ID of both tables to avoid any collision
+ max_id = [Ci::Build.maximum(:id), Ci::JobArtifact.maximum(:id)].compact.max.to_i
+ job_a = create(:ci_build, id: max_id + 1)
+ job_b = create(:ci_build, id: max_id + 2)
+ # reuse the build IDs for the job artifact IDs, but swap them
+ job_artifact_b = create(:ci_job_artifact, :archive, job: job_b, id: max_id + 1)
+ job_artifact_a = create(:ci_job_artifact, :archive, job: job_a, id: max_id + 2)
+
+ batch << artifact_path(job_artifact_a)
+ batch << artifact_path(job_artifact_b)
+
+ job_artifact_b.delete
+
+ batch.clean!
+
+ expect(File.exist?(job_artifact_a.file.path)).to be_truthy
+ expect(File.exist?(job_artifact_b.file.path)).to be_falsey
+ end
+ end
+
+ context 'with dry run' do
+ it 'does not remove files' do
+ job_artifact = create(:ci_job_artifact, :archive)
+ batch << job_artifact.file.path
+ job_artifact.delete
+
+ expect(batch).not_to receive(:remove_file!)
+
+ batch.clean!
+
+ expect(File.exist?(job_artifact.file.path)).to be_truthy
+ end
+ end
+
+ def artifact_path(job_artifact)
+ Pathname.new(job_artifact.file.path).parent.to_s
+ end
+end
diff --git a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
new file mode 100644
index 00000000000..974cc2c4660
--- /dev/null
+++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Cleanup::OrphanJobArtifactFiles do
+ let(:null_logger) { Logger.new('/dev/null') }
+ subject(:cleanup) { described_class.new(logger: null_logger) }
+
+ before do
+ allow(null_logger).to receive(:info)
+ end
+
+ it 'passes on dry_run' do
+ expect(Gitlab::Cleanup::OrphanJobArtifactFilesBatch)
+ .to receive(:new)
+ .with(dry_run: false, batch_size: anything, logger: anything)
+ .at_least(:once)
+ .and_call_original
+
+ described_class.new(dry_run: false).run!
+ end
+
+ it 'errors when invalid niceness is given' do
+ cleanup = described_class.new(logger: null_logger, niceness: 'FooBar')
+
+ expect(null_logger).to receive(:error).with(/FooBar/)
+
+ cleanup.run!
+ end
+
+ it 'finds artifacts on disk' do
+ artifact = create(:ci_job_artifact, :archive)
+
+ expect(cleanup).to receive(:find_artifacts).and_yield(artifact.file.path)
+ cleanup.run!
+ end
+
+ it 'stops when limit is reached' do
+ cleanup = described_class.new(limit: 1)
+
+ mock_artifacts_found(cleanup, 'tmp/foo/bar/1', 'tmp/foo/bar/2')
+
+ cleanup.run!
+
+ expect(cleanup.total_found).to eq(1)
+ end
+
+ it 'cleans even if batch is not full' do
+ mock_artifacts_found(cleanup, 'tmp/foo/bar/1')
+
+ expect(cleanup).to receive(:clean_batch!).and_call_original
+ cleanup.run!
+ end
+
+ it 'cleans in batches' do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+ mock_artifacts_found(cleanup, 'tmp/foo/bar/1', 'tmp/foo/bar/2', 'tmp/foo/bar/3')
+
+ expect(cleanup).to receive(:clean_batch!).twice.and_call_original
+ cleanup.run!
+ end
+
+ def mock_artifacts_found(cleanup, *files)
+ mock = allow(cleanup).to receive(:find_artifacts)
+
+ files.each { |file| mock.and_yield(file) }
+ end
+end
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 44568f2a653..fdd01f58c9d 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ClosingIssueExtractor do
diff --git a/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
index 180520b27e7..6ed9dda08d7 100644
--- a/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
+++ b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
@@ -17,9 +17,7 @@ describe Gitlab::Cluster::PumaWorkerKillerObserver do
it 'increments timeout counter' do
worker = double(index: 0)
- expect(counter)
- .to receive(:increment)
- .with({ worker: 'worker_0' })
+ expect(counter).to receive(:increment)
subject.callback.call(worker)
end
diff --git a/spec/lib/gitlab/color_schemes_spec.rb b/spec/lib/gitlab/color_schemes_spec.rb
index c7be45dbcd3..ba5573f6901 100644
--- a/spec/lib/gitlab/color_schemes_spec.rb
+++ b/spec/lib/gitlab/color_schemes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ColorSchemes do
diff --git a/spec/lib/gitlab/config/entry/attributable_spec.rb b/spec/lib/gitlab/config/entry/attributable_spec.rb
index abb4fff3ad7..6b548d5c4a8 100644
--- a/spec/lib/gitlab/config/entry/attributable_spec.rb
+++ b/spec/lib/gitlab/config/entry/attributable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Attributable do
@@ -23,7 +25,9 @@ describe Gitlab::Config::Entry::Attributable do
end
it 'returns the value of config' do
+ expect(instance).to have_name
expect(instance.name).to eq 'some name'
+ expect(instance).to have_test
expect(instance.test).to eq 'some test'
end
@@ -40,6 +44,7 @@ describe Gitlab::Config::Entry::Attributable do
end
it 'returns nil' do
+ expect(instance).not_to have_test
expect(instance.test).to be_nil
end
end
diff --git a/spec/lib/gitlab/config/entry/boolean_spec.rb b/spec/lib/gitlab/config/entry/boolean_spec.rb
index 1b7a3f850ec..0b8b720dd80 100644
--- a/spec/lib/gitlab/config/entry/boolean_spec.rb
+++ b/spec/lib/gitlab/config/entry/boolean_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Boolean do
diff --git a/spec/lib/gitlab/config/entry/configurable_spec.rb b/spec/lib/gitlab/config/entry/configurable_spec.rb
index 62dbf20ddf8..8c3a4490d08 100644
--- a/spec/lib/gitlab/config/entry/configurable_spec.rb
+++ b/spec/lib/gitlab/config/entry/configurable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Configurable do
diff --git a/spec/lib/gitlab/config/entry/factory_spec.rb b/spec/lib/gitlab/config/entry/factory_spec.rb
index 42f8be8e141..a614ef56a78 100644
--- a/spec/lib/gitlab/config/entry/factory_spec.rb
+++ b/spec/lib/gitlab/config/entry/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Factory do
diff --git a/spec/lib/gitlab/config/entry/simplifiable_spec.rb b/spec/lib/gitlab/config/entry/simplifiable_spec.rb
index bc8387ada67..65e18fe3f10 100644
--- a/spec/lib/gitlab/config/entry/simplifiable_spec.rb
+++ b/spec/lib/gitlab/config/entry/simplifiable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Simplifiable do
diff --git a/spec/lib/gitlab/config/entry/undefined_spec.rb b/spec/lib/gitlab/config/entry/undefined_spec.rb
index 48f9d276c95..83c3a6aec72 100644
--- a/spec/lib/gitlab/config/entry/undefined_spec.rb
+++ b/spec/lib/gitlab/config/entry/undefined_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Undefined do
diff --git a/spec/lib/gitlab/config/entry/unspecified_spec.rb b/spec/lib/gitlab/config/entry/unspecified_spec.rb
index 64421824a12..32c52594ecf 100644
--- a/spec/lib/gitlab/config/entry/unspecified_spec.rb
+++ b/spec/lib/gitlab/config/entry/unspecified_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Unspecified do
diff --git a/spec/lib/gitlab/config/entry/validatable_spec.rb b/spec/lib/gitlab/config/entry/validatable_spec.rb
index 5a8f9766d23..925db3594ba 100644
--- a/spec/lib/gitlab/config/entry/validatable_spec.rb
+++ b/spec/lib/gitlab/config/entry/validatable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Validatable do
diff --git a/spec/lib/gitlab/config/entry/validator_spec.rb b/spec/lib/gitlab/config/entry/validator_spec.rb
index efa16c4265c..7bf350912df 100644
--- a/spec/lib/gitlab/config/entry/validator_spec.rb
+++ b/spec/lib/gitlab/config/entry/validator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Validator do
diff --git a/spec/lib/gitlab/config/loader/yaml_spec.rb b/spec/lib/gitlab/config/loader/yaml_spec.rb
index 44c9a3896a8..ccddf340c3d 100644
--- a/spec/lib/gitlab/config/loader/yaml_spec.rb
+++ b/spec/lib/gitlab/config/loader/yaml_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Loader::Yaml do
@@ -8,7 +10,7 @@ describe Gitlab::Config::Loader::Yaml do
describe '#valid?' do
it 'returns true' do
- expect(loader.valid?).to be true
+ expect(loader).to be_valid
end
end
@@ -24,7 +26,7 @@ describe Gitlab::Config::Loader::Yaml do
describe '#valid?' do
it 'returns false' do
- expect(loader.valid?).to be false
+ expect(loader).not_to be_valid
end
end
@@ -43,7 +45,10 @@ describe Gitlab::Config::Loader::Yaml do
describe '#initialize' do
it 'raises FormatError' do
- expect { loader }.to raise_error(Gitlab::Config::Loader::FormatError, 'Unknown alias: bad_alias')
+ expect { loader }.to raise_error(
+ Gitlab::Config::Loader::FormatError,
+ 'Unknown alias: bad_alias'
+ )
end
end
end
@@ -53,7 +58,68 @@ describe Gitlab::Config::Loader::Yaml do
describe '#valid?' do
it 'returns false' do
- expect(loader.valid?).to be false
+ expect(loader).not_to be_valid
+ end
+ end
+ end
+
+ # Prevent Billion Laughs attack: https://gitlab.com/gitlab-org/gitlab-ce/issues/56018
+ context 'when yaml size is too large' do
+ let(:yml) do
+ <<~YAML
+ a: &a ["lol","lol","lol","lol","lol","lol","lol","lol","lol"]
+ b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
+ c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
+ d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]
+ e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]
+ f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e]
+ g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f]
+ h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g]
+ i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]
+ YAML
+ end
+
+ describe '#valid?' do
+ it 'returns false' do
+ expect(loader).not_to be_valid
+ end
+
+ it 'returns true if "ci_yaml_limit_size" feature flag is disabled' do
+ stub_feature_flags(ci_yaml_limit_size: false)
+
+ expect(loader).to be_valid
+ end
+ end
+
+ describe '#load!' do
+ it 'raises FormatError' do
+ expect { loader.load! }.to raise_error(
+ Gitlab::Config::Loader::FormatError,
+ 'The parsed YAML is too big'
+ )
+ end
+ end
+ end
+
+ # Prevent Billion Laughs attack: https://gitlab.com/gitlab-org/gitlab-ce/issues/56018
+ context 'when yaml has cyclic data structure' do
+ let(:yml) do
+ <<~YAML
+ --- &1
+ - hi
+ - *1
+ YAML
+ end
+
+ describe '#valid?' do
+ it 'returns false' do
+ expect(loader.valid?).to be(false)
+ end
+ end
+
+ describe '#load!' do
+ it 'raises FormatError' do
+ expect { loader.load! }.to raise_error(Gitlab::Config::Loader::FormatError, 'The parsed YAML is too big')
end
end
end
diff --git a/spec/lib/gitlab/conflict/file_collection_spec.rb b/spec/lib/gitlab/conflict/file_collection_spec.rb
index c93912db411..f3cdb1a9e59 100644
--- a/spec/lib/gitlab/conflict/file_collection_spec.rb
+++ b/spec/lib/gitlab/conflict/file_collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Conflict::FileCollection do
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index a955ce54e85..adf5a232a75 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Conflict::File do
diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
new file mode 100644
index 00000000000..1d404915617
--- /dev/null
+++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ContentSecurityPolicy::ConfigLoader do
+ let(:policy) { ActionDispatch::ContentSecurityPolicy.new }
+ let(:csp_config) do
+ {
+ enabled: true,
+ report_only: false,
+ directives: {
+ base_uri: 'http://example.com',
+ child_src: "'self' https://child.example.com",
+ default_src: "'self' https://other.example.com",
+ script_src: "'self' https://script.exammple.com ",
+ worker_src: "data: https://worker.example.com",
+ report_uri: "http://example.com"
+ }
+ }
+ end
+
+ context '.default_settings_hash' do
+ it 'returns empty defaults' do
+ settings = described_class.default_settings_hash
+
+ expect(settings['enabled']).to be_falsey
+ expect(settings['report_only']).to be_falsey
+
+ described_class::DIRECTIVES.each do |directive|
+ expect(settings['directives'].has_key?(directive)).to be_truthy
+ expect(settings['directives'][directive]).to be_nil
+ end
+ end
+ end
+
+ context '#load' do
+ subject { described_class.new(csp_config[:directives]) }
+
+ def expected_config(directive)
+ csp_config[:directives][directive].split(' ').map(&:strip)
+ end
+
+ it 'sets the policy properly' do
+ subject.load(policy)
+
+ expect(policy.directives['base-uri']).to eq([csp_config[:directives][:base_uri]])
+ expect(policy.directives['default-src']).to eq(expected_config(:default_src))
+ expect(policy.directives['child-src']).to eq(expected_config(:child_src))
+ expect(policy.directives['worker-src']).to eq(expected_config(:worker_src))
+ expect(policy.directives['report-uri']).to eq(expected_config(:report_uri))
+ end
+
+ it 'ignores malformed policy statements' do
+ csp_config[:directives][:base_uri] = 123
+
+ subject.load(policy)
+
+ expect(policy.directives['base-uri']).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index 51e5bdc6307..1154f029a8d 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ContributionsCalendar do
diff --git a/spec/lib/gitlab/cross_project_access/check_collection_spec.rb b/spec/lib/gitlab/cross_project_access/check_collection_spec.rb
index a9e7575240e..1aa5480b670 100644
--- a/spec/lib/gitlab/cross_project_access/check_collection_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/check_collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CrossProjectAccess::CheckCollection do
diff --git a/spec/lib/gitlab/cross_project_access/check_info_spec.rb b/spec/lib/gitlab/cross_project_access/check_info_spec.rb
index ea7393a7006..7d2471b6327 100644
--- a/spec/lib/gitlab/cross_project_access/check_info_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/check_info_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CrossProjectAccess::CheckInfo do
diff --git a/spec/lib/gitlab/cross_project_access/class_methods_spec.rb b/spec/lib/gitlab/cross_project_access/class_methods_spec.rb
index 5349685e633..17d265542dd 100644
--- a/spec/lib/gitlab/cross_project_access/class_methods_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/class_methods_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CrossProjectAccess::ClassMethods do
diff --git a/spec/lib/gitlab/cross_project_access_spec.rb b/spec/lib/gitlab/cross_project_access_spec.rb
index 614b0473c7e..ce18d207413 100644
--- a/spec/lib/gitlab/cross_project_access_spec.rb
+++ b/spec/lib/gitlab/cross_project_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CrossProjectAccess do
diff --git a/spec/lib/gitlab/crypto_helper_spec.rb b/spec/lib/gitlab/crypto_helper_spec.rb
index 05cc6cf15de..71bbeccb17b 100644
--- a/spec/lib/gitlab/crypto_helper_spec.rb
+++ b/spec/lib/gitlab/crypto_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CryptoHelper do
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index 909dbffa38f..eced96a4c77 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CurrentSettings do
@@ -12,6 +14,16 @@ describe Gitlab::CurrentSettings do
end
end
+ describe '.expire_current_application_settings', :use_clean_rails_memory_store_caching, :request_store do
+ include_context 'with settings in cache'
+
+ it 'expires the cache' do
+ described_class.expire_current_application_settings
+
+ expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).not_to eq(0)
+ end
+ end
+
describe '#current_application_settings', :use_clean_rails_memory_store_caching do
it 'allows keys to be called directly' do
db_settings = create(:application_setting,
@@ -80,7 +92,7 @@ describe Gitlab::CurrentSettings do
# during the initialization phase of the test suite, so instead let's mock the internals of it
expect(ActiveRecord::Base.connection).not_to receive(:active?)
expect(ActiveRecord::Base.connection).not_to receive(:cached_table_exists?)
- expect(ActiveRecord::Migrator).not_to receive(:needs_migration?)
+ expect_any_instance_of(ActiveRecord::MigrationContext).not_to receive(:needs_migration?)
expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
end
end
@@ -109,7 +121,7 @@ describe Gitlab::CurrentSettings do
context 'with pending migrations' do
before do
- expect(ActiveRecord::Migrator).to receive(:needs_migration?).and_return(true)
+ expect_any_instance_of(ActiveRecord::MigrationContext).to receive(:needs_migration?).and_return(true)
end
shared_examples 'a non-persisted ApplicationSetting object' do
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
index 8b07da11c5d..3eea791d61a 100644
--- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CycleAnalytics::BaseEventFetcher do
@@ -9,12 +11,12 @@ describe Gitlab::CycleAnalytics::BaseEventFetcher do
let(:options) do
{ start_time_attrs: start_time_attrs,
end_time_attrs: end_time_attrs,
- from: 30.days.ago }
+ from: 30.days.ago,
+ project: project }
end
subject do
- described_class.new(project: project,
- stage: :issue,
+ described_class.new(stage: :issue,
options: options).fetch
end
diff --git a/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
index 0267e8c2f69..b521ad0c6ea 100644
--- a/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
index c738cc49c1f..dd1d9ac0f16 100644
--- a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
@@ -5,40 +7,114 @@ describe Gitlab::CycleAnalytics::CodeStage do
let(:stage_name) { :code }
let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:mr_1) { create(:merge_request, source_project: project, created_at: 15.minutes.ago) }
- let!(:mr_2) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'A') }
- let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
+ let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:mr_1) { create(:merge_request, source_project: project, created_at: 15.minutes.ago) }
+ let(:mr_2) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'A') }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
issue_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
issue_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B')
create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1)
create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2)
end
it_behaves_like 'base stage'
- describe '#median' do
+ describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
+ subject { stage.events }
+
it 'exposes merge requests that closes issues' do
- result = stage.events
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
+ end
+ end
+
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_2_1) { create(:merge_request, source_project: project_2, created_at: 15.minutes.ago) }
+ let(:mr_2_2) { create(:merge_request, source_project: project_3, created_at: 10.minutes.ago, source_branch: 'A') }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
+ issue_2_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ issue_2_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ create(:merge_requests_closing_issues, merge_request: mr_2_1, issue: issue_2_1)
+ create(:merge_requests_closing_issues, merge_request: mr_2_2, issue: issue_2_2)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title)
+ end
+ end
+
+ context 'when subgroup is given' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project_4) { create(:project, group: subgroup) }
+ let(:project_5) { create(:project, group: subgroup) }
+ let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
+ let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+ let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+ let(:mr_3_1) { create(:merge_request, source_project: project_4, created_at: 15.minutes.ago) }
+ let(:mr_3_2) { create(:merge_request, source_project: project_5, created_at: 10.minutes.ago, source_branch: 'A') }
+
+ before do
+ issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
+ issue_3_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ issue_3_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ create(:merge_requests_closing_issues, merge_request: mr_3_1, issue: issue_3_1)
+ create(:merge_requests_closing_issues, merge_request: mr_3_2, issue: issue_3_2)
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(4)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title, mr_3_1.title, mr_3_2.title)
+ end
- expect(result.count).to eq(2)
- expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
+ it 'exposes merge requests that close issues with full path for subgroup' do
+ expect(subject.count).to eq(4)
+ expect(subject.find { |event| event[:title] == mr_3_1.title }[:url]).to include("#{subgroup.full_path}")
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index f8b103c0fab..a163de07967 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'cycle analytics events' do
@@ -7,7 +9,7 @@ describe 'cycle analytics events' do
let!(:context) { create(:issue, project: project, created_at: 2.days.ago) }
let(:events) do
- CycleAnalytics.new(project, { from: from_date, current_user: user })[stage].events
+ CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user })[stage].events
end
before do
diff --git a/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb
new file mode 100644
index 00000000000..d5c2f7cc579
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::CycleAnalytics::GroupStageSummary do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :repository, namespace: group) }
+ let(:project_2) { create(:project, :repository, namespace: group) }
+ let(:from) { 1.day.ago }
+ let(:user) { create(:user, :admin) }
+
+ subject { described_class.new(group, options: { from: Time.now, current_user: user }).data }
+
+ describe "#new_issues" do
+ context 'with from date' do
+ before do
+ Timecop.freeze(5.days.ago) { create(:issue, project: project) }
+ Timecop.freeze(5.days.ago) { create(:issue, project: project_2) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project_2) }
+ end
+
+ it "finds the number of issues created after it" do
+ expect(subject.first[:value]).to eq(2)
+ end
+
+ context 'with subgroups' do
+ before do
+ Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project, namespace: create(:group, parent: group))) }
+ end
+
+ it "finds issues from them" do
+ expect(subject.first[:value]).to eq(3)
+ end
+ end
+
+ context 'with projects specified in options' do
+ before do
+ Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project, namespace: group)) }
+ end
+
+ subject { described_class.new(group, options: { from: Time.now, current_user: user, projects: [project.id, project_2.id] }).data }
+
+ it 'finds issues from those projects' do
+ expect(subject.first[:value]).to eq(2)
+ end
+ end
+ end
+
+ context 'with other projects' do
+ before do
+ Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project, namespace: create(:group))) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project_2) }
+ end
+
+ it "doesn't find issues from them" do
+ expect(subject.first[:value]).to eq(2)
+ end
+ end
+ end
+
+ describe "#deploys" do
+ context 'with from date' do
+ before do
+ Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) }
+ Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) }
+ Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project_2) }
+ Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project_2) }
+ end
+
+ it "finds the number of deploys made created after it" do
+ expect(subject.second[:value]).to eq(2)
+ end
+
+ context 'with subgroups' do
+ before do
+ Timecop.freeze(5.days.from_now) do
+ create(:deployment, :success, project: create(:project, :repository, namespace: create(:group, parent: group)))
+ end
+ end
+
+ it "finds deploys from them" do
+ expect(subject.second[:value]).to eq(3)
+ end
+ end
+
+ context 'with projects specified in options' do
+ before do
+ Timecop.freeze(5.days.from_now) do
+ create(:deployment, :success, project: create(:project, :repository, namespace: group, name: 'not_applicable'))
+ end
+ end
+
+ subject { described_class.new(group, options: { from: Time.now, current_user: user, projects: [project.id, project_2.id] }).data }
+
+ it 'shows deploys from those projects' do
+ expect(subject.second[:value]).to eq(2)
+ end
+ end
+ end
+
+ context 'with other projects' do
+ before do
+ Timecop.freeze(5.days.from_now) do
+ create(:deployment, :success, project: create(:project, :repository, namespace: create(:group)))
+ end
+ end
+
+ it "doesn't find deploys from them" do
+ expect(subject.second[:value]).to eq(0)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
index fd9fa2fee49..afb7b6a13b0 100644
--- a/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
index 3b6af9cbaed..4dd21239cde 100644
--- a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
@@ -1,14 +1,16 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::IssueStage do
let(:stage_name) { :issue }
let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
+ let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
+ let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago )
@@ -24,7 +26,7 @@ describe Gitlab::CycleAnalytics::IssueStage do
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
@@ -36,4 +38,90 @@ describe Gitlab::CycleAnalytics::IssueStage do
expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title, issue_3.title)
end
end
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago)
+ issue_2_2.metrics.update!(first_added_to_board_at: 30.minutes.ago)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title)
+ end
+ end
+
+ context 'when only part of projects is chosen' do
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group, projects: [project_2.id] }) }
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(1)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title)
+ end
+ end
+ end
+
+ context 'when subgroup is given' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project_4) { create(:project, group: subgroup) }
+ let(:project_5) { create(:project, group: subgroup) }
+ let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
+ let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+ let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+
+ before do
+ issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago)
+ issue_3_2.metrics.update!(first_added_to_board_at: 30.minutes.ago)
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(4)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title, issue_3_1.title, issue_3_2.title)
+ end
+
+ it 'exposes merge requests that close issues with full path for subgroup' do
+ expect(subject.count).to eq(4)
+ expect(subject.find { |event| event[:title] == issue_3_1.title }[:url]).to include("#{subgroup.full_path}")
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb
index f670c7f6c75..2896e973a43 100644
--- a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CycleAnalytics::Permissions do
diff --git a/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
index 2e5dc5b5547..17786cd02c6 100644
--- a/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
index 506a8160412..98d2593de66 100644
--- a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
@@ -8,7 +10,7 @@ describe Gitlab::CycleAnalytics::PlanStage do
let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
@@ -18,22 +20,88 @@ describe Gitlab::CycleAnalytics::PlanStage do
it_behaves_like 'base stage'
- describe '#median' do
+ describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
+ subject { stage.events }
+
it 'exposes issues with metrics' do
- result = stage.events
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title)
+ end
+ end
+
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
+ issue_2_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago)
+ issue_2_3.metrics.update!(first_added_to_board_at: 15.minutes.ago)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title)
+ end
+ end
+
+ context 'when subgroup is given' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project_4) { create(:project, group: subgroup) }
+ let(:project_5) { create(:project, group: subgroup) }
+ let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
+ let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+ let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+
+ before do
+ issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
+ issue_3_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago)
+ issue_3_3.metrics.update!(first_added_to_board_at: 15.minutes.ago)
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(4)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title, issue_3_1.title, issue_3_2.title)
+ end
- expect(result.count).to eq(2)
- expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title)
+ it 'exposes merge requests that close issues with full path for subgroup' do
+ expect(subject.count).to eq(4)
+ expect(subject.find { |event| event[:title] == issue_3_1.title }[:url]).to include("#{subgroup.full_path}")
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
index 74001181305..3ecfad49acd 100644
--- a/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
index 916684b81eb..4d0cc91a318 100644
--- a/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
index 4f67c95ed4c..2c2169be58c 100644
--- a/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
index f072a9644e8..0f36a8c5c36 100644
--- a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
@@ -1,17 +1,19 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::ReviewStage do
let(:stage_name) { :review }
let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
- let!(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
- let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
+ let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
+ let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
+ let(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
+ let(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
let!(:mr_4) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C') }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
mr_1.metrics.update!(merged_at: 30.minutes.ago)
@@ -24,22 +26,66 @@ describe Gitlab::CycleAnalytics::ReviewStage do
it_behaves_like 'base stage'
- describe '#median' do
+ describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
+ subject { stage.events }
+
it 'exposes merge requests that close issues' do
- result = stage.events
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
+ end
+ end
+
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_2_1) { create(:merge_request, :closed, source_project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_2_2) { create(:merge_request, :closed, source_project: project_3, created_at: 40.minutes.ago, source_branch: 'A') }
+ let(:mr_2_3) { create(:merge_request, source_project: project_2, created_at: 10.minutes.ago, source_branch: 'B') }
+ let!(:mr_2_4) { create(:merge_request, source_project: project_3, created_at: 10.minutes.ago, source_branch: 'C') }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ mr_2_1.metrics.update!(merged_at: 30.minutes.ago)
+ mr_2_2.metrics.update!(merged_at: 10.minutes.ago)
+
+ create(:merge_requests_closing_issues, merge_request: mr_2_1, issue: issue_2_1)
+ create(:merge_requests_closing_issues, merge_request: mr_2_2, issue: issue_2_2)
+ create(:merge_requests_closing_issues, merge_request: mr_2_3, issue: issue_2_3)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
- expect(result.count).to eq(2)
- expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title)
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
index c22d27f60d6..c053af010b3 100644
--- a/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'default query config' do
let(:project) { create(:project) }
- let(:event) { described_class.new(project: project, stage: stage_name, options: { from: 1.day.ago }) }
+ let(:event) { described_class.new(stage: stage_name, options: { from: 1.day.ago, project: project }) }
it 'has the stage attribute' do
expect(event.stage).not_to be_nil
diff --git a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
index 1a4b572cc11..cf95741908f 100644
--- a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
@@ -1,12 +1,14 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'base stage' do
ISSUES_MEDIAN = 30.minutes.to_i
- let(:stage) { described_class.new(project: double, options: {}) }
+ let(:stage) { described_class.new(options: { project: double }) }
before do
- allow(stage).to receive(:median).and_return(1.12)
+ allow(stage).to receive(:project_median).and_return(1.12)
allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
end
diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
index f8009709ce2..778c2f479b5 100644
--- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CycleAnalytics::StageSummary do
diff --git a/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
index bbc82496340..016d2e8da5b 100644
--- a/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
index 17d5fbb9733..bd64c4aca42 100644
--- a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
@@ -5,16 +7,16 @@ describe Gitlab::CycleAnalytics::StagingStage do
let(:stage_name) { :staging }
let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
- let!(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
- let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
+ let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
+ let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
+ let(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
+ let(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
let(:build_1) { create(:ci_build, project: project) }
let(:build_2) { create(:ci_build, project: project) }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id)
@@ -28,22 +30,68 @@ describe Gitlab::CycleAnalytics::StagingStage do
it_behaves_like 'base stage'
- describe '#median' do
+ describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
+ subject { stage.events }
+
it 'exposes builds connected to merge request' do
- result = stage.events
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
+ end
+ end
+
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_1) { create(:merge_request, :closed, source_project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_2) { create(:merge_request, :closed, source_project: project_3, created_at: 40.minutes.ago, source_branch: 'A') }
+ let(:mr_3) { create(:merge_request, source_project: project_2, created_at: 10.minutes.ago, source_branch: 'B') }
+ let(:build_1) { create(:ci_build, project: project_2) }
+ let(:build_2) { create(:ci_build, project: project_3) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id)
+ mr_2.metrics.update!(merged_at: 60.minutes.ago, first_deployed_to_production_at: 30.minutes.ago, pipeline_id: build_2.commit_id)
+ mr_3.metrics.update!(merged_at: 10.minutes.ago, first_deployed_to_production_at: 3.days.ago, pipeline_id: create(:ci_build, project: project_2).commit_id)
+
+ create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_2_1)
+ create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2_2)
+ create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_2_3)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
- expect(result.count).to eq(2)
- expect(result.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
index 6639fa54e0e..be7c0e9dd59 100644
--- a/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
index eacde22cd56..9162686d17d 100644
--- a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
@@ -1,8 +1,44 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::TestStage do
let(:stage_name) { :test }
+ let(:project) { create(:project) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
it_behaves_like 'base stage'
+
+ describe '#median' do
+ before do
+ issue_1 = create(:issue, project: project, created_at: 90.minutes.ago)
+ issue_2 = create(:issue, project: project, created_at: 60.minutes.ago)
+ issue_3 = create(:issue, project: project, created_at: 60.minutes.ago)
+ mr_1 = create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago)
+ mr_2 = create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A')
+ mr_3 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B')
+ mr_4 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C')
+ mr_5 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'D')
+ mr_1.metrics.update!(latest_build_started_at: 32.minutes.ago, latest_build_finished_at: 2.minutes.ago)
+ mr_2.metrics.update!(latest_build_started_at: 62.minutes.ago, latest_build_finished_at: 32.minutes.ago)
+ mr_3.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
+ mr_4.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
+ mr_5.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
+
+ create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1)
+ create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2)
+ create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_3)
+ create(:merge_requests_closing_issues, merge_request: mr_4, issue: issue_3)
+ create(:merge_requests_closing_issues, merge_request: mr_5, issue: issue_3)
+ end
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
+ end
+ end
end
diff --git a/spec/lib/gitlab/cycle_analytics/updater_spec.rb b/spec/lib/gitlab/cycle_analytics/updater_spec.rb
index eff54cd3692..67f386f9144 100644
--- a/spec/lib/gitlab/cycle_analytics/updater_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/updater_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CycleAnalytics::Updater do
diff --git a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
index a785b17f682..e568ea633db 100644
--- a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CycleAnalytics::UsageData do
@@ -28,27 +30,7 @@ describe Gitlab::CycleAnalytics::UsageData do
end
end
- shared_examples 'a valid usage data result' do
- it 'returns the aggregated usage data of every selected project' do
- result = subject.to_json
-
- expect(result).to have_key(:avg_cycle_analytics)
-
- CycleAnalytics::STAGES.each do |stage|
- expect(result[:avg_cycle_analytics]).to have_key(stage)
-
- stage_values = result[:avg_cycle_analytics][stage]
- expected_values = expect_values_per_stage[stage]
-
- expected_values.each_pair do |op, value|
- expect(stage_values).to have_key(op)
- expect(stage_values[op]).to eq(value)
- end
- end
- end
- end
-
- context 'when using postgresql', :postgresql do
+ context 'a valid usage data result' do
let(:expect_values_per_stage) do
{
issue: {
@@ -89,51 +71,23 @@ describe Gitlab::CycleAnalytics::UsageData do
}
end
- it_behaves_like 'a valid usage data result'
- end
+ it 'returns the aggregated usage data of every selected project' do
+ result = subject.to_json
- context 'when using mysql', :mysql do
- let(:expect_values_per_stage) do
- {
- issue: {
- average: nil,
- sd: 0,
- missing: 2
- },
- plan: {
- average: nil,
- sd: 0,
- missing: 2
- },
- code: {
- average: nil,
- sd: 0,
- missing: 2
- },
- test: {
- average: nil,
- sd: 0,
- missing: 2
- },
- review: {
- average: nil,
- sd: 0,
- missing: 2
- },
- staging: {
- average: nil,
- sd: 0,
- missing: 2
- },
- production: {
- average: nil,
- sd: 0,
- missing: 2
- }
- }
- end
+ expect(result).to have_key(:avg_cycle_analytics)
+
+ CycleAnalytics::LevelBase::STAGES.each do |stage|
+ expect(result[:avg_cycle_analytics]).to have_key(stage)
- it_behaves_like 'a valid usage data result'
+ stage_values = result[:avg_cycle_analytics][stage]
+ expected_values = expect_values_per_stage[stage]
+
+ expected_values.each_pair do |op, value|
+ expect(stage_values).to have_key(op)
+ expect(stage_values[op]).to eq(value)
+ end
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/daemon_spec.rb b/spec/lib/gitlab/daemon_spec.rb
index 2bdb0dfc736..0372b770844 100644
--- a/spec/lib/gitlab/daemon_spec.rb
+++ b/spec/lib/gitlab/daemon_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Daemon do
@@ -32,12 +34,12 @@ describe Gitlab::Daemon do
end
end
- describe 'when Daemon is enabled' do
+ context 'when Daemon is enabled' do
before do
allow(subject).to receive(:enabled?).and_return(true)
end
- describe 'when Daemon is stopped' do
+ context 'when Daemon is stopped' do
describe '#start' do
it 'starts the Daemon' do
expect { subject.start.join }.to change { subject.thread? }.from(false).to(true)
@@ -55,14 +57,14 @@ describe Gitlab::Daemon do
end
end
- describe 'when Daemon is running' do
+ context 'when Daemon is running' do
before do
- subject.start.join
+ subject.start
end
describe '#start' do
it "doesn't start running Daemon" do
- expect { subject.start.join }.not_to change { subject.thread? }
+ expect { subject.start.join }.not_to change { subject.thread }
expect(subject).to have_received(:start_working).once
end
@@ -74,11 +76,29 @@ describe Gitlab::Daemon do
expect(subject).to have_received(:stop_working)
end
+
+ context 'when stop_working raises exception' do
+ before do
+ allow(subject).to receive(:start_working) do
+ sleep(1000)
+ end
+ end
+
+ it 'shutdowns Daemon' do
+ expect(subject).to receive(:stop_working) do
+ subject.thread.raise(Interrupt)
+ end
+
+ expect(subject.thread).to be_alive
+ expect { subject.stop }.not_to raise_error
+ expect(subject.thread).to be_nil
+ end
+ end
end
end
end
- describe 'when Daemon is disabled' do
+ context 'when Daemon is disabled' do
before do
allow(subject).to receive(:enabled?).and_return(false)
end
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index c8e65a3e59d..2990594c538 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -85,15 +85,29 @@ describe Gitlab::Danger::Helper do
end
end
+ describe '#markdown_list' do
+ it 'creates a markdown list of items' do
+ items = %w[a b]
+
+ expect(helper.markdown_list(items)).to eq("* `a`\n* `b`")
+ end
+
+ it 'wraps items in <details> when there are more than 10 items' do
+ items = ('a'..'k').to_a
+
+ expect(helper.markdown_list(items)).to match(%r{<details>[^<]+</details>})
+ end
+ end
+
describe '#changes_by_category' do
it 'categorizes changed files' do
- expect(fake_git).to receive(:added_files) { %w[foo foo.md foo.rb foo.js db/foo qa/foo ee/changelogs/foo.yml] }
+ expect(fake_git).to receive(:added_files) { %w[foo foo.md foo.rb foo.js db/migrate/foo lib/gitlab/database/foo.rb qa/foo ee/changelogs/foo.yml] }
allow(fake_git).to receive(:modified_files) { [] }
allow(fake_git).to receive(:renamed_files) { [] }
expect(helper.changes_by_category).to eq(
backend: %w[foo.rb],
- database: %w[db/foo],
+ database: %w[db/migrate/foo lib/gitlab/database/foo.rb],
frontend: %w[foo.js],
none: %w[ee/changelogs/foo.yml foo.md],
qa: %w[qa/foo],
@@ -159,9 +173,30 @@ describe Gitlab::Danger::Helper do
'ee/FOO_VERSION' | :unknown
- 'db/foo' | :database
- 'qa/foo' | :qa
+ 'db/schema.rb' | :database
+ 'db/migrate/foo' | :database
+ 'db/post_migrate/foo' | :database
+ 'ee/db/migrate/foo' | :database
+ 'ee/db/post_migrate/foo' | :database
+ 'ee/db/geo/migrate/foo' | :database
+ 'ee/db/geo/post_migrate/foo' | :database
+ 'app/models/project_authorization.rb' | :database
+ 'app/services/users/refresh_authorized_projects_service.rb' | :database
+ 'lib/gitlab/background_migration.rb' | :database
+ 'lib/gitlab/background_migration/foo' | :database
+ 'ee/lib/gitlab/background_migration/foo' | :database
+ 'lib/gitlab/database.rb' | :database
+ 'lib/gitlab/database/foo' | :database
+ 'ee/lib/gitlab/database/foo' | :database
+ 'lib/gitlab/github_import.rb' | :database
+ 'lib/gitlab/github_import/foo' | :database
+ 'lib/gitlab/sql/foo' | :database
+ 'rubocop/cop/migration/foo' | :database
+
+ 'db/fixtures/foo.rb' | :backend
+ 'ee/db/fixtures/foo.rb' | :backend
+ 'qa/foo' | :qa
'ee/qa/foo' | :qa
'changelogs/foo' | :none
@@ -211,4 +246,20 @@ describe Gitlab::Danger::Helper do
expect(teammates.map(&:username)).to eq(usernames)
end
end
+
+ describe '#missing_database_labels' do
+ subject { helper.missing_database_labels(current_mr_labels) }
+
+ context 'when current merge request has ~database::review pending' do
+ let(:current_mr_labels) { ['database::review pending', 'feature'] }
+
+ it { is_expected.to match_array(['database']) }
+ end
+
+ context 'when current merge request does not have ~database::review pending' do
+ let(:current_mr_labels) { ['feature'] }
+
+ it { is_expected.to match_array(['database', 'database::review pending']) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
index 6a6cf1429c8..61b43e343c1 100644
--- a/spec/lib/gitlab/danger/teammate_spec.rb
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -28,7 +28,7 @@ describe Gitlab::Danger::Teammate do
end
context 'when labels contain Create and the category is test' do
- let(:labels) { ['Create'] }
+ let(:labels) { ['devops::create'] }
context 'when role is Test Automation Engineer, Create' do
let(:role) { 'Test Automation Engineer, Create' }
@@ -40,6 +40,14 @@ describe Gitlab::Danger::Teammate do
it '#maintainer? returns false' do
expect(subject.maintainer?(project, :test, labels)).to be_falsey
end
+
+ context 'when hyperlink is mangled in the role' do
+ let(:role) { '<a href="#">Test Automation Engineer</a>, Create' }
+
+ it '#reviewer? returns true' do
+ expect(subject.reviewer?(project, :test, labels)).to be_truthy
+ end
+ end
end
context 'when role is Test Automation Engineer, Manage' do
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index 14fe196a986..b170ef788d9 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DataBuilder::Build do
diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb
index 1b5dd2538e0..3c26daba5a5 100644
--- a/spec/lib/gitlab/data_builder/note_spec.rb
+++ b/spec/lib/gitlab/data_builder/note_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DataBuilder::Note do
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index 1f36fd5c6ef..4afb7195b7b 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DataBuilder::Pipeline do
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index 46ad674a1eb..e8a9f0b06a8 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -1,9 +1,45 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DataBuilder::Push do
+ include RepoHelpers
+
let(:project) { create(:project, :repository) }
let(:user) { build(:user, public_email: 'public-email@example.com') }
+ describe '.build' do
+ let(:sample) { RepoHelpers.sample_compare }
+ let(:commits) { project.repository.commits_between(sample.commits.first, sample.commits.last) }
+ let(:subject) do
+ described_class.build(project: project,
+ user: user,
+ ref: sample.target_branch,
+ commits: commits,
+ commits_count: commits.length,
+ message: 'test message',
+ with_changed_files: with_changed_files)
+ end
+
+ context 'with changed files' do
+ let(:with_changed_files) { true }
+
+ it 'returns commit hook data' do
+ expect(subject[:project]).to eq(project.hook_attrs)
+ expect(subject[:commits].first.keys).to include(*%i(added removed modified))
+ end
+ end
+
+ context 'without changed files' do
+ let(:with_changed_files) { false }
+
+ it 'returns commit hook data without include deltas' do
+ expect(subject[:project]).to eq(project.hook_attrs)
+ expect(subject[:commits].first.keys).not_to include(*%i(added removed modified))
+ end
+ end
+ end
+
describe '.build_sample' do
let(:data) { described_class.build_sample(project, user) }
diff --git a/spec/lib/gitlab/data_builder/wiki_page_spec.rb b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
index 9c8bdf4b032..404d54bf2da 100644
--- a/spec/lib/gitlab/data_builder/wiki_page_spec.rb
+++ b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DataBuilder::WikiPage do
diff --git a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
index 3991c737a26..111833a506a 100644
--- a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::Count::ExactCountStrategy do
@@ -23,18 +25,4 @@ describe Gitlab::Database::Count::ExactCountStrategy do
expect(subject).to eq({})
end
end
-
- describe '.enabled?' do
- it 'is enabled for PostgreSQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
-
- expect(described_class.enabled?).to be_truthy
- end
-
- it 'is enabled for MySQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(described_class.enabled?).to be_truthy
- end
- end
end
diff --git a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
index bd3c66d0548..08032d19d14 100644
--- a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::Count::ReltuplesCountStrategy do
@@ -8,7 +10,7 @@ describe Gitlab::Database::Count::ReltuplesCountStrategy do
subject { described_class.new(models).count }
- describe '#count', :postgresql do
+ describe '#count' do
let(:models) { [Project, Identity] }
context 'when reltuples is up to date' do
@@ -48,18 +50,4 @@ describe Gitlab::Database::Count::ReltuplesCountStrategy do
end
end
end
-
- describe '.enabled?' do
- it 'is enabled for PostgreSQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
-
- expect(described_class.enabled?).to be_truthy
- end
-
- it 'is disabled for MySQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(described_class.enabled?).to be_falsey
- end
- end
end
diff --git a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
index 40d810b195b..0c480709c22 100644
--- a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::Count::TablesampleCountStrategy do
@@ -12,7 +14,7 @@ describe Gitlab::Database::Count::TablesampleCountStrategy do
subject { strategy.count }
- describe '#count', :postgresql do
+ describe '#count' do
let(:estimates) do
{
Project => threshold + 1,
@@ -56,22 +58,4 @@ describe Gitlab::Database::Count::TablesampleCountStrategy do
end
end
end
-
- describe '.enabled?' do
- before do
- stub_feature_flags(tablesample_counts: true)
- end
-
- it 'is enabled for PostgreSQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
-
- expect(described_class.enabled?).to be_truthy
- end
-
- it 'is disabled for MySQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(described_class.enabled?).to be_falsey
- end
- end
end
diff --git a/spec/lib/gitlab/database/count_spec.rb b/spec/lib/gitlab/database/count_spec.rb
index 1d096b8fa7c..71c25f23b6b 100644
--- a/spec/lib/gitlab/database/count_spec.rb
+++ b/spec/lib/gitlab/database/count_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::Count do
@@ -9,24 +11,13 @@ describe Gitlab::Database::Count do
let(:models) { [Project, Identity] }
context '.approximate_counts' do
- context 'selecting strategies' do
- let(:strategies) { [double('s1', enabled?: true), double('s2', enabled?: false)] }
-
- it 'uses only enabled strategies' do
- expect(strategies[0]).to receive(:new).and_return(double('strategy1', count: {}))
- expect(strategies[1]).not_to receive(:new)
-
- described_class.approximate_counts(models, strategies: strategies)
- end
- end
-
context 'fallbacks' do
subject { described_class.approximate_counts(models, strategies: strategies) }
let(:strategies) do
[
- double('s1', enabled?: true, new: first_strategy),
- double('s2', enabled?: true, new: second_strategy)
+ double('s1', new: first_strategy),
+ double('s2', new: second_strategy)
]
end
diff --git a/spec/lib/gitlab/database/grant_spec.rb b/spec/lib/gitlab/database/grant_spec.rb
index 5ebf3f399b6..02697eb2a16 100644
--- a/spec/lib/gitlab/database/grant_spec.rb
+++ b/spec/lib/gitlab/database/grant_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::Grant do
@@ -8,7 +10,7 @@ describe Gitlab::Database::Grant do
expect(described_class.create_and_execute_trigger?('users')).to eq(true)
end
- it 'returns false when the user can not create and/or execute a trigger', :postgresql do
+ it 'returns false when the user can not create and/or execute a trigger' do
# In case of MySQL the user may have SUPER permissions, making it
# impossible to have `false` returned when running tests; hence we only
# run these tests on PostgreSQL.
diff --git a/spec/lib/gitlab/database/median_spec.rb b/spec/lib/gitlab/database/median_spec.rb
deleted file mode 100644
index 1b5e30089ce..00000000000
--- a/spec/lib/gitlab/database/median_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Database::Median do
- let(:dummy_class) do
- Class.new do
- include Gitlab::Database::Median
- end
- end
-
- subject(:median) { dummy_class.new }
-
- describe '#median_datetimes' do
- it 'raises NotSupportedError', :mysql do
- expect { median.median_datetimes(nil, nil, nil, :project_id) }.to raise_error(dummy_class::NotSupportedError, "partition_column is not supported for MySQL")
- end
- end
-end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 7409572288c..cff4eb398bf 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::MigrationHelpers do
@@ -9,35 +11,87 @@ describe Gitlab::Database::MigrationHelpers do
allow(model).to receive(:puts)
end
+ describe '#remove_timestamps' do
+ it 'can remove the default timestamps' do
+ Gitlab::Database::MigrationHelpers::DEFAULT_TIMESTAMP_COLUMNS.each do |column_name|
+ expect(model).to receive(:remove_column).with(:foo, column_name)
+ end
+
+ model.remove_timestamps(:foo)
+ end
+
+ it 'can remove custom timestamps' do
+ expect(model).to receive(:remove_column).with(:foo, :bar)
+
+ model.remove_timestamps(:foo, columns: [:bar])
+ end
+ end
+
describe '#add_timestamps_with_timezone' do
+ let(:in_transaction) { false }
+
before do
- allow(model).to receive(:transaction_open?).and_return(false)
+ allow(model).to receive(:transaction_open?).and_return(in_transaction)
+ allow(model).to receive(:disable_statement_timeout)
end
- context 'using PostgreSQL' do
- before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
- allow(model).to receive(:disable_statement_timeout)
+ it 'adds "created_at" and "updated_at" fields with the "datetime_with_timezone" data type' do
+ Gitlab::Database::MigrationHelpers::DEFAULT_TIMESTAMP_COLUMNS.each do |column_name|
+ expect(model).to receive(:add_column).with(:foo, column_name, :datetime_with_timezone, { null: false })
end
- it 'adds "created_at" and "updated_at" fields with the "datetime_with_timezone" data type' do
- expect(model).to receive(:add_column).with(:foo, :created_at, :datetime_with_timezone, { null: false })
- expect(model).to receive(:add_column).with(:foo, :updated_at, :datetime_with_timezone, { null: false })
+ model.add_timestamps_with_timezone(:foo)
+ end
- model.add_timestamps_with_timezone(:foo)
+ it 'can disable the NOT NULL constraint' do
+ Gitlab::Database::MigrationHelpers::DEFAULT_TIMESTAMP_COLUMNS.each do |column_name|
+ expect(model).to receive(:add_column).with(:foo, column_name, :datetime_with_timezone, { null: true })
end
+
+ model.add_timestamps_with_timezone(:foo, null: true)
end
- context 'using MySQL' do
+ it 'can add just one column' do
+ expect(model).to receive(:add_column).with(:foo, :created_at, :datetime_with_timezone, anything)
+ expect(model).not_to receive(:add_column).with(:foo, :updated_at, :datetime_with_timezone, anything)
+
+ model.add_timestamps_with_timezone(:foo, columns: [:created_at])
+ end
+
+ it 'can add choice of acceptable columns' do
+ expect(model).to receive(:add_column).with(:foo, :created_at, :datetime_with_timezone, anything)
+ expect(model).to receive(:add_column).with(:foo, :deleted_at, :datetime_with_timezone, anything)
+ expect(model).not_to receive(:add_column).with(:foo, :updated_at, :datetime_with_timezone, anything)
+
+ model.add_timestamps_with_timezone(:foo, columns: [:created_at, :deleted_at])
+ end
+
+ it 'cannot add unacceptable column names' do
+ expect do
+ model.add_timestamps_with_timezone(:foo, columns: [:bar])
+ end.to raise_error %r/Illegal timestamp column name/
+ end
+
+ context 'in a transaction' do
+ let(:in_transaction) { true }
+
before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+ allow(model).to receive(:add_column).with(any_args).and_call_original
+ allow(model).to receive(:add_column)
+ .with(:foo, anything, :datetime_with_timezone, anything)
+ .and_return(nil)
end
- it 'adds "created_at" and "updated_at" fields with "datetime_with_timezone" data type' do
- expect(model).to receive(:add_column).with(:foo, :created_at, :datetime_with_timezone, { null: false })
- expect(model).to receive(:add_column).with(:foo, :updated_at, :datetime_with_timezone, { null: false })
+ it 'cannot add a default value' do
+ expect do
+ model.add_timestamps_with_timezone(:foo, default: :i_cause_an_error)
+ end.to raise_error %r/add_timestamps_with_timezone/
+ end
- model.add_timestamps_with_timezone(:foo)
+ it 'can add columns without defaults' do
+ expect do
+ model.add_timestamps_with_timezone(:foo)
+ end.not_to raise_error
end
end
end
@@ -46,56 +100,29 @@ describe Gitlab::Database::MigrationHelpers do
context 'outside a transaction' do
before do
allow(model).to receive(:transaction_open?).and_return(false)
+ allow(model).to receive(:disable_statement_timeout).and_call_original
end
- context 'using PostgreSQL', :postgresql do
- before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
- allow(model).to receive(:disable_statement_timeout).and_call_original
- end
-
- it 'creates the index concurrently' do
- expect(model).to receive(:add_index)
- .with(:users, :foo, algorithm: :concurrently)
-
- model.add_concurrent_index(:users, :foo)
- end
-
- it 'creates unique index concurrently' do
- expect(model).to receive(:add_index)
- .with(:users, :foo, { algorithm: :concurrently, unique: true })
-
- model.add_concurrent_index(:users, :foo, unique: true)
- end
-
- it 'does nothing if the index exists already' do
- expect(model).to receive(:index_exists?)
- .with(:users, :foo, { algorithm: :concurrently, unique: true }).and_return(true)
- expect(model).not_to receive(:add_index)
+ it 'creates the index concurrently' do
+ expect(model).to receive(:add_index)
+ .with(:users, :foo, algorithm: :concurrently)
- model.add_concurrent_index(:users, :foo, unique: true)
- end
+ model.add_concurrent_index(:users, :foo)
end
- context 'using MySQL' do
- before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
- end
-
- it 'creates a regular index' do
- expect(model).to receive(:add_index)
- .with(:users, :foo, {})
+ it 'creates unique index concurrently' do
+ expect(model).to receive(:add_index)
+ .with(:users, :foo, { algorithm: :concurrently, unique: true })
- model.add_concurrent_index(:users, :foo)
- end
+ model.add_concurrent_index(:users, :foo, unique: true)
+ end
- it 'does nothing if the index exists already' do
- expect(model).to receive(:index_exists?)
- .with(:users, :foo, { unique: true }).and_return(true)
- expect(model).not_to receive(:add_index)
+ it 'does nothing if the index exists already' do
+ expect(model).to receive(:index_exists?)
+ .with(:users, :foo, { algorithm: :concurrently, unique: true }).and_return(true)
+ expect(model).not_to receive(:add_index)
- model.add_concurrent_index(:users, :foo, unique: true)
- end
+ model.add_concurrent_index(:users, :foo, unique: true)
end
end
@@ -115,28 +142,23 @@ describe Gitlab::Database::MigrationHelpers do
allow(model).to receive(:transaction_open?).and_return(false)
allow(model).to receive(:index_exists?).and_return(true)
allow(model).to receive(:disable_statement_timeout).and_call_original
+ allow(model).to receive(:supports_drop_index_concurrently?).and_return(true)
end
- context 'using PostgreSQL' do
- before do
- allow(model).to receive(:supports_drop_index_concurrently?).and_return(true)
- end
-
- describe 'by column name' do
- it 'removes the index concurrently' do
- expect(model).to receive(:remove_index)
- .with(:users, { algorithm: :concurrently, column: :foo })
+ describe 'by column name' do
+ it 'removes the index concurrently' do
+ expect(model).to receive(:remove_index)
+ .with(:users, { algorithm: :concurrently, column: :foo })
- model.remove_concurrent_index(:users, :foo)
- end
+ model.remove_concurrent_index(:users, :foo)
+ end
- it 'does nothing if the index does not exist' do
- expect(model).to receive(:index_exists?)
- .with(:users, :foo, { algorithm: :concurrently, unique: true }).and_return(false)
- expect(model).not_to receive(:remove_index)
+ it 'does nothing if the index does not exist' do
+ expect(model).to receive(:index_exists?)
+ .with(:users, :foo, { algorithm: :concurrently, unique: true }).and_return(false)
+ expect(model).not_to receive(:remove_index)
- model.remove_concurrent_index(:users, :foo, unique: true)
- end
+ model.remove_concurrent_index(:users, :foo, unique: true)
end
describe 'by index name' do
@@ -159,17 +181,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
end
-
- context 'using MySQL' do
- it 'removes an index' do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(false).twice
-
- expect(model).to receive(:remove_index)
- .with(:users, { column: :foo })
-
- model.remove_concurrent_index(:users, :foo)
- end
- end
end
context 'inside a transaction' do
@@ -202,88 +213,44 @@ describe Gitlab::Database::MigrationHelpers do
allow(model).to receive(:transaction_open?).and_return(false)
end
- context 'using MySQL' do
- before do
- allow(Gitlab::Database).to receive(:mysql?).and_return(true)
- end
-
- it 'creates a regular foreign key' do
- expect(model).to receive(:add_foreign_key)
- .with(:projects, :users, column: :user_id, on_delete: :cascade)
+ it 'creates a concurrent foreign key and validates it' do
+ expect(model).to receive(:disable_statement_timeout).and_call_original
+ expect(model).to receive(:execute).with(/statement_timeout/)
+ expect(model).to receive(:execute).ordered.with(/NOT VALID/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+ expect(model).to receive(:execute).with(/RESET ALL/)
- model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
- end
-
- it 'allows the use of a custom key name' do
- expect(model).to receive(:add_foreign_key).with(
- :projects,
- :users,
- column: :user_id,
- on_delete: :cascade,
- name: :foo
- )
-
- model.add_concurrent_foreign_key(
- :projects,
- :users,
- column: :user_id,
- name: :foo
- )
- end
-
- it 'does not create a foreign key if it exists already' do
- expect(model).to receive(:foreign_key_exists?).with(:projects, :users, column: :user_id).and_return(true)
- expect(model).not_to receive(:add_foreign_key)
-
- model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
- end
+ model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
end
- context 'using PostgreSQL' do
- before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
- allow(Gitlab::Database).to receive(:mysql?).and_return(false)
- end
-
- it 'creates a concurrent foreign key and validates it' do
- expect(model).to receive(:disable_statement_timeout).and_call_original
- expect(model).to receive(:execute).with(/statement_timeout/)
- expect(model).to receive(:execute).ordered.with(/NOT VALID/)
- expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
- expect(model).to receive(:execute).with(/RESET ALL/)
-
- model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
- end
-
- it 'appends a valid ON DELETE statement' do
- expect(model).to receive(:disable_statement_timeout).and_call_original
- expect(model).to receive(:execute).with(/statement_timeout/)
- expect(model).to receive(:execute).with(/ON DELETE SET NULL/)
- expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
- expect(model).to receive(:execute).with(/RESET ALL/)
+ it 'appends a valid ON DELETE statement' do
+ expect(model).to receive(:disable_statement_timeout).and_call_original
+ expect(model).to receive(:execute).with(/statement_timeout/)
+ expect(model).to receive(:execute).with(/ON DELETE SET NULL/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+ expect(model).to receive(:execute).with(/RESET ALL/)
- model.add_concurrent_foreign_key(:projects, :users,
- column: :user_id,
- on_delete: :nullify)
- end
+ model.add_concurrent_foreign_key(:projects, :users,
+ column: :user_id,
+ on_delete: :nullify)
+ end
- it 'does not create a foreign key if it exists already' do
- expect(model).to receive(:foreign_key_exists?).with(:projects, :users, column: :user_id).and_return(true)
- expect(model).not_to receive(:execute).with(/ADD CONSTRAINT/)
- expect(model).to receive(:execute).with(/VALIDATE CONSTRAINT/)
+ it 'does not create a foreign key if it exists already' do
+ expect(model).to receive(:foreign_key_exists?).with(:projects, :users, column: :user_id).and_return(true)
+ expect(model).not_to receive(:execute).with(/ADD CONSTRAINT/)
+ expect(model).to receive(:execute).with(/VALIDATE CONSTRAINT/)
- model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
- end
+ model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
+ end
- it 'allows the use of a custom key name' do
- expect(model).to receive(:disable_statement_timeout).and_call_original
- expect(model).to receive(:execute).with(/statement_timeout/)
- expect(model).to receive(:execute).ordered.with(/NOT VALID/)
- expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+foo/)
- expect(model).to receive(:execute).with(/RESET ALL/)
+ it 'allows the use of a custom key name' do
+ expect(model).to receive(:disable_statement_timeout).and_call_original
+ expect(model).to receive(:execute).with(/statement_timeout/)
+ expect(model).to receive(:execute).ordered.with(/NOT VALID/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+foo/)
+ expect(model).to receive(:execute).with(/RESET ALL/)
- model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo)
- end
+ model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo)
end
end
end
@@ -322,48 +289,43 @@ describe Gitlab::Database::MigrationHelpers do
end
describe '#disable_statement_timeout' do
- context 'using PostgreSQL' do
- it 'disables statement timeouts to current transaction only' do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ it 'disables statement timeouts to current transaction only' do
+ expect(model).to receive(:execute).with('SET LOCAL statement_timeout TO 0')
- expect(model).to receive(:execute).with('SET LOCAL statement_timeout TO 0')
+ model.disable_statement_timeout
+ end
- model.disable_statement_timeout
+ # this specs runs without an enclosing transaction (:delete truncation method for db_cleaner)
+ context 'with real environment', :delete do
+ before do
+ model.execute("SET statement_timeout TO '20000'")
end
- # this specs runs without an enclosing transaction (:delete truncation method for db_cleaner)
- context 'with real environment', :postgresql, :delete do
- before do
- model.execute("SET statement_timeout TO '20000'")
- end
-
- after do
- model.execute('RESET ALL')
- end
-
- it 'defines statement to 0 only for current transaction' do
- expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s')
+ after do
+ model.execute('RESET ALL')
+ end
- model.connection.transaction do
- model.disable_statement_timeout
- expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('0')
- end
+ it 'defines statement to 0 only for current transaction' do
+ expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s')
- expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s')
+ model.connection.transaction do
+ model.disable_statement_timeout
+ expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('0')
end
+
+ expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s')
end
context 'when passing a blocks' do
it 'disables statement timeouts on session level and executes the block' do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
expect(model).to receive(:execute).with('SET statement_timeout TO 0')
- expect(model).to receive(:execute).with('RESET ALL')
+ expect(model).to receive(:execute).with('RESET ALL').at_least(:once)
expect { |block| model.disable_statement_timeout(&block) }.to yield_control
end
# this specs runs without an enclosing transaction (:delete truncation method for db_cleaner)
- context 'with real environment', :postgresql, :delete do
+ context 'with real environment', :delete do
before do
model.execute("SET statement_timeout TO '20000'")
end
@@ -386,69 +348,17 @@ describe Gitlab::Database::MigrationHelpers do
end
end
end
-
- context 'using MySQL' do
- it 'does nothing' do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(model).not_to receive(:execute)
-
- model.disable_statement_timeout
- end
-
- context 'when passing a blocks' do
- it 'executes the block of code' do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(model).not_to receive(:execute)
-
- expect { |block| model.disable_statement_timeout(&block) }.to yield_control
- end
- end
- end
end
describe '#true_value' do
- context 'using PostgreSQL' do
- before do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
- end
-
- it 'returns the appropriate value' do
- expect(model.true_value).to eq("'t'")
- end
- end
-
- context 'using MySQL' do
- before do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
- end
-
- it 'returns the appropriate value' do
- expect(model.true_value).to eq(1)
- end
+ it 'returns the appropriate value' do
+ expect(model.true_value).to eq("'t'")
end
end
describe '#false_value' do
- context 'using PostgreSQL' do
- before do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
- end
-
- it 'returns the appropriate value' do
- expect(model.false_value).to eq("'f'")
- end
- end
-
- context 'using MySQL' do
- before do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
- end
-
- it 'returns the appropriate value' do
- expect(model.false_value).to eq(0)
- end
+ it 'returns the appropriate value' do
+ expect(model.false_value).to eq("'f'")
end
end
@@ -640,77 +550,69 @@ describe Gitlab::Database::MigrationHelpers do
before do
allow(model).to receive(:transaction_open?).and_return(false)
allow(model).to receive(:column_for).and_return(old_column)
-
- # Since MySQL and PostgreSQL use different quoting styles we'll just
- # stub the methods used for this to make testing easier.
- allow(model).to receive(:quote_column_name) { |name| name.to_s }
- allow(model).to receive(:quote_table_name) { |name| name.to_s }
end
- context 'using MySQL' do
- it 'renames a column concurrently' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+ it 'renames a column concurrently' do
+ expect(model).to receive(:check_trigger_permissions!).with(:users)
- expect(model).to receive(:check_trigger_permissions!).with(:users)
+ expect(model).to receive(:install_rename_triggers_for_postgresql)
+ .with(trigger_name, '"users"', '"old"', '"new"')
- expect(model).to receive(:install_rename_triggers_for_mysql)
- .with(trigger_name, 'users', 'old', 'new')
+ expect(model).to receive(:add_column)
+ .with(:users, :new, :integer,
+ limit: old_column.limit,
+ precision: old_column.precision,
+ scale: old_column.scale)
- expect(model).to receive(:add_column)
- .with(:users, :new, :integer,
- limit: old_column.limit,
- precision: old_column.precision,
- scale: old_column.scale)
-
- expect(model).to receive(:change_column_default)
- .with(:users, :new, old_column.default)
+ expect(model).to receive(:change_column_default)
+ .with(:users, :new, old_column.default)
- expect(model).to receive(:update_column_in_batches)
+ expect(model).to receive(:update_column_in_batches)
- expect(model).to receive(:change_column_null).with(:users, :new, false)
+ expect(model).to receive(:change_column_null).with(:users, :new, false)
- expect(model).to receive(:copy_indexes).with(:users, :old, :new)
- expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
+ expect(model).to receive(:copy_indexes).with(:users, :old, :new)
+ expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
- model.rename_column_concurrently(:users, :old, :new)
- end
+ model.rename_column_concurrently(:users, :old, :new)
end
- context 'using PostgreSQL' do
- it 'renames a column concurrently' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
-
- expect(model).to receive(:check_trigger_permissions!).with(:users)
-
- expect(model).to receive(:install_rename_triggers_for_postgresql)
- .with(trigger_name, 'users', 'old', 'new')
-
- expect(model).to receive(:add_column)
- .with(:users, :new, :integer,
- limit: old_column.limit,
- precision: old_column.precision,
- scale: old_column.scale)
+ context 'when default is false' do
+ let(:old_column) do
+ double(:column,
+ type: :boolean,
+ limit: nil,
+ default: false,
+ null: false,
+ precision: nil,
+ scale: nil)
+ end
+ it 'copies the default to the new column' do
expect(model).to receive(:change_column_default)
.with(:users, :new, old_column.default)
- expect(model).to receive(:update_column_in_batches)
-
- expect(model).to receive(:change_column_null).with(:users, :new, false)
-
- expect(model).to receive(:copy_indexes).with(:users, :old, :new)
- expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
-
model.rename_column_concurrently(:users, :old, :new)
end
end
end
end
- describe '#cleanup_concurrent_column_rename' do
- it 'cleans up the renaming procedure for PostgreSQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ describe '#undo_rename_column_concurrently' do
+ it 'reverses the operations of rename_column_concurrently' do
+ expect(model).to receive(:check_trigger_permissions!).with(:users)
+ expect(model).to receive(:remove_rename_triggers_for_postgresql)
+ .with(:users, /trigger_.{12}/)
+
+ expect(model).to receive(:remove_column).with(:users, :new)
+
+ model.undo_rename_column_concurrently(:users, :old, :new)
+ end
+ end
+
+ describe '#cleanup_concurrent_column_rename' do
+ it 'cleans up the renaming procedure' do
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:remove_rename_triggers_for_postgresql)
@@ -720,18 +622,79 @@ describe Gitlab::Database::MigrationHelpers do
model.cleanup_concurrent_column_rename(:users, :old, :new)
end
+ end
+
+ describe '#undo_cleanup_concurrent_column_rename' do
+ context 'in a transaction' do
+ it 'raises RuntimeError' do
+ allow(model).to receive(:transaction_open?).and_return(true)
+
+ expect { model.undo_cleanup_concurrent_column_rename(:users, :old, :new) }
+ .to raise_error(RuntimeError)
+ end
+ end
- it 'cleans up the renaming procedure for MySQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+ context 'outside a transaction' do
+ let(:new_column) do
+ double(:column,
+ type: :integer,
+ limit: 8,
+ default: 0,
+ null: false,
+ precision: 5,
+ scale: 1)
+ end
- expect(model).to receive(:check_trigger_permissions!).with(:users)
+ let(:trigger_name) { model.rename_trigger_name(:users, :old, :new) }
- expect(model).to receive(:remove_rename_triggers_for_mysql)
- .with(/trigger_.{12}/)
+ before do
+ allow(model).to receive(:transaction_open?).and_return(false)
+ allow(model).to receive(:column_for).and_return(new_column)
+ end
- expect(model).to receive(:remove_column).with(:users, :old)
+ it 'reverses the operations of cleanup_concurrent_column_rename' do
+ expect(model).to receive(:check_trigger_permissions!).with(:users)
- model.cleanup_concurrent_column_rename(:users, :old, :new)
+ expect(model).to receive(:install_rename_triggers_for_postgresql)
+ .with(trigger_name, '"users"', '"old"', '"new"')
+
+ expect(model).to receive(:add_column)
+ .with(:users, :old, :integer,
+ limit: new_column.limit,
+ precision: new_column.precision,
+ scale: new_column.scale)
+
+ expect(model).to receive(:change_column_default)
+ .with(:users, :old, new_column.default)
+
+ expect(model).to receive(:update_column_in_batches)
+
+ expect(model).to receive(:change_column_null).with(:users, :old, false)
+
+ expect(model).to receive(:copy_indexes).with(:users, :new, :old)
+ expect(model).to receive(:copy_foreign_keys).with(:users, :new, :old)
+
+ model.undo_cleanup_concurrent_column_rename(:users, :old, :new)
+ end
+
+ context 'when default is false' do
+ let(:new_column) do
+ double(:column,
+ type: :boolean,
+ limit: nil,
+ default: false,
+ null: false,
+ precision: nil,
+ scale: nil)
+ end
+
+ it 'copies the default to the old column' do
+ expect(model).to receive(:change_column_default)
+ .with(:users, :old, new_column.default)
+
+ model.undo_cleanup_concurrent_column_rename(:users, :old, :new)
+ end
+ end
end
end
@@ -762,21 +725,17 @@ describe Gitlab::Database::MigrationHelpers do
.with(/CREATE OR REPLACE FUNCTION foo()/m)
expect(model).to receive(:execute)
+ .with(/DROP TRIGGER IF EXISTS foo/m)
+
+ expect(model).to receive(:execute)
.with(/CREATE TRIGGER foo/m)
model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
end
- end
- describe '#install_rename_triggers_for_mysql' do
- it 'installs the triggers for MySQL' do
- expect(model).to receive(:execute)
- .with(/CREATE TRIGGER foo_insert.+ON users/m)
-
- expect(model).to receive(:execute)
- .with(/CREATE TRIGGER foo_update.+ON users/m)
-
- model.install_rename_triggers_for_mysql('foo', :users, :old, :new)
+ it 'does not fail if trigger already exists' do
+ model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
+ model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
end
end
@@ -789,15 +748,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
- describe '#remove_rename_triggers_for_mysql' do
- it 'removes the triggers' do
- expect(model).to receive(:execute).with('DROP TRIGGER IF EXISTS foo_insert')
- expect(model).to receive(:execute).with('DROP TRIGGER IF EXISTS foo_update')
-
- model.remove_rename_triggers_for_mysql('foo')
- end
- end
-
describe '#rename_trigger_name' do
it 'returns a String' do
expect(model.rename_trigger_name(:users, :foo, :bar))
@@ -1017,26 +967,9 @@ describe Gitlab::Database::MigrationHelpers do
end
describe '#replace_sql' do
- context 'using postgres' do
- before do
- allow(Gitlab::Database).to receive(:mysql?).and_return(false)
- end
-
- it 'builds the sql with correct functions' do
- expect(model.replace_sql(Arel::Table.new(:users)[:first_name], "Alice", "Eve").to_s)
- .to include('regexp_replace')
- end
- end
-
- context 'using mysql' do
- before do
- allow(Gitlab::Database).to receive(:mysql?).and_return(true)
- end
-
- it 'builds the sql with the correct functions' do
- expect(model.replace_sql(Arel::Table.new(:users)[:first_name], "Alice", "Eve").to_s)
- .to include('locate', 'insert')
- end
+ it 'builds the sql with correct functions' do
+ expect(model.replace_sql(Arel::Table.new(:users)[:first_name], "Alice", "Eve").to_s)
+ .to include('regexp_replace')
end
describe 'results' do
@@ -1393,7 +1326,7 @@ describe Gitlab::Database::MigrationHelpers do
.to be_falsy
end
- context 'when an index with a function exists', :postgresql do
+ context 'when an index with a function exists' do
before do
ActiveRecord::Base.connection.execute(
'CREATE INDEX test_index ON projects (LOWER(path));'
diff --git a/spec/lib/gitlab/database/multi_threaded_migration_spec.rb b/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
index 6c45f13bb5a..53c001fbc1b 100644
--- a/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
+++ b/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::MultiThreadedMigration do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index 81419e51635..612c418e8bb 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
index b6096d4faf6..8c4d7e323fa 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :delete do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
index d4d7a83921c..1ccdb1d9447 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :delete do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
index ddd54a669a3..b09258ae227 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'renames child namespaces' do |type|
diff --git a/spec/lib/gitlab/database/sha_attribute_spec.rb b/spec/lib/gitlab/database/sha_attribute_spec.rb
index 778bfa2cc47..c6fc55291f5 100644
--- a/spec/lib/gitlab/database/sha_attribute_spec.rb
+++ b/spec/lib/gitlab/database/sha_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::ShaAttribute do
@@ -10,11 +12,7 @@ describe Gitlab::Database::ShaAttribute do
end
let(:binary_from_db) do
- if Gitlab::Database.postgresql?
- "\\x#{sha}"
- else
- binary_sha
- end
+ "\\x#{sha}"
end
let(:attribute) { described_class.new }
diff --git a/spec/db/importers/common_metrics_importer_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
index a717c8cd04d..eed2a1b7b48 100644
--- a/spec/db/importers/common_metrics_importer_spec.rb
+++ b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
@@ -1,31 +1,14 @@
# frozen_string_literal: true
require 'rails_helper'
-require Rails.root.join("db", "importers", "common_metrics_importer.rb")
-describe Importers::PrometheusMetric do
- let(:existing_group_titles) do
- ::PrometheusMetric::GROUP_DETAILS.each_with_object({}) do |(key, value), memo|
- memo[key] = value[:group_title]
- end
- end
-
- it 'group enum equals ::PrometheusMetric' do
- expect(described_class.groups).to eq(::PrometheusMetric.groups)
- end
-
- it 'GROUP_TITLES equals ::PrometheusMetric' do
- expect(described_class::GROUP_TITLES).to eq(existing_group_titles)
- end
-end
-
-describe Importers::CommonMetricsImporter do
+describe Gitlab::DatabaseImporters::CommonMetrics::Importer do
subject { described_class.new }
context "does import common_metrics.yml" do
let(:groups) { subject.content['panel_groups'] }
- let(:panels) { groups.map { |group| group['panels'] }.flatten }
- let(:metrics) { panels.map { |group| group['metrics'] }.flatten }
+ let(:panels) { groups.flat_map { |group| group['panels'] } }
+ let(:metrics) { panels.flat_map { |group| group['metrics'] } }
let(:metric_ids) { metrics.map { |metric| metric['id'] } }
before do
@@ -104,7 +87,7 @@ describe Importers::CommonMetricsImporter do
let(:query_identifier) { }
it 'raises exception' do
- expect { subject.execute }.to raise_error(described_class::MissingQueryId)
+ expect { subject.execute }.to raise_error(Gitlab::DatabaseImporters::CommonMetrics::Importer::MissingQueryId)
end
end
diff --git a/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
new file mode 100644
index 00000000000..94f544e59b3
--- /dev/null
+++ b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetric do
+ it 'group enum equals ::PrometheusMetric' do
+ expect(described_class.groups).to eq(::PrometheusMetric.groups)
+ end
+
+ it '.group_titles equals ::PrometheusMetric' do
+ existing_group_titles = ::PrometheusMetricEnums.group_details.each_with_object({}) do |(key, value), memo|
+ memo[key] = value[:group_title]
+ end
+ expect(Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetricEnums.group_titles).to eq(existing_group_titles)
+ end
+end
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
new file mode 100644
index 00000000000..b25f0f6b887
--- /dev/null
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -0,0 +1,278 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
+ describe '#execute' do
+ let(:result) { subject.execute! }
+
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ listen_address: 'localhost:9090'
+ }
+ end
+
+ before do
+ stub_config(prometheus: prometheus_settings)
+ end
+
+ context 'without application_settings' do
+ it 'does not fail' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result).to eq(
+ status: :success
+ )
+
+ expect(Project.count).to eq(0)
+ expect(Group.count).to eq(0)
+ end
+ end
+
+ context 'without admin users' do
+ let(:application_setting) { Gitlab::CurrentSettings.current_application_settings }
+
+ before do
+ allow(ApplicationSetting).to receive(:current_without_cache) { application_setting }
+ end
+
+ it 'does not fail' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result).to eq(
+ status: :success
+ )
+
+ expect(Project.count).to eq(0)
+ expect(Group.count).to eq(0)
+ end
+ end
+
+ context 'with admin users' do
+ let(:project) { result[:project] }
+ let(:group) { result[:group] }
+ let(:application_setting) { Gitlab::CurrentSettings.current_application_settings }
+
+ let!(:user) { create(:user, :admin) }
+
+ before do
+ allow(ApplicationSetting).to receive(:current_without_cache) { application_setting }
+ application_setting.allow_local_requests_from_web_hooks_and_services = true
+ end
+
+ shared_examples 'has prometheus service' do |listen_address|
+ it do
+ expect(result[:status]).to eq(:success)
+
+ prometheus = project.prometheus_service
+ expect(prometheus).not_to eq(nil)
+ expect(prometheus.api_url).to eq(listen_address)
+ expect(prometheus.active).to eq(true)
+ expect(prometheus.manual_configuration).to eq(true)
+ end
+ end
+
+ it_behaves_like 'has prometheus service', 'http://localhost:9090'
+
+ it 'creates group' do
+ expect(result[:status]).to eq(:success)
+ expect(group).to be_persisted
+ expect(group.name).to eq('GitLab Instance Administrators')
+ expect(group.path).to start_with('gitlab-instance-administrators')
+ expect(group.path.split('-').last.length).to eq(8)
+ expect(group.visibility_level).to eq(described_class::VISIBILITY_LEVEL)
+ end
+
+ it 'creates project with internal visibility' do
+ expect(result[:status]).to eq(:success)
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ expect(project).to be_persisted
+ end
+
+ it 'creates project with internal visibility even when internal visibility is restricted' do
+ application_setting.restricted_visibility_levels = [Gitlab::VisibilityLevel::INTERNAL]
+
+ expect(result[:status]).to eq(:success)
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ expect(project).to be_persisted
+ end
+
+ it 'creates project with correct name and description' do
+ path = 'administration/monitoring/gitlab_instance_administration_project/index'
+ docs_path = Rails.application.routes.url_helpers.help_page_path(path)
+
+ expect(result[:status]).to eq(:success)
+ expect(project.name).to eq(described_class::PROJECT_NAME)
+ expect(project.description).to eq(
+ 'This project is automatically generated and will be used to help monitor this GitLab instance. ' \
+ "[More information](#{docs_path})"
+ )
+ expect(File).to exist("doc/#{path}.md")
+ end
+
+ it 'adds all admins as maintainers' do
+ admin1 = create(:user, :admin)
+ admin2 = create(:user, :admin)
+ create(:user)
+
+ expect(result[:status]).to eq(:success)
+ expect(project.owner).to eq(group)
+ expect(group.members.collect(&:user)).to contain_exactly(user, admin1, admin2)
+ expect(group.members.collect(&:access_level)).to contain_exactly(
+ Gitlab::Access::OWNER,
+ Gitlab::Access::MAINTAINER,
+ Gitlab::Access::MAINTAINER
+ )
+ end
+
+ it 'saves the project id' do
+ expect(result[:status]).to eq(:success)
+ expect(application_setting.instance_administration_project_id).to eq(project.id)
+ end
+
+ it 'returns error when saving project ID fails' do
+ allow(application_setting).to receive(:update) { false }
+
+ expect { result }.to raise_error(StandardError, 'Could not save project ID')
+ end
+
+ context 'when project already exists' do
+ let(:existing_group) { create(:group) }
+ let(:existing_project) { create(:project, namespace: existing_group) }
+
+ before do
+ admin1 = create(:user, :admin)
+ admin2 = create(:user, :admin)
+
+ existing_group.add_owner(user)
+ existing_group.add_users([admin1, admin2], Gitlab::Access::MAINTAINER)
+
+ application_setting.instance_administration_project_id = existing_project.id
+ end
+
+ it 'does not fail' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result[:status]).to eq(:success)
+
+ expect(Project.count).to eq(1)
+ expect(Group.count).to eq(1)
+ end
+ end
+
+ context 'when local requests from hooks and services are not allowed' do
+ before do
+ application_setting.allow_local_requests_from_web_hooks_and_services = false
+ end
+
+ it_behaves_like 'has prometheus service', 'http://localhost:9090'
+
+ it 'does not overwrite the existing whitelist' do
+ application_setting.outbound_local_requests_whitelist = ['example.com']
+
+ expect(result[:status]).to eq(:success)
+ expect(application_setting.outbound_local_requests_whitelist).to contain_exactly(
+ 'example.com', 'localhost'
+ )
+ end
+ end
+
+ context 'with non default prometheus address' do
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ listen_address: 'https://localhost:9090'
+ }
+ end
+
+ it_behaves_like 'has prometheus service', 'https://localhost:9090'
+ end
+
+ context 'when prometheus setting is not present in gitlab.yml' do
+ before do
+ allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting)
+ end
+
+ it 'does not fail' do
+ expect(result).to include(status: :success)
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus setting is nil' do
+ before do
+ stub_config(prometheus: nil)
+ end
+
+ it 'does not fail' do
+ expect(result).to include(status: :success)
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus setting is disabled in gitlab.yml' do
+ let(:prometheus_settings) do
+ {
+ enable: false,
+ listen_address: 'http://localhost:9090'
+ }
+ end
+
+ it 'does not configure prometheus' do
+ expect(result).to include(status: :success)
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus listen address is blank in gitlab.yml' do
+ let(:prometheus_settings) { { enable: true, listen_address: '' } }
+
+ it 'does not configure prometheus' do
+ expect(result).to include(status: :success)
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when project cannot be created' do
+ let(:project) { build(:project) }
+
+ before do
+ project.errors.add(:base, "Test error")
+
+ expect_next_instance_of(::Projects::CreateService) do |project_create_service|
+ expect(project_create_service).to receive(:execute)
+ .and_return(project)
+ end
+ end
+
+ it 'returns error' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect { result }.to raise_error(StandardError, 'Could not create project')
+ end
+ end
+
+ context 'when user cannot be added to project' do
+ before do
+ subject.instance_variable_set(:@instance_admins, [user, build(:user, :admin)])
+ end
+
+ it 'returns error' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect { result }.to raise_error(StandardError, 'Could not add admins as members')
+ end
+ end
+
+ context 'when prometheus manual configuration cannot be saved' do
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ listen_address: 'httpinvalid://localhost:9090'
+ }
+ end
+
+ it 'returns error' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect { result }.to raise_error(StandardError, 'Could not save prometheus manual configuration')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 5f57cd6b825..8d37de32179 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database do
@@ -15,28 +17,18 @@ describe Gitlab::Database do
it 'returns the name of the adapter' do
expect(described_class.adapter_name).to be_an_instance_of(String)
end
- end
-
- describe '.human_adapter_name' do
- it 'returns PostgreSQL when using PostgreSQL' do
- allow(described_class).to receive(:postgresql?).and_return(true)
-
- expect(described_class.human_adapter_name).to eq('PostgreSQL')
- end
- it 'returns MySQL when using MySQL' do
+ it 'returns Unknown when using anything else' do
allow(described_class).to receive(:postgresql?).and_return(false)
- expect(described_class.human_adapter_name).to eq('MySQL')
+ expect(described_class.human_adapter_name).to eq('Unknown')
end
end
- # These are just simple smoke tests to check if the methods work (regardless
- # of what they may return).
- describe '.mysql?' do
- subject { described_class.mysql? }
-
- it { is_expected.to satisfy { |val| val == true || val == false } }
+ describe '.human_adapter_name' do
+ it 'returns PostgreSQL when using PostgreSQL' do
+ expect(described_class.human_adapter_name).to eq('PostgreSQL')
+ end
end
describe '.postgresql?' do
@@ -52,15 +44,6 @@ describe Gitlab::Database do
described_class.instance_variable_set(:@version, nil)
end
- context "on mysql" do
- it "extracts the version number" do
- allow(described_class).to receive(:database_version)
- .and_return("5.7.12-standard")
-
- expect(described_class.version).to eq '5.7.12-standard'
- end
- end
-
context "on postgresql" do
it "extracts the version number" do
allow(described_class).to receive(:database_version)
@@ -80,21 +63,18 @@ describe Gitlab::Database do
end
describe '.postgresql_9_or_less?' do
- it 'returns false when using MySQL' do
- allow(described_class).to receive(:postgresql?).and_return(false)
-
- expect(described_class.postgresql_9_or_less?).to eq(false)
+ it 'returns true when using postgresql 8.4' do
+ allow(described_class).to receive(:version).and_return('8.4')
+ expect(described_class.postgresql_9_or_less?).to eq(true)
end
it 'returns true when using PostgreSQL 9.6' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.6')
expect(described_class.postgresql_9_or_less?).to eq(true)
end
it 'returns false when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('10')
expect(described_class.postgresql_9_or_less?).to eq(false)
@@ -102,53 +82,33 @@ describe Gitlab::Database do
end
describe '.postgresql_minimum_supported_version?' do
- it 'returns false when not using PostgreSQL' do
- allow(described_class).to receive(:postgresql?).and_return(false)
+ it 'returns false when using PostgreSQL 9.5' do
+ allow(described_class).to receive(:version).and_return('9.5')
expect(described_class.postgresql_minimum_supported_version?).to eq(false)
end
- context 'when using PostgreSQL' do
- before do
- allow(described_class).to receive(:postgresql?).and_return(true)
- end
-
- it 'returns false when using PostgreSQL 9.5' do
- allow(described_class).to receive(:version).and_return('9.5')
-
- expect(described_class.postgresql_minimum_supported_version?).to eq(false)
- end
-
- it 'returns true when using PostgreSQL 9.6' do
- allow(described_class).to receive(:version).and_return('9.6')
+ it 'returns true when using PostgreSQL 9.6' do
+ allow(described_class).to receive(:version).and_return('9.6')
- expect(described_class.postgresql_minimum_supported_version?).to eq(true)
- end
+ expect(described_class.postgresql_minimum_supported_version?).to eq(true)
+ end
- it 'returns true when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:version).and_return('10')
+ it 'returns true when using PostgreSQL 10 or newer' do
+ allow(described_class).to receive(:version).and_return('10')
- expect(described_class.postgresql_minimum_supported_version?).to eq(true)
- end
+ expect(described_class.postgresql_minimum_supported_version?).to eq(true)
end
end
describe '.join_lateral_supported?' do
- it 'returns false when using MySQL' do
- allow(described_class).to receive(:postgresql?).and_return(false)
-
- expect(described_class.join_lateral_supported?).to eq(false)
- end
-
it 'returns false when using PostgreSQL 9.2' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.2.1')
expect(described_class.join_lateral_supported?).to eq(false)
end
it 'returns true when using PostgreSQL 9.3.0 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.3.0')
expect(described_class.join_lateral_supported?).to eq(true)
@@ -156,21 +116,13 @@ describe Gitlab::Database do
end
describe '.replication_slots_supported?' do
- it 'returns false when using MySQL' do
- allow(described_class).to receive(:postgresql?).and_return(false)
-
- expect(described_class.replication_slots_supported?).to eq(false)
- end
-
it 'returns false when using PostgreSQL 9.3' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.3.1')
expect(described_class.replication_slots_supported?).to eq(false)
end
it 'returns true when using PostgreSQL 9.4.0 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.4.0')
expect(described_class.replication_slots_supported?).to eq(true)
@@ -179,14 +131,12 @@ describe Gitlab::Database do
describe '.pg_wal_lsn_diff' do
it 'returns old name when using PostgreSQL 9.6' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.6')
expect(described_class.pg_wal_lsn_diff).to eq('pg_xlog_location_diff')
end
it 'returns new name when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('10')
expect(described_class.pg_wal_lsn_diff).to eq('pg_wal_lsn_diff')
@@ -195,14 +145,12 @@ describe Gitlab::Database do
describe '.pg_current_wal_insert_lsn' do
it 'returns old name when using PostgreSQL 9.6' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.6')
expect(described_class.pg_current_wal_insert_lsn).to eq('pg_current_xlog_insert_location')
end
it 'returns new name when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('10')
expect(described_class.pg_current_wal_insert_lsn).to eq('pg_current_wal_insert_lsn')
@@ -211,14 +159,12 @@ describe Gitlab::Database do
describe '.pg_last_wal_receive_lsn' do
it 'returns old name when using PostgreSQL 9.6' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.6')
expect(described_class.pg_last_wal_receive_lsn).to eq('pg_last_xlog_receive_location')
end
it 'returns new name when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('10')
expect(described_class.pg_last_wal_receive_lsn).to eq('pg_last_wal_receive_lsn')
@@ -227,14 +173,12 @@ describe Gitlab::Database do
describe '.pg_last_wal_replay_lsn' do
it 'returns old name when using PostgreSQL 9.6' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.6')
expect(described_class.pg_last_wal_replay_lsn).to eq('pg_last_xlog_replay_location')
end
it 'returns new name when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('10')
expect(described_class.pg_last_wal_replay_lsn).to eq('pg_last_wal_replay_lsn')
@@ -248,43 +192,13 @@ describe Gitlab::Database do
end
describe '.nulls_last_order' do
- context 'when using PostgreSQL' do
- before do
- expect(described_class).to receive(:postgresql?).and_return(true)
- end
-
- it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column ASC NULLS LAST'}
- it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC NULLS LAST'}
- end
-
- context 'when using MySQL' do
- before do
- expect(described_class).to receive(:postgresql?).and_return(false)
- end
-
- it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column IS NULL, column ASC'}
- it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC'}
- end
+ it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column ASC NULLS LAST'}
+ it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC NULLS LAST'}
end
describe '.nulls_first_order' do
- context 'when using PostgreSQL' do
- before do
- expect(described_class).to receive(:postgresql?).and_return(true)
- end
-
- it { expect(described_class.nulls_first_order('column', 'ASC')).to eq 'column ASC NULLS FIRST'}
- it { expect(described_class.nulls_first_order('column', 'DESC')).to eq 'column DESC NULLS FIRST'}
- end
-
- context 'when using MySQL' do
- before do
- expect(described_class).to receive(:postgresql?).and_return(false)
- end
-
- it { expect(described_class.nulls_first_order('column', 'ASC')).to eq 'column ASC'}
- it { expect(described_class.nulls_first_order('column', 'DESC')).to eq 'column IS NULL, column DESC'}
- end
+ it { expect(described_class.nulls_first_order('column', 'ASC')).to eq 'column ASC NULLS FIRST'}
+ it { expect(described_class.nulls_first_order('column', 'DESC')).to eq 'column DESC NULLS FIRST'}
end
describe '.with_connection_pool' do
@@ -394,10 +308,6 @@ describe Gitlab::Database do
end
context 'when using PostgreSQL' do
- before do
- allow(described_class).to receive(:mysql?).and_return(false)
- end
-
it 'allows the returning of the IDs of the inserted rows' do
result = double(:result, values: [['10']])
@@ -437,6 +347,17 @@ describe Gitlab::Database do
pool.disconnect!
end
end
+
+ it 'allows setting of a custom hostname and port' do
+ pool = described_class.create_connection_pool(5, '127.0.0.1', 5432)
+
+ begin
+ expect(pool.spec.config[:host]).to eq('127.0.0.1')
+ expect(pool.spec.config[:port]).to eq(5432)
+ ensure
+ pool.disconnect!
+ end
+ end
end
describe '.cached_column_exists?' do
@@ -463,31 +384,15 @@ describe Gitlab::Database do
end
describe '#true_value' do
- it 'returns correct value for PostgreSQL' do
- expect(described_class).to receive(:postgresql?).and_return(true)
-
+ it 'returns correct value' do
expect(described_class.true_value).to eq "'t'"
end
-
- it 'returns correct value for MySQL' do
- expect(described_class).to receive(:postgresql?).and_return(false)
-
- expect(described_class.true_value).to eq 1
- end
end
describe '#false_value' do
- it 'returns correct value for PostgreSQL' do
- expect(described_class).to receive(:postgresql?).and_return(true)
-
+ it 'returns correct value' do
expect(described_class.false_value).to eq "'f'"
end
-
- it 'returns correct value for MySQL' do
- expect(described_class).to receive(:postgresql?).and_return(false)
-
- expect(described_class.false_value).to eq 0
- end
end
describe '.read_only?' do
@@ -497,43 +402,32 @@ describe Gitlab::Database do
end
describe '.db_read_only?' do
- context 'when using PostgreSQL' do
- before do
- allow(ActiveRecord::Base.connection).to receive(:execute).and_call_original
- allow(described_class).to receive(:postgresql?).and_return(true)
- end
-
- it 'detects a read only database' do
- allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "t" }])
-
- expect(described_class.db_read_only?).to be_truthy
- end
+ before do
+ allow(ActiveRecord::Base.connection).to receive(:execute).and_call_original
+ end
- it 'detects a read only database' do
- allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => true }])
+ it 'detects a read only database' do
+ allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "t" }])
- expect(described_class.db_read_only?).to be_truthy
- end
+ expect(described_class.db_read_only?).to be_truthy
+ end
- it 'detects a read write database' do
- allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "f" }])
+ it 'detects a read only database' do
+ allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => true }])
- expect(described_class.db_read_only?).to be_falsey
- end
+ expect(described_class.db_read_only?).to be_truthy
+ end
- it 'detects a read write database' do
- allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => false }])
+ it 'detects a read write database' do
+ allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "f" }])
- expect(described_class.db_read_only?).to be_falsey
- end
+ expect(described_class.db_read_only?).to be_falsey
end
- context 'when using MySQL' do
- before do
- expect(described_class).to receive(:postgresql?).and_return(false)
- end
+ it 'detects a read write database' do
+ allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => false }])
- it { expect(described_class.db_read_only?).to be_falsey }
+ expect(described_class.db_read_only?).to be_falsey
end
end
diff --git a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
index 3a93d5e1e97..3eb5db51224 100644
--- a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::CartfileLinker do
diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
index 0ebd8994636..6bef6f57e64 100644
--- a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::ComposerJsonLinker do
diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
index f00f6b47b94..6ecdb0d1247 100644
--- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::GemfileLinker do
diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
index 6c6a5d70576..256fe58925c 100644
--- a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::GemspecLinker do
diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
index ae5ad39ad11..f620d1b590c 100644
--- a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::GodepsJsonLinker do
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
index 9050127af7f..71e9381eaad 100644
--- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::PackageJsonLinker do
diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
index 8f1b523653e..eb81bc07760 100644
--- a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::PodfileLinker do
diff --git a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
index d4a398c5948..938726dd434 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::PodspecJsonLinker do
diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
index bacec830103..540eb2fadfe 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::PodspecLinker do
diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
index ef952b3abd5..957a87985a2 100644
--- a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::RequirementsTxtLinker do
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 10d2f701298..98e46d62ca0 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker do
diff --git a/spec/lib/gitlab/diff/diff_refs_spec.rb b/spec/lib/gitlab/diff/diff_refs_spec.rb
index f9bfb4c469e..e12b46c15ad 100644
--- a/spec/lib/gitlab/diff/diff_refs_spec.rb
+++ b/spec/lib/gitlab/diff/diff_refs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::DiffRefs do
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
index 0697594c725..d89be6fef4e 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::FileCollection::MergeRequestDiff do
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index cc36060f864..716fc8ae987 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::File do
diff --git a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
index 2f99febe04e..2e6eb71d37d 100644
--- a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::Formatters::ImageFormatter do
diff --git a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
index 897dc917f6a..33d4994f5db 100644
--- a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::Formatters::TextFormatter do
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 5d0a603d11d..f5d3d14ccc5 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::Highlight do
diff --git a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
index 7e17437fa2a..a668bb464a4 100644
--- a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::InlineDiffMarkdownMarker do
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
index 97e65318059..26b99870b31 100644
--- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::InlineDiffMarker do
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
index 0a41362f606..fdbee3b4230 100644
--- a/spec/lib/gitlab/diff/inline_diff_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::InlineDiff do
diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb
index 42750bf9ea1..1739bcd14a8 100644
--- a/spec/lib/gitlab/diff/line_mapper_spec.rb
+++ b/spec/lib/gitlab/diff/line_mapper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::LineMapper do
diff --git a/spec/lib/gitlab/diff/line_spec.rb b/spec/lib/gitlab/diff/line_spec.rb
index 76d411973c7..29b9951ba4c 100644
--- a/spec/lib/gitlab/diff/line_spec.rb
+++ b/spec/lib/gitlab/diff/line_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe Gitlab::Diff::Line do
describe '.init_from_hash' do
it 'round-trips correctly with to_hash' do
diff --git a/spec/lib/gitlab/diff/lines_unfolder_spec.rb b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
index 8a470e12d04..3134ff3d817 100644
--- a/spec/lib/gitlab/diff/lines_unfolder_spec.rb
+++ b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
@@ -842,4 +842,37 @@ describe Gitlab::Diff::LinesUnfolder do
end
end
end
+
+ context 'positioned on an image' do
+ let(:position) do
+ Gitlab::Diff::Position.new(
+ base_sha: '1c59dfa64afbea8c721bb09a06a9d326c952ea19',
+ start_sha: '1c59dfa64afbea8c721bb09a06a9d326c952ea19',
+ head_sha: '1487062132228de836236c522fe52fed4980a46c',
+ old_path: 'image.jpg',
+ new_path: 'image.jpg',
+ position_type: 'image'
+ )
+ end
+
+ before do
+ allow(old_blob).to receive(:binary?).and_return(binary?)
+ end
+
+ context 'diff file is not text' do
+ let(:binary?) { true }
+
+ it 'returns nil' do
+ expect(subject.unfolded_diff_lines).to be_nil
+ end
+ end
+
+ context 'diff file is text' do
+ let(:binary?) { false }
+
+ it 'returns nil' do
+ expect(subject.unfolded_diff_lines).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
index e9fc7be366a..7540da71086 100644
--- a/spec/lib/gitlab/diff/parallel_diff_spec.rb
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::ParallelDiff do
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index 80c8c189665..00a446c4e20 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::Parser do
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index aea02d21048..399787635c0 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::Position do
@@ -610,4 +612,17 @@ describe Gitlab::Diff::Position do
it_behaves_like "diff position json"
end
end
+
+ describe "#file_hash" do
+ subject do
+ described_class.new(
+ old_path: "image.jpg",
+ new_path: "image.jpg"
+ )
+ end
+
+ it "returns SHA1 representation of the file_path" do
+ expect(subject.file_hash).to eq(Digest::SHA1.hexdigest(subject.file_path))
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb
new file mode 100644
index 00000000000..900816af53a
--- /dev/null
+++ b/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb
@@ -0,0 +1,238 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::PositionTracer::ImageStrategy do
+ include PositionTracerHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:current_user) { project.owner }
+ let(:file_name) { 'test-file' }
+ let(:new_file_name) { "#{file_name}-new" }
+ let(:second_file_name) { "#{file_name}-2" }
+ let(:branch_name) { 'position-tracer-test' }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, position_type: 'image') }
+
+ let(:tracer) do
+ Gitlab::Diff::PositionTracer.new(
+ project: project,
+ old_diff_refs: old_diff_refs,
+ new_diff_refs: new_diff_refs
+ )
+ end
+
+ let(:strategy) { described_class.new(tracer) }
+
+ subject { strategy.trace(old_position) }
+
+ let(:initial_commit) do
+ project.commit(create_branch(branch_name, 'master')[:branch].name)
+ end
+
+ describe '#trace' do
+ describe 'diff scenarios' do
+ let(:create_file_commit) do
+ initial_commit
+
+ create_file(
+ branch_name,
+ file_name,
+ Base64.encode64('content')
+ )
+ end
+
+ let(:update_file_commit) do
+ create_file_commit
+
+ update_file(
+ branch_name,
+ file_name,
+ Base64.encode64('updatedcontent')
+ )
+ end
+
+ let(:update_file_again_commit) do
+ update_file_commit
+
+ update_file(
+ branch_name,
+ file_name,
+ Base64.encode64('updatedcontentagain')
+ )
+ end
+
+ let(:delete_file_commit) do
+ create_file_commit
+ delete_file(branch_name, file_name)
+ end
+
+ let(:rename_file_commit) do
+ delete_file_commit
+
+ create_file(
+ branch_name,
+ new_file_name,
+ Base64.encode64('renamedcontent')
+ )
+ end
+
+ let(:create_second_file_commit) do
+ create_file_commit
+
+ create_file(
+ branch_name,
+ second_file_name,
+ Base64.encode64('morecontent')
+ )
+ end
+
+ let(:create_another_file_commit) do
+ create_file(
+ branch_name,
+ second_file_name,
+ Base64.encode64('morecontent')
+ )
+ end
+
+ let(:update_another_file_commit) do
+ update_file(
+ branch_name,
+ second_file_name,
+ Base64.encode64('updatedmorecontent')
+ )
+ end
+
+ context 'when the file was created in the old diff' do
+ context 'when the file is unchanged between the old and the new diff' do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) }
+
+ it 'returns the new position' do
+ expect_new_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+
+ context 'when the file was updated between the old and the new diff' do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_file_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
+
+ it 'returns the position of the change' do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+
+ context 'when the file was renamed in between the old and the new diff' do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, rename_file_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, rename_file_commit) }
+
+ it 'returns the position of the change' do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+
+ context 'when the file was removed in between the old and the new diff' do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, delete_file_commit) }
+
+ it 'returns the position of the change' do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+
+ context 'when the file is unchanged in the new diff' do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(create_another_file_commit, update_another_file_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, create_another_file_commit) }
+
+ it 'returns the position of the change' do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+ end
+
+ context 'when the file was changed in the old diff' do
+ context 'when the file is unchanged in between the old and the new diff' do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, create_second_file_commit) }
+
+ it 'returns the new position' do
+ expect_new_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+
+ context 'when the file was updated in between the old and the new diff' do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) }
+
+ it 'returns the position of the change' do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+
+ context 'when the file was renamed in between the old and the new diff' do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, rename_file_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_commit, rename_file_commit) }
+
+ it 'returns the position of the change' do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+
+ context 'when the file was removed in between the old and the new diff' do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_commit, delete_file_commit) }
+
+ it 'returns the position of the change' do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+
+ context 'when the file is unchanged in the new diff' do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
+ let(:new_diff_refs) { diff_refs(create_another_file_commit, update_another_file_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, create_another_file_commit) }
+
+ it 'returns the position of the change' do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
new file mode 100644
index 00000000000..7f4902c5b86
--- /dev/null
+++ b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
@@ -0,0 +1,1805 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::PositionTracer::LineStrategy do
+ # Douwe's diary New York City, 2016-06-28
+ # --------------------------------------------------------------------------
+ #
+ # Dear diary,
+ #
+ # Ideally, we would have a test for every single diff scenario that can
+ # occur and that the PositionTracer should correctly trace a position
+ # through, across the following variables:
+ #
+ # - Old diff file type: created, changed, renamed, deleted, unchanged (5)
+ # - Old diff line type: added, removed, unchanged (3)
+ # - New diff file type: created, changed, renamed, deleted, unchanged (5)
+ # - New diff line type: added, removed, unchanged (3)
+ # - Old-to-new diff line change: kept, moved, undone (3)
+ #
+ # This adds up to 5 * 3 * 5 * 3 * 3 = 675 different potential scenarios,
+ # and 675 different tests to cover them all. In reality, it would be fewer,
+ # since one cannot have a removed line in a created file diff, for example,
+ # but for the sake of this diary entry, let's be pessimistic.
+ #
+ # Writing these tests is a manual and time consuming process, as every test
+ # requires the manual construction or finding of a combination of diffs that
+ # create the exact diff scenario we are looking for, and can take between
+ # 1 and 10 minutes, depending on the farfetchedness of the scenario and
+ # complexity of creating it.
+ #
+ # This means that writing tests to cover all of these scenarios would end up
+ # taking between 11 and 112 hours in total, which I do not believe is the
+ # best use of my time.
+ #
+ # A better course of action would be to think of scenarios that are likely
+ # to occur, but also potentially tricky to trace correctly, and only cover
+ # those, with a few more obvious scenarios thrown in to cover our bases.
+ #
+ # Unfortunately, I only came to the above realization once I was about
+ # 1/5th of the way through the process of writing ALL THE SPECS, having
+ # already wasted about 3 hours trying to be thorough.
+ #
+ # I did find 2 bugs while writing those though, so that's good.
+ #
+ # In any case, all of this means that the tests below will be extremely
+ # (excessively, unjustifiably) thorough for scenarios where "the file was
+ # created in the old diff" and then drop off to comparatively lackluster
+ # testing of other scenarios.
+ #
+ # I did still try to cover most of the obvious and potentially tricky
+ # scenarios, though.
+
+ include RepoHelpers
+ include PositionTracerHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:current_user) { project.owner }
+ let(:repository) { project.repository }
+ let(:file_name) { "test-file" }
+ let(:new_file_name) { "#{file_name}-new" }
+ let(:second_file_name) { "#{file_name}-2" }
+ let(:branch_name) { "position-tracer-test" }
+
+ let(:old_diff_refs) { raise NotImplementedError }
+ let(:new_diff_refs) { raise NotImplementedError }
+ let(:change_diff_refs) { raise NotImplementedError }
+ let(:old_position) { raise NotImplementedError }
+
+ let(:tracer) do
+ Gitlab::Diff::PositionTracer.new(
+ project: project,
+ old_diff_refs: old_diff_refs,
+ new_diff_refs: new_diff_refs
+ )
+ end
+
+ let(:strategy) { described_class.new(tracer) }
+
+ subject { strategy.trace(old_position) }
+
+ let(:initial_commit) do
+ project.commit(create_branch(branch_name, 'master')[:branch].name)
+ end
+
+ describe "#trace" do
+ describe "diff scenarios" do
+ let(:create_file_commit) do
+ initial_commit
+
+ create_file(
+ branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ A
+ B
+ C
+ CONTENT
+ )
+ end
+
+ let(:create_second_file_commit) do
+ create_file_commit
+
+ create_file(
+ branch_name,
+ second_file_name,
+ <<-CONTENT.strip_heredoc
+ D
+ E
+ CONTENT
+ )
+ end
+
+ let(:update_line_commit) do
+ create_second_file_commit
+
+ update_file(
+ branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ A
+ BB
+ C
+ CONTENT
+ )
+ end
+
+ let(:update_second_file_line_commit) do
+ update_line_commit
+
+ update_file(
+ branch_name,
+ second_file_name,
+ <<-CONTENT.strip_heredoc
+ D
+ EE
+ CONTENT
+ )
+ end
+
+ let(:move_line_commit) do
+ update_second_file_line_commit
+
+ update_file(
+ branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ BB
+ A
+ C
+ CONTENT
+ )
+ end
+
+ let(:add_second_file_line_commit) do
+ move_line_commit
+
+ update_file(
+ branch_name,
+ second_file_name,
+ <<-CONTENT.strip_heredoc
+ D
+ EE
+ F
+ CONTENT
+ )
+ end
+
+ let(:move_second_file_line_commit) do
+ add_second_file_line_commit
+
+ update_file(
+ branch_name,
+ second_file_name,
+ <<-CONTENT.strip_heredoc
+ D
+ F
+ EE
+ CONTENT
+ )
+ end
+
+ let(:delete_line_commit) do
+ move_second_file_line_commit
+
+ update_file(
+ branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ BB
+ A
+ CONTENT
+ )
+ end
+
+ let(:delete_second_file_line_commit) do
+ delete_line_commit
+
+ update_file(
+ branch_name,
+ second_file_name,
+ <<-CONTENT.strip_heredoc
+ D
+ F
+ CONTENT
+ )
+ end
+
+ let(:delete_file_commit) do
+ delete_second_file_line_commit
+
+ delete_file(branch_name, file_name)
+ end
+
+ let(:rename_file_commit) do
+ delete_file_commit
+
+ create_file(
+ branch_name,
+ new_file_name,
+ <<-CONTENT.strip_heredoc
+ BB
+ A
+ CONTENT
+ )
+ end
+
+ let(:update_line_again_commit) do
+ rename_file_commit
+
+ update_file(
+ branch_name,
+ new_file_name,
+ <<-CONTENT.strip_heredoc
+ BB
+ AA
+ CONTENT
+ )
+ end
+
+ let(:move_line_again_commit) do
+ update_line_again_commit
+
+ update_file(
+ branch_name,
+ new_file_name,
+ <<-CONTENT.strip_heredoc
+ AA
+ BB
+ CONTENT
+ )
+ end
+
+ let(:delete_line_again_commit) do
+ move_line_again_commit
+
+ update_file(
+ branch_name,
+ new_file_name,
+ <<-CONTENT.strip_heredoc
+ AA
+ CONTENT
+ )
+ end
+
+ context "when the file was created in the old diff" do
+ context "when the file is created in the new diff" do
+ context "when the position pointed at an added line in the old diff" do
+ context "when the file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + A
+ # 2 + B
+ # 3 + C
+ #
+ # new diff:
+ # 1 + A
+ # 2 + B
+ # 3 + C
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when the file's content was changed between the old and the new diff" do
+ context "when that line was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 1) }
+
+ # old diff:
+ # 1 + A
+ # 2 + B
+ # 3 + C
+ #
+ # new diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when that line was moved between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+ #
+ # new diff:
+ # 1 + BB
+ # 2 + A
+ # 3 + C
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ new_line: 1
+ )
+ end
+ end
+
+ context "when that line was changed between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + A
+ # 2 + B
+ # 3 + C
+ #
+ # new diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
+ end
+ end
+
+ context "when that line was deleted between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
+ let(:change_diff_refs) { diff_refs(update_line_commit, delete_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 3) }
+
+ # old diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+ #
+ # new diff:
+ # 1 + A
+ # 2 + BB
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
+ end
+ end
+ end
+ end
+ end
+
+ context "when the file is changed in the new diff" do
+ context "when the position pointed at an added line in the old diff" do
+ context "when the file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 1) }
+
+ # old diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+ #
+ # new diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when the file's content was changed between the old and the new diff" do
+ context "when that line was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 3) }
+
+ # old diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+ #
+ # new diff:
+ # 1 - A
+ # 2 1 BB
+ # 2 + A
+ # 3 3 C
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when that line was moved between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+ #
+ # new diff:
+ # 1 - A
+ # 2 1 BB
+ # 2 + A
+ # 3 3 C
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ new_line: 1
+ )
+ end
+ end
+
+ context "when that line was changed between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + A
+ # 2 + B
+ # 3 + C
+ #
+ # new diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
+ end
+ end
+
+ context "when that line was deleted between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
+ let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 3) }
+
+ # old diff:
+ # 1 + BB
+ # 2 + A
+ # 3 + C
+ #
+ # new diff:
+ # 1 1 BB
+ # 2 2 A
+ # 3 - C
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
+ end
+ end
+ end
+ end
+ end
+
+ context "when the file is renamed in the new diff" do
+ context "when the position pointed at an added line in the old diff" do
+ context "when the file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
+ let(:new_diff_refs) { diff_refs(delete_line_commit, rename_file_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + BB
+ # 2 + A
+ #
+ # new diff:
+ # file_name -> new_file_name
+ # 1 1 BB
+ # 2 2 A
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: nil,
+ new_line: 2
+ )
+ end
+ end
+
+ context "when the file's content was changed between the old and the new diff" do
+ context "when that line was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
+ let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 1) }
+
+ # old diff:
+ # 1 + BB
+ # 2 + A
+ #
+ # new diff:
+ # file_name -> new_file_name
+ # 1 1 BB
+ # 2 - A
+ # 2 + AA
+
+ it "returns the new position" do
+ expect_new_position(
+ old_path: file_name,
+ new_path: new_file_name,
+ old_line: old_position.new_line,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when that line was moved between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
+ let(:new_diff_refs) { diff_refs(delete_line_commit, move_line_again_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 1) }
+
+ # old diff:
+ # 1 + BB
+ # 2 + A
+ #
+ # new diff:
+ # file_name -> new_file_name
+ # 1 + AA
+ # 1 2 BB
+ # 2 - A
+
+ it "returns the new position" do
+ expect_new_position(
+ old_path: file_name,
+ new_path: new_file_name,
+ old_line: 1,
+ new_line: 2
+ )
+ end
+ end
+
+ context "when that line was changed between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
+ let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) }
+ let(:change_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + BB
+ # 2 + A
+ #
+ # new diff:
+ # file_name -> new_file_name
+ # 1 1 BB
+ # 2 - A
+ # 2 + AA
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: new_file_name,
+ old_line: 2,
+ new_line: nil
+ )
+ end
+ end
+ end
+ end
+ end
+
+ context "when the file is deleted in the new diff" do
+ context "when the position pointed at an added line in the old diff" do
+ context "when the file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
+ let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + BB
+ # 2 + A
+ #
+ # new diff:
+ # 1 - BB
+ # 2 - A
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
+ end
+ end
+
+ context "when the file's content was changed between the old and the new diff" do
+ context "when that line was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
+ let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + BB
+ # 2 + A
+ # 3 + C
+ #
+ # new diff:
+ # 1 - BB
+ # 2 - A
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
+ end
+ end
+
+ context "when that line was moved between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(move_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(update_line_commit, delete_file_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+ #
+ # new diff:
+ # 1 - BB
+ # 2 - A
+ # 3 - C
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
+ end
+ end
+
+ context "when that line was changed between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(update_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, delete_file_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + A
+ # 2 + B
+ # 3 + C
+ #
+ # new diff:
+ # 1 - A
+ # 2 - BB
+ # 3 - C
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
+ end
+ end
+
+ context "when that line was deleted between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
+ let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 3) }
+
+ # old diff:
+ # 1 + BB
+ # 2 + A
+ # 3 + C
+ #
+ # new diff:
+ # 1 - BB
+ # 2 - A
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
+ end
+ end
+ end
+ end
+ end
+
+ context "when the file is unchanged in the new diff" do
+ context "when the position pointed at an added line in the old diff" do
+ context "when the file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, create_second_file_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + A
+ # 2 + B
+ # 3 + C
+ #
+ # new diff:
+ # 1 1 A
+ # 2 2 B
+ # 3 3 C
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: nil,
+ new_line: 2
+ )
+ end
+ end
+
+ context "when the file's content was changed between the old and the new diff" do
+ context "when that line was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 1) }
+
+ # old diff:
+ # 1 + A
+ # 2 + B
+ # 3 + C
+ #
+ # new diff:
+ # 1 1 A
+ # 2 2 BB
+ # 3 3 C
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: nil,
+ new_line: 1
+ )
+ end
+ end
+
+ context "when that line was moved between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(move_line_commit, move_second_file_line_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, move_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+ #
+ # new diff:
+ # 1 1 BB
+ # 2 2 A
+ # 3 3 C
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: nil,
+ new_line: 1
+ )
+ end
+ end
+
+ context "when that line was changed between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 + A
+ # 2 + B
+ # 3 + C
+ #
+ # new diff:
+ # 1 1 A
+ # 2 2 BB
+ # 3 3 C
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
+ end
+ end
+
+ context "when that line was deleted between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
+ let(:new_diff_refs) { diff_refs(delete_line_commit, delete_second_file_line_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_second_file_line_commit) }
+ let(:old_position) { position(new_path: file_name, new_line: 3) }
+
+ # old diff:
+ # 1 + BB
+ # 2 + A
+ # 3 + C
+ #
+ # new diff:
+ # 1 1 BB
+ # 2 2 A
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
+ end
+ end
+ end
+ end
+ end
+ end
+
+ context "when the file was changed in the old diff" do
+ context "when the file is created in the new diff" do
+ context "when the position pointed at an added line in the old diff" do
+ context "when the file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ #
+ # new diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ old_line: nil,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when the file's content was changed between the old and the new diff" do
+ context "when that line was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) }
+
+ # old diff:
+ # 1 + BB
+ # 1 2 A
+ # 2 - B
+ # 3 3 C
+ #
+ # new diff:
+ # 1 + BB
+ # 2 + A
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ old_line: nil,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when that line was moved between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ #
+ # new diff:
+ # 1 + BB
+ # 2 + A
+ # 3 + C
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ old_line: nil,
+ new_line: 1
+ )
+ end
+ end
+
+ context "when that line was changed or deleted between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, create_file_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) }
+
+ # old diff:
+ # 1 + BB
+ # 1 2 A
+ # 2 - B
+ # 3 3 C
+ #
+ # new diff:
+ # 1 + A
+ # 2 + B
+ # 3 + C
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 1,
+ new_line: nil
+ )
+ end
+ end
+ end
+ end
+
+ context "when the position pointed at a deleted line in the old diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, initial_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ #
+ # new diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
+ end
+ end
+
+ context "when the position pointed at an unchanged line in the old diff" do
+ context "when the file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 1, new_line: 1) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ #
+ # new diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ old_line: nil,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when the file's content was changed between the old and the new diff" do
+ context "when that line was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 1, new_line: 2) }
+
+ # old diff:
+ # 1 + BB
+ # 1 2 A
+ # 2 - B
+ # 3 3 C
+ #
+ # new diff:
+ # 1 + BB
+ # 2 + A
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ old_line: nil,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when that line was moved between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2, new_line: 2) }
+
+ # old diff:
+ # 1 1 BB
+ # 2 2 A
+ # 3 - C
+ #
+ # new diff:
+ # 1 + A
+ # 2 + BB
+ # 3 + C
+
+ it "returns the new position" do
+ expect_new_position(
+ new_path: old_position.new_path,
+ old_line: nil,
+ new_line: 1
+ )
+ end
+ end
+
+ context "when that line was changed or deleted between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
+ let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 3, new_line: 3) }
+
+ # old diff:
+ # 1 + BB
+ # 1 2 A
+ # 2 - B
+ # 3 3 C
+ #
+ # new diff:
+ # 1 + A
+ # 2 + B
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
+ end
+ end
+ end
+ end
+ end
+
+ context "when the file is changed in the new diff" do
+ context "when the position pointed at an added line in the old diff" do
+ context "when the file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ #
+ # new diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+
+ it "returns the new position" do
+ expect_new_position(
+ old_path: old_position.old_path,
+ new_path: old_position.new_path,
+ old_line: nil,
+ new_line: old_position.new_line
+ )
+ end
+ end
+
+ context "when the file's content was changed between the old and the new diff" do
+ context "when that line was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
+ let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) }
+
+ # old diff:
+ # 1 + BB
+ # 1 2 A
+ # 2 - B
+ # 3 3 C
+ #
+ # new diff:
+ # 1 1 BB
+ # 2 2 A
+ # 3 - C
+
+ it "returns the new position" do
+ expect_new_position(
+ old_path: old_position.old_path,
+ new_path: old_position.new_path,
+ old_line: 1,
+ new_line: 1
+ )
+ end
+ end
+
+ context "when that line was moved between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ #
+ # new diff:
+ # 1 - A
+ # 2 1 BB
+ # 2 + A
+ # 3 3 C
+
+ it "returns the new position" do
+ expect_new_position(
+ old_path: old_position.old_path,
+ new_path: old_position.new_path,
+ old_line: 2,
+ new_line: 1
+ )
+ end
+ end
+
+ context "when that line was changed or deleted between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, update_line_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) }
+
+ # old diff:
+ # 1 + BB
+ # 1 2 A
+ # 2 - B
+ # 3 3 C
+ #
+ # new diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 1,
+ new_line: nil
+ )
+ end
+ end
+ end
+ end
+
+ context "when the position pointed at a deleted line in the old diff" do
+ context "when the file's content was unchanged between the old and the new diff" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) }
+ let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ #
+ # new diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+
+ it "returns the new position" do
+ expect_new_position(
+ old_path: old_position.old_path,
+ new_path: old_position.new_path,
+ old_line: old_position.old_line,
+ new_line: nil
+ )
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe "typical use scenarios" do
+ let(:second_branch_name) { "#{branch_name}-2" }
+
+ def expect_new_positions(old_attrs, new_attrs)
+ old_positions = old_attrs.map do |old_attrs|
+ position(old_attrs)
+ end
+
+ new_positions = old_positions.map do |old_position|
+ strategy.trace(old_position)
+ end
+
+ aggregate_failures do
+ new_positions.zip(new_attrs).each do |new_position, new_attrs|
+ if new_attrs&.delete(:change)
+ expect_change_position(new_attrs, new_position)
+ else
+ expect_new_position(new_attrs, new_position)
+ end
+ end
+ end
+ end
+
+ let(:create_file_commit) do
+ initial_commit
+
+ create_file(
+ branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ A
+ B
+ C
+ D
+ E
+ F
+ CONTENT
+ )
+ end
+
+ let(:second_create_file_commit) do
+ create_file_commit
+
+ create_branch(second_branch_name, branch_name)
+
+ update_file(
+ second_branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ Z
+ Z
+ Z
+ A
+ B
+ C
+ D
+ E
+ F
+ CONTENT
+ )
+ end
+
+ let(:update_file_commit) do
+ second_create_file_commit
+
+ update_file(
+ branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ A
+ C
+ DD
+ E
+ F
+ G
+ CONTENT
+ )
+ end
+
+ let(:update_file_again_commit) do
+ update_file_commit
+
+ update_file(
+ branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ A
+ BB
+ C
+ D
+ E
+ FF
+ G
+ CONTENT
+ )
+ end
+
+ describe "simple push of new commit" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 3 2 C
+ # 4 - D
+ # 3 + DD
+ # 5 4 E
+ # 6 5 F
+ # 6 + G
+ #
+ # new diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ # 4 4 D
+ # 5 5 E
+ # 6 - F
+ # 6 + FF
+ # 7 + G
+
+ it "returns the new positions" do
+ old_position_attrs = [
+ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A
+ { old_path: file_name, old_line: 2 }, # - B
+ { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, # C
+ { old_path: file_name, old_line: 4 }, # - D
+ { new_path: file_name, new_line: 3 }, # + DD
+ { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, # E
+ { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, # F
+ { new_path: file_name, new_line: 6 } # + G
+ ]
+
+ new_position_attrs = [
+ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
+ { old_path: file_name, old_line: 2 },
+ { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 },
+ { new_path: file_name, new_line: 4, change: true },
+ { new_path: file_name, old_line: 3, change: true },
+ { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 },
+ { new_path: file_name, old_line: 5, change: true },
+ { new_path: file_name, new_line: 7 }
+ ]
+
+ expect_new_positions(old_position_attrs, new_position_attrs)
+ end
+ end
+
+ describe "force push to overwrite last commit" do
+ let(:second_create_file_commit) do
+ create_file_commit
+
+ create_branch(second_branch_name, branch_name)
+
+ update_file(
+ second_branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ A
+ BB
+ C
+ D
+ E
+ FF
+ G
+ CONTENT
+ )
+ end
+
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, second_create_file_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_commit, second_create_file_commit) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 3 2 C
+ # 4 - D
+ # 3 + DD
+ # 5 4 E
+ # 6 5 F
+ # 6 + G
+ #
+ # new diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ # 4 4 D
+ # 5 5 E
+ # 6 - F
+ # 6 + FF
+ # 7 + G
+
+ it "returns the new positions" do
+ old_position_attrs = [
+ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A
+ { old_path: file_name, old_line: 2 }, # - B
+ { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, # C
+ { old_path: file_name, old_line: 4 }, # - D
+ { new_path: file_name, new_line: 3 }, # + DD
+ { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, # E
+ { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, # F
+ { new_path: file_name, new_line: 6 } # + G
+ ]
+
+ new_position_attrs = [
+ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
+ { old_path: file_name, old_line: 2 },
+ { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 },
+ { new_path: file_name, new_line: 4, change: true },
+ { old_path: file_name, old_line: 3, change: true },
+ { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 },
+ { old_path: file_name, old_line: 5, change: true },
+ { new_path: file_name, new_line: 7 }
+ ]
+
+ expect_new_positions(old_position_attrs, new_position_attrs)
+ end
+ end
+
+ describe "force push to delete last commit" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_again_commit, update_file_commit) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ # 4 4 D
+ # 5 5 E
+ # 6 - F
+ # 6 + FF
+ # 7 + G
+ #
+ # new diff:
+ # 1 1 A
+ # 2 - B
+ # 3 2 C
+ # 4 - D
+ # 3 + DD
+ # 5 4 E
+ # 6 5 F
+ # 6 + G
+
+ it "returns the new positions" do
+ old_position_attrs = [
+ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A
+ { old_path: file_name, old_line: 2 }, # - B
+ { new_path: file_name, new_line: 2 }, # + BB
+ { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C
+ { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D
+ { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E
+ { old_path: file_name, old_line: 6 }, # - F
+ { new_path: file_name, new_line: 6 }, # + FF
+ { new_path: file_name, new_line: 7 } # + G
+ ]
+
+ new_position_attrs = [
+ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
+ { old_path: file_name, old_line: 2 },
+ { old_path: file_name, old_line: 2, change: true },
+ { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 },
+ { old_path: file_name, old_line: 4, change: true },
+ { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 },
+ { new_path: file_name, new_line: 5, change: true },
+ { old_path: file_name, old_line: 6, change: true },
+ { new_path: file_name, new_line: 6 }
+ ]
+
+ expect_new_positions(old_position_attrs, new_position_attrs)
+ end
+ end
+
+ describe "rebase on top of target branch" do
+ let(:second_update_file_commit) do
+ update_file_commit
+
+ update_file(
+ second_branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ Z
+ Z
+ Z
+ A
+ C
+ DD
+ E
+ F
+ G
+ CONTENT
+ )
+ end
+
+ let(:update_file_again_commit) do
+ second_update_file_commit
+
+ update_file(
+ branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ A
+ BB
+ C
+ D
+ E
+ FF
+ G
+ CONTENT
+ )
+ end
+
+ let(:overwrite_update_file_again_commit) do
+ update_file_again_commit
+
+ update_file(
+ second_branch_name,
+ file_name,
+ <<-CONTENT.strip_heredoc
+ Z
+ Z
+ Z
+ A
+ BB
+ C
+ D
+ E
+ FF
+ G
+ CONTENT
+ )
+ end
+
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, overwrite_update_file_again_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_again_commit, overwrite_update_file_again_commit) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ # 4 4 D
+ # 5 5 E
+ # 6 - F
+ # 6 + FF
+ # 7 + G
+ #
+ # new diff:
+ # 1 + Z
+ # 2 + Z
+ # 3 + Z
+ # 1 4 A
+ # 2 - B
+ # 5 + BB
+ # 3 6 C
+ # 4 7 D
+ # 5 8 E
+ # 6 - F
+ # 9 + FF
+ # 0 + G
+
+ it "returns the new positions" do
+ old_position_attrs = [
+ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A
+ { old_path: file_name, old_line: 2 }, # - B
+ { new_path: file_name, new_line: 2 }, # + BB
+ { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C
+ { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D
+ { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E
+ { old_path: file_name, old_line: 6 }, # - F
+ { new_path: file_name, new_line: 6 }, # + FF
+ { new_path: file_name, new_line: 7 } # + G
+ ]
+
+ new_position_attrs = [
+ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 4 }, # A
+ { old_path: file_name, old_line: 2 }, # - B
+ { new_path: file_name, new_line: 5 }, # + BB
+ { old_path: file_name, new_path: file_name, old_line: 3, new_line: 6 }, # C
+ { old_path: file_name, new_path: file_name, old_line: 4, new_line: 7 }, # D
+ { old_path: file_name, new_path: file_name, old_line: 5, new_line: 8 }, # E
+ { old_path: file_name, old_line: 6 }, # - F
+ { new_path: file_name, new_line: 9 }, # + FF
+ { new_path: file_name, new_line: 10 } # + G
+ ]
+
+ expect_new_positions(old_position_attrs, new_position_attrs)
+ end
+ end
+
+ describe "merge of target branch" do
+ let(:merge_commit) do
+ second_create_file_commit
+
+ merge_request = create(:merge_request, source_branch: second_branch_name, target_branch: branch_name, source_project: project)
+
+ repository.merge(current_user, merge_request.diff_head_sha, merge_request, "Merge branches")
+
+ project.commit(branch_name)
+ end
+
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
+ let(:new_diff_refs) { diff_refs(create_file_commit, merge_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_again_commit, merge_commit) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ # 4 4 D
+ # 5 5 E
+ # 6 - F
+ # 6 + FF
+ # 7 + G
+ #
+ # new diff:
+ # 1 + Z
+ # 2 + Z
+ # 3 + Z
+ # 1 4 A
+ # 2 - B
+ # 5 + BB
+ # 3 6 C
+ # 4 7 D
+ # 5 8 E
+ # 6 - F
+ # 9 + FF
+ # 0 + G
+
+ it "returns the new positions" do
+ old_position_attrs = [
+ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A
+ { old_path: file_name, old_line: 2 }, # - B
+ { new_path: file_name, new_line: 2 }, # + BB
+ { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C
+ { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D
+ { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E
+ { old_path: file_name, old_line: 6 }, # - F
+ { new_path: file_name, new_line: 6 }, # + FF
+ { new_path: file_name, new_line: 7 } # + G
+ ]
+
+ new_position_attrs = [
+ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 4 }, # A
+ { old_path: file_name, old_line: 2 }, # - B
+ { new_path: file_name, new_line: 5 }, # + BB
+ { old_path: file_name, new_path: file_name, old_line: 3, new_line: 6 }, # C
+ { old_path: file_name, new_path: file_name, old_line: 4, new_line: 7 }, # D
+ { old_path: file_name, new_path: file_name, old_line: 5, new_line: 8 }, # E
+ { old_path: file_name, old_line: 6 }, # - F
+ { new_path: file_name, new_line: 9 }, # + FF
+ { new_path: file_name, new_line: 10 } # + G
+ ]
+
+ expect_new_positions(old_position_attrs, new_position_attrs)
+ end
+ end
+
+ describe "changing target branch" do
+ let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
+ let(:new_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
+
+ # old diff:
+ # 1 1 A
+ # 2 - B
+ # 2 + BB
+ # 3 3 C
+ # 4 4 D
+ # 5 5 E
+ # 6 - F
+ # 6 + FF
+ # 7 + G
+ #
+ # new diff:
+ # 1 1 A
+ # 2 + BB
+ # 2 3 C
+ # 3 - DD
+ # 4 + D
+ # 4 5 E
+ # 5 - F
+ # 6 + FF
+ # 7 G
+
+ it "returns the new positions" do
+ old_position_attrs = [
+ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A
+ { old_path: file_name, old_line: 2 }, # - B
+ { new_path: file_name, new_line: 2 }, # + BB
+ { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C
+ { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D
+ { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E
+ { old_path: file_name, old_line: 6 }, # - F
+ { new_path: file_name, new_line: 6 }, # + FF
+ { new_path: file_name, new_line: 7 } # + G
+ ]
+
+ new_position_attrs = [
+ { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
+ { old_path: file_name, old_line: 2, change: true },
+ { new_path: file_name, new_line: 2 },
+ { old_path: file_name, new_path: file_name, old_line: 2, new_line: 3 },
+ { new_path: file_name, new_line: 4 },
+ { old_path: file_name, new_path: file_name, old_line: 4, new_line: 5 },
+ { old_path: file_name, old_line: 5 },
+ { new_path: file_name, new_line: 6 },
+ { new_path: file_name, new_line: 7 }
+ ]
+
+ expect_new_positions(old_position_attrs, new_position_attrs)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index 866550753a8..47d78e0b18c 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -1,1896 +1,100 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::PositionTracer do
- # Douwe's diary New York City, 2016-06-28
- # --------------------------------------------------------------------------
- #
- # Dear diary,
- #
- # Ideally, we would have a test for every single diff scenario that can
- # occur and that the PositionTracer should correctly trace a position
- # through, across the following variables:
- #
- # - Old diff file type: created, changed, renamed, deleted, unchanged (5)
- # - Old diff line type: added, removed, unchanged (3)
- # - New diff file type: created, changed, renamed, deleted, unchanged (5)
- # - New diff line type: added, removed, unchanged (3)
- # - Old-to-new diff line change: kept, moved, undone (3)
- #
- # This adds up to 5 * 3 * 5 * 3 * 3 = 675 different potential scenarios,
- # and 675 different tests to cover them all. In reality, it would be fewer,
- # since one cannot have a removed line in a created file diff, for example,
- # but for the sake of this diary entry, let's be pessimistic.
- #
- # Writing these tests is a manual and time consuming process, as every test
- # requires the manual construction or finding of a combination of diffs that
- # create the exact diff scenario we are looking for, and can take between
- # 1 and 10 minutes, depending on the farfetchedness of the scenario and
- # complexity of creating it.
- #
- # This means that writing tests to cover all of these scenarios would end up
- # taking between 11 and 112 hours in total, which I do not believe is the
- # best use of my time.
- #
- # A better course of action would be to think of scenarios that are likely
- # to occur, but also potentially tricky to trace correctly, and only cover
- # those, with a few more obvious scenarios thrown in to cover our bases.
- #
- # Unfortunately, I only came to the above realization once I was about
- # 1/5th of the way through the process of writing ALL THE SPECS, having
- # already wasted about 3 hours trying to be thorough.
- #
- # I did find 2 bugs while writing those though, so that's good.
- #
- # In any case, all of this means that the tests below will be extremely
- # (excessively, unjustifiably) thorough for scenarios where "the file was
- # created in the old diff" and then drop off to comparatively lackluster
- # testing of other scenarios.
- #
- # I did still try to cover most of the obvious and potentially tricky
- # scenarios, though.
-
- include RepoHelpers
-
- let(:project) { create(:project, :repository) }
- let(:current_user) { project.owner }
- let(:repository) { project.repository }
- let(:file_name) { "test-file" }
- let(:new_file_name) { "#{file_name}-new" }
- let(:second_file_name) { "#{file_name}-2" }
- let(:branch_name) { "position-tracer-test" }
-
- let(:old_diff_refs) { raise NotImplementedError }
- let(:new_diff_refs) { raise NotImplementedError }
- let(:change_diff_refs) { raise NotImplementedError }
- let(:old_position) { raise NotImplementedError }
-
- let(:position_tracer) { described_class.new(project: project, old_diff_refs: old_diff_refs, new_diff_refs: new_diff_refs) }
- subject { position_tracer.trace(old_position) }
-
- def diff_refs(base_commit, head_commit)
- Gitlab::Diff::DiffRefs.new(base_sha: base_commit.id, head_sha: head_commit.id)
- end
-
- def text_position_attrs
- [:old_line, :new_line]
- end
+ include PositionTracerHelpers
- def position(attrs = {})
- attrs.reverse_merge!(
- diff_refs: old_diff_refs
+ subject do
+ described_class.new(
+ project: project,
+ old_diff_refs: old_diff_refs,
+ new_diff_refs: new_diff_refs
)
- Gitlab::Diff::Position.new(attrs)
end
- def expect_new_position(attrs, result = subject)
- aggregate_failures("expect new position #{attrs.inspect}") do
- if attrs.nil?
- expect(result[:outdated]).to be_truthy
- else
- expect(result[:outdated]).to be_falsey
+ describe '#trace' do
+ let(:diff_refs) { double(complete?: true) }
+ let(:project) { double }
+ let(:old_diff_refs) { diff_refs }
+ let(:new_diff_refs) { diff_refs }
+ let(:position) { double(on_text?: on_text?, diff_refs: diff_refs) }
+ let(:tracer) { double }
- new_position = result[:position]
- expect(new_position).not_to be_nil
+ context 'position is on text' do
+ let(:on_text?) { true }
- expect(new_position.diff_refs).to eq(new_diff_refs)
+ it 'calls LineStrategy#trace' do
+ expect(Gitlab::Diff::PositionTracer::LineStrategy)
+ .to receive(:new)
+ .with(subject)
+ .and_return(tracer)
+ expect(tracer).to receive(:trace).with(position)
- attrs.each do |attr, value|
- if text_position_attrs.include?(attr)
- expect(new_position.formatter.send(attr)).to eq(value)
- else
- expect(new_position.send(attr)).to eq(value)
- end
- end
+ subject.trace(position)
end
end
- end
- def expect_change_position(attrs, result = subject)
- aggregate_failures("expect change position #{attrs.inspect}") do
- expect(result[:outdated]).to be_truthy
+ context 'position is not on text' do
+ let(:on_text?) { false }
- change_position = result[:position]
- if attrs.nil? || attrs.empty?
- expect(change_position).to be_nil
- else
- expect(change_position).not_to be_nil
+ it 'calls ImageStrategy#trace' do
+ expect(Gitlab::Diff::PositionTracer::ImageStrategy)
+ .to receive(:new)
+ .with(subject)
+ .and_return(tracer)
+ expect(tracer).to receive(:trace).with(position)
- expect(change_position.diff_refs).to eq(change_diff_refs)
-
- attrs.each do |attr, value|
- if text_position_attrs.include?(attr)
- expect(change_position.formatter.send(attr)).to eq(value)
- else
- expect(change_position.send(attr)).to eq(value)
- end
- end
+ subject.trace(position)
end
end
end
- def create_branch(new_name, branch_name)
- CreateBranchService.new(project, current_user).execute(new_name, branch_name)
- end
-
- def create_file(branch_name, file_name, content)
- Files::CreateService.new(
- project,
- current_user,
- start_branch: branch_name,
- branch_name: branch_name,
- commit_message: "Create file",
- file_path: file_name,
- file_content: content
- ).execute
- project.commit(branch_name)
- end
-
- def update_file(branch_name, file_name, content)
- Files::UpdateService.new(
- project,
- current_user,
- start_branch: branch_name,
- branch_name: branch_name,
- commit_message: "Update file",
- file_path: file_name,
- file_content: content
- ).execute
- project.commit(branch_name)
- end
-
- def delete_file(branch_name, file_name)
- Files::DeleteService.new(
- project,
- current_user,
- start_branch: branch_name,
- branch_name: branch_name,
- commit_message: "Delete file",
- file_path: file_name
- ).execute
- project.commit(branch_name)
- end
-
- let(:initial_commit) do
- create_branch(branch_name, "master")[:branch].name
- project.commit(branch_name)
- end
-
- describe "#trace" do
- describe "diff scenarios" do
- let(:create_file_commit) do
- initial_commit
-
- create_file(
- branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- A
- B
- C
- CONTENT
- )
- end
-
- let(:create_second_file_commit) do
- create_file_commit
-
- create_file(
- branch_name,
- second_file_name,
- <<-CONTENT.strip_heredoc
- D
- E
- CONTENT
- )
- end
-
- let(:update_line_commit) do
- create_second_file_commit
-
- update_file(
- branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- A
- BB
- C
- CONTENT
- )
- end
-
- let(:update_second_file_line_commit) do
- update_line_commit
-
- update_file(
- branch_name,
- second_file_name,
- <<-CONTENT.strip_heredoc
- D
- EE
- CONTENT
- )
- end
-
- let(:move_line_commit) do
- update_second_file_line_commit
-
- update_file(
- branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- BB
- A
- C
- CONTENT
- )
- end
-
- let(:add_second_file_line_commit) do
- move_line_commit
-
- update_file(
- branch_name,
- second_file_name,
- <<-CONTENT.strip_heredoc
- D
- EE
- F
- CONTENT
- )
- end
-
- let(:move_second_file_line_commit) do
- add_second_file_line_commit
-
- update_file(
- branch_name,
- second_file_name,
- <<-CONTENT.strip_heredoc
- D
- F
- EE
- CONTENT
- )
- end
-
- let(:delete_line_commit) do
- move_second_file_line_commit
-
- update_file(
- branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- BB
- A
- CONTENT
- )
- end
-
- let(:delete_second_file_line_commit) do
- delete_line_commit
-
- update_file(
- branch_name,
- second_file_name,
- <<-CONTENT.strip_heredoc
- D
- F
- CONTENT
- )
- end
-
- let(:delete_file_commit) do
- delete_second_file_line_commit
-
- delete_file(branch_name, file_name)
- end
-
- let(:rename_file_commit) do
- delete_file_commit
-
- create_file(
- branch_name,
- new_file_name,
- <<-CONTENT.strip_heredoc
- BB
- A
- CONTENT
- )
- end
-
- let(:update_line_again_commit) do
- rename_file_commit
-
- update_file(
- branch_name,
- new_file_name,
- <<-CONTENT.strip_heredoc
- BB
- AA
- CONTENT
- )
- end
-
- let(:move_line_again_commit) do
- update_line_again_commit
-
- update_file(
- branch_name,
- new_file_name,
- <<-CONTENT.strip_heredoc
- AA
- BB
- CONTENT
- )
- end
-
- let(:delete_line_again_commit) do
- move_line_again_commit
-
- update_file(
- branch_name,
- new_file_name,
- <<-CONTENT.strip_heredoc
- AA
- CONTENT
- )
- end
-
- context "when the file was created in the old diff" do
- context "when the file is created in the new diff" do
- context "when the position pointed at an added line in the old diff" do
- context "when the file's content was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + A
- # 2 + B
- # 3 + C
- #
- # new diff:
- # 1 + A
- # 2 + B
- # 3 + C
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- new_line: old_position.new_line
- )
- end
- end
-
- context "when the file's content was changed between the old and the new diff" do
- context "when that line was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 1) }
-
- # old diff:
- # 1 + A
- # 2 + B
- # 3 + C
- #
- # new diff:
- # 1 + A
- # 2 + BB
- # 3 + C
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- new_line: old_position.new_line
- )
- end
- end
-
- context "when that line was moved between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + A
- # 2 + BB
- # 3 + C
- #
- # new diff:
- # 1 + BB
- # 2 + A
- # 3 + C
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- new_line: 1
- )
- end
- end
-
- context "when that line was changed between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + A
- # 2 + B
- # 3 + C
- #
- # new diff:
- # 1 + A
- # 2 + BB
- # 3 + C
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 2,
- new_line: nil
- )
- end
- end
-
- context "when that line was deleted between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
- let(:change_diff_refs) { diff_refs(update_line_commit, delete_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 3) }
-
- # old diff:
- # 1 + A
- # 2 + BB
- # 3 + C
- #
- # new diff:
- # 1 + A
- # 2 + BB
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 3,
- new_line: nil
- )
- end
- end
- end
- end
- end
-
- context "when the file is changed in the new diff" do
- context "when the position pointed at an added line in the old diff" do
- context "when the file's content was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 1) }
-
- # old diff:
- # 1 + A
- # 2 + BB
- # 3 + C
- #
- # new diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- new_line: old_position.new_line
- )
- end
- end
-
- context "when the file's content was changed between the old and the new diff" do
- context "when that line was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 3) }
-
- # old diff:
- # 1 + A
- # 2 + BB
- # 3 + C
- #
- # new diff:
- # 1 - A
- # 2 1 BB
- # 2 + A
- # 3 3 C
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- new_line: old_position.new_line
- )
- end
- end
-
- context "when that line was moved between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + A
- # 2 + BB
- # 3 + C
- #
- # new diff:
- # 1 - A
- # 2 1 BB
- # 2 + A
- # 3 3 C
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- new_line: 1
- )
- end
- end
-
- context "when that line was changed between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
- let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
- let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + A
- # 2 + B
- # 3 + C
- #
- # new diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 2,
- new_line: nil
- )
- end
- end
-
- context "when that line was deleted between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
- let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
- let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 3) }
-
- # old diff:
- # 1 + BB
- # 2 + A
- # 3 + C
- #
- # new diff:
- # 1 1 BB
- # 2 2 A
- # 3 - C
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 3,
- new_line: nil
- )
- end
- end
- end
- end
- end
-
- context "when the file is renamed in the new diff" do
- context "when the position pointed at an added line in the old diff" do
- context "when the file's content was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
- let(:new_diff_refs) { diff_refs(delete_line_commit, rename_file_commit) }
- let(:change_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + BB
- # 2 + A
- #
- # new diff:
- # file_name -> new_file_name
- # 1 1 BB
- # 2 2 A
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: nil,
- new_line: 2
- )
- end
- end
-
- context "when the file's content was changed between the old and the new diff" do
- context "when that line was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
- let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 1) }
-
- # old diff:
- # 1 + BB
- # 2 + A
- #
- # new diff:
- # file_name -> new_file_name
- # 1 1 BB
- # 2 - A
- # 2 + AA
-
- it "returns the new position" do
- expect_new_position(
- old_path: file_name,
- new_path: new_file_name,
- old_line: old_position.new_line,
- new_line: old_position.new_line
- )
- end
- end
-
- context "when that line was moved between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
- let(:new_diff_refs) { diff_refs(delete_line_commit, move_line_again_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 1) }
-
- # old diff:
- # 1 + BB
- # 2 + A
- #
- # new diff:
- # file_name -> new_file_name
- # 1 + AA
- # 1 2 BB
- # 2 - A
-
- it "returns the new position" do
- expect_new_position(
- old_path: file_name,
- new_path: new_file_name,
- old_line: 1,
- new_line: 2
- )
- end
- end
-
- context "when that line was changed between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
- let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) }
- let(:change_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + BB
- # 2 + A
- #
- # new diff:
- # file_name -> new_file_name
- # 1 1 BB
- # 2 - A
- # 2 + AA
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: new_file_name,
- old_line: 2,
- new_line: nil
- )
- end
- end
- end
- end
- end
-
- context "when the file is deleted in the new diff" do
- context "when the position pointed at an added line in the old diff" do
- context "when the file's content was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
- let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
- let(:change_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + BB
- # 2 + A
- #
- # new diff:
- # 1 - BB
- # 2 - A
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 2,
- new_line: nil
- )
- end
- end
-
- context "when the file's content was changed between the old and the new diff" do
- context "when that line was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
- let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
- let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + BB
- # 2 + A
- # 3 + C
- #
- # new diff:
- # 1 - BB
- # 2 - A
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 2,
- new_line: nil
- )
- end
- end
-
- context "when that line was moved between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(move_line_commit, delete_file_commit) }
- let(:change_diff_refs) { diff_refs(update_line_commit, delete_file_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + A
- # 2 + BB
- # 3 + C
- #
- # new diff:
- # 1 - BB
- # 2 - A
- # 3 - C
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 2,
- new_line: nil
- )
- end
- end
-
- context "when that line was changed between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
- let(:new_diff_refs) { diff_refs(update_line_commit, delete_file_commit) }
- let(:change_diff_refs) { diff_refs(create_file_commit, delete_file_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + A
- # 2 + B
- # 3 + C
- #
- # new diff:
- # 1 - A
- # 2 - BB
- # 3 - C
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 2,
- new_line: nil
- )
- end
- end
-
- context "when that line was deleted between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
- let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
- let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 3) }
-
- # old diff:
- # 1 + BB
- # 2 + A
- # 3 + C
- #
- # new diff:
- # 1 - BB
- # 2 - A
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 3,
- new_line: nil
- )
- end
- end
- end
- end
- end
-
- context "when the file is unchanged in the new diff" do
- context "when the position pointed at an added line in the old diff" do
- context "when the file's content was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
- let(:new_diff_refs) { diff_refs(create_file_commit, create_second_file_commit) }
- let(:change_diff_refs) { diff_refs(initial_commit, create_file_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + A
- # 2 + B
- # 3 + C
- #
- # new diff:
- # 1 1 A
- # 2 2 B
- # 3 3 C
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: nil,
- new_line: 2
- )
- end
- end
-
- context "when the file's content was changed between the old and the new diff" do
- context "when that line was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
- let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) }
- let(:change_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 1) }
-
- # old diff:
- # 1 + A
- # 2 + B
- # 3 + C
- #
- # new diff:
- # 1 1 A
- # 2 2 BB
- # 3 3 C
+ describe 'diffs methods' do
+ let(:project) { create(:project, :repository) }
+ let(:current_user) { project.owner }
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: nil,
- new_line: 1
- )
- end
- end
-
- context "when that line was moved between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(move_line_commit, move_second_file_line_commit) }
- let(:change_diff_refs) { diff_refs(initial_commit, move_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + A
- # 2 + BB
- # 3 + C
- #
- # new diff:
- # 1 1 BB
- # 2 2 A
- # 3 3 C
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: nil,
- new_line: 1
- )
- end
- end
-
- context "when that line was changed between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
- let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) }
- let(:change_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 + A
- # 2 + B
- # 3 + C
- #
- # new diff:
- # 1 1 A
- # 2 2 BB
- # 3 3 C
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 2,
- new_line: nil
- )
- end
- end
-
- context "when that line was deleted between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
- let(:new_diff_refs) { diff_refs(delete_line_commit, delete_second_file_line_commit) }
- let(:change_diff_refs) { diff_refs(move_line_commit, delete_second_file_line_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 3) }
-
- # old diff:
- # 1 + BB
- # 2 + A
- # 3 + C
- #
- # new diff:
- # 1 1 BB
- # 2 2 A
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 3,
- new_line: nil
- )
- end
- end
- end
- end
- end
- end
-
- context "when the file was changed in the old diff" do
- context "when the file is created in the new diff" do
- context "when the position pointed at an added line in the old diff" do
- context "when the file's content was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- #
- # new diff:
- # 1 + A
- # 2 + BB
- # 3 + C
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- old_line: nil,
- new_line: old_position.new_line
- )
- end
- end
-
- context "when the file's content was changed between the old and the new diff" do
- context "when that line was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) }
-
- # old diff:
- # 1 + BB
- # 1 2 A
- # 2 - B
- # 3 3 C
- #
- # new diff:
- # 1 + BB
- # 2 + A
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- old_line: nil,
- new_line: old_position.new_line
- )
- end
- end
-
- context "when that line was moved between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- #
- # new diff:
- # 1 + BB
- # 2 + A
- # 3 + C
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- old_line: nil,
- new_line: 1
- )
- end
- end
-
- context "when that line was changed or deleted between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, create_file_commit) }
- let(:change_diff_refs) { diff_refs(move_line_commit, create_file_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) }
-
- # old diff:
- # 1 + BB
- # 1 2 A
- # 2 - B
- # 3 3 C
- #
- # new diff:
- # 1 + A
- # 2 + B
- # 3 + C
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 1,
- new_line: nil
- )
- end
- end
- end
- end
-
- context "when the position pointed at a deleted line in the old diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:change_diff_refs) { diff_refs(create_file_commit, initial_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- #
- # new diff:
- # 1 + A
- # 2 + BB
- # 3 + C
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 2,
- new_line: nil
- )
- end
- end
-
- context "when the position pointed at an unchanged line in the old diff" do
- context "when the file's content was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 1, new_line: 1) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- #
- # new diff:
- # 1 + A
- # 2 + BB
- # 3 + C
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- old_line: nil,
- new_line: old_position.new_line
- )
- end
- end
-
- context "when the file's content was changed between the old and the new diff" do
- context "when that line was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 1, new_line: 2) }
-
- # old diff:
- # 1 + BB
- # 1 2 A
- # 2 - B
- # 3 3 C
- #
- # new diff:
- # 1 + BB
- # 2 + A
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- old_line: nil,
- new_line: old_position.new_line
- )
- end
- end
-
- context "when that line was moved between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2, new_line: 2) }
-
- # old diff:
- # 1 1 BB
- # 2 2 A
- # 3 - C
- #
- # new diff:
- # 1 + A
- # 2 + BB
- # 3 + C
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- old_line: nil,
- new_line: 1
- )
- end
- end
-
- context "when that line was changed or deleted between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
- let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 3, new_line: 3) }
-
- # old diff:
- # 1 + BB
- # 1 2 A
- # 2 - B
- # 3 3 C
- #
- # new diff:
- # 1 + A
- # 2 + B
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 3,
- new_line: nil
- )
- end
- end
- end
- end
- end
-
- context "when the file is changed in the new diff" do
- context "when the position pointed at an added line in the old diff" do
- context "when the file's content was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- #
- # new diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
-
- it "returns the new position" do
- expect_new_position(
- old_path: old_position.old_path,
- new_path: old_position.new_path,
- old_line: nil,
- new_line: old_position.new_line
- )
- end
- end
-
- context "when the file's content was changed between the old and the new diff" do
- context "when that line was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
- let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) }
-
- # old diff:
- # 1 + BB
- # 1 2 A
- # 2 - B
- # 3 3 C
- #
- # new diff:
- # 1 1 BB
- # 2 2 A
- # 3 - C
-
- it "returns the new position" do
- expect_new_position(
- old_path: old_position.old_path,
- new_path: old_position.new_path,
- old_line: 1,
- new_line: 1
- )
- end
- end
-
- context "when that line was moved between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- #
- # new diff:
- # 1 - A
- # 2 1 BB
- # 2 + A
- # 3 3 C
-
- it "returns the new position" do
- expect_new_position(
- old_path: old_position.old_path,
- new_path: old_position.new_path,
- old_line: 2,
- new_line: 1
- )
- end
- end
-
- context "when that line was changed or deleted between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
- let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
- let(:change_diff_refs) { diff_refs(move_line_commit, update_line_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) }
-
- # old diff:
- # 1 + BB
- # 1 2 A
- # 2 - B
- # 3 3 C
- #
- # new diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 1,
- new_line: nil
- )
- end
- end
- end
- end
-
- context "when the position pointed at a deleted line in the old diff" do
- context "when the file's content was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
- let(:new_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- #
- # new diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
-
- it "returns the new position" do
- expect_new_position(
- old_path: old_position.old_path,
- new_path: old_position.new_path,
- old_line: old_position.old_line,
- new_line: nil
- )
- end
- end
- end
- end
- end
- end
- end
-
- describe "typical use scenarios" do
- let(:second_branch_name) { "#{branch_name}-2" }
-
- def expect_new_positions(old_attrs, new_attrs)
- old_positions = old_attrs.map do |old_attrs|
- position(old_attrs)
- end
-
- new_positions = old_positions.map do |old_position|
- position_tracer.trace(old_position)
- end
-
- aggregate_failures do
- new_positions.zip(new_attrs).each do |new_position, new_attrs|
- if new_attrs&.delete(:change)
- expect_change_position(new_attrs, new_position)
- else
- expect_new_position(new_attrs, new_position)
- end
- end
- end
- end
-
- let(:create_file_commit) do
- initial_commit
-
- create_file(
- branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- A
- B
- C
- D
- E
- F
- CONTENT
- )
- end
-
- let(:second_create_file_commit) do
- create_file_commit
-
- create_branch(second_branch_name, branch_name)
-
- update_file(
- second_branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- Z
- Z
- Z
- A
- B
- C
- D
- E
- F
- CONTENT
+ let(:old_diff_refs) do
+ diff_refs(
+ project.commit(create_branch('new-branch', 'master')[:branch].name),
+ create_file('new-branch', 'file.md', 'content')
)
end
- let(:update_file_commit) do
- second_create_file_commit
-
- update_file(
- branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- A
- C
- DD
- E
- F
- G
- CONTENT
- )
- end
-
- let(:update_file_again_commit) do
- update_file_commit
-
- update_file(
- branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- A
- BB
- C
- D
- E
- FF
- G
- CONTENT
+ let(:new_diff_refs) do
+ diff_refs(
+ create_file('new-branch', 'file.md', 'content'),
+ update_file('new-branch', 'file.md', 'updatedcontent')
)
end
- describe "simple push of new commit" do
- let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
- let(:new_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
- let(:change_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 3 2 C
- # 4 - D
- # 3 + DD
- # 5 4 E
- # 6 5 F
- # 6 + G
- #
- # new diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- # 4 4 D
- # 5 5 E
- # 6 - F
- # 6 + FF
- # 7 + G
-
- it "returns the new positions" do
- old_position_attrs = [
- { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A
- { old_path: file_name, old_line: 2 }, # - B
- { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, # C
- { old_path: file_name, old_line: 4 }, # - D
- { new_path: file_name, new_line: 3 }, # + DD
- { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, # E
- { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, # F
- { new_path: file_name, new_line: 6 }, # + G
- ]
-
- new_position_attrs = [
- { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
- { old_path: file_name, old_line: 2 },
- { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 },
- { new_path: file_name, new_line: 4, change: true },
- { new_path: file_name, old_line: 3, change: true },
- { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 },
- { new_path: file_name, old_line: 5, change: true },
- { new_path: file_name, new_line: 7 }
- ]
+ describe '#ac_diffs' do
+ it 'returns the diffs between the base of old and new diff' do
+ diff_refs = subject.ac_diffs.diff_refs
- expect_new_positions(old_position_attrs, new_position_attrs)
+ expect(diff_refs.base_sha).to eq(old_diff_refs.base_sha)
+ expect(diff_refs.start_sha).to eq(old_diff_refs.base_sha)
+ expect(diff_refs.head_sha).to eq(new_diff_refs.base_sha)
end
end
- describe "force push to overwrite last commit" do
- let(:second_create_file_commit) do
- create_file_commit
+ describe '#bd_diffs' do
+ it 'returns the diffs between the HEAD of old and new diff' do
+ diff_refs = subject.bd_diffs.diff_refs
- create_branch(second_branch_name, branch_name)
-
- update_file(
- second_branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- A
- BB
- C
- D
- E
- FF
- G
- CONTENT
- )
- end
-
- let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
- let(:new_diff_refs) { diff_refs(create_file_commit, second_create_file_commit) }
- let(:change_diff_refs) { diff_refs(update_file_commit, second_create_file_commit) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 3 2 C
- # 4 - D
- # 3 + DD
- # 5 4 E
- # 6 5 F
- # 6 + G
- #
- # new diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- # 4 4 D
- # 5 5 E
- # 6 - F
- # 6 + FF
- # 7 + G
-
- it "returns the new positions" do
- old_position_attrs = [
- { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A
- { old_path: file_name, old_line: 2 }, # - B
- { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, # C
- { old_path: file_name, old_line: 4 }, # - D
- { new_path: file_name, new_line: 3 }, # + DD
- { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, # E
- { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, # F
- { new_path: file_name, new_line: 6 }, # + G
- ]
-
- new_position_attrs = [
- { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
- { old_path: file_name, old_line: 2 },
- { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 },
- { new_path: file_name, new_line: 4, change: true },
- { old_path: file_name, old_line: 3, change: true },
- { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 },
- { old_path: file_name, old_line: 5, change: true },
- { new_path: file_name, new_line: 7 }
- ]
-
- expect_new_positions(old_position_attrs, new_position_attrs)
+ expect(diff_refs.base_sha).to eq(old_diff_refs.head_sha)
+ expect(diff_refs.start_sha).to eq(old_diff_refs.head_sha)
+ expect(diff_refs.head_sha).to eq(new_diff_refs.head_sha)
end
end
- describe "force push to delete last commit" do
- let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
- let(:new_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
- let(:change_diff_refs) { diff_refs(update_file_again_commit, update_file_commit) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- # 4 4 D
- # 5 5 E
- # 6 - F
- # 6 + FF
- # 7 + G
- #
- # new diff:
- # 1 1 A
- # 2 - B
- # 3 2 C
- # 4 - D
- # 3 + DD
- # 5 4 E
- # 6 5 F
- # 6 + G
-
- it "returns the new positions" do
- old_position_attrs = [
- { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A
- { old_path: file_name, old_line: 2 }, # - B
- { new_path: file_name, new_line: 2 }, # + BB
- { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C
- { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D
- { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E
- { old_path: file_name, old_line: 6 }, # - F
- { new_path: file_name, new_line: 6 }, # + FF
- { new_path: file_name, new_line: 7 }, # + G
- ]
-
- new_position_attrs = [
- { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
- { old_path: file_name, old_line: 2 },
- { old_path: file_name, old_line: 2, change: true },
- { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 },
- { old_path: file_name, old_line: 4, change: true },
- { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 },
- { new_path: file_name, new_line: 5, change: true },
- { old_path: file_name, old_line: 6, change: true },
- { new_path: file_name, new_line: 6 }
- ]
-
- expect_new_positions(old_position_attrs, new_position_attrs)
- end
- end
-
- describe "rebase on top of target branch" do
- let(:second_update_file_commit) do
- update_file_commit
-
- update_file(
- second_branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- Z
- Z
- Z
- A
- C
- DD
- E
- F
- G
- CONTENT
- )
- end
-
- let(:update_file_again_commit) do
- second_update_file_commit
-
- update_file(
- branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- A
- BB
- C
- D
- E
- FF
- G
- CONTENT
- )
- end
-
- let(:overwrite_update_file_again_commit) do
- update_file_again_commit
-
- update_file(
- second_branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- Z
- Z
- Z
- A
- BB
- C
- D
- E
- FF
- G
- CONTENT
- )
- end
-
- let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
- let(:new_diff_refs) { diff_refs(create_file_commit, overwrite_update_file_again_commit) }
- let(:change_diff_refs) { diff_refs(update_file_again_commit, overwrite_update_file_again_commit) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- # 4 4 D
- # 5 5 E
- # 6 - F
- # 6 + FF
- # 7 + G
- #
- # new diff:
- # 1 + Z
- # 2 + Z
- # 3 + Z
- # 1 4 A
- # 2 - B
- # 5 + BB
- # 3 6 C
- # 4 7 D
- # 5 8 E
- # 6 - F
- # 9 + FF
- # 0 + G
-
- it "returns the new positions" do
- old_position_attrs = [
- { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A
- { old_path: file_name, old_line: 2 }, # - B
- { new_path: file_name, new_line: 2 }, # + BB
- { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C
- { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D
- { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E
- { old_path: file_name, old_line: 6 }, # - F
- { new_path: file_name, new_line: 6 }, # + FF
- { new_path: file_name, new_line: 7 }, # + G
- ]
-
- new_position_attrs = [
- { old_path: file_name, new_path: file_name, old_line: 1, new_line: 4 }, # A
- { old_path: file_name, old_line: 2 }, # - B
- { new_path: file_name, new_line: 5 }, # + BB
- { old_path: file_name, new_path: file_name, old_line: 3, new_line: 6 }, # C
- { old_path: file_name, new_path: file_name, old_line: 4, new_line: 7 }, # D
- { old_path: file_name, new_path: file_name, old_line: 5, new_line: 8 }, # E
- { old_path: file_name, old_line: 6 }, # - F
- { new_path: file_name, new_line: 9 }, # + FF
- { new_path: file_name, new_line: 10 }, # + G
- ]
-
- expect_new_positions(old_position_attrs, new_position_attrs)
- end
- end
-
- describe "merge of target branch" do
- let(:merge_commit) do
- second_create_file_commit
-
- merge_request = create(:merge_request, source_branch: second_branch_name, target_branch: branch_name, source_project: project)
-
- repository.merge(current_user, merge_request.diff_head_sha, merge_request, "Merge branches")
-
- project.commit(branch_name)
- end
-
- let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
- let(:new_diff_refs) { diff_refs(create_file_commit, merge_commit) }
- let(:change_diff_refs) { diff_refs(update_file_again_commit, merge_commit) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- # 4 4 D
- # 5 5 E
- # 6 - F
- # 6 + FF
- # 7 + G
- #
- # new diff:
- # 1 + Z
- # 2 + Z
- # 3 + Z
- # 1 4 A
- # 2 - B
- # 5 + BB
- # 3 6 C
- # 4 7 D
- # 5 8 E
- # 6 - F
- # 9 + FF
- # 0 + G
-
- it "returns the new positions" do
- old_position_attrs = [
- { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A
- { old_path: file_name, old_line: 2 }, # - B
- { new_path: file_name, new_line: 2 }, # + BB
- { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C
- { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D
- { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E
- { old_path: file_name, old_line: 6 }, # - F
- { new_path: file_name, new_line: 6 }, # + FF
- { new_path: file_name, new_line: 7 }, # + G
- ]
-
- new_position_attrs = [
- { old_path: file_name, new_path: file_name, old_line: 1, new_line: 4 }, # A
- { old_path: file_name, old_line: 2 }, # - B
- { new_path: file_name, new_line: 5 }, # + BB
- { old_path: file_name, new_path: file_name, old_line: 3, new_line: 6 }, # C
- { old_path: file_name, new_path: file_name, old_line: 4, new_line: 7 }, # D
- { old_path: file_name, new_path: file_name, old_line: 5, new_line: 8 }, # E
- { old_path: file_name, old_line: 6 }, # - F
- { new_path: file_name, new_line: 9 }, # + FF
- { new_path: file_name, new_line: 10 }, # + G
- ]
-
- expect_new_positions(old_position_attrs, new_position_attrs)
- end
- end
-
- describe "changing target branch" do
- let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
- let(:new_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) }
- let(:change_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
-
- # old diff:
- # 1 1 A
- # 2 - B
- # 2 + BB
- # 3 3 C
- # 4 4 D
- # 5 5 E
- # 6 - F
- # 6 + FF
- # 7 + G
- #
- # new diff:
- # 1 1 A
- # 2 + BB
- # 2 3 C
- # 3 - DD
- # 4 + D
- # 4 5 E
- # 5 - F
- # 6 + FF
- # 7 G
-
- it "returns the new positions" do
- old_position_attrs = [
- { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A
- { old_path: file_name, old_line: 2 }, # - B
- { new_path: file_name, new_line: 2 }, # + BB
- { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C
- { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D
- { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E
- { old_path: file_name, old_line: 6 }, # - F
- { new_path: file_name, new_line: 6 }, # + FF
- { new_path: file_name, new_line: 7 }, # + G
- ]
-
- new_position_attrs = [
- { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
- { old_path: file_name, old_line: 2, change: true },
- { new_path: file_name, new_line: 2 },
- { old_path: file_name, new_path: file_name, old_line: 2, new_line: 3 },
- { new_path: file_name, new_line: 4 },
- { old_path: file_name, new_path: file_name, old_line: 4, new_line: 5 },
- { old_path: file_name, old_line: 5 },
- { new_path: file_name, new_line: 6 },
- { new_path: file_name, new_line: 7 }
- ]
+ describe '#cd_diffs' do
+ it 'returns the diffs in the new diff' do
+ diff_refs = subject.cd_diffs.diff_refs
- expect_new_positions(old_position_attrs, new_position_attrs)
+ expect(diff_refs.base_sha).to eq(new_diff_refs.base_sha)
+ expect(diff_refs.start_sha).to eq(new_diff_refs.base_sha)
+ expect(diff_refs.head_sha).to eq(new_diff_refs.head_sha)
end
end
end
diff --git a/spec/lib/gitlab/downtime_check/message_spec.rb b/spec/lib/gitlab/downtime_check/message_spec.rb
index a5a398abf78..2beb5a19a32 100644
--- a/spec/lib/gitlab/downtime_check/message_spec.rb
+++ b/spec/lib/gitlab/downtime_check/message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DowntimeCheck::Message do
diff --git a/spec/lib/gitlab/downtime_check_spec.rb b/spec/lib/gitlab/downtime_check_spec.rb
index 1f1e4e0216c..56ad49d528f 100644
--- a/spec/lib/gitlab/downtime_check_spec.rb
+++ b/spec/lib/gitlab/downtime_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DowntimeCheck do
diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb
index 45c690842bc..d66a746284d 100644
--- a/spec/lib/gitlab/email/attachment_uploader_spec.rb
+++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Gitlab::Email::AttachmentUploader do
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
index 48139c2f9dc..7833b9f387d 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -105,6 +105,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
context "when the issue could not be saved" do
before do
allow_any_instance_of(Issue).to receive(:persisted?).and_return(false)
+ allow_any_instance_of(Issue).to receive(:ensure_metrics).and_return(nil)
end
it "raises an InvalidIssueError" do
diff --git a/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb b/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb
index ae61ece8029..65e4e27d56f 100644
--- a/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb
+++ b/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Hook::AdditionalHeadersInterceptor do
diff --git a/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb b/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb
index 4497d4002da..24da47c42ac 100644
--- a/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb
+++ b/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Hook::DeliveryMetricsObserver do
diff --git a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
index 91aa3bc7c2e..c8ed12523d0 100644
--- a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
+++ b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Hook::DisableEmailInterceptor do
@@ -11,9 +13,6 @@ describe Gitlab::Email::Hook::DisableEmailInterceptor do
end
after do
- # Removing interceptor from the list because unregister_interceptor is
- # implemented in later version of mail gem
- # See: https://github.com/mikel/mail/pull/705
Mail.unregister_interceptor(described_class)
end
diff --git a/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
new file mode 100644
index 00000000000..35aa663b0a5
--- /dev/null
+++ b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe Gitlab::Email::Hook::SmimeSignatureInterceptor do
+ include SmimeHelper
+
+ # cert generation is an expensive operation and they are used read-only,
+ # so we share them as instance variables in all tests
+ before :context do
+ @root_ca = generate_root
+ @cert = generate_cert(root_ca: @root_ca)
+ end
+
+ let(:root_certificate) do
+ Gitlab::Email::Smime::Certificate.new(@root_ca[:key], @root_ca[:cert])
+ end
+
+ let(:certificate) do
+ Gitlab::Email::Smime::Certificate.new(@cert[:key], @cert[:cert])
+ end
+
+ let(:mail) do
+ ActionMailer::Base.mail(to: 'test@example.com', from: 'info@example.com', body: 'signed hello')
+ end
+
+ before do
+ allow(Gitlab::Email::Smime::Certificate).to receive_messages(from_files: certificate)
+
+ Mail.register_interceptor(described_class)
+ mail.deliver_now
+ end
+
+ after do
+ Mail.unregister_interceptor(described_class)
+ end
+
+ it 'signs the email appropriately with SMIME' do
+ expect(mail.header['To'].value).to eq('test@example.com')
+ expect(mail.header['From'].value).to eq('info@example.com')
+ expect(mail.header['Content-Type'].value).to match('multipart/signed').and match('protocol="application/x-pkcs7-signature"')
+
+ # verify signature and obtain pkcs7 encoded content
+ p7enc = Gitlab::Email::Smime::Signer.verify_signature(
+ cert: certificate.cert,
+ ca_cert: root_certificate.cert,
+ signed_data: mail.encoded)
+
+ # envelope in a Mail object and obtain the body
+ decoded_mail = Mail.new(p7enc.data)
+
+ expect(decoded_mail.body.encoded).to eq('signed hello')
+ end
+end
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
index 0ec1f931037..84c5b38127e 100644
--- a/spec/lib/gitlab/email/message/repository_push_spec.rb
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Message::RepositoryPush do
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index 0af978eced3..c9fde06cbae 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Receiver do
diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb
index 376d3accd55..646575b2edd 100644
--- a/spec/lib/gitlab/email/reply_parser_spec.rb
+++ b/spec/lib/gitlab/email/reply_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
# Inspired in great part by Discourse's Email::Receiver
diff --git a/spec/lib/gitlab/email/smime/certificate_spec.rb b/spec/lib/gitlab/email/smime/certificate_spec.rb
new file mode 100644
index 00000000000..90b27602413
--- /dev/null
+++ b/spec/lib/gitlab/email/smime/certificate_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Email::Smime::Certificate do
+ include SmimeHelper
+
+ # cert generation is an expensive operation and they are used read-only,
+ # so we share them as instance variables in all tests
+ before :context do
+ @root_ca = generate_root
+ @cert = generate_cert(root_ca: @root_ca)
+ end
+
+ describe 'testing environment setup' do
+ describe 'generate_root' do
+ subject { @root_ca }
+
+ it 'generates a root CA that expires a long way in the future' do
+ expect(subject[:cert].not_after).to be > 999.years.from_now
+ end
+ end
+
+ describe 'generate_cert' do
+ subject { @cert }
+
+ it 'generates a cert properly signed by the root CA' do
+ expect(subject[:cert].issuer).to eq(@root_ca[:cert].subject)
+ end
+
+ it 'generates a cert that expires soon' do
+ expect(subject[:cert].not_after).to be < 60.minutes.from_now
+ end
+
+ it 'generates a cert intended for email signing' do
+ expect(subject[:cert].extensions).to include(an_object_having_attributes(oid: 'extendedKeyUsage', value: match('E-mail Protection')))
+ end
+
+ context 'passing in INFINITE_EXPIRY' do
+ subject { generate_cert(root_ca: @root_ca, expires_in: SmimeHelper::INFINITE_EXPIRY) }
+
+ it 'generates a cert that expires a long way in the future' do
+ expect(subject[:cert].not_after).to be > 999.years.from_now
+ end
+ end
+ end
+ end
+
+ describe '.from_strings' do
+ it 'parses correctly a certificate and key' do
+ parsed_cert = described_class.from_strings(@cert[:key].to_s, @cert[:cert].to_pem)
+
+ common_cert_tests(parsed_cert, @cert, @root_ca)
+ end
+ end
+
+ describe '.from_files' do
+ it 'parses correctly a certificate and key' do
+ allow(File).to receive(:read).with('a_key').and_return(@cert[:key].to_s)
+ allow(File).to receive(:read).with('a_cert').and_return(@cert[:cert].to_pem)
+
+ parsed_cert = described_class.from_files('a_key', 'a_cert')
+
+ common_cert_tests(parsed_cert, @cert, @root_ca)
+ end
+ end
+
+ def common_cert_tests(parsed_cert, cert, root_ca)
+ expect(parsed_cert.cert).to be_a(OpenSSL::X509::Certificate)
+ expect(parsed_cert.cert.subject).to eq(cert[:cert].subject)
+ expect(parsed_cert.cert.issuer).to eq(root_ca[:cert].subject)
+ expect(parsed_cert.cert.not_before).to eq(cert[:cert].not_before)
+ expect(parsed_cert.cert.not_after).to eq(cert[:cert].not_after)
+ expect(parsed_cert.cert.extensions).to include(an_object_having_attributes(oid: 'extendedKeyUsage', value: match('E-mail Protection')))
+ expect(parsed_cert.key).to be_a(OpenSSL::PKey::RSA)
+ end
+end
diff --git a/spec/lib/gitlab/email/smime/signer_spec.rb b/spec/lib/gitlab/email/smime/signer_spec.rb
new file mode 100644
index 00000000000..56048b7148c
--- /dev/null
+++ b/spec/lib/gitlab/email/smime/signer_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Email::Smime::Signer do
+ include SmimeHelper
+
+ it 'signs data appropriately with SMIME' do
+ root_certificate = generate_root
+ certificate = generate_cert(root_ca: root_certificate)
+
+ signed_content = described_class.sign(
+ cert: certificate[:cert],
+ key: certificate[:key],
+ data: 'signed content')
+ expect(signed_content).not_to be_nil
+
+ p7enc = described_class.verify_signature(
+ cert: certificate[:cert],
+ ca_cert: root_certificate[:cert],
+ signed_data: signed_content)
+
+ expect(p7enc).not_to be_nil
+ expect(p7enc.data).to eq('signed content')
+ end
+end
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index 88ea98eb1e1..b24b71522ec 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require "spec_helper"
describe Gitlab::EncodingHelper do
@@ -9,8 +11,8 @@ describe Gitlab::EncodingHelper do
[
["nil", nil, nil],
["empty string", "".encode("ASCII-8BIT"), "".encode("UTF-8")],
- ["invalid utf-8 encoded string", "my bad string\xE5".force_encoding("UTF-8"), "my bad string"],
- ["frozen non-ascii string", "é".force_encoding("ASCII-8BIT").freeze, "é".encode("UTF-8")],
+ ["invalid utf-8 encoded string", (+"my bad string\xE5").force_encoding("UTF-8"), "my bad string"],
+ ["frozen non-ascii string", (+"é").force_encoding("ASCII-8BIT").freeze, "é".encode("UTF-8")],
[
'leaves ascii only string as is',
'ascii only string',
@@ -23,7 +25,7 @@ describe Gitlab::EncodingHelper do
],
[
'removes invalid bytes from ASCII-8bit encoded multibyte string. This can occur when a git diff match line truncates in the middle of a multibyte character. This occurs after the second word in this example. The test string is as short as we can get while still triggering the error condition when not looking at `detect[:confidence]`.',
- "mu ns\xC3\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ".force_encoding('ASCII-8BIT'),
+ (+"mu ns\xC3\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ").force_encoding('ASCII-8BIT'),
"mu ns\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi "
],
[
@@ -93,7 +95,7 @@ describe Gitlab::EncodingHelper do
[
["nil", nil, nil],
["empty string", "".encode("ASCII-8BIT"), "".encode("UTF-8")],
- ["invalid utf-8 encoded string", "my bad string\xE5".force_encoding("UTF-8"), "my bad stringå"],
+ ["invalid utf-8 encoded string", (+"my bad string\xE5").force_encoding("UTF-8"), "my bad stringå"],
[
"encodes valid utf8 encoded string to utf8",
"λ, λ, λ".encode("UTF-8"),
@@ -160,12 +162,12 @@ describe Gitlab::EncodingHelper do
],
[
'removes invalid bytes from ASCII-8bit encoded multibyte string.',
- "Lorem ipsum\xC3\n dolor sit amet, xy\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg".force_encoding('ASCII-8BIT'),
+ (+"Lorem ipsum\xC3\n dolor sit amet, xy\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg").force_encoding('ASCII-8BIT'),
"Lorem ipsum\n dolor sit amet, xyàyùabcdùefg"
],
[
'handles UTF-16BE encoded strings',
- "\xFE\xFF\x00\x41".force_encoding('ASCII-8BIT'), # An "A" prepended with UTF-16 BOM
+ (+"\xFE\xFF\x00\x41").force_encoding('ASCII-8BIT'), # An "A" prepended with UTF-16 BOM
"\xEF\xBB\xBFA" # An "A" prepended with UTF-8 BOM
]
].each do |description, test_string, xpect|
diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb
index 4a54d641b4e..9ead55075fa 100644
--- a/spec/lib/gitlab/etag_caching/middleware_spec.rb
+++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::EtagCaching::Middleware do
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
index a7cb0bb2a87..fbc49d894a6 100644
--- a/spec/lib/gitlab/etag_caching/router_spec.rb
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::EtagCaching::Router do
diff --git a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
index 5107e1efbbd..c3b706fc538 100644
--- a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
@@ -25,13 +25,13 @@ describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state do
end
it 'calls the given block' do
- expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_control.once
+ expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_with_args(false)
end
it 'calls the given block continuously' do
- expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_control.once
- expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_control.once
- expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_control.once
+ expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_with_args(false)
+ expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_with_args(false)
+ expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_with_args(false)
end
it 'cancels the exclusive lease after the block' do
@@ -68,6 +68,15 @@ describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state do
expect { subject }.to raise_error('Failed to obtain a lock')
end
+
+ context 'when lease is granted after retry' do
+ it 'yields block with true' do
+ expect(lease).to receive(:try_obtain).exactly(3).times { nil }
+ expect(lease).to receive(:try_obtain).once { unique_key }
+
+ expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_with_args(true)
+ end
+ end
end
context 'when sleep second is specified' do
diff --git a/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb b/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb
new file mode 100644
index 00000000000..503fe897e29
--- /dev/null
+++ b/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::FogbugzImport::ProjectCreator do
+ let(:user) { create(:user) }
+
+ let(:repo) do
+ instance_double(Gitlab::FogbugzImport::Repository,
+ name: 'Vim',
+ safe_name: 'vim',
+ path: 'vim',
+ raw_data: '')
+ end
+
+ let(:uri) { 'https://testing.fogbugz.com' }
+ let(:token) { 'token' }
+ let(:fb_session) { { uri: uri, token: token } }
+ let(:project_creator) { described_class.new(repo, fb_session, user.namespace, user) }
+
+ subject do
+ project_creator.execute
+ end
+
+ it 'creates project with private visibility level' do
+ expect(subject.persisted?).to eq(true)
+ expect(subject.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
+end
diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
index ef52a25f47e..d24f5c45107 100644
--- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
@@ -55,6 +55,17 @@ describe Gitlab::Gfm::UploadsRewriter do
end
end
+ it 'does not rewrite plain links as embedded' do
+ embedded_link = image_uploader.markdown_link
+ plain_image_link = embedded_link.sub(/\A!/, "")
+ text = "#{plain_image_link} and #{embedded_link}"
+
+ moved_text = described_class.new(text, old_project, user).rewrite(new_project)
+
+ expect(moved_text.scan(/!\[.*?\]/).count).to eq(1)
+ expect(moved_text.scan(/\A\[.*?\]/).count).to eq(1)
+ end
+
context "file are stored locally" do
include_examples "files are accessible"
end
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 25052a79916..3f0e6b34291 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -408,7 +408,7 @@ describe Gitlab::Git::Commit, :seed_helper do
context 'when oids is empty' do
it 'makes no Gitaly request' do
- expect(Gitlab::GitalyClient).not_to receive(:call)
+ expect(Gitlab::GitalyClient).not_to receive(:call).with(repository.storage, :commit_service, :list_commits_by_oid)
described_class.batch_by_oid(repository, [])
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index cceeae8afe6..dccd50bc472 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -186,6 +186,12 @@ describe Gitlab::Git::Repository, :seed_helper do
it { is_expected.to be < 2 }
end
+ describe '#to_s' do
+ subject { repository.to_s }
+
+ it { is_expected.to eq("<Gitlab::Git::Repository: group/project>") }
+ end
+
describe '#object_directory_size' do
before do
allow(repository.gitaly_repository_client)
@@ -256,6 +262,22 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#submodule_urls_for' do
+ let(:ref) { 'master' }
+
+ it 'returns url mappings for submodules' do
+ urls = repository.submodule_urls_for(ref)
+
+ expect(urls).to eq({
+ "deeper/nested/six" => "git://github.com/randx/six.git",
+ "gitlab-grack" => "https://gitlab.com/gitlab-org/gitlab-grack.git",
+ "gitlab-shell" => "https://github.com/gitlabhq/gitlab-shell.git",
+ "nested/six" => "git://github.com/randx/six.git",
+ "six" => "git://github.com/randx/six.git"
+ })
+ end
+ end
+
describe '#commit_count' do
it { expect(repository.commit_count("master")).to eq(25) }
it { expect(repository.commit_count("feature")).to eq(9) }
@@ -1694,14 +1716,15 @@ describe Gitlab::Git::Repository, :seed_helper do
let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
let(:left_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
let(:right_branch) { 'test-master' }
+ let(:first_parent_ref) { 'refs/heads/test-master' }
let(:target_ref) { 'refs/merge-requests/999/merge' }
before do
- repository.create_branch(right_branch, branch_head) unless repository.branch_exists?(right_branch)
+ repository.create_branch(right_branch, branch_head) unless repository.ref_exists?(first_parent_ref)
end
def merge_to_ref
- repository.merge_to_ref(user, left_sha, right_branch, target_ref, 'Merge message')
+ repository.merge_to_ref(user, left_sha, right_branch, target_ref, 'Merge message', first_parent_ref)
end
it 'generates a commit in the target_ref' do
@@ -1716,7 +1739,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
it 'does not change the right branch HEAD' do
- expect { merge_to_ref }.not_to change { repository.find_branch(right_branch).target }
+ expect { merge_to_ref }.not_to change { repository.commit(first_parent_ref).sha }
end
end
diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
new file mode 100644
index 00000000000..1a4168f7317
--- /dev/null
+++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'json'
+require 'tempfile'
+
+describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:feature_flag_name) { 'feature-flag-name' }
+ let(:feature_flag) { Feature.get(feature_flag_name) }
+ let(:temp_gitaly_metadata_file) { create_temporary_gitaly_metadata_file }
+
+ before(:all) do
+ create_gitaly_metadata_file
+ end
+
+ subject(:wrapper) do
+ klazz = Class.new do
+ include Gitlab::Git::RuggedImpl::UseRugged
+
+ def rugged_test(ref, test_number)
+ end
+ end
+
+ klazz.new
+ end
+
+ before do
+ allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_call_original
+ Gitlab::GitalyClient.instance_variable_set(:@can_use_disk, {})
+ end
+
+ context '#execute_rugged_call', :request_store do
+ let(:args) { ['refs/heads/master', 1] }
+
+ before do
+ allow(Gitlab::RuggedInstrumentation).to receive(:peek_enabled?).and_return(true)
+ end
+
+ it 'instruments Rugged call' do
+ expect(subject).to receive(:rugged_test).with(args)
+
+ subject.execute_rugged_call(:rugged_test, args)
+
+ expect(Gitlab::RuggedInstrumentation.query_count).to eq(1)
+ expect(Gitlab::RuggedInstrumentation.list_call_details.count).to eq(1)
+ end
+ end
+
+ context 'when feature flag is not persisted' do
+ before do
+ allow(Feature).to receive(:persisted?).with(feature_flag).and_return(false)
+ end
+
+ it 'returns true when gitaly matches disk' do
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be true
+ end
+
+ it 'returns false when disk access fails' do
+ allow(Gitlab::GitalyClient).to receive(:storage_metadata_file_path).and_return("/fake/path/doesnt/exist")
+
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be false
+ end
+
+ it "returns false when gitaly doesn't match disk" do
+ allow(Gitlab::GitalyClient).to receive(:storage_metadata_file_path).and_return(temp_gitaly_metadata_file)
+
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be_falsey
+
+ File.delete(temp_gitaly_metadata_file)
+ end
+
+ it "doesn't lead to a second rpc call because gitaly client should use the cached value" do
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be true
+
+ expect(Gitlab::GitalyClient).not_to receive(:filesystem_id)
+
+ subject.use_rugged?(repository, feature_flag_name)
+ end
+ end
+
+ context 'when feature flag is persisted' do
+ before do
+ allow(Feature).to receive(:persisted?).with(feature_flag).and_return(true)
+ end
+
+ it 'returns false when the feature flag is off' do
+ allow(feature_flag).to receive(:enabled?).and_return(false)
+
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be_falsey
+ end
+
+ it "returns true when feature flag is on" do
+ allow(feature_flag).to receive(:enabled?).and_return(true)
+ allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(false)
+
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be true
+ end
+ end
+
+ def create_temporary_gitaly_metadata_file
+ tmp = Tempfile.new('.gitaly-metadata')
+ gitaly_metadata = {
+ "gitaly_filesystem_id" => "some-value"
+ }
+ tmp.write(gitaly_metadata.to_json)
+ tmp.flush
+ tmp.close
+ tmp.path
+ end
+
+ def create_gitaly_metadata_file
+ File.open(File.join(SEED_STORAGE_PATH, '.gitaly-metadata'), 'w+') do |f|
+ gitaly_metadata = {
+ "gitaly_filesystem_id" => SecureRandom.uuid
+ }
+ f.write(gitaly_metadata.to_json)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git_post_receive_spec.rb b/spec/lib/gitlab/git_post_receive_spec.rb
new file mode 100644
index 00000000000..f0df3794e29
--- /dev/null
+++ b/spec/lib/gitlab/git_post_receive_spec.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::Gitlab::GitPostReceive do
+ set(:project) { create(:project, :repository) }
+
+ subject { described_class.new(project, "project-#{project.id}", changes.dup, {}) }
+
+ describe '#includes_branches?' do
+ context 'with no branches' do
+ let(:changes) do
+ <<~EOF
+ 654321 210987 refs/nobranches/tag1
+ 654322 210986 refs/tags/test1
+ 654323 210985 refs/merge-requests/mr1
+ EOF
+ end
+
+ it 'returns false' do
+ expect(subject.includes_branches?).to be_falsey
+ end
+ end
+
+ context 'with branches' do
+ let(:changes) do
+ <<~EOF
+ 654322 210986 refs/heads/test1
+ 654321 210987 refs/tags/tag1
+ 654323 210985 refs/merge-requests/mr1
+ EOF
+ end
+
+ it 'returns true' do
+ expect(subject.includes_branches?).to be_truthy
+ end
+ end
+
+ context 'with malformed changes' do
+ let(:changes) do
+ <<~EOF
+ ref/heads/1 a
+ somebranch refs/heads/2
+ EOF
+ end
+
+ it 'returns false' do
+ expect(subject.includes_branches?).to be_falsey
+ end
+ end
+ end
+
+ describe '#includes_tags?' do
+ context 'with no tags' do
+ let(:changes) do
+ <<~EOF
+ 654321 210987 refs/notags/tag1
+ 654322 210986 refs/heads/test1
+ 654323 210985 refs/merge-requests/mr1
+ EOF
+ end
+
+ it 'returns false' do
+ expect(subject.includes_tags?).to be_falsey
+ end
+ end
+
+ context 'with tags' do
+ let(:changes) do
+ <<~EOF
+ 654322 210986 refs/heads/test1
+ 654321 210987 refs/tags/tag1
+ 654323 210985 refs/merge-requests/mr1
+ EOF
+ end
+
+ it 'returns true' do
+ expect(subject.includes_tags?).to be_truthy
+ end
+ end
+
+ context 'with malformed changes' do
+ let(:changes) do
+ <<~EOF
+ ref/tags/1 a
+ sometag refs/tags/2
+ EOF
+ end
+
+ it 'returns false' do
+ expect(subject.includes_tags?).to be_falsey
+ end
+ end
+ end
+
+ describe '#includes_default_branch?' do
+ context 'with no default branch' do
+ let(:changes) do
+ <<~EOF
+ 654321 210987 refs/heads/test1
+ 654322 210986 refs/tags/#{project.default_branch}
+ 654323 210985 refs/heads/test3
+ EOF
+ end
+
+ it 'returns false' do
+ expect(subject.includes_default_branch?).to be_falsey
+ end
+ end
+
+ context 'with a project with no default branch' do
+ let(:changes) do
+ <<~EOF
+ 654321 210987 refs/heads/test1
+ EOF
+ end
+
+ it 'returns true' do
+ expect(project).to receive(:default_branch).and_return(nil)
+ expect(subject.includes_default_branch?).to be_truthy
+ end
+ end
+
+ context 'with default branch' do
+ let(:changes) do
+ <<~EOF
+ 654322 210986 refs/heads/test1
+ 654321 210987 refs/tags/test2
+ 654323 210985 refs/heads/#{project.default_branch}
+ EOF
+ end
+
+ it 'returns true' do
+ expect(subject.includes_default_branch?).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
index ce15057dd7d..6515be85ae3 100644
--- a/spec/lib/gitlab/git_spec.rb
+++ b/spec/lib/gitlab/git_spec.rb
@@ -39,6 +39,26 @@ describe Gitlab::Git do
end
end
+ describe '.commit_id?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:sha, :result) do
+ '' | false
+ 'foobar' | false
+ '4b825dc' | false
+ 'zzz25dc642cb6eb9a060e54bf8d69288fbee4904' | false
+
+ '4b825dc642cb6eb9a060e54bf8d69288fbee4904' | true
+ Gitlab::Git::BLANK_SHA | true
+ end
+
+ with_them do
+ it 'returns the expected result' do
+ expect(described_class.commit_id?(sha)).to eq(result)
+ end
+ end
+ end
+
describe '.shas_eql?' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/gitaly_client/notification_service_spec.rb b/spec/lib/gitlab/gitaly_client/notification_service_spec.rb
deleted file mode 100644
index ffc3a09be30..00000000000
--- a/spec/lib/gitlab/gitaly_client/notification_service_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::GitalyClient::NotificationService do
- describe '#post_receive' do
- let(:project) { create(:project) }
- let(:storage_name) { project.repository_storage }
- let(:relative_path) { project.disk_path + '.git' }
- subject { described_class.new(project.repository) }
-
- it 'sends a post_receive message' do
- expect_any_instance_of(Gitaly::NotificationService::Stub)
- .to receive(:post_receive).with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
-
- subject.post_receive
- end
- end
-end
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 18663a72fcd..f38b8d31237 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -79,13 +79,13 @@ describe Gitlab::GitalyClient::OperationService do
end
describe '#user_merge_to_ref' do
- let(:branch) { 'my-branch' }
+ let(:first_parent_ref) { 'refs/heads/my-branch' }
let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
let(:ref) { 'refs/merge-requests/x/merge' }
let(:message) { 'validación' }
let(:response) { Gitaly::UserMergeToRefResponse.new(commit_id: 'new-commit-id') }
- subject { client.user_merge_to_ref(user, source_sha, branch, ref, message) }
+ subject { client.user_merge_to_ref(user, source_sha, nil, ref, message, first_parent_ref) }
it 'sends a user_merge_to_ref message' do
expect_any_instance_of(Gitaly::OperationService::Stub)
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index eed233f1f3e..99d563e03ec 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -17,6 +17,26 @@ describe Gitlab::GitalyClient do
})
end
+ describe '.filesystem_id_from_disk' do
+ it 'catches errors' do
+ [Errno::ENOENT, Errno::EACCES, JSON::ParserError].each do |error|
+ allow(File).to receive(:read).with(described_class.storage_metadata_file_path('default')).and_raise(error)
+
+ expect(described_class.filesystem_id_from_disk('default')).to be_nil
+ end
+ end
+ end
+
+ describe '.filesystem_id' do
+ it 'returns an empty string when the storage is not found in the response' do
+ response = double("response")
+ allow(response).to receive(:storage_statuses).and_return([])
+ allow_any_instance_of(Gitlab::GitalyClient::ServerService).to receive(:info).and_return(response)
+
+ expect(described_class.filesystem_id('default')).to eq(nil)
+ end
+ end
+
describe '.stub_class' do
it 'returns the gRPC health check stub' do
expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub)
@@ -119,6 +139,19 @@ describe Gitlab::GitalyClient do
end
end
+ describe '.can_use_disk?' do
+ it 'properly caches a false result' do
+ # spec_helper stubs this globally
+ allow(described_class).to receive(:can_use_disk?).and_call_original
+ expect(described_class).to receive(:filesystem_id).once
+ expect(described_class).to receive(:filesystem_id_from_disk).once
+
+ 2.times do
+ described_class.can_use_disk?('unknown')
+ end
+ end
+ end
+
describe '.connection_data' do
it 'returns connection data' do
address = 'tcp://localhost:9876'
@@ -171,17 +204,6 @@ describe Gitlab::GitalyClient do
end
end
end
-
- context 'when catfile-cache feature is disabled' do
- before do
- stub_feature_flags({ 'gitaly_catfile-cache': false })
- end
-
- it 'does not set the gitaly-session-id in the metadata' do
- results = described_class.request_kwargs('default', nil)
- expect(results[:metadata]).not_to include('gitaly-session-id')
- end
- end
end
describe 'enforce_gitaly_request_limits?' do
diff --git a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
index 23ae026fb14..0e5419e6c5e 100644
--- a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
@@ -6,6 +6,7 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
let(:importer) { described_class.new(project, client) }
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
+ let(:released_at) { Time.new(2017, 1, 1, 12, 00) }
let(:release) do
double(
@@ -13,7 +14,8 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
tag_name: '1.0',
body: 'This is my release',
created_at: created_at,
- updated_at: updated_at
+ updated_at: updated_at,
+ published_at: released_at
)
end
@@ -23,7 +25,8 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
tag_name: '1.0',
description: 'This is my release',
created_at: created_at,
- updated_at: updated_at
+ updated_at: updated_at,
+ released_at: released_at
}
expect(importer).to receive(:build_releases).and_return([release_hash])
diff --git a/spec/lib/gitlab/global_id_spec.rb b/spec/lib/gitlab/global_id_spec.rb
new file mode 100644
index 00000000000..d35b5da0b75
--- /dev/null
+++ b/spec/lib/gitlab/global_id_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::GlobalId do
+ describe '.build' do
+ set(:object) { create(:issue) }
+
+ it 'returns a standard GlobalId if only object is passed' do
+ expect(described_class.build(object).to_s).to eq(object.to_global_id.to_s)
+ end
+
+ it 'returns a GlobalId from params' do
+ expect(described_class.build(model_name: 'MyModel', id: 'myid').to_s).to eq(
+ 'gid://gitlab/MyModel/myid'
+ )
+ end
+
+ it 'returns a GlobalId from object and `id` param' do
+ expect(described_class.build(object, id: 'myid').to_s).to eq(
+ 'gid://gitlab/Issue/myid'
+ )
+ end
+
+ it 'returns a GlobalId from object and `model_name` param' do
+ expect(described_class.build(object, model_name: 'MyModel').to_s).to eq(
+ "gid://gitlab/MyModel/#{object.id}"
+ )
+ end
+
+ it 'returns an error if model_name and id are not able to be determined' do
+ expect { described_class.build(id: 'myid') }.to raise_error(URI::InvalidComponentError)
+ expect { described_class.build(model_name: 'MyModel') }.to raise_error(URI::InvalidComponentError)
+ expect { described_class.build }.to raise_error(URI::InvalidComponentError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
index aec9c4baf0a..7a7ae373058 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
@@ -7,35 +7,42 @@ require 'spec_helper'
describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
def type(type_authorizations = [])
Class.new(Types::BaseObject) do
- graphql_name "TestType"
+ graphql_name 'TestType'
authorize type_authorizations
end
end
- def type_with_field(field_type, field_authorizations = [], resolved_value = "Resolved value")
+ def type_with_field(field_type, field_authorizations = [], resolved_value = 'Resolved value', **options)
Class.new(Types::BaseObject) do
- graphql_name "TestTypeWithField"
- field :test_field, field_type, null: true, authorize: field_authorizations, resolve: -> (_, _, _) { resolved_value}
+ graphql_name 'TestTypeWithField'
+ options.reverse_merge!(null: true)
+ field :test_field, field_type,
+ authorize: field_authorizations,
+ resolve: -> (_, _, _) { resolved_value },
+ **options
end
end
let(:current_user) { double(:current_user) }
subject(:service) { described_class.new(field) }
- describe "#authorized_resolve" do
- let(:presented_object) { double("presented object") }
- let(:presented_type) { double("parent type", object: presented_object) }
- subject(:resolved) { service.authorized_resolve.call(presented_type, {}, { current_user: current_user }) }
-
- context "scalar types" do
- shared_examples "checking permissions on the presented object" do
- it "checks the abilities on the object being presented and returns the value" do
+ describe '#authorized_resolve' do
+ let(:presented_object) { double('presented object') }
+ let(:presented_type) { double('parent type', object: presented_object) }
+ let(:query_type) { GraphQL::ObjectType.new }
+ let(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)}
+ let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: { current_user: current_user }, object: nil) }
+ subject(:resolved) { service.authorized_resolve.call(presented_type, {}, context) }
+
+ context 'scalar types' do
+ shared_examples 'checking permissions on the presented object' do
+ it 'checks the abilities on the object being presented and returns the value' do
expected_permissions.each do |permission|
spy_ability_check_for(permission, presented_object, passed: true)
end
- expect(resolved).to eq("Resolved value")
+ expect(resolved).to eq('Resolved value')
end
it "returns nil if the value wasn't authorized" do
@@ -45,61 +52,71 @@ describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
end
end
- context "when the field is a built-in scalar type" do
- let(:field) { type_with_field(GraphQL::STRING_TYPE, :read_field).fields["testField"].to_graphql }
+ context 'when the field is a built-in scalar type' do
+ let(:field) { type_with_field(GraphQL::STRING_TYPE, :read_field).fields['testField'].to_graphql }
let(:expected_permissions) { [:read_field] }
- it_behaves_like "checking permissions on the presented object"
+ it_behaves_like 'checking permissions on the presented object'
end
- context "when the field is a list of scalar types" do
- let(:field) { type_with_field([GraphQL::STRING_TYPE], :read_field).fields["testField"].to_graphql }
+ context 'when the field is a list of scalar types' do
+ let(:field) { type_with_field([GraphQL::STRING_TYPE], :read_field).fields['testField'].to_graphql }
let(:expected_permissions) { [:read_field] }
- it_behaves_like "checking permissions on the presented object"
+ it_behaves_like 'checking permissions on the presented object'
end
- context "when the field is sub-classed scalar type" do
- let(:field) { type_with_field(Types::TimeType, :read_field).fields["testField"].to_graphql }
+ context 'when the field is sub-classed scalar type' do
+ let(:field) { type_with_field(Types::TimeType, :read_field).fields['testField'].to_graphql }
let(:expected_permissions) { [:read_field] }
- it_behaves_like "checking permissions on the presented object"
+ it_behaves_like 'checking permissions on the presented object'
end
- context "when the field is a list of sub-classed scalar types" do
- let(:field) { type_with_field([Types::TimeType], :read_field).fields["testField"].to_graphql }
+ context 'when the field is a list of sub-classed scalar types' do
+ let(:field) { type_with_field([Types::TimeType], :read_field).fields['testField'].to_graphql }
let(:expected_permissions) { [:read_field] }
- it_behaves_like "checking permissions on the presented object"
+ it_behaves_like 'checking permissions on the presented object'
end
end
- context "when the field is a specific type" do
+ context 'when the field is a specific type' do
let(:custom_type) { type(:read_type) }
- let(:object_in_field) { double("presented in field") }
- let(:field) { type_with_field(custom_type, :read_field, object_in_field).fields["testField"].to_graphql }
+ let(:object_in_field) { double('presented in field') }
+ let(:field) { type_with_field(custom_type, :read_field, object_in_field).fields['testField'].to_graphql }
- it "checks both field & type permissions" do
+ it 'checks both field & type permissions' do
spy_ability_check_for(:read_field, object_in_field, passed: true)
spy_ability_check_for(:read_type, object_in_field, passed: true)
expect(resolved).to eq(object_in_field)
end
- it "returns nil if viewing was not allowed" do
+ it 'returns nil if viewing was not allowed' do
spy_ability_check_for(:read_field, object_in_field, passed: false)
spy_ability_check_for(:read_type, object_in_field, passed: true)
expect(resolved).to be_nil
end
- context "when the field is a list" do
- let(:object_1) { double("presented in field 1") }
- let(:object_2) { double("presented in field 2") }
+ context 'when the field is not nullable' do
+ let(:field) { type_with_field(custom_type, [], object_in_field, null: false).fields['testField'].to_graphql }
+
+ it 'returns nil when viewing is not allowed' do
+ spy_ability_check_for(:read_type, object_in_field, passed: false)
+
+ expect(resolved).to be_nil
+ end
+ end
+
+ context 'when the field is a list' do
+ let(:object_1) { double('presented in field 1') }
+ let(:object_2) { double('presented in field 2') }
let(:presented_types) { [double(object: object_1), double(object: object_2)] }
- let(:field) { type_with_field([custom_type], :read_field, presented_types).fields["testField"].to_graphql }
+ let(:field) { type_with_field([custom_type], :read_field, presented_types).fields['testField'].to_graphql }
- it "checks all permissions" do
+ it 'checks all permissions' do
allow(Ability).to receive(:allowed?) { true }
spy_ability_check_for(:read_field, object_1, passed: true)
@@ -110,7 +127,7 @@ describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
expect(resolved).to eq(presented_types)
end
- it "filters out objects that the user cannot see" do
+ it 'filters out objects that the user cannot see' do
allow(Ability).to receive(:allowed?) { true }
spy_ability_check_for(:read_type, object_1, passed: false)
diff --git a/spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb b/spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb
new file mode 100644
index 00000000000..d93ce464a92
--- /dev/null
+++ b/spec/lib/gitlab/graphql/calls_gitaly/instrumentation_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::Graphql::CallsGitaly::Instrumentation do
+ subject { described_class.new }
+
+ describe '#calls_gitaly_check' do
+ let(:gitaly_field) { Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true) }
+ let(:no_gitaly_field) { Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, null: true, calls_gitaly: false) }
+
+ context 'if there are no Gitaly calls' do
+ it 'does not raise an error if calls_gitaly is false' do
+ expect { subject.send(:calls_gitaly_check, no_gitaly_field, 0) }.not_to raise_error
+ end
+ end
+
+ context 'if there is at least 1 Gitaly call' do
+ it 'raises an error if calls_gitaly: is false or not defined' do
+ expect { subject.send(:calls_gitaly_check, no_gitaly_field, 1) }.to raise_error(/specify a constant complexity or add `calls_gitaly: true`/)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb b/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb
new file mode 100644
index 00000000000..91e90315b3e
--- /dev/null
+++ b/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::FindArgumentInParent do
+ describe '#find' do
+ def build_node(parent = nil, args: {})
+ props = { irep_node: double(arguments: args) }
+ props[:parent] = parent if parent # The root node shouldn't respond to parent
+
+ double(props)
+ end
+
+ let(:parent) do
+ build_node(
+ build_node(
+ build_node(
+ build_node,
+ args: { myArg: 1 }
+ )
+ )
+ )
+ end
+ let(:arg_name) { :my_arg }
+
+ it 'searches parents and returns the argument' do
+ expect(described_class.find(parent, :my_arg)).to eq(1)
+ end
+
+ it 'can find argument when passed in as both Ruby and GraphQL-formatted symbols and strings' do
+ [:my_arg, :myArg, 'my_arg', 'myArg'].each do |arg|
+ expect(described_class.find(parent, arg)).to eq(1)
+ end
+ end
+
+ it 'returns nil if no arguments found in parents' do
+ expect(described_class.find(parent, :bar)).to eq(nil)
+ end
+
+ it 'can limit the depth it searches to' do
+ expect(described_class.find(parent, :my_arg, limit_depth: 1)).to eq(nil)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb
new file mode 100644
index 00000000000..38931f7ab5e
--- /dev/null
+++ b/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader do
+ describe '#find' do
+ it 'only queries once for project statistics' do
+ stats = create_list(:namespace_root_storage_statistics, 2)
+ namespace1 = stats.first.namespace
+ namespace2 = stats.last.namespace
+
+ expect do
+ described_class.new(namespace1.id).find
+ described_class.new(namespace2.id).find
+ end.not_to exceed_query_limit(1)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/markdown_field_spec.rb b/spec/lib/gitlab/graphql/markdown_field_spec.rb
index a8566aa8e1c..866a20801d3 100644
--- a/spec/lib/gitlab/graphql/markdown_field_spec.rb
+++ b/spec/lib/gitlab/graphql/markdown_field_spec.rb
@@ -30,17 +30,20 @@ describe Gitlab::Graphql::MarkdownField do
let(:note) { build(:note, note: '# Markdown!') }
let(:thing_with_markdown) { double('markdown thing', object: note) }
let(:expected_markdown) { '<h1 data-sourcepos="1:1-1:11" dir="auto">Markdown!</h1>' }
+ let(:query_type) { GraphQL::ObjectType.new }
+ let(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)}
+ let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: nil, object: nil) }
it 'renders markdown from the same property as the field name without the `_html` suffix' do
field = class_with_markdown_field(:note_html, null: false).fields['noteHtml']
- expect(field.to_graphql.resolve(thing_with_markdown, {}, {})).to eq(expected_markdown)
+ expect(field.to_graphql.resolve(thing_with_markdown, {}, context)).to eq(expected_markdown)
end
it 'renders markdown from a specific property when a `method` argument is passed' do
field = class_with_markdown_field(:test_html, null: false, method: :note).fields['testHtml']
- expect(field.to_graphql.resolve(thing_with_markdown, {}, {})).to eq(expected_markdown)
+ expect(field.to_graphql.resolve(thing_with_markdown, {}, context)).to eq(expected_markdown)
end
end
end
diff --git a/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb b/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb
new file mode 100644
index 00000000000..28056a6085d
--- /dev/null
+++ b/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Representation::SubmoduleTreeEntry do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ describe '.decorate' do
+ let(:submodules) { repository.tree.submodules }
+
+ it 'returns array of SubmoduleTreeEntry' do
+ entries = described_class.decorate(submodules, repository.tree)
+
+ expect(entries.first).to be_a(described_class)
+
+ expect(entries.map(&:web_url)).to contain_exactly(
+ "https://gitlab.com/gitlab-org/gitlab-grack",
+ "https://github.com/gitlabhq/gitlab-shell",
+ "https://github.com/randx/six"
+ )
+
+ expect(entries.map(&:tree_url)).to contain_exactly(
+ "https://gitlab.com/gitlab-org/gitlab-grack/tree/645f6c4c82fd3f5e06f67134450a570b795e55a6",
+ "https://github.com/gitlabhq/gitlab-shell/tree/79bceae69cb5750d6567b223597999bfa91cb3b9",
+ "https://github.com/randx/six/tree/409f37c4f05865e4fb208c771485f211a22c4c2d"
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb
index 2734fcef0a0..53a91a35ec9 100644
--- a/spec/lib/gitlab/group_search_results_spec.rb
+++ b/spec/lib/gitlab/group_search_results_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::GroupSearchResults do
expect(result).to eq [user1]
end
- it 'returns the user belonging to the subgroup matching the search query', :nested_groups do
+ it 'returns the user belonging to the subgroup matching the search query' do
user1 = create(:user, username: 'gob_bluth')
subgroup = create(:group, parent: group)
create(:group_member, :developer, user: user1, group: subgroup)
@@ -32,7 +32,7 @@ describe Gitlab::GroupSearchResults do
expect(result).to eq [user1]
end
- it 'returns the user belonging to the parent group matching the search query', :nested_groups do
+ it 'returns the user belonging to the parent group matching the search query' do
user1 = create(:user, username: 'gob_bluth')
parent_group = create(:group, children: [group])
create(:group_member, :developer, user: user1, group: parent_group)
@@ -44,7 +44,7 @@ describe Gitlab::GroupSearchResults do
expect(result).to eq [user1]
end
- it 'does not return the user belonging to the private subgroup', :nested_groups do
+ it 'does not return the user belonging to the private subgroup' do
user1 = create(:user, username: 'gob_bluth')
subgroup = create(:group, :private, parent: group)
create(:group_member, :developer, user: user1, group: subgroup)
diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb
index 930d1f62272..1532fd1103e 100644
--- a/spec/lib/gitlab/http_connection_adapter_spec.rb
+++ b/spec/lib/gitlab/http_connection_adapter_spec.rb
@@ -3,7 +3,13 @@
require 'spec_helper'
describe Gitlab::HTTPConnectionAdapter do
+ include StubRequests
+
describe '#connection' do
+ before do
+ stub_all_dns('https://example.org', ip_address: '93.184.216.34')
+ end
+
context 'when local requests are not allowed' do
it 'sets up the connection' do
uri = URI('https://example.org')
diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb
index 158f77cab2c..d3f9be845dd 100644
--- a/spec/lib/gitlab/http_spec.rb
+++ b/spec/lib/gitlab/http_spec.rb
@@ -23,14 +23,14 @@ describe Gitlab::HTTP do
end
end
- describe 'allow_local_requests_from_hooks_and_services is' do
+ describe 'allow_local_requests_from_web_hooks_and_services is' do
before do
WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success')
end
context 'disabled' do
before do
- allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_hooks_and_services?).and_return(false)
+ allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(false)
end
it 'deny requests to localhost' do
@@ -52,7 +52,7 @@ describe Gitlab::HTTP do
context 'enabled' do
before do
- allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_hooks_and_services?).and_return(true)
+ allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(true)
end
it 'allow requests to localhost' do
diff --git a/spec/lib/gitlab/import/database_helpers_spec.rb b/spec/lib/gitlab/import/database_helpers_spec.rb
index e716155b7d5..3ac34455177 100644
--- a/spec/lib/gitlab/import/database_helpers_spec.rb
+++ b/spec/lib/gitlab/import/database_helpers_spec.rb
@@ -15,32 +15,15 @@ describe Gitlab::Import::DatabaseHelpers do
let(:attributes) { { iid: 1, title: 'foo' } }
let(:project) { create(:project) }
- context 'on PostgreSQL' do
- it 'returns the ID returned by the query' do
- expect(Gitlab::Database)
- .to receive(:bulk_insert)
- .with(Issue.table_name, [attributes], return_ids: true)
- .and_return([10])
+ it 'returns the ID returned by the query' do
+ expect(Gitlab::Database)
+ .to receive(:bulk_insert)
+ .with(Issue.table_name, [attributes], return_ids: true)
+ .and_return([10])
- id = subject.insert_and_return_id(attributes, project.issues)
+ id = subject.insert_and_return_id(attributes, project.issues)
- expect(id).to eq(10)
- end
- end
-
- context 'on MySQL' do
- it 'uses a separate query to retrieve the ID' do
- issue = create(:issue, project: project, iid: attributes[:iid])
-
- expect(Gitlab::Database)
- .to receive(:bulk_insert)
- .with(Issue.table_name, [attributes], return_ids: true)
- .and_return([])
-
- id = subject.insert_and_return_id(attributes, project.issues)
-
- expect(id).to eq(issue.id)
- end
+ expect(id).to eq(10)
end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 7baa52ffb4f..3c6b17c10ec 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -242,6 +242,7 @@ project:
- cluster_project
- cluster_ingresses
- creator
+- cycle_analytics_stages
- group
- namespace
- boards
@@ -277,7 +278,6 @@ project:
- bugzilla_service
- gitlab_issue_tracker_service
- external_wiki_service
-- kubernetes_service
- mock_ci_service
- mock_deployment_service
- mock_monitoring_service
@@ -343,7 +343,6 @@ project:
- fork_network_member
- fork_network
- custom_attributes
-- prometheus_metrics
- lfs_file_locks
- project_badges
- source_of_merge_requests
@@ -470,3 +469,17 @@ incident_management_setting:
merge_trains:
- project
- merge_request
+boards:
+- group
+- lists
+- destroyable_lists
+- milestone
+- board_labels
+- board_assignee
+- assignee
+- labels
+lists:
+- user
+- milestone
+- board
+- label
diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
index 99669285d5b..873728f9909 100644
--- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
@@ -22,7 +22,9 @@ describe Gitlab::ImportExport::AttributeCleaner do
'some_html' => '<p>dodgy html</p>',
'legit_html' => '<p>legit html</p>',
'_html' => '<p>perfectly ordinary html</p>',
- 'cached_markdown_version' => 12345
+ 'cached_markdown_version' => 12345,
+ 'group_id' => 99,
+ 'commit_id' => 99
}
end
@@ -31,7 +33,9 @@ describe Gitlab::ImportExport::AttributeCleaner do
'project_id' => 99,
'user_id' => 99,
'random_id_in_the_middle' => 99,
- 'notid' => 99
+ 'notid' => 99,
+ 'group_id' => 99,
+ 'commit_id' => 99
}
end
@@ -59,6 +63,6 @@ describe Gitlab::ImportExport::AttributeCleaner do
it 'does not remove excluded key if not listed' do
parsed_hash = described_class.clean(relation_hash: unsafe_hash, relation_class: relation_class)
- expect(parsed_hash.keys).to eq post_safe_hash.keys + excluded_keys
+ expect(parsed_hash.keys).to match_array post_safe_hash.keys + excluded_keys
end
end
diff --git a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
index 70eeb9ee66b..2b0bdb909ae 100644
--- a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
@@ -6,6 +6,7 @@ describe Gitlab::ImportExport::LfsRestorer do
let(:export_path) { "#{Dir.tmpdir}/lfs_object_restorer_spec" }
let(:project) { create(:project) }
let(:shared) { project.import_export_shared }
+ let(:saver) { Gitlab::ImportExport::LfsSaver.new(project: project, shared: shared) }
subject(:restorer) { described_class.new(project: project, shared: shared) }
before do
@@ -19,49 +20,98 @@ describe Gitlab::ImportExport::LfsRestorer do
describe '#restore' do
context 'when the archive contains lfs files' do
- let(:dummy_lfs_file_path) { File.join(shared.export_path, 'lfs-objects', 'dummy') }
-
- def create_lfs_object_with_content(content)
- dummy_lfs_file = Tempfile.new('existing')
- File.write(dummy_lfs_file.path, content)
- size = dummy_lfs_file.size
- oid = LfsObject.calculate_oid(dummy_lfs_file.path)
- LfsObject.create!(oid: oid, size: size, file: dummy_lfs_file)
+ let(:lfs_object) { create(:lfs_object, :correct_oid, :with_file) }
+
+ # Use the LfsSaver to save data to be restored
+ def save_lfs_data
+ %w(project wiki).each do |repository_type|
+ create(
+ :lfs_objects_project,
+ project: project,
+ repository_type: repository_type,
+ lfs_object: lfs_object
+ )
+ end
+
+ saver.save
+
+ project.lfs_objects.delete_all
end
before do
- FileUtils.mkdir_p(File.dirname(dummy_lfs_file_path))
- File.write(dummy_lfs_file_path, 'not very large')
- allow(restorer).to receive(:lfs_file_paths).and_return([dummy_lfs_file_path])
+ save_lfs_data
+ project.reload
end
- it 'creates an lfs object for the project' do
- expect { restorer.restore }.to change { project.reload.lfs_objects.size }.by(1)
+ it 'succeeds' do
+ expect(restorer.restore).to eq(true)
+ expect(shared.errors).to be_empty
end
- it 'assigns the file correctly' do
+ it 'does not create a new `LfsObject` records, as one already exists' do
+ expect { restorer.restore }.not_to change { LfsObject.count }
+ end
+
+ it 'creates new `LfsObjectsProject` records in order to link the project to the existing `LfsObject`' do
+ expect { restorer.restore }.to change { LfsObjectsProject.count }.by(2)
+ end
+
+ it 'restores the correct `LfsObject` records' do
restorer.restore
- expect(project.lfs_objects.first.file.read).to eq('not very large')
+ expect(project.lfs_objects).to contain_exactly(lfs_object)
end
- it 'links an existing LFS object if it existed' do
- lfs_object = create_lfs_object_with_content('not very large')
+ it 'restores the correct `LfsObjectsProject` records for the project' do
+ restorer.restore
+ expect(
+ project.lfs_objects_projects.pluck(:repository_type)
+ ).to contain_exactly('project', 'wiki')
+ end
+
+ it 'assigns the file correctly' do
restorer.restore
- expect(project.lfs_objects).to include(lfs_object)
+ expect(project.lfs_objects.first.file.read).to eq(lfs_object.file.read)
end
- it 'succeeds' do
- expect(restorer.restore).to be_truthy
- expect(shared.errors).to be_empty
+ context 'when there is not an existing `LfsObject`' do
+ before do
+ lfs_object.destroy
+ end
+
+ it 'creates a new lfs object' do
+ expect { restorer.restore }.to change { LfsObject.count }.by(1)
+ end
+
+ it 'stores the upload' do
+ expect_any_instance_of(LfsObjectUploader).to receive(:store!)
+
+ restorer.restore
+ end
end
- it 'stores the upload' do
- expect_any_instance_of(LfsObjectUploader).to receive(:store!)
+ context 'when there is no lfs-objects.json file' do
+ before do
+ json_file = File.join(shared.export_path, ::Gitlab::ImportExport.lfs_objects_filename)
- restorer.restore
+ FileUtils.rm_rf(json_file)
+ end
+
+ it 'restores the correct `LfsObject` records' do
+ restorer.restore
+
+ expect(project.lfs_objects).to contain_exactly(lfs_object)
+ end
+
+ it 'restores a single `LfsObjectsProject` record for the project with "project" for the `repository_type`' do
+ restorer.restore
+
+ expect(
+ project.lfs_objects_projects.pluck(:repository_type)
+ ).to contain_exactly('project')
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/lfs_saver_spec.rb b/spec/lib/gitlab/import_export/lfs_saver_spec.rb
index 9b0e21deb2e..c3c88486e16 100644
--- a/spec/lib/gitlab/import_export/lfs_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb
@@ -19,6 +19,11 @@ describe Gitlab::ImportExport::LfsSaver do
describe '#save' do
context 'when the project has LFS objects locally stored' do
let(:lfs_object) { create(:lfs_object, :with_file) }
+ let(:lfs_json_file) { File.join(shared.export_path, Gitlab::ImportExport.lfs_objects_filename) }
+
+ def lfs_json
+ JSON.parse(IO.read(lfs_json_file))
+ end
before do
project.lfs_objects << lfs_object
@@ -35,6 +40,45 @@ describe Gitlab::ImportExport::LfsSaver do
expect(File).to exist("#{shared.export_path}/lfs-objects/#{lfs_object.oid}")
end
+
+ describe 'saving a json file' do
+ before do
+ # Create two more LfsObjectProject records with different `repository_type`s
+ %w(wiki design).each do |repository_type|
+ create(
+ :lfs_objects_project,
+ project: project,
+ repository_type: repository_type,
+ lfs_object: lfs_object
+ )
+ end
+
+ FileUtils.rm_rf(lfs_json_file)
+ end
+
+ it 'saves a json file correctly' do
+ saver.save
+
+ expect(File.exist?(lfs_json_file)).to eq(true)
+ expect(lfs_json).to eq(
+ {
+ lfs_object.oid => [
+ LfsObjectsProject.repository_types['wiki'],
+ LfsObjectsProject.repository_types['design'],
+ nil
+ ]
+ }
+ )
+ end
+
+ it 'does not save a json file if feature is disabled' do
+ stub_feature_flags(export_lfs_objects_projects: false)
+
+ saver.save
+
+ expect(File.exist?(lfs_json_file)).to eq(false)
+ end
+ end
end
context 'when the LFS objects are stored in object storage' do
@@ -42,8 +86,11 @@ describe Gitlab::ImportExport::LfsSaver do
before do
allow(LfsObjectUploader).to receive(:object_store_enabled?).and_return(true)
- allow(lfs_object.file).to receive(:url).and_return('http://my-object-storage.local')
project.lfs_objects << lfs_object
+
+ expect_next_instance_of(LfsObjectUploader) do |instance|
+ expect(instance).to receive(:url).and_return('http://my-object-storage.local')
+ end
end
it 'downloads the file to include in an archive' do
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index b95b5dfe791..a9e8431acba 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -154,5 +154,15 @@ describe Gitlab::ImportExport::MembersMapper do
expect(members_mapper.map[exported_user_id]).to eq(user2.id)
end
end
+
+ context 'when importer mapping fails' do
+ let(:exception_message) { 'Something went wrong' }
+
+ it 'includes importer specific error message' do
+ expect(ProjectMember).to receive(:create!).and_raise(StandardError.new(exception_message))
+
+ expect { members_mapper.map }.to raise_error(StandardError, "Error adding importer user to project members. #{exception_message}")
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/project.group.json b/spec/lib/gitlab/import_export/project.group.json
index 1a561e81e4a..66f5bb4c87b 100644
--- a/spec/lib/gitlab/import_export/project.group.json
+++ b/spec/lib/gitlab/import_export/project.group.json
@@ -19,7 +19,7 @@
"labels": [
{
"id": 2,
- "title": "project label",
+ "title": "A project label",
"color": "#428bca",
"project_id": 8,
"created_at": "2016-07-22T08:55:44.161Z",
@@ -105,7 +105,7 @@
"updated_at": "2017-08-15T18:37:40.795Z",
"label": {
"id": 6,
- "title": "project label",
+ "title": "A project label",
"color": "#A8D695",
"project_id": null,
"created_at": "2017-08-15T18:37:19.698Z",
@@ -162,7 +162,7 @@
"updated_at": "2017-08-15T18:37:40.795Z",
"label": {
"id": 2,
- "title": "project label",
+ "title": "A project label",
"color": "#A8D695",
"project_id": null,
"created_at": "2017-08-15T18:37:19.698Z",
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 8be074f4b9b..a211675bbf2 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -2450,7 +2450,21 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": []
+ "events": [],
+ "suggestions": [
+ {
+ "id": 1,
+ "note_id": 674,
+ "relative_order": 0,
+ "applied": false,
+ "commit_id": null,
+ "from_content": "Original line\n",
+ "to_content": "New line\n",
+ "lines_above": 0,
+ "lines_below": 0,
+ "outdated": false
+ }
+ ]
},
{
"id": 675,
@@ -2775,7 +2789,8 @@
"action": 1,
"author_id": 1
}
- ]
+ ],
+ "approvals_before_merge": 1
},
{
"id": 26,
@@ -6630,8 +6645,16 @@
"id": 123,
"token": "cdbfasdf44a5958c83654733449e585",
"project_id": 5,
+ "owner_id": 1,
"created_at": "2017-01-16T15:25:28.637Z",
"updated_at": "2017-01-16T15:25:28.637Z"
+ },
+ {
+ "id": 456,
+ "token": "33a66349b5ad01fc00174af87804e40",
+ "project_id": 5,
+ "created_at": "2017-01-16T15:25:29.637Z",
+ "updated_at": "2017-01-16T15:25:29.637Z"
}
],
"deploy_keys": [],
@@ -7138,5 +7161,65 @@
"link_url": "http://www.example.com",
"image_url": "http://www.example.com"
}
+ ],
+ "boards": [
+ {
+ "id": 29,
+ "project_id": 49,
+ "created_at": "2019-06-06T14:01:06.204Z",
+ "updated_at": "2019-06-06T14:22:37.045Z",
+ "name": "TestBoardABC",
+ "milestone_id": null,
+ "group_id": null,
+ "weight": null,
+ "lists": [
+ {
+ "id": 59,
+ "board_id": 29,
+ "label_id": null,
+ "list_type": "backlog",
+ "position": null,
+ "created_at": "2019-06-06T14:01:06.214Z",
+ "updated_at": "2019-06-06T14:01:06.214Z",
+ "user_id": null,
+ "milestone_id": null
+ },
+ {
+ "id": 61,
+ "board_id": 29,
+ "label_id": 20,
+ "list_type": "label",
+ "position": 0,
+ "created_at": "2019-06-06T14:01:43.197Z",
+ "updated_at": "2019-06-06T14:01:43.197Z",
+ "user_id": null,
+ "milestone_id": null,
+ "label": {
+ "id": 20,
+ "title": "testlabel",
+ "color": "#0033CC",
+ "project_id": 49,
+ "created_at": "2019-06-06T14:01:19.698Z",
+ "updated_at": "2019-06-06T14:01:19.698Z",
+ "template": false,
+ "description": null,
+ "group_id": null,
+ "type": "ProjectLabel",
+ "priorities": []
+ }
+ },
+ {
+ "id": 60,
+ "board_id": 29,
+ "label_id": null,
+ "list_type": "closed",
+ "position": null,
+ "created_at": "2019-06-06T14:01:06.221Z",
+ "updated_at": "2019-06-06T14:01:06.221Z",
+ "user_id": null,
+ "milestone_id": null
+ }
+ ]
+ }
]
}
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index ca46006ea58..d6e1fbaa979 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -32,6 +32,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
context 'JSON' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ end
+
it 'restores models based on JSON' do
expect(@restored_project_json).to be_truthy
end
@@ -121,6 +125,13 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(MergeRequest.find_by(title: 'MR1').resource_label_events).not_to be_empty
end
+ it 'restores suggestion' do
+ note = Note.find_by("note LIKE 'Saepe asperiores exercitationem non dignissimos laborum reiciendis et ipsum%'")
+
+ expect(note.suggestions.count).to eq(1)
+ expect(note.suggestions.first.from_content).to eq("Original line\n")
+ end
+
context 'event at forth level of the tree' do
let(:event) { Event.where(action: 6).first }
@@ -156,13 +167,21 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
it 'has project labels' do
- expect(ProjectLabel.count).to eq(2)
+ expect(ProjectLabel.count).to eq(3)
end
it 'has no group labels' do
expect(GroupLabel.count).to eq(0)
end
+ it 'has issue boards' do
+ expect(Project.find_by_path('project').boards.count).to eq(1)
+ end
+
+ it 'has lists associated with the issue board' do
+ expect(Project.find_by_path('project').boards.find_by_name('TestBoardABC').lists.count).to eq(3)
+ end
+
it 'has a project feature' do
expect(@project.project_feature).not_to be_nil
end
@@ -198,8 +217,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
context 'tokens are regenerated' do
- it 'has a new CI trigger token' do
- expect(Ci::Trigger.where(token: 'cdbfasdf44a5958c83654733449e585')).to be_empty
+ it 'has new CI trigger tokens' do
+ expect(Ci::Trigger.where(token: %w[cdbfasdf44a5958c83654733449e585 33a66349b5ad01fc00174af87804e40]))
+ .to be_empty
end
it 'has a new CI build token' do
@@ -212,7 +232,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(@project.merge_requests.size).to eq(9)
end
- it 'has the correct number of triggers' do
+ it 'only restores valid triggers' do
expect(@project.triggers.size).to eq(1)
end
@@ -267,7 +287,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
it 'has label priorities' do
- expect(project.labels.first.priorities).not_to be_empty
+ expect(project.labels.find_by(title: 'A project label').priorities).not_to be_empty
end
it 'has milestones' do
@@ -320,7 +340,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it_behaves_like 'restores project correctly',
issues: 1,
- labels: 1,
+ labels: 2,
milestones: 1,
first_issue_labels: 1,
services: 1
@@ -397,7 +417,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it_behaves_like 'restores project successfully'
it_behaves_like 'restores project correctly',
issues: 2,
- labels: 1,
+ labels: 2,
milestones: 2,
first_issue_labels: 1
@@ -491,6 +511,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
end
+ context 'with restricted internal visibility' do
+ describe 'internal project' do
+ let(:visibility) { Gitlab::VisibilityLevel::INTERNAL }
+
+ it 'uses private visibility' do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
+
+ expect(restorer.restored_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
+ end
+ end
+
context 'with group visibility' do
before do
group = create(:group, visibility_level: group_visibility)
@@ -523,6 +555,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it 'uses the group visibility' do
expect(restorer.restored_project.visibility_level).to eq(group_visibility)
end
+
+ context 'with restricted internal visibility' do
+ it 'sets private visibility' do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
+
+ expect(restorer.restored_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index bc4f867e891..fefbed93316 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -42,6 +42,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(saved_project_json).to include({ 'description' => 'description', 'visibility_level' => 20 })
end
+ it 'has approvals_before_merge set' do
+ expect(saved_project_json['approvals_before_merge']).to eq(1)
+ end
+
it 'has milestones' do
expect(saved_project_json['milestones']).not_to be_empty
end
@@ -175,9 +179,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
end
it 'has priorities associated to labels' do
- priorities = saved_project_json['issues'].first['label_links'].map { |link| link['label']['priorities'] }
+ priorities = saved_project_json['issues'].first['label_links'].flat_map { |link| link['label']['priorities'] }
- expect(priorities.flatten).not_to be_empty
+ expect(priorities).not_to be_empty
end
it 'has issue resource label events' do
@@ -268,6 +272,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(saved_project_json).not_to include("runners_token" => 'token')
end
end
+
+ it 'has a board and a list' do
+ expect(saved_project_json['boards'].first['lists']).not_to be_empty
+ end
end
end
@@ -287,7 +295,8 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
issues: [issue],
snippets: [snippet],
releases: [release],
- group: group
+ group: group,
+ approvals_before_merge: 1
)
project_label = create(:label, project: project)
group_label = create(:group_label, group: group)
@@ -322,6 +331,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
create(:project_badge, project: project)
create(:project_badge, project: project)
+ board = create(:board, project: project, name: 'TestBoard')
+ create(:list, board: board, position: 0, label: project_label)
+
project
end
diff --git a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb
index a20a844a492..15748407f0c 100644
--- a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb
@@ -28,6 +28,7 @@ describe Gitlab::ImportExport::RelationRenameService do
before do
allow(shared).to receive(:export_path).and_return(import_path)
+ allow(ActiveSupport::JSON).to receive(:decode).and_call_original
allow(ActiveSupport::JSON).to receive(:decode).with(file_content).and_return(json_file)
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index a406c25b1d8..f0545176a90 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -123,6 +123,7 @@ Release:
- project_id
- created_at
- updated_at
+- released_at
Releases::Link:
- id
- release_id
@@ -234,6 +235,12 @@ MergeRequest::Metrics:
- latest_build_started_at
- latest_build_finished_at
- first_deployed_to_production_at
+- first_comment_at
+- first_commit_at
+- last_commit_at
+- diff_size
+- modified_paths_size
+- commits_count
Ci::Pipeline:
- id
- project_id
@@ -429,6 +436,7 @@ Service:
- confidential_issues_events
- confidential_note_events
- deployment_events
+- description
ProjectHook:
- id
- url
@@ -685,3 +693,22 @@ ProjectMetricsSetting:
- external_dashboard_url
- created_at
- updated_at
+Board:
+- id
+- project_id
+- created_at
+- updated_at
+- group_id
+- milestone_id
+- weight
+- name
+List:
+- id
+- board_id
+- label_id
+- list_type
+- position
+- created_at
+- updated_at
+- milestone_id
+- user_id
diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb
index 916f3876a8e..032467b8b4e 100644
--- a/spec/lib/gitlab/issuable_metadata_spec.rb
+++ b/spec/lib/gitlab/issuable_metadata_spec.rb
@@ -7,11 +7,11 @@ describe Gitlab::IssuableMetadata do
subject { Class.new { include Gitlab::IssuableMetadata }.new }
it 'returns an empty Hash if an empty collection is provided' do
- expect(subject.issuable_meta_data(Issue.none, 'Issue')).to eq({})
+ expect(subject.issuable_meta_data(Issue.none, 'Issue', user)).to eq({})
end
it 'raises an error when given a collection with no limit' do
- expect { subject.issuable_meta_data(Issue.all, 'Issue') }.to raise_error(/must have a limit/)
+ expect { subject.issuable_meta_data(Issue.all, 'Issue', user) }.to raise_error(/must have a limit/)
end
context 'issues' do
@@ -23,7 +23,7 @@ describe Gitlab::IssuableMetadata do
let!(:closing_issues) { create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request) }
it 'aggregates stats on issues' do
- data = subject.issuable_meta_data(Issue.all.limit(10), 'Issue')
+ data = subject.issuable_meta_data(Issue.all.limit(10), 'Issue', user)
expect(data.count).to eq(2)
expect(data[issue.id].upvotes).to eq(1)
@@ -46,7 +46,7 @@ describe Gitlab::IssuableMetadata do
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
it 'aggregates stats on merge requests' do
- data = subject.issuable_meta_data(MergeRequest.all.limit(10), 'MergeRequest')
+ data = subject.issuable_meta_data(MergeRequest.all.limit(10), 'MergeRequest', user)
expect(data.count).to eq(2)
expect(data[merge_request.id].upvotes).to eq(1)
diff --git a/spec/lib/gitlab/kubernetes/default_namespace_spec.rb b/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
new file mode 100644
index 00000000000..1fda547f35c
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::DefaultNamespace do
+ let(:generator) { described_class.new(cluster, project: environment.project) }
+
+ describe '#from_environment_name' do
+ let(:cluster) { create(:cluster) }
+ let(:environment) { create(:environment) }
+
+ subject { generator.from_environment_name(environment.name) }
+
+ it 'generates a slug and passes it to #from_environment_slug' do
+ expect(Gitlab::Slug::Environment).to receive(:new)
+ .with(environment.name)
+ .and_return(double(generate: environment.slug))
+
+ expect(generator).to receive(:from_environment_slug)
+ .with(environment.slug)
+ .and_return(:mock_namespace)
+
+ expect(subject).to eq :mock_namespace
+ end
+ end
+
+ describe '#from_environment_slug' do
+ let(:platform) { create(:cluster_platform_kubernetes, namespace: platform_namespace) }
+ let(:cluster) { create(:cluster, platform_kubernetes: platform) }
+ let(:project) { create(:project, path: "Path-With-Capitals") }
+ let(:environment) { create(:environment, project: project) }
+
+ subject { generator.from_environment_slug(environment.slug) }
+
+ context 'namespace per environment is enabled' do
+ context 'platform namespace is specified' do
+ let(:platform_namespace) { 'platform-namespace' }
+
+ it { is_expected.to eq "#{platform_namespace}-#{environment.slug}" }
+
+ context 'cluster is unmanaged' do
+ let(:cluster) { create(:cluster, :not_managed, platform_kubernetes: platform) }
+
+ it { is_expected.to eq platform_namespace }
+ end
+ end
+
+ context 'platform namespace is blank' do
+ let(:platform_namespace) { nil }
+ let(:mock_namespace) { 'mock-namespace' }
+
+ it 'constructs a namespace from the project and environment' do
+ expect(Gitlab::NamespaceSanitizer).to receive(:sanitize)
+ .with("#{project.path}-#{project.id}-#{environment.slug}".downcase)
+ .and_return(mock_namespace)
+
+ expect(subject).to eq mock_namespace
+ end
+ end
+ end
+
+ context 'namespace per environment is disabled' do
+ let(:cluster) { create(:cluster, :namespace_per_environment_disabled, platform_kubernetes: platform) }
+
+ context 'platform namespace is specified' do
+ let(:platform_namespace) { 'platform-namespace' }
+
+ it { is_expected.to eq platform_namespace }
+ end
+
+ context 'platform namespace is blank' do
+ let(:platform_namespace) { nil }
+ let(:mock_namespace) { 'mock-namespace' }
+
+ it 'constructs a namespace from the project and environment' do
+ expect(Gitlab::NamespaceSanitizer).to receive(:sanitize)
+ .with("#{project.path}-#{project.id}".downcase)
+ .and_return(mock_namespace)
+
+ expect(subject).to eq mock_namespace
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
index cae92305b19..39a46f9bc6d 100644
--- a/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
@@ -20,6 +20,15 @@ describe Gitlab::Kubernetes::Helm::DeleteCommand do
end
end
+ let(:tls_flags) do
+ <<~EOS.squish
+ --tls
+ --tls-ca-cert /data/helm/app-name/config/ca.pem
+ --tls-cert /data/helm/app-name/config/cert.pem
+ --tls-key /data/helm/app-name/config/key.pem
+ EOS
+ end
+
context 'when there is a ca.pem file' do
let(:files) { { 'ca.pem': 'some file content' } }
@@ -27,7 +36,7 @@ describe Gitlab::Kubernetes::Helm::DeleteCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
#{helm_delete_command}
EOS
end
@@ -35,10 +44,7 @@ describe Gitlab::Kubernetes::Helm::DeleteCommand do
let(:helm_delete_command) do
<<~EOS.squish
helm delete --purge app-name
- --tls
- --tls-ca-cert /data/helm/app-name/config/ca.pem
- --tls-cert /data/helm/app-name/config/cert.pem
- --tls-key /data/helm/app-name/config/key.pem
+ #{tls_flags}
EOS
end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index db76d5d207e..7395b095454 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -36,7 +36,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_comand}
@@ -64,7 +64,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
@@ -93,7 +93,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
#{helm_install_command}
EOS
end
@@ -120,7 +120,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
helm repo add app-name https://repository.example.com
helm repo update
/bin/date
@@ -151,7 +151,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
@@ -210,7 +210,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index 06c8d127951..fce2aded786 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -30,7 +30,7 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'generates the appropriate specifications for the container' do
container = subject.generate.spec.containers.first
expect(container.name).to eq('helm')
- expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.12.3-kube-1.11.7')
+ expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.14.3-kube-1.11.10')
expect(container.env.count).to eq(3)
expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT])
expect(container.command).to match_array(["/bin/sh"])
diff --git a/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
new file mode 100644
index 00000000000..d49d4779735
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::Helm::ResetCommand do
+ let(:rbac) { true }
+ let(:name) { 'helm' }
+ let(:files) { {} }
+ let(:reset_command) { described_class.new(name: name, rbac: rbac, files: files) }
+
+ subject { reset_command }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS
+ helm reset
+ kubectl delete replicaset -n gitlab-managed-apps -l name\\=tiller
+ EOS
+ end
+ end
+
+ context 'when there is a ca.pem file' do
+ let(:files) { { 'ca.pem': 'some file content' } }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS1.squish + "\n" + <<~EOS2
+ helm reset
+ --tls
+ --tls-ca-cert /data/helm/helm/config/ca.pem
+ --tls-cert /data/helm/helm/config/cert.pem
+ --tls-key /data/helm/helm/config/key.pem
+ EOS1
+ kubectl delete replicaset -n gitlab-managed-apps -l name\\=tiller
+ EOS2
+ end
+ end
+ end
+
+ describe '#pod_resource' do
+ subject { reset_command.pod_resource }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it 'generates a pod that uses the tiller serviceAccountName' do
+ expect(subject.spec.serviceAccountName).to eq('tiller')
+ end
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it 'generates a pod that uses the default serviceAccountName' do
+ expect(subject.spec.serviceAcccountName).to be_nil
+ end
+ end
+ end
+
+ describe '#pod_name' do
+ subject { reset_command.pod_name }
+
+ it { is_expected.to eq('uninstall-helm') }
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index 978e64c4407..f49d4e23e39 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -58,7 +58,7 @@ describe Gitlab::Kubernetes::KubeClient do
context 'when local requests are allowed' do
before do
- stub_application_setting(allow_local_requests_from_hooks_and_services: true)
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
end
it 'allows local addresses' do
@@ -176,6 +176,9 @@ describe Gitlab::Kubernetes::KubeClient do
let(:rbac_client) { client.rbac_client }
[
+ :create_role,
+ :get_role,
+ :update_role,
:create_cluster_role_binding,
:get_cluster_role_binding,
:update_cluster_role_binding
diff --git a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
new file mode 100644
index 00000000000..f24ab5579df
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Kubernetes::KubectlCmd do
+ describe '.delete' do
+ it 'constructs string properly' do
+ args = %w(resource_type type --flag-1 --flag-2)
+
+ expected_command = 'kubectl delete resource_type type --flag-1 --flag-2'
+
+ expect(described_class.delete(*args)).to eq expected_command
+ end
+ end
+
+ describe '.apply_file' do
+ context 'without optional args' do
+ it 'requires filename to be present' do
+ expect { described_class.apply_file(nil) }.to raise_error(ArgumentError, "filename is not present")
+ expect { described_class.apply_file(" ") }.to raise_error(ArgumentError, "filename is not present")
+ end
+
+ it 'constructs string properly' do
+ expected_command = 'kubectl apply -f filename'
+
+ expect(described_class.apply_file('filename')).to eq expected_command
+ end
+ end
+
+ context 'with optional args' do
+ it 'constructs command properly with many args' do
+ args = %w(arg-1 --flag-0-1 arg-2 --flag-0-2)
+
+ expected_command = 'kubectl apply -f filename arg-1 --flag-0-1 arg-2 --flag-0-2'
+
+ expect(described_class.apply_file('filename', *args)).to eq expected_command
+ end
+
+ it 'constructs command properly with single arg' do
+ args = "arg-1"
+
+ expected_command = 'kubectl apply -f filename arg-1'
+
+ expect(described_class.apply_file('filename', args)).to eq(expected_command)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
index 50acee254cb..4c200eb545f 100644
--- a/spec/lib/gitlab/kubernetes/role_binding_spec.rb
+++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::Kubernetes::RoleBinding, '#generate' do
let(:role_name) { 'edit' }
+ let(:role_kind) { 'ClusterRole' }
let(:namespace) { 'my-namespace' }
let(:service_account_name) { 'my-service-account' }
@@ -20,7 +21,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
let(:role_ref) do
{
apiGroup: 'rbac.authorization.k8s.io',
- kind: 'ClusterRole',
+ kind: role_kind,
name: role_name
}
end
@@ -37,6 +38,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
described_class.new(
name: "gitlab-#{namespace}",
role_name: role_name,
+ role_kind: role_kind,
namespace: namespace,
service_account_name: service_account_name
).generate
diff --git a/spec/lib/gitlab/kubernetes/role_spec.rb b/spec/lib/gitlab/kubernetes/role_spec.rb
new file mode 100644
index 00000000000..3a5cd3b6704
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/role_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::Role do
+ let(:role) { described_class.new(name: name, namespace: namespace, rules: rules) }
+ let(:name) { 'example-name' }
+ let(:namespace) { 'example-namespace' }
+
+ let(:rules) do
+ [{
+ apiGroups: %w(hello.world),
+ resources: %w(oil diamonds coffee),
+ verbs: %w(say do walk run)
+ }]
+ end
+
+ describe '#generate' do
+ subject { role.generate }
+
+ let(:resource) do
+ ::Kubeclient::Resource.new(
+ metadata: { name: name, namespace: namespace },
+ rules: rules
+ )
+ end
+
+ it { is_expected.to eq(resource) }
+ end
+end
diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
index a0c664da185..9163019514b 100644
--- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
@@ -132,6 +132,7 @@ describe Gitlab::LegacyGithubImport::Importer do
body: 'Release v1.0.0',
draft: false,
created_at: created_at,
+ published_at: created_at,
updated_at: updated_at,
url: "#{api_root}/repos/octocat/Hello-World/releases/1"
)
@@ -144,6 +145,7 @@ describe Gitlab::LegacyGithubImport::Importer do
body: nil,
draft: false,
created_at: created_at,
+ published_at: created_at,
updated_at: updated_at,
url: "#{api_root}/repos/octocat/Hello-World/releases/2"
)
diff --git a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
index c57b96fb00d..2cf4b367c0b 100644
--- a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
@@ -1,9 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::LegacyGithubImport::ReleaseFormatter do
let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
+ let(:published_at) { DateTime.strptime('2011-01-26T20:00:00Z') }
let(:base_data) do
{
@@ -11,7 +14,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do
name: 'First release',
draft: false,
created_at: created_at,
- published_at: created_at,
+ published_at: published_at,
body: 'Release v1.0.0'
}
end
@@ -28,6 +31,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do
name: 'First release',
description: 'Release v1.0.0',
created_at: created_at,
+ released_at: published_at,
updated_at: created_at
}
diff --git a/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
index 3cd096eb0ad..919847fe061 100644
--- a/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::LegacyGithubImport::UserFormatter do
diff --git a/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
index 7519707293c..639fb9d80eb 100644
--- a/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::LegacyGithubImport::WikiFormatter do
diff --git a/spec/lib/gitlab/lets_encrypt_spec.rb b/spec/lib/gitlab/lets_encrypt_spec.rb
index 674b114e9d3..65aea0937f1 100644
--- a/spec/lib/gitlab/lets_encrypt_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt_spec.rb
@@ -10,21 +10,10 @@ describe ::Gitlab::LetsEncrypt do
end
describe '.enabled?' do
- let(:project) { create(:project) }
- let(:pages_domain) { create(:pages_domain, project: project) }
-
- subject { described_class.enabled?(pages_domain) }
+ subject { described_class.enabled? }
context 'when terms of service are accepted' do
it { is_expected.to eq(true) }
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(pages_auto_ssl: false)
- end
-
- it { is_expected.to eq(false) }
- end
end
context 'when terms of service are not accepted' do
@@ -34,23 +23,5 @@ describe ::Gitlab::LetsEncrypt do
it { is_expected.to eq(false) }
end
-
- context 'when feature flag for project is disabled' do
- before do
- stub_feature_flags(pages_auto_ssl_for_project: false)
- end
-
- it 'returns false' do
- is_expected.to eq(false)
- end
- end
-
- context 'when domain has not project' do
- let(:pages_domain) { create(:pages_domain) }
-
- it 'returns false' do
- is_expected.to eq(false)
- end
- end
end
end
diff --git a/spec/lib/gitlab/loop_helpers_spec.rb b/spec/lib/gitlab/loop_helpers_spec.rb
index e17a0342d64..7e59b41d5b9 100644
--- a/spec/lib/gitlab/loop_helpers_spec.rb
+++ b/spec/lib/gitlab/loop_helpers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::LoopHelpers do
diff --git a/spec/lib/gitlab/manifest_import/manifest_spec.rb b/spec/lib/gitlab/manifest_import/manifest_spec.rb
index ab305fb2316..c1135f710ea 100644
--- a/spec/lib/gitlab/manifest_import/manifest_spec.rb
+++ b/spec/lib/gitlab/manifest_import/manifest_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Gitlab::ManifestImport::Manifest, :postgresql do
+describe Gitlab::ManifestImport::Manifest do
let(:file) { File.open(Rails.root.join('spec/fixtures/aosp_manifest.xml')) }
let(:manifest) { described_class.new(file) }
diff --git a/spec/lib/gitlab/manifest_import/project_creator_spec.rb b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
index 1d01d437535..a8cfcfb41d3 100644
--- a/spec/lib/gitlab/manifest_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Gitlab::ManifestImport::ProjectCreator, :postgresql do
+describe Gitlab::ManifestImport::ProjectCreator do
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:repository) do
diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
index 18052b1991c..c5fc74afea5 100644
--- a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
@@ -9,12 +9,13 @@ describe Gitlab::MarkdownCache::ActiveRecord::Extension do
cache_markdown_field :title, whitelisted: true
cache_markdown_field :description, pipeline: :single_line
- attr_accessor :author, :project
+ attribute :author
+ attribute :project
end
end
let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 }
- let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+ let(:thing) { klass.create(title: markdown, title_html: html, cached_markdown_version: cache_version) }
let(:markdown) { '`Foo`' }
let(:html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Foo</code></p>' }
@@ -37,7 +38,7 @@ describe Gitlab::MarkdownCache::ActiveRecord::Extension do
end
context 'a changed markdown field' do
- let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+ let(:thing) { klass.create(title: markdown, title_html: html, cached_markdown_version: cache_version) }
before do
thing.title = updated_markdown
diff --git a/spec/lib/gitlab/markup_helper_spec.rb b/spec/lib/gitlab/markup_helper_spec.rb
index 09e518ff989..b93538cae5a 100644
--- a/spec/lib/gitlab/markup_helper_spec.rb
+++ b/spec/lib/gitlab/markup_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::MarkupHelper do
diff --git a/spec/lib/gitlab/metrics/background_transaction_spec.rb b/spec/lib/gitlab/metrics/background_transaction_spec.rb
index 17445fe6de5..d87d2c839ad 100644
--- a/spec/lib/gitlab/metrics/background_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/background_transaction_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::BackgroundTransaction do
diff --git a/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb
new file mode 100644
index 00000000000..420b246b3f5
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Dashboard::Defaults do
+ it { is_expected.to be_const_defined(:DEFAULT_PANEL_TYPE) }
+ it { is_expected.to be_const_defined(:DEFAULT_PANEL_WEIGHT) }
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
index d8ed54c0248..ce1bb49f5c9 100644
--- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
@@ -5,10 +5,9 @@ require 'spec_helper'
describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
- set(:project) { build(:project) }
+ set(:project) { create(:project) }
set(:user) { create(:user) }
set(:environment) { create(:environment, project: project) }
- let(:system_dashboard_path) { Gitlab::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH}
before do
project.add_maintainer(user)
@@ -52,9 +51,80 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
end
context 'when the dashboard is expected to be embedded' do
- let(:service_call) { described_class.find(project, user, environment, dashboard_path: nil, embedded: true) }
+ let(:service_call) { described_class.find(project, user, environment, **params) }
+ let(:params) { { embedded: true } }
it_behaves_like 'valid embedded dashboard service response'
+
+ context 'when params are incomplete' do
+ let(:params) { { embedded: true, dashboard_path: system_dashboard_path } }
+
+ it_behaves_like 'valid embedded dashboard service response'
+ end
+
+ context 'when the panel is specified' do
+ context 'as a custom metric' do
+ let(:params) do
+ { embedded: true,
+ dashboard_path: system_dashboard_path,
+ group: business_metric_title,
+ title: 'title',
+ y_label: 'y_label' }
+ end
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+
+ context 'when the metric exists' do
+ before do
+ create(:prometheus_metric, project: project)
+ end
+
+ it_behaves_like 'valid embedded dashboard service response'
+ end
+ end
+
+ context 'as a project-defined panel' do
+ let(:dashboard_path) { '.gitlab/dashboard/test.yml' }
+ let(:params) do
+ { embedded: true,
+ dashboard_path: dashboard_path,
+ group: 'Group A',
+ title: 'Super Chart A1',
+ y_label: 'y_label' }
+ end
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+
+ context 'when the metric exists' do
+ let(:project) { project_with_dashboard(dashboard_path) }
+
+ it_behaves_like 'valid embedded dashboard service response'
+ end
+ end
+ end
+ end
+ end
+
+ describe '.find_raw' do
+ let(:dashboard) { YAML.load_file(Rails.root.join('config', 'prometheus', 'common_metrics.yml')) }
+ let(:params) { {} }
+
+ subject { described_class.find_raw(project, **params) }
+
+ it { is_expected.to eq dashboard }
+
+ context 'when the system dashboard is specified' do
+ let(:params) { { dashboard_path: system_dashboard_path } }
+
+ it { is_expected.to eq dashboard }
+ end
+
+ context 'when an existing project dashboard is specified' do
+ let(:dashboard) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')) }
+ let(:params) { { dashboard_path: '.gitlab/dashboards/test.yml' } }
+ let(:project) { project_with_dashboard(params[:dashboard_path]) }
+
+ it { is_expected.to eq dashboard }
end
end
diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
index 797d4daabe3..d7891e69dd0 100644
--- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
@@ -101,9 +101,9 @@ describe Gitlab::Metrics::Dashboard::Processor do
private
def all_metrics
- dashboard[:panel_groups].map do |group|
- group[:panels].map { |panel| panel[:metrics] }
- end.flatten
+ dashboard[:panel_groups].flat_map do |group|
+ group[:panels].flat_map { |panel| panel[:metrics] }
+ end
end
def get_metric_details(metric)
diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
new file mode 100644
index 00000000000..095d0a2df78
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Dashboard::ServiceSelector do
+ include MetricsDashboardHelpers
+
+ describe '#call' do
+ let(:arguments) { {} }
+
+ subject { described_class.call(arguments) }
+
+ it { is_expected.to be Metrics::Dashboard::SystemDashboardService }
+
+ context 'when just the dashboard path is provided' do
+ let(:arguments) { { dashboard_path: '.gitlab/dashboards/test.yml' } }
+
+ it { is_expected.to be Metrics::Dashboard::ProjectDashboardService }
+
+ context 'when the path is for the system dashboard' do
+ let(:arguments) { { dashboard_path: system_dashboard_path } }
+
+ it { is_expected.to be Metrics::Dashboard::SystemDashboardService }
+ end
+ end
+
+ context 'when the embedded flag is provided' do
+ let(:arguments) { { embedded: true } }
+
+ it { is_expected.to be Metrics::Dashboard::DefaultEmbedService }
+
+ context 'when an incomplete set of dashboard identifiers are provided' do
+ let(:arguments) { { embedded: true, dashboard_path: '.gitlab/dashboards/test.yml' } }
+
+ it { is_expected.to be Metrics::Dashboard::DefaultEmbedService }
+ end
+
+ context 'when all the chart identifiers are provided' do
+ let(:arguments) do
+ {
+ embedded: true,
+ dashboard_path: '.gitlab/dashboards/test.yml',
+ group: 'Important Metrics',
+ title: 'Total Requests',
+ y_label: 'req/sec'
+ }
+ end
+
+ it { is_expected.to be Metrics::Dashboard::DynamicEmbedService }
+ end
+
+ context 'when all chart params expect dashboard_path are provided' do
+ let(:arguments) do
+ {
+ embedded: true,
+ group: 'Important Metrics',
+ title: 'Total Requests',
+ y_label: 'req/sec'
+ }
+ end
+
+ it { is_expected.to be Metrics::Dashboard::DynamicEmbedService }
+ end
+
+ context 'with a system dashboard and "custom" group' do
+ let(:arguments) do
+ {
+ embedded: true,
+ dashboard_path: system_dashboard_path,
+ group: business_metric_title,
+ title: 'Total Requests',
+ y_label: 'req/sec'
+ }
+ end
+
+ it { is_expected.to be Metrics::Dashboard::CustomMetricEmbedService }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/url_spec.rb b/spec/lib/gitlab/metrics/dashboard/url_spec.rb
new file mode 100644
index 00000000000..e0dc6d98efc
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/url_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Dashboard::Url do
+ describe '#regex' do
+ it 'returns a regular expression' do
+ expect(described_class.regex).to be_a Regexp
+ end
+
+ it 'matches a metrics dashboard link with named params' do
+ url = Gitlab::Routing.url_helpers.metrics_namespace_project_environment_url(
+ 'foo',
+ 'bar',
+ 1,
+ start: '2019-08-02T05:43:09.000Z',
+ dashboard: 'config/prometheus/common_metrics.yml',
+ group: 'awesome group',
+ anchor: 'title'
+ )
+
+ expected_params = {
+ 'url' => url,
+ 'namespace' => 'foo',
+ 'project' => 'bar',
+ 'environment' => '1',
+ 'query' => '?dashboard=config%2Fprometheus%2Fcommon_metrics.yml&group=awesome+group&start=2019-08-02T05%3A43%3A09.000Z',
+ 'anchor' => '#title'
+ }
+
+ expect(described_class.regex).to match url
+
+ described_class.regex.match(url) do |m|
+ expect(m.named_captures).to eq expected_params
+ end
+ end
+
+ it 'does not match other gitlab urls that contain the term metrics' do
+ url = Gitlab::Routing.url_helpers.active_common_namespace_project_prometheus_metrics_url('foo', 'bar', :json)
+
+ expect(described_class.regex).not_to match url
+ end
+
+ it 'does not match other gitlab urls' do
+ url = Gitlab.config.gitlab.url
+
+ expect(described_class.regex).not_to match url
+ end
+
+ it 'does not match non-gitlab urls' do
+ url = 'https://www.super_awesome_site.com/'
+
+ expect(described_class.regex).not_to match url
+ end
+ end
+
+ describe '#build_dashboard_url' do
+ it 'builds the url for the dashboard endpoint' do
+ url = described_class.build_dashboard_url('foo', 'bar', 1)
+
+ expect(url).to match described_class.regex
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/delta_spec.rb b/spec/lib/gitlab/metrics/delta_spec.rb
index 718387cdee1..9bb011dc8fc 100644
--- a/spec/lib/gitlab/metrics/delta_spec.rb
+++ b/spec/lib/gitlab/metrics/delta_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Delta do
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index 977bc250049..0e2f274f157 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Instrumentation do
diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb
index d9379cfe674..3b5e04e2df5 100644
--- a/spec/lib/gitlab/metrics/method_call_spec.rb
+++ b/spec/lib/gitlab/metrics/method_call_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::MethodCall do
diff --git a/spec/lib/gitlab/metrics/methods_spec.rb b/spec/lib/gitlab/metrics/methods_spec.rb
index 9d41ed2442b..bca94deb1d8 100644
--- a/spec/lib/gitlab/metrics/methods_spec.rb
+++ b/spec/lib/gitlab/metrics/methods_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Methods do
diff --git a/spec/lib/gitlab/metrics/metric_spec.rb b/spec/lib/gitlab/metrics/metric_spec.rb
index d240b8a01fd..611b59231ba 100644
--- a/spec/lib/gitlab/metrics/metric_spec.rb
+++ b/spec/lib/gitlab/metrics/metric_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Metric do
diff --git a/spec/lib/gitlab/metrics/prometheus_spec.rb b/spec/lib/gitlab/metrics/prometheus_spec.rb
index 3d4dd5fdf01..b37624982e2 100644
--- a/spec/lib/gitlab/metrics/prometheus_spec.rb
+++ b/spec/lib/gitlab/metrics/prometheus_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Prometheus, :prometheus do
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
index b84387204ee..1c1681cc5ab 100644
--- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::RackMiddleware do
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index ebe66948a91..c29db3a93ec 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::RequestsRackMiddleware do
diff --git a/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb
index 81954fcf8c5..2d4b27a6ac1 100644
--- a/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Samplers::InfluxSampler do
@@ -17,18 +19,10 @@ describe Gitlab::Metrics::Samplers::InfluxSampler do
it 'samples various statistics' do
expect(sampler).to receive(:sample_memory_usage)
expect(sampler).to receive(:sample_file_descriptors)
- expect(sampler).to receive(:sample_gc)
expect(sampler).to receive(:flush)
sampler.sample
end
-
- it 'clears any GC profiles' do
- expect(sampler).to receive(:flush)
- expect(GC::Profiler).to receive(:clear)
-
- sampler.sample
- end
end
describe '#flush' do
@@ -67,18 +61,6 @@ describe Gitlab::Metrics::Samplers::InfluxSampler do
end
end
- describe '#sample_gc' do
- it 'adds a metric containing garbage collection statistics' do
- expect(GC::Profiler).to receive(:total_time).and_return(0.24)
-
- expect(sampler).to receive(:add_metric)
- .with(/gc_statistics/, an_instance_of(Hash))
- .and_call_original
-
- sampler.sample_gc
- end
- end
-
describe '#add_metric' do
it 'prefixes the series name for a Rails process' do
expect(sampler).to receive(:sidekiq?).and_return(false)
diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
index f4a6e1fc7d9..b8add3c1324 100644
--- a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
@@ -46,8 +46,6 @@ describe Gitlab::Metrics::Samplers::PumaSampler do
expect(subject.metrics[:puma_workers]).to receive(:set).with(labels, 2)
expect(subject.metrics[:puma_running_workers]).to receive(:set).with(labels, 2)
expect(subject.metrics[:puma_stale_workers]).to receive(:set).with(labels, 0)
- expect(subject.metrics[:puma_phase]).to receive(:set).once.with(labels, 2)
- expect(subject.metrics[:puma_phase]).to receive(:set).once.with({ worker: 'worker_0' }, 1)
subject.sample
end
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index aaf8c9fa2a0..8c4071a7ed1 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Samplers::RubySampler do
@@ -8,12 +10,19 @@ describe Gitlab::Metrics::Samplers::RubySampler do
allow(Gitlab::Metrics::NullMetric).to receive(:instance).and_return(null_metric)
end
+ describe '#initialize' do
+ it 'sets process_start_time_seconds' do
+ Timecop.freeze do
+ expect(sampler.metrics[:process_start_time_seconds].get).to eq(Time.now.to_i)
+ end
+ end
+ end
+
describe '#sample' do
it 'samples various statistics' do
expect(Gitlab::Metrics::System).to receive(:cpu_time)
expect(Gitlab::Metrics::System).to receive(:file_descriptor_count)
expect(Gitlab::Metrics::System).to receive(:memory_usage)
- expect(Gitlab::Metrics::System).to receive(:process_start_time)
expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors)
expect(sampler).to receive(:sample_gc)
@@ -44,13 +53,6 @@ describe Gitlab::Metrics::Samplers::RubySampler do
sampler.sample
end
- it 'adds a metric containing the process start time' do
- expect(Gitlab::Metrics::System).to receive(:process_start_time).and_return(12345)
- expect(sampler.metrics[:process_start_time_seconds]).to receive(:set).with({}, 12345)
-
- sampler.sample
- end
-
it 'adds a metric containing the process max file descriptors' do
expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors).and_return(1024)
expect(sampler.metrics[:process_max_fds]).to receive(:set).with({}, 1024)
@@ -59,17 +61,29 @@ describe Gitlab::Metrics::Samplers::RubySampler do
end
it 'clears any GC profiles' do
- expect(GC::Profiler).to receive(:clear)
+ expect(GC::Profiler).to receive(:clear).at_least(:once)
sampler.sample
end
end
describe '#sample_gc' do
- it 'adds a metric containing garbage collection time statistics' do
- expect(GC::Profiler).to receive(:total_time).and_return(0.24)
+ let!(:sampler) { described_class.new(5) }
+
+ let(:gc_reports) { [{ GC_TIME: 0.1 }, { GC_TIME: 0.2 }, { GC_TIME: 0.3 }] }
+
+ it 're-enables GC::Profiler if needed' do
+ expect(GC::Profiler).to receive(:enable)
+
+ sampler.sample
+ end
+
+ it 'observes GC cycles time' do
+ expect(sampler).to receive(:sample_gc_reports).and_return(gc_reports)
- expect(sampler.metrics[:total_time]).to receive(:increment).with({}, 0.24)
+ expect(sampler.metrics[:gc_duration_seconds]).to receive(:observe).with({}, 0.1).ordered
+ expect(sampler.metrics[:gc_duration_seconds]).to receive(:observe).with({}, 0.2).ordered
+ expect(sampler.metrics[:gc_duration_seconds]).to receive(:observe).with({}, 0.3).ordered
sampler.sample
end
diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
index 090e456644f..cdfd95e3885 100644
--- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Samplers::UnicornSampler do
subject { described_class.new(1.second) }
describe '#sample' do
- let(:unicorn) { double('unicorn') }
+ let(:unicorn) { Module.new }
let(:raindrops) { double('raindrops') }
let(:stats) { double('stats') }
@@ -78,19 +80,32 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
end
end
- context 'additional metrics' do
- let(:unicorn_workers) { 2 }
-
+ context 'unicorn workers' do
before do
- allow(unicorn).to receive(:listener_names).and_return([""])
- allow(::Gitlab::Metrics::System).to receive(:cpu_time).and_return(3.14)
- allow(subject).to receive(:unicorn_workers_count).and_return(unicorn_workers)
+ allow(unicorn).to receive(:listener_names).and_return([])
end
- it "sets additional metrics" do
- expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, unicorn_workers)
+ context 'without http server' do
+ it "does set unicorn_workers to 0" do
+ expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, 0)
- subject.sample
+ subject.sample
+ end
+ end
+
+ context 'with http server' do
+ let(:http_server_class) { Struct.new(:worker_processes) }
+ let!(:http_server) { http_server_class.new(5) }
+
+ before do
+ stub_const('Unicorn::HttpServer', http_server_class)
+ end
+
+ it "sets additional metrics" do
+ expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, 5)
+
+ subject.sample
+ end
end
end
end
diff --git a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
index 61eb059a731..9eea3eb79dc 100644
--- a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
+++ b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::SidekiqMetricsExporter do
diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
index ae1d8b47fe9..bb95d5ab2ad 100644
--- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::SidekiqMiddleware do
diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
index 9f3af1acef7..25c0e7b695a 100644
--- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Subscribers::ActionView do
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index ee6d6fc961f..1624cea8bda 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Subscribers::ActiveRecord do
diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
index 6795c1ab56b..ab0d89b2683 100644
--- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Subscribers::RailsCache do
@@ -201,7 +203,15 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
it 'observes cache metric' do
expect(subscriber.send(:metric_cache_operation_duration_seconds))
.to receive(:observe)
- .with(transaction.labels.merge(operation: :delete), event.duration / 1000.0)
+ .with({ operation: :delete }, event.duration / 1000.0)
+
+ subscriber.observe(:delete, event.duration)
+ end
+
+ it 'increments the operations total' do
+ expect(subscriber.send(:metric_cache_operations_total))
+ .to receive(:increment)
+ .with(transaction.labels.merge(operation: :delete))
subscriber.observe(:delete, event.duration)
end
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index b0603d96eb2..6d2764a06f2 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::System do
@@ -19,12 +21,6 @@ describe Gitlab::Metrics::System do
expect(described_class.max_open_file_descriptors).to be > 0
end
end
-
- describe '.process_start_time' do
- it 'returns the process start time' do
- expect(described_class.process_start_time).to be > 0
- end
- end
else
describe '.memory_usage' do
it 'returns 0.0' do
@@ -43,22 +39,16 @@ describe Gitlab::Metrics::System do
expect(described_class.max_open_file_descriptors).to eq(0)
end
end
-
- describe 'process_start_time' do
- it 'returns 0' do
- expect(described_class.process_start_time).to eq(0)
- end
- end
end
describe '.cpu_time' do
- it 'returns a Fixnum' do
+ it 'returns a Float' do
expect(described_class.cpu_time).to be_an(Float)
end
end
describe '.real_time' do
- it 'returns a Fixnum' do
+ it 'returns a Float' do
expect(described_class.real_time).to be_an(Float)
end
end
diff --git a/spec/lib/gitlab/metrics/web_transaction_spec.rb b/spec/lib/gitlab/metrics/web_transaction_spec.rb
index 0b3b23e930f..2b35f07cc0d 100644
--- a/spec/lib/gitlab/metrics/web_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/web_transaction_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::WebTransaction do
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 03c185ddc07..f0ba12c1cd0 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics do
diff --git a/spec/lib/gitlab/middleware/basic_health_check_spec.rb b/spec/lib/gitlab/middleware/basic_health_check_spec.rb
index 86bdc479b66..07fda691ac8 100644
--- a/spec/lib/gitlab/middleware/basic_health_check_spec.rb
+++ b/spec/lib/gitlab/middleware/basic_health_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Middleware::BasicHealthCheck do
diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb
index 3f6ada6832a..33797817578 100644
--- a/spec/lib/gitlab/middleware/multipart_spec.rb
+++ b/spec/lib/gitlab/middleware/multipart_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'tempfile'
diff --git a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
index 14f2c3cb86f..31359abdce3 100644
--- a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
+++ b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Middleware::RailsQueueDuration do
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index 24d49a049b6..d2c8f4ab0bd 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Middleware::ReadOnly do
diff --git a/spec/lib/gitlab/middleware/release_env_spec.rb b/spec/lib/gitlab/middleware/release_env_spec.rb
index 5e3aa877409..3ca40f4ebd0 100644
--- a/spec/lib/gitlab/middleware/release_env_spec.rb
+++ b/spec/lib/gitlab/middleware/release_env_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Middleware::ReleaseEnv do
diff --git a/spec/lib/gitlab/multi_collection_paginator_spec.rb b/spec/lib/gitlab/multi_collection_paginator_spec.rb
index 28cd704b05a..f2049884b83 100644
--- a/spec/lib/gitlab/multi_collection_paginator_spec.rb
+++ b/spec/lib/gitlab/multi_collection_paginator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::MultiCollectionPaginator do
diff --git a/spec/lib/gitlab/namespaced_session_store_spec.rb b/spec/lib/gitlab/namespaced_session_store_spec.rb
index c0af2ede32a..e177c44ad67 100644
--- a/spec/lib/gitlab/namespaced_session_store_spec.rb
+++ b/spec/lib/gitlab/namespaced_session_store_spec.rb
@@ -4,19 +4,33 @@ require 'spec_helper'
describe Gitlab::NamespacedSessionStore do
let(:key) { :some_key }
- subject { described_class.new(key) }
- it 'stores data under the specified key' do
- Gitlab::Session.with_session({}) do
- subject[:new_data] = 123
+ context 'current session' do
+ subject { described_class.new(key) }
- expect(Thread.current[:session_storage][key]).to eq(new_data: 123)
+ it 'stores data under the specified key' do
+ Gitlab::Session.with_session({}) do
+ subject[:new_data] = 123
+
+ expect(Thread.current[:session_storage][key]).to eq(new_data: 123)
+ end
+ end
+
+ it 'retrieves data from the given key' do
+ Thread.current[:session_storage] = { key => { existing_data: 123 } }
+
+ expect(subject[:existing_data]).to eq 123
end
end
- it 'retrieves data from the given key' do
- Thread.current[:session_storage] = { key => { existing_data: 123 } }
+ context 'passed in session' do
+ let(:data) { { 'data' => 42 } }
+ let(:session) { { 'some_key' => data } }
+
+ subject { described_class.new(key, session.with_indifferent_access) }
- expect(subject[:existing_data]).to eq 123
+ it 'retrieves data from the given key' do
+ expect(subject['data']).to eq 42
+ end
end
end
diff --git a/spec/lib/gitlab/object_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb
index e6e9ae3223e..b16eccbcb2c 100644
--- a/spec/lib/gitlab/object_hierarchy_spec.rb
+++ b/spec/lib/gitlab/object_hierarchy_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Gitlab::ObjectHierarchy, :postgresql do
+describe Gitlab::ObjectHierarchy do
let!(:parent) { create(:group) }
let!(:child1) { create(:group, parent: parent) }
let!(:child2) { create(:group, parent: child1) }
diff --git a/spec/lib/gitlab/octokit/middleware_spec.rb b/spec/lib/gitlab/octokit/middleware_spec.rb
new file mode 100644
index 00000000000..8aa6d17ac9e
--- /dev/null
+++ b/spec/lib/gitlab/octokit/middleware_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Octokit::Middleware do
+ let(:app) { double(:app) }
+ let(:middleware) { described_class.new(app) }
+
+ shared_examples 'Public URL' do
+ it 'does not raise an error' do
+ expect(app).to receive(:call).with(env)
+
+ expect { middleware.call(env) }.not_to raise_error
+ end
+ end
+
+ shared_examples 'Local URL' do
+ it 'raises an error' do
+ expect { middleware.call(env) }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
+ end
+ end
+
+ describe '#call' do
+ context 'when the URL is a public URL' do
+ let(:env) { { url: 'https://public-url.com' } }
+
+ it_behaves_like 'Public URL'
+ end
+
+ context 'when the URL is a localhost adresss' do
+ let(:env) { { url: 'http://127.0.0.1' } }
+
+ context 'when localhost requests are not allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+ end
+
+ it_behaves_like 'Local URL'
+ end
+
+ context 'when localhost requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ it_behaves_like 'Public URL'
+ end
+ end
+
+ context 'when the URL is a local network address' do
+ let(:env) { { url: 'http://172.16.0.0' } }
+
+ context 'when local network requests are not allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+ end
+
+ it_behaves_like 'Local URL'
+ end
+
+ context 'when local network requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ it_behaves_like 'Public URL'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb
index f9c0daf1ef1..99684bb2ab2 100644
--- a/spec/lib/gitlab/omniauth_initializer_spec.rb
+++ b/spec/lib/gitlab/omniauth_initializer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::OmniauthInitializer do
@@ -83,5 +85,33 @@ describe Gitlab::OmniauthInitializer do
subject.execute([cas3_config])
end
+
+ it 'converts client_auth_method to a Symbol for openid_connect' do
+ openid_connect_config = {
+ 'name' => 'openid_connect',
+ 'args' => { name: 'openid_connect', client_auth_method: 'basic' }
+ }
+
+ expect(devise_config).to receive(:omniauth).with(
+ :openid_connect,
+ { name: 'openid_connect', client_auth_method: :basic }
+ )
+
+ subject.execute([openid_connect_config])
+ end
+
+ it 'converts client_auth_method to a Symbol for strategy_class OpenIDConnect' do
+ openid_connect_config = {
+ 'name' => 'openid_connect',
+ 'args' => { strategy_class: OmniAuth::Strategies::OpenIDConnect, client_auth_method: 'jwt_bearer' }
+ }
+
+ expect(devise_config).to receive(:omniauth).with(
+ :openid_connect,
+ { strategy_class: OmniAuth::Strategies::OpenIDConnect, client_auth_method: :jwt_bearer }
+ )
+
+ subject.execute([openid_connect_config])
+ end
end
end
diff --git a/spec/lib/gitlab/optimistic_locking_spec.rb b/spec/lib/gitlab/optimistic_locking_spec.rb
index 6fdf61ee0a7..9dfcb775dfa 100644
--- a/spec/lib/gitlab/optimistic_locking_spec.rb
+++ b/spec/lib/gitlab/optimistic_locking_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::OptimisticLocking do
diff --git a/spec/lib/gitlab/other_markup_spec.rb b/spec/lib/gitlab/other_markup_spec.rb
index e26f39e193e..b5cf5b0999d 100644
--- a/spec/lib/gitlab/other_markup_spec.rb
+++ b/spec/lib/gitlab/other_markup_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::OtherMarkup do
diff --git a/spec/lib/gitlab/otp_key_rotator_spec.rb b/spec/lib/gitlab/otp_key_rotator_spec.rb
index 6e6e9ce29ac..f5a567d5ea0 100644
--- a/spec/lib/gitlab/otp_key_rotator_spec.rb
+++ b/spec/lib/gitlab/otp_key_rotator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::OtpKeyRotator do
diff --git a/spec/lib/gitlab/pages_client_spec.rb b/spec/lib/gitlab/pages_client_spec.rb
index da6d26f4aee..84381843221 100644
--- a/spec/lib/gitlab/pages_client_spec.rb
+++ b/spec/lib/gitlab/pages_client_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PagesClient do
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 84b2e2dc823..7dcdad7ff92 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PathRegex do
diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb
index f480376acb4..8d8ac2aebbe 100644
--- a/spec/lib/gitlab/performance_bar_spec.rb
+++ b/spec/lib/gitlab/performance_bar_spec.rb
@@ -1,19 +1,46 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PerformanceBar do
shared_examples 'allowed user IDs are cached' do
before do
- # Warm the Redis cache
+ # Warm the caches
described_class.enabled?(user)
end
it 'caches the allowed user IDs in cache', :use_clean_rails_memory_store_caching do
expect do
+ expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
+ expect(described_class.l2_cache_backend).not_to receive(:fetch)
expect(described_class.enabled?(user)).to be_truthy
end.not_to exceed_query_limit(0)
end
+
+ it 'caches the allowed user IDs in L1 cache for 1 minute', :use_clean_rails_memory_store_caching do
+ Timecop.travel 2.minutes do
+ expect do
+ expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
+ expect(described_class.l2_cache_backend).to receive(:fetch).and_call_original
+ expect(described_class.enabled?(user)).to be_truthy
+ end.not_to exceed_query_limit(0)
+ end
+ end
+
+ it 'caches the allowed user IDs in L2 cache for 5 minutes', :use_clean_rails_memory_store_caching do
+ Timecop.travel 6.minutes do
+ expect do
+ expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
+ expect(described_class.l2_cache_backend).to receive(:fetch).and_call_original
+ expect(described_class.enabled?(user)).to be_truthy
+ end.not_to exceed_query_limit(2)
+ end
+ end
end
+ it { expect(described_class.l1_cache_backend).to eq(Gitlab::ThreadMemoryCache.cache_backend) }
+ it { expect(described_class.l2_cache_backend).to eq(Rails.cache) }
+
describe '.enabled?' do
let(:user) { create(:user) }
@@ -71,7 +98,7 @@ describe Gitlab::PerformanceBar do
end
end
- context 'when allowed group is nested', :nested_groups do
+ context 'when allowed group is nested' do
let!(:nested_my_group) { create(:group, parent: create(:group, path: 'my-org'), path: 'my-group') }
before do
@@ -85,7 +112,7 @@ describe Gitlab::PerformanceBar do
end
end
- context 'when a nested group has the same path', :nested_groups do
+ context 'when a nested group has the same path' do
before do
create(:group, :nested, path: 'my-group').add_developer(user)
end
diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
index 52c7a02219f..b6629fad453 100644
--- a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
@@ -28,6 +30,21 @@ describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
expect(ttl).to be > 10.seconds
end
+
+ it 'sets the object in redis once if a block was given and nothing was cached' do
+ issue = create(:issue, project: project)
+
+ expect(map.get_gitlab_model('does not exist') { issue }).to eq(issue)
+
+ expect { |b| map.get_gitlab_model('does not exist', &b) }
+ .not_to yield_control
+ end
+
+ it 'does not cache `nil` objects' do
+ expect(map).not_to receive(:set_gitlab_model)
+
+ map.get_gitlab_model('does not exist') { nil }
+ end
end
describe '#set_gitlab_model' do
diff --git a/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb
new file mode 100644
index 00000000000..e88eec2c393
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::User do
+ let(:user_client) do
+ described_class.new(phabricator_url: 'https://see-ya-later.phabricator', api_token: 'api-token')
+ end
+
+ describe '#users' do
+ let(:fake_client) { double('Phabricator client') }
+
+ before do
+ allow(user_client).to receive(:client).and_return(fake_client)
+ end
+
+ it 'calls the api with the correct params' do
+ expected_params = {
+ constraints: { phids: ['phid-1', 'phid-2'] }
+ }
+
+ expect(fake_client).to receive(:get).with('user.search',
+ params: expected_params)
+
+ user_client.users(['phid-1', 'phid-2'])
+ end
+
+ it 'returns an array of parsed responses' do
+ response = Gitlab::PhabricatorImport::Conduit::Response
+ .new(fixture_file('phabricator_responses/user.search.json'))
+
+ allow(fake_client).to receive(:get).and_return(response)
+
+ expect(user_client.users(%w[some phids])).to match_array([an_instance_of(Gitlab::PhabricatorImport::Conduit::UsersResponse)])
+ end
+
+ it 'performs multiple requests if more phids than the maximum page size are passed' do
+ stub_const('Gitlab::PhabricatorImport::Conduit::User::MAX_PAGE_SIZE', 1)
+ first_params = { constraints: { phids: ['phid-1'] } }
+ second_params = { constraints: { phids: ['phid-2'] } }
+
+ expect(fake_client).to receive(:get).with('user.search',
+ params: first_params).once
+ expect(fake_client).to receive(:get).with('user.search',
+ params: second_params).once
+
+ user_client.users(['phid-1', 'phid-2'])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb
new file mode 100644
index 00000000000..00778ad90fd
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::UsersResponse do
+ let(:conduit_response) do
+ Gitlab::PhabricatorImport::Conduit::Response
+ .new(JSON.parse(fixture_file('phabricator_responses/user.search.json')))
+ end
+
+ subject(:response) { described_class.new(conduit_response) }
+
+ describe '#users' do
+ it 'builds the correct users representation' do
+ tasks = response.users
+
+ usernames = tasks.map(&:username)
+
+ expect(usernames).to contain_exactly('jane', 'john')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/importer_spec.rb b/spec/lib/gitlab/phabricator_import/importer_spec.rb
index bf14010a187..99a6e4dad6b 100644
--- a/spec/lib/gitlab/phabricator_import/importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/importer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PhabricatorImport::Importer do
diff --git a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
index 2412cf76f79..667321409da 100644
--- a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
describe Gitlab::PhabricatorImport::Issues::Importer do
- set(:project) { create(:project) }
+ let(:project) { create(:project) }
let(:response) do
Gitlab::PhabricatorImport::Conduit::TasksResponse.new(
@@ -15,7 +15,6 @@ describe Gitlab::PhabricatorImport::Issues::Importer do
before do
client = instance_double(Gitlab::PhabricatorImport::Conduit::Maniphest)
-
allow(client).to receive(:tasks).and_return(response)
allow(importer).to receive(:client).and_return(client)
end
@@ -34,20 +33,29 @@ describe Gitlab::PhabricatorImport::Issues::Importer do
importer.execute
end
- it 'schedules the next batch if there is one' do
- expect(Gitlab::PhabricatorImport::ImportTasksWorker)
- .to receive(:schedule).with(project.id, response.pagination.next_page)
+ context 'stubbed task import' do
+ before do
+ # Stub out the actual importing so we don't perform aditional requests
+ expect_next_instance_of(Gitlab::PhabricatorImport::Issues::TaskImporter) do |task_importer|
+ allow(task_importer).to receive(:execute)
+ end.at_least(1)
+ end
- importer.execute
- end
+ it 'schedules the next batch if there is one' do
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker)
+ .to receive(:schedule).with(project.id, response.pagination.next_page)
- it 'does not reschedule when there is no next page' do
- allow(response.pagination).to receive(:has_next_page?).and_return(false)
+ importer.execute
+ end
- expect(Gitlab::PhabricatorImport::ImportTasksWorker)
- .not_to receive(:schedule)
+ it 'does not reschedule when there is no next page' do
+ allow(response.pagination).to receive(:has_next_page?).and_return(false)
- importer.execute
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker)
+ .not_to receive(:schedule)
+
+ importer.execute
+ end
end
end
end
diff --git a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
index 1625604e754..06ed264e781 100644
--- a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
@@ -12,6 +12,8 @@ describe Gitlab::PhabricatorImport::Issues::TaskImporter do
'description' => {
'raw' => '# This is markdown\n it can contain more text.'
},
+ 'authorPHID' => 'PHID-USER-456',
+ 'ownerPHID' => 'PHID-USER-123',
'dateCreated' => '1518688921',
'dateClosed' => '1518789995'
}
@@ -19,9 +21,18 @@ describe Gitlab::PhabricatorImport::Issues::TaskImporter do
)
end
+ subject(:importer) { described_class.new(project, task) }
+
describe '#execute' do
+ let(:fake_user_finder) { instance_double(Gitlab::PhabricatorImport::UserFinder) }
+
+ before do
+ allow(fake_user_finder).to receive(:find)
+ allow(importer).to receive(:user_finder).and_return(fake_user_finder)
+ end
+
it 'creates the issue with the expected attributes' do
- issue = described_class.new(project, task).execute
+ issue = importer.execute
expect(issue.project).to eq(project)
expect(issue).to be_persisted
@@ -34,21 +45,38 @@ describe Gitlab::PhabricatorImport::Issues::TaskImporter do
end
it 'does not recreate the issue when called multiple times' do
- expect { described_class.new(project, task).execute }
+ expect { importer.execute }
.to change { project.issues.reload.size }.from(0).to(1)
- expect { described_class.new(project, task).execute }
+ expect { importer.execute }
.not_to change { project.issues.reload.size }
end
it 'does not trigger a save when the object did not change' do
existing_issue = create(:issue,
task.issue_attributes.merge(author: User.ghost))
- importer = described_class.new(project, task)
allow(importer).to receive(:issue).and_return(existing_issue)
expect(existing_issue).not_to receive(:save!)
importer.execute
end
+
+ it 'links the author if the author can be found' do
+ author = create(:user)
+ expect(fake_user_finder).to receive(:find).with('PHID-USER-456').and_return(author)
+
+ issue = importer.execute
+
+ expect(issue.author).to eq(author)
+ end
+
+ it 'links an assignee if the user can be found' do
+ assignee = create(:user)
+ expect(fake_user_finder).to receive(:find).with('PHID-USER-123').and_return(assignee)
+
+ issue = importer.execute
+
+ expect(issue.assignees).to include(assignee)
+ end
end
end
diff --git a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
index dfbd8c546eb..5603a6961d6 100644
--- a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PhabricatorImport::Representation::Task do
@@ -7,6 +9,8 @@ describe Gitlab::PhabricatorImport::Representation::Task do
'phid' => 'the-phid',
'fields' => {
'name' => 'Title'.ljust(257, '.'), # A string padded to 257 chars
+ 'authorPHID' => 'a phid',
+ 'ownerPHID' => 'another user phid',
'description' => {
'raw' => '# This is markdown\n it can contain more text.'
},
@@ -30,4 +34,16 @@ describe Gitlab::PhabricatorImport::Representation::Task do
expect(task.issue_attributes).to eq(expected_attributes)
end
end
+
+ describe '#author_phid' do
+ it 'returns the correct field' do
+ expect(task.author_phid).to eq('a phid')
+ end
+ end
+
+ describe '#owner_phid' do
+ it 'returns the correct field' do
+ expect(task.owner_phid).to eq('another user phid')
+ end
+ end
end
diff --git a/spec/lib/gitlab/phabricator_import/representation/user_spec.rb b/spec/lib/gitlab/phabricator_import/representation/user_spec.rb
new file mode 100644
index 00000000000..f52467a0cf1
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/representation/user_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Representation::User do
+ subject(:user) do
+ described_class.new(
+ {
+ 'phid' => 'the-phid',
+ 'fields' => {
+ 'username' => 'the-username'
+ }
+ }
+ )
+ end
+
+ describe '#phabricator_id' do
+ it 'returns the phabricator id' do
+ expect(user.phabricator_id).to eq('the-phid')
+ end
+ end
+
+ describe '#username' do
+ it 'returns the username' do
+ expect(user.username).to eq('the-username')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/user_finder_spec.rb b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
new file mode 100644
index 00000000000..918ff28c8f5
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::UserFinder, :clean_gitlab_redis_cache do
+ let(:project) { create(:project, namespace: create(:group)) }
+ subject(:finder) { described_class.new(project, ['first-phid', 'second-phid']) }
+
+ before do
+ project.namespace.add_developer(existing_user)
+ end
+
+ describe '#find' do
+ let!(:existing_user) { create(:user, username: 'existing-user') }
+ let(:cache) { Gitlab::PhabricatorImport::Cache::Map.new(project) }
+
+ before do
+ allow(finder).to receive(:object_map).and_return(cache)
+ end
+
+ context 'for a cached phid' do
+ before do
+ cache.set_gitlab_model(existing_user, 'first-phid')
+ end
+
+ it 'returns the existing user' do
+ expect(finder.find('first-phid')).to eq(existing_user)
+ end
+
+ it 'does not perform a find using the API' do
+ expect(finder).not_to receive(:find_user_for_phid)
+
+ finder.find('first-phid')
+ end
+
+ it 'excludes the phid from the request if one needs to be made' do
+ client = instance_double(Gitlab::PhabricatorImport::Conduit::User)
+ allow(finder).to receive(:client).and_return(client)
+
+ expect(client).to receive(:users).with(['second-phid']).and_return([])
+
+ finder.find('first-phid')
+ finder.find('second-phid')
+ end
+ end
+
+ context 'when the phid is not cached' do
+ let(:response) do
+ [
+ instance_double(
+ Gitlab::PhabricatorImport::Conduit::UsersResponse,
+ users: [instance_double(Gitlab::PhabricatorImport::Representation::User, phabricator_id: 'second-phid', username: 'existing-user')]
+ ),
+ instance_double(
+ Gitlab::PhabricatorImport::Conduit::UsersResponse,
+ users: [instance_double(Gitlab::PhabricatorImport::Representation::User, phabricator_id: 'first-phid', username: 'other-user')]
+ )
+ ]
+ end
+ let(:client) do
+ client = instance_double(Gitlab::PhabricatorImport::Conduit::User)
+ allow(client).to receive(:users).and_return(response)
+
+ client
+ end
+
+ before do
+ allow(finder).to receive(:client).and_return(client)
+ end
+
+ it 'loads the users from the API once' do
+ expect(client).to receive(:users).and_return(response).once
+
+ expect(finder.find('second-phid')).to eq(existing_user)
+ expect(finder.find('first-phid')).to be_nil
+ end
+
+ it 'adds found users to the cache' do
+ expect { finder.find('second-phid') }
+ .to change { cache.get_gitlab_model('second-phid') }
+ .from(nil).to(existing_user)
+ end
+
+ it 'only returns users that are members of the project' do
+ create(:user, username: 'other-user')
+
+ expect(finder.find('first-phid')).to eq(nil)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
index a44947445c9..b6f2524a9d0 100644
--- a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do
diff --git a/spec/lib/gitlab/plugin_spec.rb b/spec/lib/gitlab/plugin_spec.rb
index 33dd4f79130..a8ddd774f3f 100644
--- a/spec/lib/gitlab/plugin_spec.rb
+++ b/spec/lib/gitlab/plugin_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Plugin do
diff --git a/spec/lib/gitlab/polling_interval_spec.rb b/spec/lib/gitlab/polling_interval_spec.rb
index eb8e618156b..979164269bd 100644
--- a/spec/lib/gitlab/polling_interval_spec.rb
+++ b/spec/lib/gitlab/polling_interval_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PollingInterval do
diff --git a/spec/lib/gitlab/popen/runner_spec.rb b/spec/lib/gitlab/popen/runner_spec.rb
index 2e2cb4ca28f..de19106eaee 100644
--- a/spec/lib/gitlab/popen/runner_spec.rb
+++ b/spec/lib/gitlab/popen/runner_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Popen::Runner do
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index c1b84e9f077..29afd9df74e 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Popen do
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 5af52db7a1f..a19392f4bcb 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Profiler do
diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb
index bd0bc2c9044..82ccb42f8a6 100644
--- a/spec/lib/gitlab/project_authorizations_spec.rb
+++ b/spec/lib/gitlab/project_authorizations_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ProjectAuthorizations do
@@ -20,13 +22,7 @@ describe Gitlab::ProjectAuthorizations do
end
let(:authorizations) do
- klass = if Group.supports_nested_objects?
- Gitlab::ProjectAuthorizations::WithNestedGroups
- else
- Gitlab::ProjectAuthorizations::WithoutNestedGroups
- end
-
- klass.new(user).calculate
+ described_class.new(user).calculate
end
it 'returns the correct number of authorizations' do
@@ -46,28 +42,26 @@ describe Gitlab::ProjectAuthorizations do
expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER)
end
- if Group.supports_nested_objects?
- context 'with nested groups' do
- let!(:nested_group) { create(:group, parent: group) }
- let!(:nested_project) { create(:project, namespace: nested_group) }
+ context 'with nested groups' do
+ let!(:nested_group) { create(:group, parent: group) }
+ let!(:nested_project) { create(:project, namespace: nested_group) }
- it 'includes nested groups' do
- expect(authorizations.pluck(:project_id)).to include(nested_project.id)
- end
+ it 'includes nested groups' do
+ expect(authorizations.pluck(:project_id)).to include(nested_project.id)
+ end
- it 'inherits access levels when the user is not a member of a nested group' do
- mapping = map_access_levels(authorizations)
+ it 'inherits access levels when the user is not a member of a nested group' do
+ mapping = map_access_levels(authorizations)
- expect(mapping[nested_project.id]).to eq(Gitlab::Access::DEVELOPER)
- end
+ expect(mapping[nested_project.id]).to eq(Gitlab::Access::DEVELOPER)
+ end
- it 'uses the greatest access level when a user is a member of a nested group' do
- nested_group.add_maintainer(user)
+ it 'uses the greatest access level when a user is a member of a nested group' do
+ nested_group.add_maintainer(user)
- mapping = map_access_levels(authorizations)
+ mapping = map_access_levels(authorizations)
- expect(mapping[nested_project.id]).to eq(Gitlab::Access::MAINTAINER)
- end
+ expect(mapping[nested_project.id]).to eq(Gitlab::Access::MAINTAINER)
end
end
end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 4a41d5cf51e..0dbfcf96124 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ProjectSearchResults do
@@ -22,6 +24,28 @@ describe Gitlab::ProjectSearchResults do
it { expect(results.query).to eq('hello world') }
end
+ describe '#formatted_count' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:results) { described_class.new(user, project, query) }
+
+ where(:scope, :count_method, :expected) do
+ 'blobs' | :blobs_count | '1234'
+ 'notes' | :limited_notes_count | '1000+'
+ 'wiki_blobs' | :wiki_blobs_count | '1234'
+ 'commits' | :commits_count | '1234'
+ 'projects' | :limited_projects_count | '1000+'
+ 'unknown' | nil | nil
+ end
+
+ with_them do
+ it 'returns the expected formatted count' do
+ expect(results).to receive(count_method).and_return(1234) if count_method
+ expect(results.formatted_count(scope)).to eq(expected)
+ end
+ end
+ end
+
shared_examples 'general blob search' do |entity_type, blob_kind|
let(:query) { 'files' }
subject(:results) { described_class.new(user, project, query).objects(blob_type) }
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index 8c2fc048a54..83acd979a80 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ProjectTemplate do
@@ -28,6 +30,18 @@ describe Gitlab::ProjectTemplate do
end
end
+ describe '#project_path' do
+ subject { described_class.new('name', 'title', 'description', 'https://gitlab.com/some/project/path').project_path }
+
+ it { is_expected.to eq 'some/project/path' }
+ end
+
+ describe '#uri_encoded_project_path' do
+ subject { described_class.new('name', 'title', 'description', 'https://gitlab.com/some/project/path').uri_encoded_project_path }
+
+ it { is_expected.to eq 'some%2Fproject%2Fpath' }
+ end
+
describe '.find' do
subject { described_class.find(query) }
@@ -44,6 +58,12 @@ describe Gitlab::ProjectTemplate do
end
end
+ describe '.archive_directory' do
+ subject { described_class.archive_directory }
+
+ it { is_expected.to be_a Pathname }
+ end
+
describe 'instance methods' do
subject { described_class.new('phoenix', 'Phoenix Framework', 'Phoenix description', 'link-to-template') }
diff --git a/spec/lib/gitlab/project_transfer_spec.rb b/spec/lib/gitlab/project_transfer_spec.rb
index 0b9b1f537b5..d54817ea02b 100644
--- a/spec/lib/gitlab/project_transfer_spec.rb
+++ b/spec/lib/gitlab/project_transfer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ProjectTransfer do
diff --git a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
index 1a108003bc2..3f97a69b5eb 100644
--- a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
+++ b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Prometheus::AdditionalMetricsParser do
diff --git a/spec/lib/gitlab/prometheus/metric_group_spec.rb b/spec/lib/gitlab/prometheus/metric_group_spec.rb
index 5cc6827488b..a45dd0af91e 100644
--- a/spec/lib/gitlab/prometheus/metric_group_spec.rb
+++ b/spec/lib/gitlab/prometheus/metric_group_spec.rb
@@ -17,7 +17,7 @@ describe Gitlab::Prometheus::MetricGroup do
end
it 'returns exactly three metric queries' do
- expect(subject.map(&:metrics).flatten.map(&:id)).to contain_exactly(
+ expect(subject.flat_map(&:metrics).map(&:id)).to contain_exactly(
common_metric_group_a.id, common_metric_group_b_q1.id,
common_metric_group_b_q2.id)
end
@@ -37,7 +37,7 @@ describe Gitlab::Prometheus::MetricGroup do
subject do
described_class.for_project(other_project)
- .map(&:metrics).flatten
+ .flat_map(&:metrics)
.map(&:id)
end
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
index c7169717fc1..4bdc57c8c04 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
index a6589f0c0a3..35dbdd55cfa 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery do
diff --git a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
index ffe3ad85baa..0ad2de218fe 100644
--- a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Prometheus::Queries::DeploymentQuery do
diff --git a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
index 936447b8474..35034d814bf 100644
--- a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Prometheus::Queries::MatchedMetricQuery do
diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb
index 6dc99ef26ec..3f9b245a3fb 100644
--- a/spec/lib/gitlab/prometheus/query_variables_spec.rb
+++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb
@@ -23,7 +23,7 @@ describe Gitlab::Prometheus::QueryVariables do
context 'with deployment platform' do
context 'with project cluster' do
- let(:kube_namespace) { environment.deployment_platform.cluster.kubernetes_namespace_for(project) }
+ let(:kube_namespace) { environment.deployment_namespace }
before do
create(:cluster, :project, :provided_by_user, projects: [project])
@@ -38,8 +38,8 @@ describe Gitlab::Prometheus::QueryVariables do
let(:project2) { create(:project) }
let(:kube_namespace) { k8s_ns.namespace }
- let!(:k8s_ns) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project) }
- let!(:k8s_ns2) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project2) }
+ let!(:k8s_ns) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project, environment: environment) }
+ let!(:k8s_ns2) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project2, environment: environment) }
before do
group.projects << project
diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index f15ae83a02c..86a1c14ed3f 100644
--- a/spec/lib/gitlab/prometheus_client_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PrometheusClient do
include PrometheusHelpers
- subject { described_class.new(RestClient::Resource.new('https://prometheus.example.com')) }
+ subject { described_class.new('https://prometheus.example.com') }
describe '#ping' do
it 'issues a "query" request to the API endpoint' do
@@ -79,8 +81,16 @@ describe Gitlab::PrometheusClient do
expect(req_stub).to have_been_requested
end
- it 'raises a Gitlab::PrometheusClient::Error error when a RestClient::Exception is rescued' do
- req_stub = stub_prometheus_request_with_exception(prometheus_url, RestClient::Exception)
+ it 'raises a Gitlab::PrometheusClient::Error error when a Gitlab::HTTP::ResponseError is rescued' do
+ req_stub = stub_prometheus_request_with_exception(prometheus_url, Gitlab::HTTP::ResponseError)
+
+ expect { subject }
+ .to raise_error(Gitlab::PrometheusClient::Error, "Network connection error")
+ expect(req_stub).to have_been_requested
+ end
+
+ it 'raises a Gitlab::PrometheusClient::Error error when a Gitlab::HTTP::ResponseError with a code is rescued' do
+ req_stub = stub_prometheus_request_with_exception(prometheus_url, Gitlab::HTTP::ResponseError.new(code: 400))
expect { subject }
.to raise_error(Gitlab::PrometheusClient::Error, "Network connection error")
@@ -89,13 +99,13 @@ describe Gitlab::PrometheusClient do
end
context 'ping' do
- subject { described_class.new(RestClient::Resource.new(prometheus_url)).ping }
+ subject { described_class.new(prometheus_url).ping }
it_behaves_like 'exceptions are raised'
end
context 'proxy' do
- subject { described_class.new(RestClient::Resource.new(prometheus_url)).proxy('query', { query: '1' }) }
+ subject { described_class.new(prometheus_url).proxy('query', { query: '1' }) }
it_behaves_like 'exceptions are raised'
end
@@ -310,15 +320,32 @@ describe Gitlab::PrometheusClient do
end
end
- context 'when RestClient::Exception is raised' do
+ context 'when Gitlab::HTTP::ResponseError is raised' do
before do
- stub_prometheus_request_with_exception(query_url, RestClient::Exception)
+ stub_prometheus_request_with_exception(query_url, response_error)
+ end
+
+ context "without response code" do
+ let(:response_error) { Gitlab::HTTP::ResponseError }
+ it 'raises PrometheusClient::Error' do
+ expect { subject.proxy('query', { query: prometheus_query }) }.to(
+ raise_error(Gitlab::PrometheusClient::Error, 'Network connection error')
+ )
+ end
end
- it 'raises PrometheusClient::Error' do
- expect { subject.proxy('query', { query: prometheus_query }) }.to(
- raise_error(Gitlab::PrometheusClient::Error, 'Network connection error')
- )
+ context "with response code" do
+ let(:response_error) do
+ response = Net::HTTPResponse.new(1.1, 400, '{}sumpthin')
+ allow(response).to receive(:body) { '{}' }
+ Gitlab::HTTP::ResponseError.new(response)
+ end
+
+ it 'raises Gitlab::PrometheusClient::QueryError' do
+ expect { subject.proxy('query', { query: prometheus_query }) }.to(
+ raise_error(Gitlab::PrometheusClient::QueryError, 'Bad data received')
+ )
+ end
end
end
end
diff --git a/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb b/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
index f8faeffb935..2db6d2fb60f 100644
--- a/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
+++ b/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QueryLimiting::ActiveSupportSubscriber do
diff --git a/spec/lib/gitlab/query_limiting/middleware_spec.rb b/spec/lib/gitlab/query_limiting/middleware_spec.rb
index a04bcdecb4b..fb1c30118c2 100644
--- a/spec/lib/gitlab/query_limiting/middleware_spec.rb
+++ b/spec/lib/gitlab/query_limiting/middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QueryLimiting::Middleware do
diff --git a/spec/lib/gitlab/query_limiting/transaction_spec.rb b/spec/lib/gitlab/query_limiting/transaction_spec.rb
index b72b8574174..39d5a575efc 100644
--- a/spec/lib/gitlab/query_limiting/transaction_spec.rb
+++ b/spec/lib/gitlab/query_limiting/transaction_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QueryLimiting::Transaction do
diff --git a/spec/lib/gitlab/query_limiting_spec.rb b/spec/lib/gitlab/query_limiting_spec.rb
index 42877b1e2dd..f0d0340cd6e 100644
--- a/spec/lib/gitlab/query_limiting_spec.rb
+++ b/spec/lib/gitlab/query_limiting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QueryLimiting do
diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
index b6e0adbc1c2..45b710adf07 100644
--- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QuickActions::CommandDefinition do
@@ -219,6 +221,52 @@ describe Gitlab::QuickActions::CommandDefinition do
end
end
+ describe "#execute_message" do
+ context "when the command is a noop" do
+ it 'returns nil' do
+ expect(subject.execute_message({}, nil)).to be_nil
+ end
+ end
+
+ context "when the command is not a noop" do
+ before do
+ subject.action_block = proc { self.run = true }
+ end
+
+ context "when the command is not available" do
+ before do
+ subject.condition_block = proc { false }
+ end
+
+ it 'returns nil' do
+ expect(subject.execute_message({}, nil)).to be_nil
+ end
+ end
+
+ context "when the command is available" do
+ context 'when the execution_message is a static string' do
+ before do
+ subject.execution_message = 'Assigned jacopo'
+ end
+
+ it 'returns this static string' do
+ expect(subject.execute_message({}, nil)).to eq('Assigned jacopo')
+ end
+ end
+
+ context 'when the explanation is dynamic' do
+ before do
+ subject.execution_message = proc { |arg| "Assigned #{arg}" }
+ end
+
+ it 'invokes the proc' do
+ expect(subject.execute_message({}, 'Jacopo')).to eq('Assigned Jacopo')
+ end
+ end
+ end
+ end
+ end
+
describe '#explain' do
context 'when the command is not available' do
before do
diff --git a/spec/lib/gitlab/quick_actions/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb
index 185adab1ff6..c98c36622f5 100644
--- a/spec/lib/gitlab/quick_actions/dsl_spec.rb
+++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QuickActions::Dsl do
@@ -20,6 +22,9 @@ describe Gitlab::QuickActions::Dsl do
desc do
"A dynamic description for #{noteable.upcase}"
end
+ execution_message do |arg|
+ "A dynamic execution message for #{noteable.upcase} passing #{arg}"
+ end
params 'The first argument', 'The second argument'
command :dynamic_description do |args|
args.split
@@ -30,6 +35,7 @@ describe Gitlab::QuickActions::Dsl do
explanation do |arg|
"Action does something with #{arg}"
end
+ execution_message 'Command applied correctly'
condition do
project == 'foo'
end
@@ -67,6 +73,7 @@ describe Gitlab::QuickActions::Dsl do
expect(no_args_def.aliases).to eq([:none])
expect(no_args_def.description).to eq('A command with no args')
expect(no_args_def.explanation).to eq('')
+ expect(no_args_def.execution_message).to eq('')
expect(no_args_def.params).to eq([])
expect(no_args_def.condition_block).to be_nil
expect(no_args_def.types).to eq([])
@@ -78,6 +85,8 @@ describe Gitlab::QuickActions::Dsl do
expect(explanation_with_aliases_def.aliases).to eq([:once, :first])
expect(explanation_with_aliases_def.description).to eq('')
expect(explanation_with_aliases_def.explanation).to eq('Static explanation')
+ expect(explanation_with_aliases_def.execution_message).to eq('')
+ expect(no_args_def.params).to eq([])
expect(explanation_with_aliases_def.params).to eq(['The first argument'])
expect(explanation_with_aliases_def.condition_block).to be_nil
expect(explanation_with_aliases_def.types).to eq([])
@@ -88,7 +97,7 @@ describe Gitlab::QuickActions::Dsl do
expect(dynamic_description_def.name).to eq(:dynamic_description)
expect(dynamic_description_def.aliases).to eq([])
expect(dynamic_description_def.to_h(OpenStruct.new(noteable: 'issue'))[:description]).to eq('A dynamic description for ISSUE')
- expect(dynamic_description_def.explanation).to eq('')
+ expect(dynamic_description_def.execute_message(OpenStruct.new(noteable: 'issue'), 'arg')).to eq('A dynamic execution message for ISSUE passing arg')
expect(dynamic_description_def.params).to eq(['The first argument', 'The second argument'])
expect(dynamic_description_def.condition_block).to be_nil
expect(dynamic_description_def.types).to eq([])
@@ -100,6 +109,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cc_def.aliases).to eq([])
expect(cc_def.description).to eq('')
expect(cc_def.explanation).to eq('')
+ expect(cc_def.execution_message).to eq('')
expect(cc_def.params).to eq([])
expect(cc_def.condition_block).to be_nil
expect(cc_def.types).to eq([])
@@ -111,6 +121,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cond_action_def.aliases).to eq([])
expect(cond_action_def.description).to eq('')
expect(cond_action_def.explanation).to be_a_kind_of(Proc)
+ expect(cond_action_def.execution_message).to eq('Command applied correctly')
expect(cond_action_def.params).to eq([])
expect(cond_action_def.condition_block).to be_a_kind_of(Proc)
expect(cond_action_def.types).to eq([])
@@ -122,6 +133,7 @@ describe Gitlab::QuickActions::Dsl do
expect(with_params_parsing_def.aliases).to eq([])
expect(with_params_parsing_def.description).to eq('')
expect(with_params_parsing_def.explanation).to eq('')
+ expect(with_params_parsing_def.execution_message).to eq('')
expect(with_params_parsing_def.params).to eq([])
expect(with_params_parsing_def.condition_block).to be_nil
expect(with_params_parsing_def.types).to eq([])
@@ -133,6 +145,7 @@ describe Gitlab::QuickActions::Dsl do
expect(substitution_def.aliases).to eq([])
expect(substitution_def.description).to eq('')
expect(substitution_def.explanation).to eq('')
+ expect(substitution_def.execution_message).to eq('')
expect(substitution_def.params).to eq(['<Comment>'])
expect(substitution_def.condition_block).to be_nil
expect(substitution_def.types).to eq([])
@@ -144,6 +157,7 @@ describe Gitlab::QuickActions::Dsl do
expect(has_types.aliases).to eq([])
expect(has_types.description).to eq('A command with types')
expect(has_types.explanation).to eq('')
+ expect(has_types.execution_message).to eq('')
expect(has_types.params).to eq([])
expect(has_types.condition_block).to be_nil
expect(has_types.types).to eq([Issue, Commit])
diff --git a/spec/lib/gitlab/quick_actions/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb
index 873bb359d6e..f1acb5b7049 100644
--- a/spec/lib/gitlab/quick_actions/extractor_spec.rb
+++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QuickActions::Extractor do
diff --git a/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb b/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb
index 8b58f0b3725..fd149cd1114 100644
--- a/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb
+++ b/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QuickActions::SpendTimeAndDateSeparator do
diff --git a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
index 1bb8bc51c96..e4f25bc35a9 100644
--- a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QuickActions::SubstitutionDefinition do
diff --git a/spec/lib/gitlab/redis/cache_spec.rb b/spec/lib/gitlab/redis/cache_spec.rb
index 5a4f17cfcf6..0718998f981 100644
--- a/spec/lib/gitlab/redis/cache_spec.rb
+++ b/spec/lib/gitlab/redis/cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Redis::Cache do
diff --git a/spec/lib/gitlab/redis/queues_spec.rb b/spec/lib/gitlab/redis/queues_spec.rb
index 01ca25635a9..93207b6f469 100644
--- a/spec/lib/gitlab/redis/queues_spec.rb
+++ b/spec/lib/gitlab/redis/queues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Redis::Queues do
diff --git a/spec/lib/gitlab/redis/shared_state_spec.rb b/spec/lib/gitlab/redis/shared_state_spec.rb
index 24b73745dc5..aa61fd99eb5 100644
--- a/spec/lib/gitlab/redis/shared_state_spec.rb
+++ b/spec/lib/gitlab/redis/shared_state_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Redis::SharedState do
diff --git a/spec/lib/gitlab/redis/wrapper_spec.rb b/spec/lib/gitlab/redis/wrapper_spec.rb
index 0c22a0d62cc..e4cc42130db 100644
--- a/spec/lib/gitlab/redis/wrapper_spec.rb
+++ b/spec/lib/gitlab/redis/wrapper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Redis::Wrapper do
diff --git a/spec/lib/gitlab/reference_counter_spec.rb b/spec/lib/gitlab/reference_counter_spec.rb
index b2344d1870a..f9361d08faf 100644
--- a/spec/lib/gitlab/reference_counter_spec.rb
+++ b/spec/lib/gitlab/reference_counter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ReferenceCounter do
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index a1079e54975..e19210d8fbf 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Regex do
@@ -32,6 +34,14 @@ describe Gitlab::Regex do
it { is_expected.not_to match('/') }
end
+ describe '.environment_scope_regex' do
+ subject { described_class.environment_scope_regex }
+
+ it { is_expected.to match('foo') }
+ it { is_expected.to match('foo*Z') }
+ it { is_expected.not_to match('!!()()') }
+ end
+
describe '.environment_slug_regex' do
subject { described_class.environment_slug_regex }
diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index 8fbda929064..cffd7cc89e7 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ::Gitlab::RepoPath do
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index 0295138fc3a..fd1338b55a6 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -1,9 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::RepositoryCacheAdapter do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:cache) { repository.send(:cache) }
+ let(:redis_set_cache) { repository.send(:redis_set_cache) }
describe '#cache_method_output', :use_clean_rails_memory_store_caching do
let(:fallback) { 10 }
@@ -206,9 +209,11 @@ describe Gitlab::RepositoryCacheAdapter do
describe '#expire_method_caches' do
it 'expires the caches of the given methods' do
expect(cache).to receive(:expire).with(:rendered_readme)
- expect(cache).to receive(:expire).with(:gitignore)
+ expect(cache).to receive(:expire).with(:branch_names)
+ expect(redis_set_cache).to receive(:expire).with(:rendered_readme)
+ expect(redis_set_cache).to receive(:expire).with(:branch_names)
- repository.expire_method_caches(%i(rendered_readme gitignore))
+ repository.expire_method_caches(%i(rendered_readme branch_names))
end
it 'does not expire caches for non-existent methods' do
diff --git a/spec/lib/gitlab/repository_cache_spec.rb b/spec/lib/gitlab/repository_cache_spec.rb
index 741ee12633f..6a684595eb8 100644
--- a/spec/lib/gitlab/repository_cache_spec.rb
+++ b/spec/lib/gitlab/repository_cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::RepositoryCache do
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
new file mode 100644
index 00000000000..87e51f801e5
--- /dev/null
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:namespace) { "#{repository.full_path}:#{project.id}" }
+ let(:cache) { described_class.new(repository) }
+
+ describe '#cache_key' do
+ subject { cache.cache_key(:foo) }
+
+ it 'includes the namespace' do
+ is_expected.to eq("foo:#{namespace}:set")
+ end
+
+ context 'with a given namespace' do
+ let(:extra_namespace) { 'my:data' }
+ let(:cache) { described_class.new(repository, extra_namespace: extra_namespace) }
+
+ it 'includes the full namespace' do
+ is_expected.to eq("foo:#{namespace}:#{extra_namespace}:set")
+ end
+ end
+ end
+
+ describe '#expire' do
+ it 'expires the given key from the cache' do
+ cache.write(:foo, ['value'])
+
+ expect(cache.read(:foo)).to contain_exactly('value')
+ expect(cache.expire(:foo)).to eq(1)
+ expect(cache.read(:foo)).to be_empty
+ end
+ end
+
+ describe '#exist?' do
+ it 'checks whether the key exists' do
+ expect(cache.exist?(:foo)).to be(false)
+
+ cache.write(:foo, ['value'])
+
+ expect(cache.exist?(:foo)).to be(true)
+ end
+ end
+
+ describe '#fetch' do
+ let(:blk) { -> { ['block value'] } }
+
+ subject { cache.fetch(:foo, &blk) }
+
+ it 'fetches the key from the cache when filled' do
+ cache.write(:foo, ['value'])
+
+ is_expected.to contain_exactly('value')
+ end
+
+ it 'writes the value of the provided block when empty' do
+ cache.expire(:foo)
+
+ is_expected.to contain_exactly('block value')
+ expect(cache.read(:foo)).to contain_exactly('block value')
+ end
+ end
+
+ describe '#include?' do
+ it 'checks inclusion in the Redis set' do
+ cache.write(:foo, ['value'])
+
+ expect(cache.include?(:foo, 'value')).to be(true)
+ expect(cache.include?(:foo, 'bar')).to be(false)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
index 23e45aff1c5..a744f48da1f 100644
--- a/spec/lib/gitlab/request_context_spec.rb
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::RequestContext do
diff --git a/spec/lib/gitlab/request_forgery_protection_spec.rb b/spec/lib/gitlab/request_forgery_protection_spec.rb
index 305de613866..b7a3dc16eff 100644
--- a/spec/lib/gitlab/request_forgery_protection_spec.rb
+++ b/spec/lib/gitlab/request_forgery_protection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::RequestForgeryProtection, :allow_forgery_protection do
diff --git a/spec/lib/gitlab/request_profiler/profile_spec.rb b/spec/lib/gitlab/request_profiler/profile_spec.rb
new file mode 100644
index 00000000000..a75f3c66156
--- /dev/null
+++ b/spec/lib/gitlab/request_profiler/profile_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::RequestProfiler::Profile do
+ let(:profile) { described_class.new(filename) }
+
+ describe '.new' do
+ context 'using old filename' do
+ let(:filename) { '|api|v4|version.txt_1562854738.html' }
+
+ it 'returns valid data' do
+ expect(profile).to be_valid
+ expect(profile.request_path).to eq('/api/v4/version.txt')
+ expect(profile.time).to eq(Time.at(1562854738).utc)
+ expect(profile.type).to eq('html')
+ end
+ end
+
+ context 'using new filename' do
+ let(:filename) { '|api|v4|version.txt_1563547949_execution.html' }
+
+ it 'returns valid data' do
+ expect(profile).to be_valid
+ expect(profile.request_path).to eq('/api/v4/version.txt')
+ expect(profile.profile_mode).to eq('execution')
+ expect(profile.time).to eq(Time.at(1563547949).utc)
+ expect(profile.type).to eq('html')
+ end
+ end
+ end
+
+ describe '#content_type' do
+ context 'when using html file' do
+ let(:filename) { '|api|v4|version.txt_1562854738_memory.html' }
+
+ it 'returns valid data' do
+ expect(profile).to be_valid
+ expect(profile.content_type).to eq('text/html')
+ end
+ end
+
+ context 'when using text file' do
+ let(:filename) { '|api|v4|version.txt_1562854738_memory.txt' }
+
+ it 'returns valid data' do
+ expect(profile).to be_valid
+ expect(profile.content_type).to eq('text/plain')
+ end
+ end
+
+ context 'when file is unknown' do
+ let(:filename) { '|api|v4|version.txt_1562854738_memory.xxx' }
+
+ it 'returns valid data' do
+ expect(profile).not_to be_valid
+ expect(profile.content_type).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/request_profiler_spec.rb b/spec/lib/gitlab/request_profiler_spec.rb
index fd8cbf39bce..f157189a72d 100644
--- a/spec/lib/gitlab/request_profiler_spec.rb
+++ b/spec/lib/gitlab/request_profiler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::RequestProfiler do
@@ -13,15 +15,42 @@ describe Gitlab::RequestProfiler do
end
end
- describe '.remove_all_profiles' do
- it 'removes Gitlab::RequestProfiler::PROFILES_DIR directory' do
- dir = described_class::PROFILES_DIR
- FileUtils.mkdir_p(dir)
+ context 'with temporary PROFILES_DIR' do
+ let(:tmpdir) { Dir.mktmpdir('profiler-test') }
+ let(:profile_name) { '|api|v4|version.txt_1562854738_memory.html' }
+ let(:profile_path) { File.join(tmpdir, profile_name) }
+
+ before do
+ stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir)
+ FileUtils.touch(profile_path)
+ end
+
+ after do
+ FileUtils.rm_rf(tmpdir)
+ end
+
+ describe '.remove_all_profiles' do
+ it 'removes Gitlab::RequestProfiler::PROFILES_DIR directory' do
+ described_class.remove_all_profiles
+
+ expect(Dir.exist?(tmpdir)).to be false
+ end
+ end
+
+ describe '.all' do
+ subject { described_class.all }
+
+ it 'returns all profiles' do
+ expect(subject.map(&:name)).to contain_exactly(profile_name)
+ end
+ end
- expect(Dir.exist?(dir)).to be true
+ describe '.find' do
+ subject { described_class.find(profile_name) }
- described_class.remove_all_profiles
- expect(Dir.exist?(dir)).to be false
+ it 'returns all profiles' do
+ expect(subject.name).to eq(profile_name)
+ end
end
end
end
diff --git a/spec/lib/gitlab/route_map_spec.rb b/spec/lib/gitlab/route_map_spec.rb
index a39c774429e..d5e70b91fb4 100644
--- a/spec/lib/gitlab/route_map_spec.rb
+++ b/spec/lib/gitlab/route_map_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::RouteMap do
diff --git a/spec/lib/gitlab/routing_spec.rb b/spec/lib/gitlab/routing_spec.rb
index 01d5acfc15b..965564cb83b 100644
--- a/spec/lib/gitlab/routing_spec.rb
+++ b/spec/lib/gitlab/routing_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Routing do
diff --git a/spec/lib/gitlab/rugged_instrumentation_spec.rb b/spec/lib/gitlab/rugged_instrumentation_spec.rb
new file mode 100644
index 00000000000..4dcc8ae514a
--- /dev/null
+++ b/spec/lib/gitlab/rugged_instrumentation_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::RuggedInstrumentation, :request_store do
+ subject { described_class }
+
+ describe '.query_time' do
+ it 'increments query times' do
+ subject.query_time += 0.451
+ subject.query_time += 0.322
+
+ expect(subject.query_time).to be_within(0.001).of(0.773)
+ expect(subject.query_time_ms).to eq(773.0)
+ end
+ end
+
+ context '.increment_query_count' do
+ it 'tracks query counts' do
+ expect(subject.query_count).to eq(0)
+
+ 2.times { subject.increment_query_count }
+
+ expect(subject.query_count).to eq(2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
index bd5f330c7a1..22c5f27dc6d 100644
--- a/spec/lib/gitlab/sanitizers/exif_spec.rb
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sanitizers::Exif do
diff --git a/spec/lib/gitlab/sanitizers/svg_spec.rb b/spec/lib/gitlab/sanitizers/svg_spec.rb
index df46a874528..a8c7495376d 100644
--- a/spec/lib/gitlab/sanitizers/svg_spec.rb
+++ b/spec/lib/gitlab/sanitizers/svg_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sanitizers::SVG do
diff --git a/spec/lib/gitlab/search/found_blob_spec.rb b/spec/lib/gitlab/search/found_blob_spec.rb
index da263bc7523..3496fb29836 100644
--- a/spec/lib/gitlab/search/found_blob_spec.rb
+++ b/spec/lib/gitlab/search/found_blob_spec.rb
@@ -1,4 +1,5 @@
# coding: utf-8
+# frozen_string_literal: true
require 'spec_helper'
@@ -108,7 +109,7 @@ describe Gitlab::Search::FoundBlob do
end
context 'with ISO-8859-1' do
- let(:search_result) { "master:encoding/iso8859.txt\x001\x00\xC4\xFC\nmaster:encoding/iso8859.txt\x002\x00\nmaster:encoding/iso8859.txt\x003\x00foo\n".force_encoding(Encoding::ASCII_8BIT) }
+ let(:search_result) { (+"master:encoding/iso8859.txt\x001\x00\xC4\xFC\nmaster:encoding/iso8859.txt\x002\x00\nmaster:encoding/iso8859.txt\x003\x00foo\n").force_encoding(Encoding::ASCII_8BIT) }
it 'returns results as UTF-8' do
expect(subject.filename).to eq('encoding/iso8859.txt')
diff --git a/spec/lib/gitlab/search/query_spec.rb b/spec/lib/gitlab/search/query_spec.rb
index 2d00428fffa..112e9a59f04 100644
--- a/spec/lib/gitlab/search/query_spec.rb
+++ b/spec/lib/gitlab/search/query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Search::Query do
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 3d27156b356..5621c686b8a 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SearchResults do
@@ -29,6 +31,43 @@ describe Gitlab::SearchResults do
end
end
+ describe '#formatted_count' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:scope, :count_method, :expected) do
+ 'projects' | :limited_projects_count | '1000+'
+ 'issues' | :limited_issues_count | '1000+'
+ 'merge_requests' | :limited_merge_requests_count | '1000+'
+ 'milestones' | :limited_milestones_count | '1000+'
+ 'users' | :limited_users_count | '1000+'
+ 'unknown' | nil | nil
+ end
+
+ with_them do
+ it 'returns the expected formatted count' do
+ expect(results).to receive(count_method).and_return(1234) if count_method
+ expect(results.formatted_count(scope)).to eq(expected)
+ end
+ end
+ end
+
+ describe '#formatted_limited_count' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:count, :expected) do
+ 23 | '23'
+ 1000 | '1000'
+ 1001 | '1000+'
+ 1234 | '1000+'
+ end
+
+ with_them do
+ it 'returns the expected formatted limited count' do
+ expect(results.formatted_limited_count(count)).to eq(expected)
+ end
+ end
+ end
+
context "when count_limit is lower than total amount" do
before do
allow(results).to receive(:count_limit).and_return(1)
diff --git a/spec/lib/gitlab/sentry_spec.rb b/spec/lib/gitlab/sentry_spec.rb
index af8b059b984..9c4f3b8f42e 100644
--- a/spec/lib/gitlab/sentry_spec.rb
+++ b/spec/lib/gitlab/sentry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sentry do
@@ -65,6 +67,7 @@ describe Gitlab::Sentry do
context '.track_acceptable_exception' do
let(:exception) { RuntimeError.new('boom') }
+ let(:issue_url) { 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1' }
before do
allow(described_class).to receive(:enabled?).and_return(true)
@@ -74,7 +77,7 @@ describe Gitlab::Sentry do
it 'calls Raven.capture_exception' do
expected_extras = {
some_other_info: 'info',
- issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1'
+ issue_url: issue_url
}
expected_tags = {
@@ -88,9 +91,33 @@ describe Gitlab::Sentry do
described_class.track_acceptable_exception(
exception,
- issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1',
+ issue_url: issue_url,
extra: { some_other_info: 'info' }
)
end
+
+ context 'the exception implements :sentry_extra_data' do
+ let(:extra_info) { { event: 'explosion', size: :massive } }
+ let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info) }
+
+ it 'includes the extra data from the exception in the tracking information' do
+ expect(Raven).to receive(:capture_exception)
+ .with(exception, a_hash_including(extra: a_hash_including(extra_info)))
+
+ described_class.track_acceptable_exception(exception)
+ end
+ end
+
+ context 'the exception implements :sentry_extra_data, which returns nil' do
+ let(:exception) { double(message: 'bang!', sentry_extra_data: nil) }
+
+ it 'just includes the other extra info' do
+ extra_info = { issue_url: issue_url }
+ expect(Raven).to receive(:capture_exception)
+ .with(exception, a_hash_including(extra: a_hash_including(extra_info)))
+
+ described_class.track_acceptable_exception(exception, extra_info)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/serializer/ci/variables_spec.rb b/spec/lib/gitlab/serializer/ci/variables_spec.rb
index 1d1fd5b0763..900508420c9 100644
--- a/spec/lib/gitlab/serializer/ci/variables_spec.rb
+++ b/spec/lib/gitlab/serializer/ci/variables_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Serializer::Ci::Variables do
diff --git a/spec/lib/gitlab/serializer/pagination_spec.rb b/spec/lib/gitlab/serializer/pagination_spec.rb
index c54be78f050..1e7f441f258 100644
--- a/spec/lib/gitlab/serializer/pagination_spec.rb
+++ b/spec/lib/gitlab/serializer/pagination_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Serializer::Pagination do
diff --git a/spec/lib/gitlab/shard_health_cache_spec.rb b/spec/lib/gitlab/shard_health_cache_spec.rb
index e1a69261939..f747849b5e9 100644
--- a/spec/lib/gitlab/shard_health_cache_spec.rb
+++ b/spec/lib/gitlab/shard_health_cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ShardHealthCache, :clean_gitlab_redis_cache do
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index bce2e754176..0ba16b93ee7 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'stringio'
diff --git a/spec/lib/gitlab/sherlock/collection_spec.rb b/spec/lib/gitlab/sherlock/collection_spec.rb
index 873ed14f804..bdc89c3d3cf 100644
--- a/spec/lib/gitlab/sherlock/collection_spec.rb
+++ b/spec/lib/gitlab/sherlock/collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::Collection do
diff --git a/spec/lib/gitlab/sherlock/file_sample_spec.rb b/spec/lib/gitlab/sherlock/file_sample_spec.rb
index 394421504e0..b09ba5c62dc 100644
--- a/spec/lib/gitlab/sherlock/file_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/file_sample_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::FileSample do
diff --git a/spec/lib/gitlab/sherlock/line_profiler_spec.rb b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
index f2f8040fa0b..c1997606839 100644
--- a/spec/lib/gitlab/sherlock/line_profiler_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::LineProfiler do
diff --git a/spec/lib/gitlab/sherlock/line_sample_spec.rb b/spec/lib/gitlab/sherlock/line_sample_spec.rb
index 5f02f6a3213..b68e8cc0266 100644
--- a/spec/lib/gitlab/sherlock/line_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_sample_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::LineSample do
diff --git a/spec/lib/gitlab/sherlock/location_spec.rb b/spec/lib/gitlab/sherlock/location_spec.rb
index b295a624b35..7b40c84c2d1 100644
--- a/spec/lib/gitlab/sherlock/location_spec.rb
+++ b/spec/lib/gitlab/sherlock/location_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::Location do
diff --git a/spec/lib/gitlab/sherlock/middleware_spec.rb b/spec/lib/gitlab/sherlock/middleware_spec.rb
index 2016023df06..8d6e362f622 100644
--- a/spec/lib/gitlab/sherlock/middleware_spec.rb
+++ b/spec/lib/gitlab/sherlock/middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::Middleware do
diff --git a/spec/lib/gitlab/sherlock/query_spec.rb b/spec/lib/gitlab/sherlock/query_spec.rb
index 426071c7f92..13c7e6f8f8b 100644
--- a/spec/lib/gitlab/sherlock/query_spec.rb
+++ b/spec/lib/gitlab/sherlock/query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::Query do
diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb
index 4a14dfbec56..2245c3ee8e2 100644
--- a/spec/lib/gitlab/sherlock/transaction_spec.rb
+++ b/spec/lib/gitlab/sherlock/transaction_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::Transaction do
diff --git a/spec/lib/gitlab/sidekiq_config_spec.rb b/spec/lib/gitlab/sidekiq_config_spec.rb
index 0c66d764851..1e8ccb447b1 100644
--- a/spec/lib/gitlab/sidekiq_config_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::SidekiqConfig do
diff --git a/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb b/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
index fed9aeba30c..a2cb38ec5b1 100644
--- a/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqLogging::JSONFormatter do
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index 7bc4599e20f..1b89c094a6b 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -1,8 +1,13 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqLogging::StructuredLogger do
describe '#call' do
- let(:timestamp) { Time.new('2018-01-01 12:00:00').utc }
+ let(:timestamp) { Time.iso8601('2018-01-01T12:00:00Z') }
+ let(:created_at) { timestamp - 1.second }
+ let(:scheduling_latency_s) { 1.0 }
+
let(:job) do
{
"class" => "TestWorker",
@@ -11,19 +16,21 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
"queue" => "cronjob:test_queue",
"queue_namespace" => "cronjob",
"jid" => "da883554ee4fe414012f5f42",
- "created_at" => timestamp.to_f,
- "enqueued_at" => timestamp.to_f,
+ "created_at" => created_at.to_f,
+ "enqueued_at" => created_at.to_f,
"correlation_id" => 'cid'
}
end
+
let(:logger) { double }
let(:start_payload) do
job.merge(
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: start',
'job_status' => 'start',
'pid' => Process.pid,
- 'created_at' => timestamp.iso8601(3),
- 'enqueued_at' => timestamp.iso8601(3)
+ 'created_at' => created_at.iso8601(3),
+ 'enqueued_at' => created_at.iso8601(3),
+ 'scheduling_latency_s' => scheduling_latency_s
)
end
let(:end_payload) do
@@ -31,7 +38,9 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: done: 0.0 sec',
'job_status' => 'done',
'duration' => 0.0,
- "completed_at" => timestamp.iso8601(3)
+ "completed_at" => timestamp.iso8601(3),
+ "system_s" => 0.0,
+ "user_s" => 0.0
)
end
let(:exception_payload) do
@@ -47,6 +56,13 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
allow(Sidekiq).to receive(:logger).and_return(logger)
allow(subject).to receive(:current_time).and_return(timestamp.to_f)
+
+ allow(Process).to receive(:times).and_return(
+ stime: 0.0,
+ utime: 0.0,
+ cutime: 0.0,
+ cstime: 0.0
+ )
end
subject { described_class.new }
@@ -118,6 +134,85 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
subject.call(job, 'test_queue') { }
end
end
+
+ it 'logs without created_at and enqueued_at fields' do
+ Timecop.freeze(timestamp) do
+ excluded_fields = %w(created_at enqueued_at args scheduling_latency_s)
+
+ expect(logger).to receive(:info).with(start_payload.except(*excluded_fields)).ordered
+ expect(logger).to receive(:info).with(end_payload.except(*excluded_fields)).ordered
+ expect(subject).to receive(:log_job_start).and_call_original
+ expect(subject).to receive(:log_job_done).and_call_original
+
+ subject.call(job.except("created_at", "enqueued_at"), 'test_queue') { }
+ end
+ end
+ end
+
+ context 'with latency' do
+ let(:created_at) { Time.iso8601('2018-01-01T10:00:00Z') }
+ let(:scheduling_latency_s) { 7200.0 }
+
+ it 'logs with scheduling latency' do
+ Timecop.freeze(timestamp) do
+ expect(logger).to receive(:info).with(start_payload.except('args')).ordered
+ expect(logger).to receive(:info).with(end_payload.except('args')).ordered
+ expect(subject).to receive(:log_job_start).and_call_original
+ expect(subject).to receive(:log_job_done).and_call_original
+
+ subject.call(job, 'test_queue') { }
+ end
+ end
+ end
+
+ context 'with Gitaly and Rugged calls' do
+ let(:timing_data) do
+ {
+ gitaly_calls: 10,
+ gitaly_duration: 10000,
+ rugged_calls: 1,
+ rugged_duration_ms: 5000
+ }
+ end
+
+ before do
+ job.merge!(timing_data)
+ end
+
+ it 'logs with Gitaly and Rugged timing data' do
+ Timecop.freeze(timestamp) do
+ expect(logger).to receive(:info).with(start_payload.except('args')).ordered
+ expect(logger).to receive(:info).with(end_payload.except('args')).ordered
+
+ subject.call(job, 'test_queue') { }
+ end
+ end
+ end
+
+ def ctime(times)
+ times[:cstime] + times[:cutime]
+ end
+
+ context 'with ctime value greater than 0' do
+ let(:times_start) { { stime: 0.04999, utime: 0.0483, cstime: 0.0188, cutime: 0.0188 } }
+ let(:times_end) { { stime: 0.0699, utime: 0.0699, cstime: 0.0399, cutime: 0.0399 } }
+
+ before do
+ end_payload['system_s'] = 0.02
+ end_payload['user_s'] = 0.022
+ end_payload['child_s'] = 0.042
+
+ allow(Process).to receive(:times).and_return(times_start, times_end)
+ end
+
+ it 'logs with ctime data and other cpu data' do
+ Timecop.freeze(timestamp) do
+ expect(logger).to receive(:info).with(start_payload.except('args')).ordered
+ expect(logger).to receive(:info).with(end_payload.except('args')).ordered
+
+ subject.call(job, 'test_queue') { }
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
index 1a5a38b5d99..bf3bc8e1add 100644
--- a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqMiddleware::MemoryKiller do
subject { described_class.new }
let(:pid) { 999 }
- let(:worker) { double(:worker, class: 'TestWorker') }
+ let(:worker) { double(:worker, class: ProjectCacheWorker) }
let(:job) { { 'jid' => 123 } }
let(:queue) { 'test_queue' }
@@ -45,6 +47,12 @@ describe Gitlab::SidekiqMiddleware::MemoryKiller do
expect(subject).to receive(:sleep).with(10).ordered
expect(Process).to receive(:kill).with('SIGKILL', pid).ordered
+ expect(Sidekiq.logger)
+ .to receive(:warn).with(class: 'ProjectCacheWorker',
+ message: anything,
+ pid: pid,
+ signal: anything).at_least(:once)
+
run
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
new file mode 100644
index 00000000000..e430599bd94
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqMiddleware::Metrics do
+ describe '#call' do
+ let(:middleware) { described_class.new }
+ let(:worker) { double(:worker) }
+
+ let(:completion_seconds_metric) { double('completion seconds metric') }
+ let(:failed_total_metric) { double('failed total metric') }
+ let(:retried_total_metric) { double('retried total metric') }
+ let(:running_jobs_metric) { double('running jobs metric') }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything, anything).and_return(completion_seconds_metric)
+ allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_failed_total, anything).and_return(failed_total_metric)
+ allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_retried_total, anything).and_return(retried_total_metric)
+ allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :livesum).and_return(running_jobs_metric)
+
+ allow(running_jobs_metric).to receive(:increment)
+ end
+
+ it 'yields block' do
+ allow(completion_seconds_metric).to receive(:observe)
+
+ expect { |b| middleware.call(worker, {}, :test, &b) }.to yield_control.once
+ end
+
+ it 'sets metrics' do
+ labels = { queue: :test }
+
+ expect(running_jobs_metric).to receive(:increment).with(labels, 1)
+ expect(running_jobs_metric).to receive(:increment).with(labels, -1)
+ expect(completion_seconds_metric).to receive(:observe).with(labels, kind_of(Numeric))
+
+ middleware.call(worker, {}, :test) { nil }
+ end
+
+ context 'when job is retried' do
+ it 'sets sidekiq_jobs_retried_total metric' do
+ allow(completion_seconds_metric).to receive(:observe)
+
+ expect(retried_total_metric).to receive(:increment)
+
+ middleware.call(worker, { 'retry_count' => 1 }, :test) { nil }
+ end
+ end
+
+ context 'when error is raised' do
+ it 'sets sidekiq_jobs_failed_total and reraises' do
+ expect(failed_total_metric).to receive(:increment)
+ expect { middleware.call(worker, {}, :test) { raise } }.to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb b/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
new file mode 100644
index 00000000000..7319cdc2399
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqMiddleware::Monitor do
+ let(:monitor) { described_class.new }
+
+ describe '#call' do
+ let(:worker) { double }
+ let(:job) { { 'jid' => 'job-id' } }
+ let(:queue) { 'my-queue' }
+
+ it 'calls SidekiqMonitor' do
+ expect(Gitlab::SidekiqMonitor.instance).to receive(:within_job)
+ .with('job-id', 'my-queue')
+ .and_call_original
+
+ expect { |blk| monitor.call(worker, job, queue, &blk) }.to yield_control
+ end
+
+ it 'passthroughs the return value' do
+ result = monitor.call(worker, job, queue) do
+ 'value'
+ end
+
+ expect(result).to eq('value')
+ end
+
+ context 'when cancel happens' do
+ subject do
+ monitor.call(worker, job, queue) do
+ raise Gitlab::SidekiqMonitor::CancelledError
+ end
+ end
+
+ it 'skips the job' do
+ expect { subject }.to raise_error(Sidekiq::JobRetry::Skip)
+ end
+
+ it 'puts job in DeadSet' do
+ ::Sidekiq::DeadSet.new.clear
+
+ expect do
+ subject rescue Sidekiq::JobRetry::Skip
+ end.to change { ::Sidekiq::DeadSet.new.size }.by(1)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_monitor_spec.rb b/spec/lib/gitlab/sidekiq_monitor_spec.rb
new file mode 100644
index 00000000000..bbd7bf90217
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_monitor_spec.rb
@@ -0,0 +1,261 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqMonitor do
+ let(:monitor) { described_class.new }
+
+ describe '#within_job' do
+ it 'tracks thread' do
+ blk = proc do
+ expect(monitor.jobs_thread['jid']).not_to be_nil
+
+ "OK"
+ end
+
+ expect(monitor.within_job('jid', 'queue', &blk)).to eq("OK")
+ end
+
+ context 'when job is canceled' do
+ let(:jid) { SecureRandom.hex }
+
+ before do
+ described_class.cancel_job(jid)
+ end
+
+ it 'does not execute a block' do
+ expect do |blk|
+ monitor.within_job(jid, 'queue', &blk)
+ rescue described_class::CancelledError
+ end.not_to yield_control
+ end
+
+ it 'raises exception' do
+ expect { monitor.within_job(jid, 'queue') }.to raise_error(
+ described_class::CancelledError)
+ end
+ end
+ end
+
+ describe '#start_working' do
+ subject { monitor.send(:start_working) }
+
+ before do
+ # we want to run at most once cycle
+ # we toggle `enabled?` flag after the first call
+ stub_const('Gitlab::SidekiqMonitor::RECONNECT_TIME', 0)
+ allow(monitor).to receive(:enabled?).and_return(true, false)
+
+ allow(Sidekiq.logger).to receive(:info)
+ allow(Sidekiq.logger).to receive(:warn)
+ end
+
+ context 'when structured logging is used' do
+ it 'logs start message' do
+ expect(Sidekiq.logger).to receive(:info)
+ .with(
+ class: described_class.to_s,
+ action: 'start',
+ message: 'Starting Monitor Daemon')
+
+ expect(::Gitlab::Redis::SharedState).to receive(:with)
+
+ subject
+ end
+
+ it 'logs stop message' do
+ expect(Sidekiq.logger).to receive(:warn)
+ .with(
+ class: described_class.to_s,
+ action: 'stop',
+ message: 'Stopping Monitor Daemon')
+
+ expect(::Gitlab::Redis::SharedState).to receive(:with)
+
+ subject
+ end
+
+ it 'logs StandardError message' do
+ expect(Sidekiq.logger).to receive(:warn)
+ .with(
+ class: described_class.to_s,
+ action: 'exception',
+ message: 'My Exception')
+
+ expect(::Gitlab::Redis::SharedState).to receive(:with)
+ .and_raise(StandardError, 'My Exception')
+
+ expect { subject }.not_to raise_error
+ end
+
+ it 'logs and raises Exception message' do
+ expect(Sidekiq.logger).to receive(:warn)
+ .with(
+ class: described_class.to_s,
+ action: 'exception',
+ message: 'My Exception')
+
+ expect(::Gitlab::Redis::SharedState).to receive(:with)
+ .and_raise(Exception, 'My Exception')
+
+ expect { subject }.to raise_error(Exception, 'My Exception')
+ end
+ end
+
+ context 'when StandardError is raised' do
+ it 'does retry connection' do
+ expect(::Gitlab::Redis::SharedState).to receive(:with)
+ .and_raise(StandardError, 'My Exception')
+
+ expect(::Gitlab::Redis::SharedState).to receive(:with)
+
+ # we expect to run `process_messages` twice
+ expect(monitor).to receive(:enabled?).and_return(true, true, false)
+
+ subject
+ end
+ end
+
+ context 'when message is published' do
+ let(:subscribed) { double }
+
+ before do
+ expect_any_instance_of(::Redis).to receive(:subscribe)
+ .and_yield(subscribed)
+
+ expect(subscribed).to receive(:message)
+ .and_yield(
+ described_class::NOTIFICATION_CHANNEL,
+ payload
+ )
+
+ expect(Sidekiq.logger).to receive(:info)
+ .with(
+ class: described_class.to_s,
+ action: 'start',
+ message: 'Starting Monitor Daemon')
+
+ expect(Sidekiq.logger).to receive(:info)
+ .with(
+ class: described_class.to_s,
+ channel: described_class::NOTIFICATION_CHANNEL,
+ message: 'Received payload on channel',
+ payload: payload
+ )
+ end
+
+ context 'and message is valid' do
+ let(:payload) { '{"action":"cancel","jid":"my-jid"}' }
+
+ it 'processes cancel' do
+ expect(monitor).to receive(:process_job_cancel).with('my-jid')
+
+ subject
+ end
+ end
+
+ context 'and message is not valid json' do
+ let(:payload) { '{"action"}' }
+
+ it 'skips processing' do
+ expect(monitor).not_to receive(:process_job_cancel)
+
+ subject
+ end
+ end
+ end
+ end
+
+ describe '#stop' do
+ let!(:monitor_thread) { monitor.start }
+
+ it 'does stop the thread' do
+ expect(monitor_thread).to be_alive
+
+ expect { monitor.stop }.not_to raise_error
+
+ expect(monitor_thread).not_to be_alive
+ expect { monitor_thread.value }.to raise_error(Interrupt)
+ end
+ end
+
+ describe '#process_job_cancel' do
+ subject { monitor.send(:process_job_cancel, jid) }
+
+ context 'when jid is missing' do
+ let(:jid) { nil }
+
+ it 'does not run thread' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'when jid is provided' do
+ let(:jid) { 'my-jid' }
+
+ context 'when jid is not found' do
+ it 'does not log cancellation message' do
+ expect(Sidekiq.logger).not_to receive(:warn)
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'when jid is found' do
+ let(:thread) { Thread.new { sleep 1000 } }
+
+ before do
+ monitor.jobs_thread[jid] = thread
+ end
+
+ after do
+ thread.kill
+ rescue
+ end
+
+ it 'does log cancellation message' do
+ expect(Sidekiq.logger).to receive(:warn)
+ .with(
+ class: described_class.to_s,
+ action: 'cancel',
+ message: 'Canceling thread with CancelledError',
+ jid: 'my-jid',
+ thread_id: thread.object_id)
+
+ expect(subject).to be_a(Thread)
+
+ subject.join
+ end
+
+ it 'does cancel the thread' do
+ expect(subject).to be_a(Thread)
+
+ subject.join
+
+ # we wait for the thread to be cancelled
+ # by `process_job_cancel`
+ expect { thread.join(5) }.to raise_error(described_class::CancelledError)
+ end
+ end
+ end
+ end
+
+ describe '.cancel_job' do
+ subject { described_class.cancel_job('my-jid') }
+
+ it 'sets a redis key' do
+ expect_any_instance_of(::Redis).to receive(:setex)
+ .with('sidekiq:cancel:my-jid', anything, 1)
+
+ subject
+ end
+
+ it 'notifies all workers' do
+ payload = '{"action":"cancel","jid":"my-jid"}'
+
+ expect_any_instance_of(::Redis).to receive(:publish)
+ .with('sidekiq:cancel:notifications', payload)
+
+ subject
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_signals_spec.rb b/spec/lib/gitlab/sidekiq_signals_spec.rb
index 77ecd1840d2..10f1bad32cd 100644
--- a/spec/lib/gitlab/sidekiq_signals_spec.rb
+++ b/spec/lib/gitlab/sidekiq_signals_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqSignals do
diff --git a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
index 37d9e1d3e6b..1ca8cea66fc 100644
--- a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqStatus::ClientMiddleware do
diff --git a/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
index 04e09d3dec8..40bcb49d1d3 100644
--- a/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqStatus::ServerMiddleware do
diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb
index 884f27b212c..7b5c75b2f3b 100644
--- a/spec/lib/gitlab/sidekiq_status_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqStatus do
diff --git a/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb b/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
index 7debf70a16f..2aa7d1fd6d8 100644
--- a/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
+++ b/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqVersioning::Manager do
diff --git a/spec/lib/gitlab/sidekiq_versioning_spec.rb b/spec/lib/gitlab/sidekiq_versioning_spec.rb
index fa6d42e730d..dade5961775 100644
--- a/spec/lib/gitlab/sidekiq_versioning_spec.rb
+++ b/spec/lib/gitlab/sidekiq_versioning_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqVersioning, :sidekiq, :redis do
diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb
index eceacac58af..c4ea8cbf2b1 100644
--- a/spec/lib/gitlab/slash_commands/command_spec.rb
+++ b/spec/lib/gitlab/slash_commands/command_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Command do
diff --git a/spec/lib/gitlab/slash_commands/deploy_spec.rb b/spec/lib/gitlab/slash_commands/deploy_spec.rb
index 25f3e8a0409..93a724d8e12 100644
--- a/spec/lib/gitlab/slash_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/deploy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Deploy do
diff --git a/spec/lib/gitlab/slash_commands/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
index 9a990e1fad7..962ac3668bc 100644
--- a/spec/lib/gitlab/slash_commands/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::IssueMove, service: true do
diff --git a/spec/lib/gitlab/slash_commands/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
index 59de11766d8..90f0518a63e 100644
--- a/spec/lib/gitlab/slash_commands/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::IssueNew do
diff --git a/spec/lib/gitlab/slash_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
index 47787307990..b766a9a1361 100644
--- a/spec/lib/gitlab/slash_commands/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::IssueSearch do
diff --git a/spec/lib/gitlab/slash_commands/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/issue_show_spec.rb
index 5c4ba2736ba..e53f79dcd86 100644
--- a/spec/lib/gitlab/slash_commands/issue_show_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::IssueShow do
diff --git a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
index ef3d217f7be..286fec892e6 100644
--- a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::Access do
diff --git a/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
index d16d122c64e..9c2e9ab982f 100644
--- a/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::Deploy do
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
index 58c341a284e..56b64d32192 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::IssueMove do
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
index 76e4bad88fd..f926783fbea 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::IssueNew do
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
index 5a7ec0685fe..e1c011133c4 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::IssueSearch do
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
index 8f607d7a9c9..56d6bf1c788 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::IssueShow do
diff --git a/spec/lib/gitlab/slug/environment_spec.rb b/spec/lib/gitlab/slug/environment_spec.rb
new file mode 100644
index 00000000000..7dc583a94b8
--- /dev/null
+++ b/spec/lib/gitlab/slug/environment_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Slug::Environment do
+ describe '#generate' do
+ {
+ "staging-12345678901234567" => "staging-123456789-q517sa",
+ "9-staging-123456789012345" => "env-9-staging-123-q517sa",
+ "staging-1234567890123456" => "staging-1234567890123456",
+ "staging-1234567890123456-" => "staging-123456789-q517sa",
+ "production" => "production",
+ "PRODUCTION" => "production-q517sa",
+ "review/1-foo" => "review-1-foo-q517sa",
+ "1-foo" => "env-1-foo-q517sa",
+ "1/foo" => "env-1-foo-q517sa",
+ "foo-" => "foo",
+ "foo--bar" => "foo-bar-q517sa",
+ "foo**bar" => "foo-bar-q517sa",
+ "*-foo" => "env-foo-q517sa",
+ "staging-12345678-" => "staging-12345678",
+ "staging-12345678-01234567" => "staging-12345678-q517sa",
+ "" => "env-q517sa",
+ nil => "env-q517sa"
+ }.each do |name, matcher|
+ before do
+ # ('a' * 64).to_i(16).to_s(36).last(6) gives 'q517sa'
+ allow(Digest::SHA2).to receive(:hexdigest).with(name).and_return('a' * 64)
+ end
+
+ it "returns a slug matching #{matcher}, given #{name}" do
+ slug = described_class.new(name).generate
+
+ expect(slug).to match(/\A#{matcher}\z/)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
index b661a894c0c..89d290aaa81 100644
--- a/spec/lib/gitlab/snippet_search_results_spec.rb
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SnippetSearchResults do
@@ -16,4 +18,22 @@ describe Gitlab::SnippetSearchResults do
expect(results.snippet_blobs_count).to eq(1)
end
end
+
+ describe '#formatted_count' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:scope, :count_method, :expected) do
+ 'snippet_titles' | :snippet_titles_count | '1234'
+ 'snippet_blobs' | :snippet_blobs_count | '1234'
+ 'projects' | :limited_projects_count | '1000+'
+ 'unknown' | nil | nil
+ end
+
+ with_them do
+ it 'returns the expected formatted count' do
+ expect(results).to receive(count_method).and_return(1234) if count_method
+ expect(results.formatted_count(scope)).to eq(expected)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/snowplow_tracker_spec.rb b/spec/lib/gitlab/snowplow_tracker_spec.rb
new file mode 100644
index 00000000000..073a33e5973
--- /dev/null
+++ b/spec/lib/gitlab/snowplow_tracker_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::SnowplowTracker do
+ let(:timestamp) { Time.utc(2017, 3, 22) }
+
+ around do |example|
+ Timecop.freeze(timestamp) { example.run }
+ end
+
+ subject { described_class.track_event('epics', 'action', property: 'what', value: 'doit') }
+
+ context '.track_event' do
+ context 'when Snowplow tracker is disabled' do
+ it 'does not track the event' do
+ expect(SnowplowTracker::Tracker).not_to receive(:new)
+
+ subject
+ end
+ end
+
+ context 'when Snowplow tracker is enabled' do
+ before do
+ stub_application_setting(snowplow_enabled: true)
+ stub_application_setting(snowplow_site_id: 'awesome gitlab')
+ stub_application_setting(snowplow_collector_hostname: 'url.com')
+ end
+
+ it 'tracks the event' do
+ tracker = double
+
+ expect(::SnowplowTracker::Tracker).to receive(:new)
+ .with(
+ an_instance_of(::SnowplowTracker::Emitter),
+ an_instance_of(::SnowplowTracker::Subject),
+ 'cf', 'awesome gitlab'
+ ).and_return(tracker)
+ expect(tracker).to receive(:track_struct_event)
+ .with('epics', 'action', nil, 'what', 'doit', nil, timestamp.to_i)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sql/cte_spec.rb b/spec/lib/gitlab/sql/cte_spec.rb
index d6763c7b2e1..e6194924f5a 100644
--- a/spec/lib/gitlab/sql/cte_spec.rb
+++ b/spec/lib/gitlab/sql/cte_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Gitlab::SQL::CTE, :postgresql do
+describe Gitlab::SQL::CTE do
describe '#to_arel' do
it 'generates an Arel relation for the CTE body' do
relation = User.where(id: 1)
diff --git a/spec/lib/gitlab/sql/glob_spec.rb b/spec/lib/gitlab/sql/glob_spec.rb
index 3147b52dcc5..83eed309ecc 100644
--- a/spec/lib/gitlab/sql/glob_spec.rb
+++ b/spec/lib/gitlab/sql/glob_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SQL::Glob do
diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb
index 5b5052de372..31944d51b3c 100644
--- a/spec/lib/gitlab/sql/pattern_spec.rb
+++ b/spec/lib/gitlab/sql/pattern_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SQL::Pattern do
@@ -10,6 +12,12 @@ describe Gitlab::SQL::Pattern do
it 'returns exact matching pattern' do
expect(to_pattern).to eq('12')
end
+
+ context 'and ignore_minimum_char_limit is true' do
+ it 'returns partial matching pattern' do
+ expect(User.to_pattern(query, use_minimum_char_limit: false)).to eq('%12%')
+ end
+ end
end
context 'when a query with a escape character is shorter than 3 chars' do
@@ -18,6 +26,12 @@ describe Gitlab::SQL::Pattern do
it 'returns sanitized exact matching pattern' do
expect(to_pattern).to eq('\_2')
end
+
+ context 'and ignore_minimum_char_limit is true' do
+ it 'returns sanitized partial matching pattern' do
+ expect(User.to_pattern(query, use_minimum_char_limit: false)).to eq('%\_2%')
+ end
+ end
end
context 'when a query is equal to 3 chars' do
diff --git a/spec/lib/gitlab/sql/recursive_cte_spec.rb b/spec/lib/gitlab/sql/recursive_cte_spec.rb
index 7fe39dd5a96..20e36c224b0 100644
--- a/spec/lib/gitlab/sql/recursive_cte_spec.rb
+++ b/spec/lib/gitlab/sql/recursive_cte_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Gitlab::SQL::RecursiveCTE, :postgresql do
+describe Gitlab::SQL::RecursiveCTE do
let(:cte) { described_class.new(:cte_name) }
describe '#to_arel' do
diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb
index fe6422c32b6..f8f6da19fa5 100644
--- a/spec/lib/gitlab/sql/union_spec.rb
+++ b/spec/lib/gitlab/sql/union_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SQL::Union do
diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb
index a6ea07e8b6d..f8becb0c796 100644
--- a/spec/lib/gitlab/ssh_public_key_spec.rb
+++ b/spec/lib/gitlab/ssh_public_key_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SSHPublicKey, lib: true do
diff --git a/spec/lib/gitlab/string_placeholder_replacer_spec.rb b/spec/lib/gitlab/string_placeholder_replacer_spec.rb
index 7a03ea4154c..0295bf1265f 100644
--- a/spec/lib/gitlab/string_placeholder_replacer_spec.rb
+++ b/spec/lib/gitlab/string_placeholder_replacer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::StringPlaceholderReplacer do
diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb
index 6bc02459dbd..7ed43db3d10 100644
--- a/spec/lib/gitlab/string_range_marker_spec.rb
+++ b/spec/lib/gitlab/string_range_marker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::StringRangeMarker do
diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb
index 37b1298b962..2b19edbe7f9 100644
--- a/spec/lib/gitlab/string_regex_marker_spec.rb
+++ b/spec/lib/gitlab/string_regex_marker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::StringRegexMarker do
diff --git a/spec/lib/gitlab/submodule_links_spec.rb b/spec/lib/gitlab/submodule_links_spec.rb
new file mode 100644
index 00000000000..a84602cd07d
--- /dev/null
+++ b/spec/lib/gitlab/submodule_links_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SubmoduleLinks do
+ let(:submodule_item) { double(id: 'hash', path: 'gitlab-ce') }
+ let(:repo) { double }
+ let(:links) { described_class.new(repo) }
+
+ describe '#for' do
+ subject { links.for(submodule_item, 'ref') }
+
+ context 'when there is no .gitmodules file' do
+ before do
+ stub_urls(nil)
+ end
+
+ it 'returns no links' do
+ expect(subject).to eq([nil, nil])
+ end
+ end
+
+ context 'when the submodule is unknown' do
+ before do
+ stub_urls({ 'path' => 'url' })
+ end
+
+ it 'returns no links' do
+ expect(subject).to eq([nil, nil])
+ end
+ end
+
+ context 'when the submodule is known' do
+ before do
+ stub_urls({ 'gitlab-ce' => 'git@gitlab.com:gitlab-org/gitlab-ce.git' })
+ end
+
+ it 'returns links' do
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ end
+ end
+ end
+
+ def stub_urls(urls)
+ allow(repo).to receive(:submodule_urls_for).and_return(urls)
+ end
+end
diff --git a/spec/lib/gitlab/tcp_checker_spec.rb b/spec/lib/gitlab/tcp_checker_spec.rb
index 4acf0334496..49f04f269ae 100644
--- a/spec/lib/gitlab/tcp_checker_spec.rb
+++ b/spec/lib/gitlab/tcp_checker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::TcpChecker do
diff --git a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
index c7f58fbd2a5..082ffa855b7 100644
--- a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
+++ b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Template::Finders::GlobalTemplateFinder do
diff --git a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
index e329d55d837..c8f2a37c5d6 100644
--- a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
+++ b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Template::Finders::RepoTemplateFinder do
diff --git a/spec/lib/gitlab/template/gitignore_template_spec.rb b/spec/lib/gitlab/template/gitignore_template_spec.rb
index 97797f42aaa..e8f632889ad 100644
--- a/spec/lib/gitlab/template/gitignore_template_spec.rb
+++ b/spec/lib/gitlab/template/gitignore_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Template::GitignoreTemplate do
diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
index 5f0a7e925ca..52e100768a7 100644
--- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
+++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Template::GitlabCiYmlTemplate do
diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb
index 7098499f996..54e46d3a9ec 100644
--- a/spec/lib/gitlab/template/issue_template_spec.rb
+++ b/spec/lib/gitlab/template/issue_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Template::IssueTemplate do
diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb
index bd7ff64aa8a..bbc184d4dfc 100644
--- a/spec/lib/gitlab/template/merge_request_template_spec.rb
+++ b/spec/lib/gitlab/template/merge_request_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Template::MergeRequestTemplate do
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
index a8213988f70..e0278eb9c7f 100644
--- a/spec/lib/gitlab/themes_spec.rb
+++ b/spec/lib/gitlab/themes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Themes, lib: true do
diff --git a/spec/lib/gitlab/tree_summary_spec.rb b/spec/lib/gitlab/tree_summary_spec.rb
index e22f898dc4c..e15463ed0eb 100644
--- a/spec/lib/gitlab/tree_summary_spec.rb
+++ b/spec/lib/gitlab/tree_summary_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::TreeSummary do
diff --git a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
index f1882e03581..68402e64012 100644
--- a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require 'support/shared_examples/malicious_regexp_shared_examples'
require 'support/helpers/stub_feature_flags'
diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb
index 9d483f13a5e..4cc21e94a83 100644
--- a/spec/lib/gitlab/untrusted_regexp_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require 'support/shared_examples/malicious_regexp_shared_examples'
diff --git a/spec/lib/gitlab/uploads_transfer_spec.rb b/spec/lib/gitlab/uploads_transfer_spec.rb
index 4275e7b015b..16560fc8f12 100644
--- a/spec/lib/gitlab/uploads_transfer_spec.rb
+++ b/spec/lib/gitlab/uploads_transfer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::UploadsTransfer do
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 253366e0789..df8a1f82f81 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::UrlBlocker do
@@ -68,6 +70,16 @@ describe Gitlab::UrlBlocker do
expect(uri).to eq(Addressable::URI.parse('https://example.org'))
expect(hostname).to eq(nil)
end
+
+ context 'when it cannot be resolved' do
+ let(:import_url) { 'http://foobar.x' }
+
+ it 'raises error' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError)
+ end
+ end
end
context 'when the URL hostname is an IP address' do
@@ -79,6 +91,16 @@ describe Gitlab::UrlBlocker do
expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
expect(hostname).to be(nil)
end
+
+ context 'when it is invalid' do
+ let(:import_url) { 'http://1.1.1.1.1' }
+
+ it 'raises an error' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError)
+ end
+ end
end
end
end
@@ -180,8 +202,6 @@ describe Gitlab::UrlBlocker do
end
it 'returns true for a non-alphanumeric hostname' do
- stub_resolv
-
aggregate_failures do
expect(described_class).to be_blocked_url('ssh://-oProxyCommand=whoami/a')
@@ -220,53 +240,53 @@ describe Gitlab::UrlBlocker do
end
let(:fake_domain) { 'www.fakedomain.fake' }
- context 'true (default)' do
+ shared_examples 'allows local requests' do |url_blocker_attributes|
it 'does not block urls from private networks' do
local_ips.each do |ip|
- stub_domain_resolv(fake_domain, ip)
-
- expect(described_class).not_to be_blocked_url("http://#{fake_domain}")
+ stub_domain_resolv(fake_domain, ip) do
+ expect(described_class).not_to be_blocked_url("http://#{fake_domain}", url_blocker_attributes)
+ end
- unstub_domain_resolv
-
- expect(described_class).not_to be_blocked_url("http://#{ip}")
+ expect(described_class).not_to be_blocked_url("http://#{ip}", url_blocker_attributes)
end
end
it 'allows localhost endpoints' do
- expect(described_class).not_to be_blocked_url('http://0.0.0.0', allow_localhost: true)
- expect(described_class).not_to be_blocked_url('http://localhost', allow_localhost: true)
- expect(described_class).not_to be_blocked_url('http://127.0.0.1', allow_localhost: true)
+ expect(described_class).not_to be_blocked_url('http://0.0.0.0', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://localhost', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://127.0.0.1', url_blocker_attributes)
end
it 'allows loopback endpoints' do
- expect(described_class).not_to be_blocked_url('http://127.0.0.2', allow_localhost: true)
+ expect(described_class).not_to be_blocked_url('http://127.0.0.2', url_blocker_attributes)
end
it 'allows IPv4 link-local endpoints' do
- expect(described_class).not_to be_blocked_url('http://169.254.169.254')
- expect(described_class).not_to be_blocked_url('http://169.254.168.100')
+ expect(described_class).not_to be_blocked_url('http://169.254.169.254', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://169.254.168.100', url_blocker_attributes)
end
it 'allows IPv6 link-local endpoints' do
- expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.169.254]')
- expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.169.254]')
- expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a9fe]')
- expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.168.100]')
- expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.168.100]')
- expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a864]')
- expect(described_class).not_to be_blocked_url('http://[fe80::c800:eff:fe74:8]')
+ expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.169.254]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.169.254]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a9fe]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.168.100]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.168.100]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a864]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[fe80::c800:eff:fe74:8]', url_blocker_attributes)
end
end
+ context 'true (default)' do
+ it_behaves_like 'allows local requests', { allow_localhost: true, allow_local_network: true }
+ end
+
context 'false' do
it 'blocks urls from private networks' do
local_ips.each do |ip|
- stub_domain_resolv(fake_domain, ip)
-
- expect(described_class).to be_blocked_url("http://#{fake_domain}", allow_local_network: false)
-
- unstub_domain_resolv
+ stub_domain_resolv(fake_domain, ip) do
+ expect(described_class).to be_blocked_url("http://#{fake_domain}", allow_local_network: false)
+ end
expect(described_class).to be_blocked_url("http://#{ip}", allow_local_network: false)
end
@@ -286,24 +306,174 @@ describe Gitlab::UrlBlocker do
expect(described_class).to be_blocked_url('http://[::ffff:a9fe:a864]', allow_local_network: false)
expect(described_class).to be_blocked_url('http://[fe80::c800:eff:fe74:8]', allow_local_network: false)
end
+
+ context 'when local domain/IP is whitelisted' do
+ let(:url_blocker_attributes) do
+ {
+ allow_localhost: false,
+ allow_local_network: false
+ }
+ end
+
+ before do
+ stub_application_setting(outbound_local_requests_whitelist: whitelist)
+ end
+
+ context 'with IPs in whitelist' do
+ let(:whitelist) do
+ [
+ '0.0.0.0',
+ '127.0.0.1',
+ '127.0.0.2',
+ '192.168.1.1',
+ '192.168.1.2',
+ '0:0:0:0:0:ffff:192.168.1.2',
+ '::ffff:c0a8:102',
+ '10.0.0.2',
+ '0:0:0:0:0:ffff:10.0.0.2',
+ '::ffff:a00:2',
+ '172.16.0.2',
+ '0:0:0:0:0:ffff:172.16.0.2',
+ '::ffff:ac10:20',
+ 'feef::1',
+ 'fee2::',
+ 'fc00:bf8b:e62c:abcd:abcd:aaaa:aaaa:aaaa',
+ '0:0:0:0:0:ffff:169.254.169.254',
+ '::ffff:a9fe:a9fe',
+ '::ffff:169.254.168.100',
+ '::ffff:a9fe:a864',
+ 'fe80::c800:eff:fe74:8',
+
+ # garbage IPs
+ '45645632345',
+ 'garbage456:more345gar:bage'
+ ]
+ end
+
+ it_behaves_like 'allows local requests', { allow_localhost: false, allow_local_network: false }
+
+ it 'whitelists IP when dns_rebind_protection is disabled' do
+ stub_domain_resolv('example.com', '192.168.1.1') do
+ expect(described_class).not_to be_blocked_url("http://example.com",
+ url_blocker_attributes.merge(dns_rebind_protection: false))
+ end
+ end
+ end
+
+ context 'with domains in whitelist' do
+ let(:whitelist) do
+ [
+ 'www.example.com',
+ 'example.com',
+ 'xn--itlab-j1a.com',
+ 'garbage$^$%#$^&$'
+ ]
+ end
+
+ it 'allows domains present in whitelist' do
+ domain = 'example.com'
+ subdomain1 = 'www.example.com'
+ subdomain2 = 'subdomain.example.com'
+
+ stub_domain_resolv(domain, '192.168.1.1') do
+ expect(described_class).not_to be_blocked_url("http://#{domain}",
+ url_blocker_attributes)
+ end
+
+ stub_domain_resolv(subdomain1, '192.168.1.1') do
+ expect(described_class).not_to be_blocked_url("http://#{subdomain1}",
+ url_blocker_attributes)
+ end
+
+ # subdomain2 is not part of the whitelist so it should be blocked
+ stub_domain_resolv(subdomain2, '192.168.1.1') do
+ expect(described_class).to be_blocked_url("http://#{subdomain2}",
+ url_blocker_attributes)
+ end
+ end
+
+ it 'works with unicode and idna encoded domains' do
+ unicode_domain = 'ğitlab.com'
+ idna_encoded_domain = 'xn--itlab-j1a.com'
+
+ stub_domain_resolv(unicode_domain, '192.168.1.1') do
+ expect(described_class).not_to be_blocked_url("http://#{unicode_domain}",
+ url_blocker_attributes)
+ end
+
+ stub_domain_resolv(idna_encoded_domain, '192.168.1.1') do
+ expect(described_class).not_to be_blocked_url("http://#{idna_encoded_domain}",
+ url_blocker_attributes)
+ end
+ end
+ end
+
+ context 'with ip ranges in whitelist' do
+ let(:ipv4_range) { '127.0.0.0/28' }
+ let(:ipv6_range) { 'fd84:6d02:f6d8:c89e::/124' }
+
+ let(:whitelist) do
+ [
+ ipv4_range,
+ ipv6_range
+ ]
+ end
+
+ it 'blocks ipv4 range when not in whitelist' do
+ stub_application_setting(outbound_local_requests_whitelist: [])
+
+ IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
+ expect(described_class).to be_blocked_url("http://#{ip}",
+ url_blocker_attributes)
+ end
+ end
+
+ it 'allows all ipv4s in the range when in whitelist' do
+ IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
+ expect(described_class).not_to be_blocked_url("http://#{ip}",
+ url_blocker_attributes)
+ end
+ end
+
+ it 'blocks ipv6 range when not in whitelist' do
+ stub_application_setting(outbound_local_requests_whitelist: [])
+
+ IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
+ expect(described_class).to be_blocked_url("http://[#{ip}]",
+ url_blocker_attributes)
+ end
+ end
+
+ it 'allows all ipv6s in the range when in whitelist' do
+ IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
+ expect(described_class).not_to be_blocked_url("http://[#{ip}]",
+ url_blocker_attributes)
+ end
+ end
+
+ it 'blocks IPs outside the range' do
+ expect(described_class).to be_blocked_url("http://[fd84:6d02:f6d8:c89e:0:0:1:f]",
+ url_blocker_attributes)
+
+ expect(described_class).to be_blocked_url("http://127.0.1.15",
+ url_blocker_attributes)
+ end
+ end
+ end
end
- def stub_domain_resolv(domain, ip)
+ def stub_domain_resolv(domain, ip, &block)
address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false, ipv4?: false)
allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([address])
allow(address).to receive(:ipv6_v4mapped?).and_return(false)
- end
- def unstub_domain_resolv
+ yield
+
allow(Addrinfo).to receive(:getaddrinfo).and_call_original
end
end
context 'when enforce_user is' do
- before do
- stub_resolv
- end
-
context 'false (default)' do
it 'does not block urls with a non-alphanumeric username' do
expect(described_class).not_to be_blocked_url('ssh://-oProxyCommand=whoami@example.com/a')
@@ -351,9 +521,21 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://git‌lab.com/foo/foo.bar', ascii_only: true)).to be true
end
end
+
+ it 'blocks urls with invalid ip address' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect(described_class).to be_blocked_url('http://8.8.8.8.8')
+ end
+
+ it 'blocks urls whose hostname cannot be resolved' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect(described_class).to be_blocked_url('http://foobar.x')
+ end
end
- describe '#validate_hostname!' do
+ describe '#validate_hostname' do
let(:ip_addresses) do
[
'2001:db8:1f70::999:de8:7648:6e8',
@@ -378,14 +560,8 @@ describe Gitlab::UrlBlocker do
it 'does not raise error for valid Ip addresses' do
ip_addresses.each do |ip|
- expect { described_class.send(:validate_hostname!, ip) }.not_to raise_error
+ expect { described_class.send(:validate_hostname, ip) }.not_to raise_error
end
end
end
-
- # Resolv does not support resolving UTF-8 domain names
- # See https://bugs.ruby-lang.org/issues/4270
- def stub_resolv
- allow(Resolv).to receive(:getaddresses).and_return([])
- end
end
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index bbcb92608d8..08d3c638f9e 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::UrlBuilder do
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index 7242255d535..b39609c594b 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::UrlSanitizer do
diff --git a/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb
new file mode 100644
index 00000000000..71be37692e2
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::CycleAnalyticsCounter do
+ it_behaves_like 'a redis usage counter', 'CycleAnalytics', :views
+
+ it_behaves_like 'a redis usage counter with totals', :cycle_analytics, views: 3
+end
diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb
new file mode 100644
index 00000000000..4be4a661260
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::MergeRequestCounter do
+ it_behaves_like 'a redis usage counter', 'Merge Request', :create
+
+ it_behaves_like 'a redis usage counter with totals', :merge_request, create: 5
+end
diff --git a/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
new file mode 100644
index 00000000000..b385d1b07c7
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::NoteCounter, :clean_gitlab_redis_shared_state do
+ shared_examples 'a note usage counter' do |event, noteable_type|
+ describe ".count(#{event})" do
+ it "increments the Note #{event} counter by 1" do
+ expect do
+ described_class.count(event, noteable_type)
+ end.to change { described_class.read(event, noteable_type) }.by 1
+ end
+ end
+
+ describe ".read(#{event})" do
+ event_count = 5
+
+ it "returns the total number of #{event} events" do
+ event_count.times do
+ described_class.count(event, noteable_type)
+ end
+
+ expect(described_class.read(event, noteable_type)).to eq(event_count)
+ end
+ end
+ end
+
+ it_behaves_like 'a note usage counter', :create, 'Snippet'
+ it_behaves_like 'a note usage counter', :create, 'MergeRequest'
+ it_behaves_like 'a note usage counter', :create, 'Commit'
+
+ describe '.totals' do
+ let(:combinations) do
+ [
+ [:create, 'Snippet', 3],
+ [:create, 'MergeRequest', 4],
+ [:create, 'Commit', 5]
+ ]
+ end
+
+ let(:expected_totals) do
+ { snippet_comment: 3,
+ merge_request_comment: 4,
+ commit_comment: 5 }
+ end
+
+ before do
+ combinations.each do |event, noteable_type, n|
+ n.times do
+ described_class.count(event, noteable_type)
+ end
+ end
+ end
+
+ it 'can report all totals' do
+ expect(described_class.totals).to include(expected_totals)
+ end
+ end
+
+ describe 'unknown events or noteable_type' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:unknown_event_error) { Gitlab::UsageDataCounters::BaseCounter::UnknownEvent }
+
+ where(:event, :noteable_type, :expected_count, :should_raise) do
+ :create | 'Snippet' | 1 | false
+ :wibble | 'Snippet' | 0 | true
+ :create | 'MergeRequest' | 1 | false
+ :wibble | 'MergeRequest' | 0 | true
+ :create | 'Commit' | 1 | false
+ :wibble | 'Commit' | 0 | true
+ :create | 'Issue' | 0 | false
+ :wibble | 'Issue' | 0 | false
+ end
+
+ with_them do
+ it 'handles event' do
+ if should_raise
+ expect { described_class.count(event, noteable_type) }.to raise_error(unknown_event_error)
+ else
+ described_class.count(event, noteable_type)
+
+ expect(described_class.read(event, noteable_type)).to eq(expected_count)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
new file mode 100644
index 00000000000..c34ac7867ab
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shared_state do
+ let(:redis_key) { 'foobar' }
+
+ subject { Class.new.extend(described_class) }
+
+ before do
+ stub_application_setting(usage_ping_enabled: setting_value)
+ end
+
+ context 'when usage_ping is disabled' do
+ let(:setting_value) { false }
+
+ it 'counter is not increased' do
+ expect do
+ subject.increment(redis_key)
+ end.not_to change { subject.total_count(redis_key) }
+ end
+ end
+
+ context 'when usage_ping is enabled' do
+ let(:setting_value) { true }
+
+ it 'counter is increased' do
+ expect do
+ subject.increment(redis_key)
+ end.to change { subject.total_count(redis_key) }.by(1)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
new file mode 100644
index 00000000000..50a9f980dc7
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::SearchCounter, :clean_gitlab_redis_shared_state do
+ it 'increments counter and return the total count' do
+ expect(described_class.total_navbar_searches_count).to eq(0)
+
+ 2.times { described_class.increment_navbar_searches_count }
+
+ expect(described_class.total_navbar_searches_count).to eq(2)
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb
new file mode 100644
index 00000000000..65381ed36d1
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::SnippetCounter do
+ it_behaves_like 'a redis usage counter', 'Snippet', :create
+ it_behaves_like 'a redis usage counter', 'Snippet', :update
+
+ it_behaves_like 'a redis usage counter with totals', :snippet,
+ create: 3,
+ update: 2
+end
diff --git a/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb
new file mode 100644
index 00000000000..47077345e0c
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::SourceCodeCounter do
+ it_behaves_like 'a redis usage counter', 'Source Code', :pushes
+
+ it_behaves_like 'a redis usage counter with totals', :source_code, pushes: 5
+end
diff --git a/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
new file mode 100644
index 00000000000..7a01f7d1de8
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::WebIdeCounter, :clean_gitlab_redis_shared_state do
+ shared_examples 'counter examples' do
+ it 'increments counter and return the total count' do
+ expect(described_class.public_send(total_counter_method)).to eq(0)
+
+ 2.times { described_class.public_send(increment_counter_method) }
+
+ expect(described_class.public_send(total_counter_method)).to eq(2)
+ end
+ end
+
+ describe 'commits counter' do
+ let(:increment_counter_method) { :increment_commits_count }
+ let(:total_counter_method) { :total_commits_count }
+
+ it_behaves_like 'counter examples'
+ end
+
+ describe 'merge requests counter' do
+ let(:increment_counter_method) { :increment_merge_requests_count }
+ let(:total_counter_method) { :total_merge_requests_count }
+
+ it_behaves_like 'counter examples'
+ end
+
+ describe 'views counter' do
+ let(:increment_counter_method) { :increment_views_count }
+ let(:total_counter_method) { :total_views_count }
+
+ it_behaves_like 'counter examples'
+ end
+
+ describe '.totals' do
+ commits = 5
+ merge_requests = 3
+ views = 2
+
+ before do
+ commits.times { described_class.increment_commits_count }
+ merge_requests.times { described_class.increment_merge_requests_count }
+ views.times { described_class.increment_views_count }
+ end
+
+ it 'can report all totals' do
+ expect(described_class.totals).to include(
+ web_ide_commits: commits,
+ web_ide_views: views,
+ web_ide_merge_requests: merge_requests
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
new file mode 100644
index 00000000000..4e8ae35187e
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::WikiPageCounter do
+ it_behaves_like 'a redis usage counter', 'Wiki Page', :create
+ it_behaves_like 'a redis usage counter', 'Wiki Page', :update
+ it_behaves_like 'a redis usage counter', 'Wiki Page', :delete
+
+ it_behaves_like 'a redis usage counter with totals', :wiki_pages,
+ create: 5,
+ update: 3,
+ delete: 2
+end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 67d49a30825..b3a179e276b 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::UsageData do
@@ -8,7 +10,7 @@ describe Gitlab::UsageData do
before do
create(:jira_service, project: projects[0])
create(:jira_service, project: projects[1])
- create(:jira_cloud_service, project: projects[2])
+ create(:jira_service, :jira_cloud_service, project: projects[2])
create(:prometheus_service, project: projects[1])
create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true)
create(:service, project: projects[1], type: 'SlackService', active: true)
@@ -24,7 +26,7 @@ describe Gitlab::UsageData do
create(:cluster, :group, :disabled)
create(:clusters_applications_helm, :installed, cluster: gcp_cluster)
create(:clusters_applications_ingress, :installed, cluster: gcp_cluster)
- create(:clusters_applications_cert_managers, :installed, cluster: gcp_cluster)
+ create(:clusters_applications_cert_manager, :installed, cluster: gcp_cluster)
create(:clusters_applications_prometheus, :installed, cluster: gcp_cluster)
create(:clusters_applications_runner, :installed, cluster: gcp_cluster)
create(:clusters_applications_knative, :installed, cluster: gcp_cluster)
@@ -34,7 +36,7 @@ describe Gitlab::UsageData do
subject { described_class.data }
- it "gathers usage data" do
+ it 'gathers usage data' do
expect(subject.keys).to include(*%i(
active_user_count
counts
@@ -57,13 +59,31 @@ describe Gitlab::UsageData do
gitaly
database
avg_cycle_analytics
- web_ide_commits
influxdb_metrics_enabled
prometheus_metrics_enabled
+ cycle_analytics_views
))
+
+ expect(subject).to include(
+ snippet_create: a_kind_of(Integer),
+ snippet_update: a_kind_of(Integer),
+ snippet_comment: a_kind_of(Integer),
+ merge_request_comment: a_kind_of(Integer),
+ merge_request_create: a_kind_of(Integer),
+ commit_comment: a_kind_of(Integer),
+ wiki_pages_create: a_kind_of(Integer),
+ wiki_pages_update: a_kind_of(Integer),
+ wiki_pages_delete: a_kind_of(Integer),
+ web_ide_views: a_kind_of(Integer),
+ web_ide_commits: a_kind_of(Integer),
+ web_ide_merge_requests: a_kind_of(Integer),
+ navbar_searches: a_kind_of(Integer),
+ cycle_analytics_views: a_kind_of(Integer),
+ source_code_pushes: a_kind_of(Integer)
+ )
end
- it "gathers usage counts" do
+ it 'gathers usage counts' do
expected_keys = %i(
assignee_lists
boards
@@ -139,11 +159,6 @@ describe Gitlab::UsageData do
expect(expected_keys - count_data.keys).to be_empty
end
- it 'does not gather user preferences usage data when the feature is disabled' do
- stub_feature_flags(group_overview_security_dashboard: false)
- expect(subject[:counts].keys).not_to include(:user_preferences)
- end
-
it 'gathers projects data correctly' do
count_data = subject[:counts]
@@ -182,6 +197,28 @@ describe Gitlab::UsageData do
end
end
+ describe '#usage_data_counters' do
+ subject { described_class.usage_data_counters }
+
+ it { is_expected.to all(respond_to :totals) }
+
+ describe 'the results of calling #totals on all objects in the array' do
+ subject { described_class.usage_data_counters.map(&:totals) }
+
+ it do
+ is_expected
+ .to all(be_a Hash)
+ .and all(have_attributes(keys: all(be_a Symbol), values: all(be_a Integer)))
+ end
+ end
+
+ it 'does not have any conflicts' do
+ all_keys = subject.flat_map { |counter| counter.totals.keys }
+
+ expect(all_keys.size).to eq all_keys.to_set.size
+ end
+ end
+
describe '#features_usage_data_ce' do
subject { described_class.features_usage_data_ce }
@@ -216,7 +253,7 @@ describe Gitlab::UsageData do
describe '#license_usage_data' do
subject { described_class.license_usage_data }
- it "gathers license data" do
+ it 'gathers license data' do
expect(subject[:uuid]).to eq(Gitlab::CurrentSettings.uuid)
expect(subject[:version]).to eq(Gitlab::VERSION)
expect(subject[:installation_type]).to eq('gitlab-development-kit')
@@ -234,6 +271,12 @@ describe Gitlab::UsageData do
expect(described_class.count(relation)).to eq(1)
end
+ it 'returns the count for count_by when provided' do
+ allow(relation).to receive(:count).with(:creator_id).and_return(2)
+
+ expect(described_class.count(relation, count_by: :creator_id)).to eq(2)
+ end
+
it 'returns the fallback value when counting fails' do
allow(relation).to receive(:count).and_raise(ActiveRecord::StatementInvalid.new(''))
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index 9da06bb40f4..c25bd14fcba 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::UserAccess do
diff --git a/spec/lib/gitlab/user_extractor_spec.rb b/spec/lib/gitlab/user_extractor_spec.rb
deleted file mode 100644
index b86ec5445b8..00000000000
--- a/spec/lib/gitlab/user_extractor_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::UserExtractor do
- let(:text) do
- <<~TXT
- This is a long texth that mentions some users.
- @user-1, @user-2 and user@gitlab.org take a walk in the park.
- There they meet @user-4 that was out with other-user@gitlab.org.
- @user-1 thought it was late, so went home straight away
- TXT
- end
- subject(:extractor) { described_class.new(text) }
-
- describe '#users' do
- it 'returns an empty relation when nil was passed' do
- extractor = described_class.new(nil)
-
- expect(extractor.users).to be_empty
- expect(extractor.users).to be_a(ActiveRecord::Relation)
- end
-
- it 'returns the user case insensitive for usernames' do
- user = create(:user, username: "USER-4")
-
- expect(extractor.users).to include(user)
- end
-
- it 'returns users by primary email' do
- user = create(:user, email: 'user@gitlab.org')
-
- expect(extractor.users).to include(user)
- end
-
- it 'returns users by secondary email' do
- user = create(:email, email: 'other-user@gitlab.org').user
-
- expect(extractor.users).to include(user)
- end
-
- context 'input as array of strings' do
- it 'is treated as one string' do
- extractor = described_class.new(text.lines)
-
- user_1 = create(:user, username: "USER-1")
- user_4 = create(:user, username: "USER-4")
- user_email = create(:user, email: 'user@gitlab.org')
-
- expect(extractor.users).to contain_exactly(user_1, user_4, user_email)
- end
- end
- end
-
- describe '#matches' do
- it 'includes all mentioned email adresses' do
- expect(extractor.matches[:emails]).to contain_exactly('user@gitlab.org', 'other-user@gitlab.org')
- end
-
- it 'includes all mentioned usernames' do
- expect(extractor.matches[:usernames]).to contain_exactly('user-1', 'user-2', 'user-4')
- end
-
- context 'input has no matching e-mail or usernames' do
- it 'returns an empty list of users' do
- extractor = described_class.new('My test')
-
- expect(extractor.users).to be_empty
- end
- end
- end
-
- describe '#references' do
- it 'includes all user-references once' do
- expect(extractor.references).to contain_exactly('user-1', 'user-2', 'user@gitlab.org', 'user-4', 'other-user@gitlab.org')
- end
- end
-end
diff --git a/spec/lib/gitlab/utils/deep_size_spec.rb b/spec/lib/gitlab/utils/deep_size_spec.rb
new file mode 100644
index 00000000000..47dfc04f46f
--- /dev/null
+++ b/spec/lib/gitlab/utils/deep_size_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Utils::DeepSize do
+ let(:data) do
+ {
+ a: [1, 2, 3],
+ b: {
+ c: [4, 5],
+ d: [
+ { e: [[6], [7]] }
+ ]
+ }
+ }
+ end
+
+ let(:max_size) { 1.kilobyte }
+ let(:max_depth) { 10 }
+ let(:deep_size) { described_class.new(data, max_size: max_size, max_depth: max_depth) }
+
+ describe '#evaluate' do
+ context 'when data within size and depth limits' do
+ it 'returns true' do
+ expect(deep_size).to be_valid
+ end
+ end
+
+ context 'when data not within size limit' do
+ let(:max_size) { 200.bytes }
+
+ it 'returns false' do
+ expect(deep_size).not_to be_valid
+ end
+ end
+
+ context 'when data not within depth limit' do
+ let(:max_depth) { 2 }
+
+ it 'returns false' do
+ expect(deep_size).not_to be_valid
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils/merge_hash_spec.rb b/spec/lib/gitlab/utils/merge_hash_spec.rb
index 4fa7bb31301..72620e549a9 100644
--- a/spec/lib/gitlab/utils/merge_hash_spec.rb
+++ b/spec/lib/gitlab/utils/merge_hash_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Utils::MergeHash do
describe '.crush' do
diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb
index 9e7c97f8095..5855c4374a9 100644
--- a/spec/lib/gitlab/utils/override_spec.rb
+++ b/spec/lib/gitlab/utils/override_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Utils::Override do
diff --git a/spec/lib/gitlab/utils/sanitize_node_link_spec.rb b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
new file mode 100644
index 00000000000..80b0935a7ed
--- /dev/null
+++ b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Utils::SanitizeNodeLink do
+ let(:klass) do
+ struct = Struct.new(:value)
+ struct.include(described_class)
+
+ struct
+ end
+
+ subject(:object) { klass.new(:value) }
+
+ invalid_schemes = [
+ "javascript:",
+ "JaVaScRiPt:",
+ "\u0001java\u0003script:",
+ "javascript :",
+ "javascript: ",
+ "javascript : ",
+ ":javascript:",
+ "javascript&#58;",
+ "javascript&#0058;",
+ " &#14; javascript:"
+ ]
+
+ invalid_schemes.each do |scheme|
+ context "with the scheme: #{scheme}" do
+ describe "#remove_unsafe_links" do
+ tags = {
+ a: {
+ doc: HTML::Pipeline.parse("<a href='#{scheme}alert(1);'>foo</a>"),
+ attr: "href",
+ node_to_check: -> (doc) { doc.children.first }
+ },
+ img: {
+ doc: HTML::Pipeline.parse("<img src='#{scheme}alert(1);'>"),
+ attr: "src",
+ node_to_check: -> (doc) { doc.children.first }
+ },
+ video: {
+ doc: HTML::Pipeline.parse("<video><source src='#{scheme}alert(1);'></video>"),
+ attr: "src",
+ node_to_check: -> (doc) { doc.children.first.children.filter("source").first }
+ }
+ }
+
+ tags.each do |tag, opts|
+ context "<#{tag}> tags" do
+ it "removes the unsafe link" do
+ node = opts[:node_to_check].call(opts[:doc])
+
+ expect { object.remove_unsafe_links({ node: node }, remove_invalid_links: true) }
+ .to change { node[opts[:attr]] }
+
+ expect(node[opts[:attr]]).to be_blank
+ end
+ end
+ end
+ end
+
+ describe "#safe_protocol?" do
+ let(:doc) { HTML::Pipeline.parse("<a href='#{scheme}alert(1);'>foo</a>") }
+ let(:node) { doc.children.first }
+ let(:uri) { Addressable::URI.parse(node['href'])}
+
+ it "returns false" do
+ expect(object.safe_protocol?(scheme)).to be_falsy
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils/strong_memoize_spec.rb b/spec/lib/gitlab/utils/strong_memoize_spec.rb
index 473f8100771..26baaf873a8 100644
--- a/spec/lib/gitlab/utils/strong_memoize_spec.rb
+++ b/spec/lib/gitlab/utils/strong_memoize_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Utils::StrongMemoize do
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 4645339f439..890918d4a7c 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Utils do
@@ -231,4 +233,23 @@ describe Gitlab::Utils do
end
end
end
+
+ describe '.string_to_ip_object' do
+ it 'returns nil when string is nil' do
+ expect(described_class.string_to_ip_object(nil)).to eq(nil)
+ end
+
+ it 'returns nil when string is invalid IP' do
+ expect(described_class.string_to_ip_object('invalid ip')).to eq(nil)
+ expect(described_class.string_to_ip_object('')).to eq(nil)
+ end
+
+ it 'returns IP object when string is valid IP' do
+ expect(described_class.string_to_ip_object('192.168.1.1')).to eq(IPAddr.new('192.168.1.1'))
+ expect(described_class.string_to_ip_object('::ffff:a9fe:a864')).to eq(IPAddr.new('::ffff:a9fe:a864'))
+ expect(described_class.string_to_ip_object('[::ffff:a9fe:a864]')).to eq(IPAddr.new('::ffff:a9fe:a864'))
+ expect(described_class.string_to_ip_object('127.0.0.0/28')).to eq(IPAddr.new('127.0.0.0/28'))
+ expect(described_class.string_to_ip_object('1:0:0:0:0:0:0:0/124')).to eq(IPAddr.new('1:0:0:0:0:0:0:0/124'))
+ end
+ end
end
diff --git a/spec/lib/gitlab/verify/job_artifacts_spec.rb b/spec/lib/gitlab/verify/job_artifacts_spec.rb
index 6e916a56564..b50ec1528d4 100644
--- a/spec/lib/gitlab/verify/job_artifacts_spec.rb
+++ b/spec/lib/gitlab/verify/job_artifacts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Verify::JobArtifacts do
diff --git a/spec/lib/gitlab/verify/lfs_objects_spec.rb b/spec/lib/gitlab/verify/lfs_objects_spec.rb
index 2feaedd6f14..c27c9b6efa1 100644
--- a/spec/lib/gitlab/verify/lfs_objects_spec.rb
+++ b/spec/lib/gitlab/verify/lfs_objects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Verify::LfsObjects do
diff --git a/spec/lib/gitlab/verify/uploads_spec.rb b/spec/lib/gitlab/verify/uploads_spec.rb
index 38c30fab1ba..a3d3f5d46f3 100644
--- a/spec/lib/gitlab/verify/uploads_spec.rb
+++ b/spec/lib/gitlab/verify/uploads_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Verify::Uploads do
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index 30035c79e58..8c14b187410 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Gitlab::VersionInfo' do
diff --git a/spec/lib/gitlab/view/presenter/base_spec.rb b/spec/lib/gitlab/view/presenter/base_spec.rb
index 02c2fd47197..e196ab23482 100644
--- a/spec/lib/gitlab/view/presenter/base_spec.rb
+++ b/spec/lib/gitlab/view/presenter/base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::View::Presenter::Base do
diff --git a/spec/lib/gitlab/view/presenter/delegated_spec.rb b/spec/lib/gitlab/view/presenter/delegated_spec.rb
index 940a2ce6ebd..0a21cd1358e 100644
--- a/spec/lib/gitlab/view/presenter/delegated_spec.rb
+++ b/spec/lib/gitlab/view/presenter/delegated_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::View::Presenter::Delegated do
diff --git a/spec/lib/gitlab/view/presenter/factory_spec.rb b/spec/lib/gitlab/view/presenter/factory_spec.rb
index 6120bafb2e3..515a1b0a8e4 100644
--- a/spec/lib/gitlab/view/presenter/factory_spec.rb
+++ b/spec/lib/gitlab/view/presenter/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::View::Presenter::Factory do
diff --git a/spec/lib/gitlab/view/presenter/simple_spec.rb b/spec/lib/gitlab/view/presenter/simple_spec.rb
index 1795ed2405b..70e2b170a36 100644
--- a/spec/lib/gitlab/view/presenter/simple_spec.rb
+++ b/spec/lib/gitlab/view/presenter/simple_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::View::Presenter::Simple do
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index 0a170a157fe..75dc7d8e6d1 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::VisibilityLevel do
diff --git a/spec/lib/gitlab/web_ide_commits_counter_spec.rb b/spec/lib/gitlab/web_ide_commits_counter_spec.rb
deleted file mode 100644
index c51889a1c63..00000000000
--- a/spec/lib/gitlab/web_ide_commits_counter_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::WebIdeCommitsCounter, :clean_gitlab_redis_shared_state do
- describe '.increment' do
- it 'increments the web ide commits counter by 1' do
- expect do
- described_class.increment
- end.to change { described_class.total_count }.from(0).to(1)
- end
- end
-
- describe '.total_count' do
- it 'returns the total amount of web ide commits' do
- expect(described_class.total_count).to eq(0)
- end
- end
-end
diff --git a/spec/lib/gitlab/wiki_file_finder_spec.rb b/spec/lib/gitlab/wiki_file_finder_spec.rb
index 025d1203dc5..fdd95d5e6e6 100644
--- a/spec/lib/gitlab/wiki_file_finder_spec.rb
+++ b/spec/lib/gitlab/wiki_file_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::WikiFileFinder do
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index f8332757fcd..5c5ff46112f 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Workhorse do
@@ -404,6 +406,7 @@ describe Gitlab::Workhorse do
end
it 'set and notify' do
+ expect(Gitlab::Redis::SharedState).to receive(:with).and_call_original
expect_any_instance_of(::Redis).to receive(:publish)
.with(described_class::NOTIFICATION_CHANNEL, "test-key=test-value")
diff --git a/spec/lib/gitlab/zoom_link_extractor_spec.rb b/spec/lib/gitlab/zoom_link_extractor_spec.rb
new file mode 100644
index 00000000000..c3d1679d031
--- /dev/null
+++ b/spec/lib/gitlab/zoom_link_extractor_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ZoomLinkExtractor do
+ describe "#links" do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:text, :links) do
+ 'issue text https://zoom.us/j/123 and https://zoom.us/s/1123433' | %w[https://zoom.us/j/123 https://zoom.us/s/1123433]
+ 'https://zoom.us/j/1123433 issue text' | %w[https://zoom.us/j/1123433]
+ 'issue https://zoom.us/my/1123433 text' | %w[https://zoom.us/my/1123433]
+ 'issue https://gitlab.com and https://gitlab.zoom.us/s/1123433' | %w[https://gitlab.zoom.us/s/1123433]
+ 'https://gitlab.zoom.us/j/1123433' | %w[https://gitlab.zoom.us/j/1123433]
+ 'https://gitlab.zoom.us/my/1123433' | %w[https://gitlab.zoom.us/my/1123433]
+ end
+
+ with_them do
+ subject { described_class.new(text).links }
+
+ it { is_expected.to eq(links) }
+ end
+
+ describe '#match?' do
+ it 'is true when a zoom link found' do
+ expect(described_class.new('issue text https://zoom.us/j/123')).to be_match
+ end
+
+ it 'is false when no zoom link found' do
+ expect(described_class.new('issue text')).not_to be_match
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index 82b0e819063..cbc5649fc09 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require_dependency 'gitlab'
@@ -136,6 +138,12 @@ describe Gitlab do
expect(described_class.ee?).to eq(false)
end
+
+ it 'returns true when the IS_GITLAB_EE variable is not empty' do
+ stub_env('IS_GITLAB_EE', '1')
+
+ expect(described_class.ee?).to eq(true)
+ end
end
describe '.http_proxy_env?' do
diff --git a/spec/lib/google_api/auth_spec.rb b/spec/lib/google_api/auth_spec.rb
index 87a3f43274f..a25004ac385 100644
--- a/spec/lib/google_api/auth_spec.rb
+++ b/spec/lib/google_api/auth_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GoogleApi::Auth do
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index 1fefc947636..c24998d32f8 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GoogleApi::CloudPlatform::Client do
diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb
index a3c54651e80..a127c787e28 100644
--- a/spec/lib/json_web_token/rsa_token_spec.rb
+++ b/spec/lib/json_web_token/rsa_token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe JSONWebToken::RSAToken do
let(:rsa_key) do
OpenSSL::PKey::RSA.new <<-eos.strip_heredoc
diff --git a/spec/lib/json_web_token/token_spec.rb b/spec/lib/json_web_token/token_spec.rb
index d7e7560d962..916d11ce0ed 100644
--- a/spec/lib/json_web_token/token_spec.rb
+++ b/spec/lib/json_web_token/token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe JSONWebToken::Token do
let(:token) { described_class.new }
diff --git a/spec/lib/mattermost/client_spec.rb b/spec/lib/mattermost/client_spec.rb
index dc11a414717..5fe35eb5f93 100644
--- a/spec/lib/mattermost/client_spec.rb
+++ b/spec/lib/mattermost/client_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Mattermost::Client do
diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb
index 7c194749dfb..f8c451a1522 100644
--- a/spec/lib/mattermost/command_spec.rb
+++ b/spec/lib/mattermost/command_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Mattermost::Command do
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index 346455067a7..ea12bd76c8d 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Mattermost::Session, type: :request do
diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb
index 030aa5d06a8..2823dab67c9 100644
--- a/spec/lib/mattermost/team_spec.rb
+++ b/spec/lib/mattermost/team_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Mattermost::Team do
diff --git a/spec/lib/microsoft_teams/activity_spec.rb b/spec/lib/microsoft_teams/activity_spec.rb
index 7890ae2e7b0..3fad2437f3e 100644
--- a/spec/lib/microsoft_teams/activity_spec.rb
+++ b/spec/lib/microsoft_teams/activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MicrosoftTeams::Activity do
diff --git a/spec/lib/microsoft_teams/notifier_spec.rb b/spec/lib/microsoft_teams/notifier_spec.rb
index 2aaa7c24ad8..64ab8d85807 100644
--- a/spec/lib/microsoft_teams/notifier_spec.rb
+++ b/spec/lib/microsoft_teams/notifier_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MicrosoftTeams::Notifier do
diff --git a/spec/lib/milestone_array_spec.rb b/spec/lib/milestone_array_spec.rb
index df91677b925..375cb87dde6 100644
--- a/spec/lib/milestone_array_spec.rb
+++ b/spec/lib/milestone_array_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MilestoneArray do
diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb
index 8ccbd90ddb8..fae0c636bdc 100644
--- a/spec/lib/object_storage/direct_upload_spec.rb
+++ b/spec/lib/object_storage/direct_upload_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ObjectStorage::DirectUpload do
diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb
index c1eaf0bb0bf..bdf3ea6be98 100644
--- a/spec/lib/omni_auth/strategies/jwt_spec.rb
+++ b/spec/lib/omni_auth/strategies/jwt_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe OmniAuth::Strategies::Jwt do
diff --git a/spec/lib/peek/views/redis_detailed_spec.rb b/spec/lib/peek/views/redis_detailed_spec.rb
new file mode 100644
index 00000000000..61096e6c69e
--- /dev/null
+++ b/spec/lib/peek/views/redis_detailed_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Peek::Views::RedisDetailed, :request_store do
+ subject { described_class.new }
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:cmd, :expected) do
+ [:auth, 'test'] | 'auth <redacted>'
+ [:set, 'key', 'value'] | 'set key <redacted>'
+ [:set, 'bad'] | 'set bad'
+ [:hmset, 'key1', 'value1', 'key2', 'value2'] | 'hmset key1 <redacted>'
+ [:get, 'key'] | 'get key'
+ end
+
+ with_them do
+ it 'scrubs Redis commands' do
+ subject.detail_store << { cmd: cmd, duration: 1.second }
+
+ expect(subject.results[:details].count).to eq(1)
+ expect(subject.results[:details].first)
+ .to eq({
+ cmd: expected,
+ duration: 1000
+ })
+ end
+ end
+
+ it 'returns aggregated results' do
+ subject.detail_store << { cmd: [:get, 'test'], duration: 0.001 }
+ subject.detail_store << { cmd: [:get, 'test'], duration: 1.second }
+
+ expect(subject.results[:calls]).to eq(2)
+ expect(subject.results[:duration]).to eq('1001.00ms')
+ expect(subject.results[:details].count).to eq(2)
+ end
+end
diff --git a/spec/lib/peek/views/rugged_spec.rb b/spec/lib/peek/views/rugged_spec.rb
new file mode 100644
index 00000000000..d07d6b51a1f
--- /dev/null
+++ b/spec/lib/peek/views/rugged_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Peek::Views::Rugged, :request_store do
+ subject { described_class.new }
+
+ let(:project) { create(:project) }
+
+ before do
+ allow(Gitlab::RuggedInstrumentation).to receive(:peek_enabled?).and_return(true)
+ end
+
+ it 'returns no results' do
+ expect(subject.results).to eq({})
+ end
+
+ it 'returns aggregated results' do
+ ::Gitlab::RuggedInstrumentation.query_time += 1.234
+ ::Gitlab::RuggedInstrumentation.increment_query_count
+ ::Gitlab::RuggedInstrumentation.increment_query_count
+
+ ::Gitlab::RuggedInstrumentation.add_call_details(feature: :rugged_test,
+ args: [project.repository.raw, 'HEAD'],
+ duration: 0.123)
+ ::Gitlab::RuggedInstrumentation.add_call_details(feature: :rugged_test2,
+ args: [project.repository, 'refs/heads/master'],
+ duration: 0.456)
+
+ results = subject.results
+ expect(results[:calls]).to eq(2)
+ expect(results[:duration]).to eq('1234.00ms')
+ expect(results[:details].count).to eq(2)
+
+ expected = [
+ [project.repository.raw.to_s, "HEAD"],
+ [project.repository.to_s, "refs/heads/master"]
+ ]
+
+ expect(results[:details].map { |data| data[:args] }).to match_array(expected)
+ end
+end
diff --git a/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb b/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb
new file mode 100644
index 00000000000..c7302a1a656
--- /dev/null
+++ b/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Prometheus::CleanupMultiprocDirService do
+ describe '.call' do
+ subject { described_class.new.execute }
+
+ let(:metrics_multiproc_dir) { Dir.mktmpdir }
+ let(:metrics_file_path) { File.join(metrics_multiproc_dir, 'counter_puma_master-0.db') }
+
+ before do
+ FileUtils.touch(metrics_file_path)
+ end
+
+ after do
+ FileUtils.rm_r(metrics_multiproc_dir)
+ end
+
+ context 'when `multiprocess_files_dir` is defined' do
+ before do
+ expect(Prometheus::Client.configuration)
+ .to receive(:multiprocess_files_dir)
+ .and_return(metrics_multiproc_dir)
+ .at_least(:once)
+ end
+
+ it 'removes old metrics' do
+ expect { subject }
+ .to change { File.exist?(metrics_file_path) }
+ .from(true)
+ .to(false)
+ end
+ end
+
+ context 'when `multiprocess_files_dir` is not defined' do
+ before do
+ expect(Prometheus::Client.configuration)
+ .to receive(:multiprocess_files_dir)
+ .and_return(nil)
+ .at_least(:once)
+ end
+
+ it 'does not remove any files' do
+ expect { subject }
+ .not_to change { File.exist?(metrics_file_path) }
+ .from(true)
+ end
+ end
+ end
+end
diff --git a/spec/lib/prometheus/pid_provider_spec.rb b/spec/lib/prometheus/pid_provider_spec.rb
new file mode 100644
index 00000000000..ba843b27254
--- /dev/null
+++ b/spec/lib/prometheus/pid_provider_spec.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Prometheus::PidProvider do
+ describe '.worker_id' do
+ subject { described_class.worker_id }
+
+ let(:sidekiq_module) { Module.new }
+
+ before do
+ allow(sidekiq_module).to receive(:server?).and_return(false)
+ stub_const('Sidekiq', sidekiq_module)
+ end
+
+ context 'when running in Sidekiq server mode' do
+ before do
+ expect(Sidekiq).to receive(:server?).and_return(true)
+ end
+
+ it { is_expected.to eq 'sidekiq' }
+ end
+
+ context 'when running in Unicorn mode' do
+ before do
+ stub_const('Unicorn::Worker', Class.new)
+ hide_const('Puma')
+
+ expect(described_class).to receive(:process_name)
+ .at_least(:once)
+ .and_return(process_name)
+ end
+
+ context 'when unicorn master is specified in process name' do
+ context 'when running in Omnibus' do
+ context 'before the process was renamed' do
+ let(:process_name) { "/opt/gitlab/embedded/bin/unicorn"}
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+
+ context 'after the process was renamed' do
+ let(:process_name) { "unicorn master -D -E production -c /var/opt/gitlab/gitlab-rails/etc/unicorn.rb /opt/gitlab/embedded/service/gitlab-rails/config.ru" }
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+ end
+
+ context 'when in development env' do
+ context 'before the process was renamed' do
+ let(:process_name) { "path_to_bindir/bin/unicorn_rails"}
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+
+ context 'after the process was renamed' do
+ let(:process_name) { "unicorn_rails master -c /gitlab_dir/config/unicorn.rb -E development" }
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+ end
+ end
+
+ context 'when unicorn worker id is specified in process name' do
+ context 'when running in Omnibus' do
+ let(:process_name) { "unicorn worker[1] -D -E production -c /var/opt/gitlab/gitlab-rails/etc/unicorn.rb /opt/gitlab/embedded/service/gitlab-rails/config.ru" }
+
+ it { is_expected.to eq 'unicorn_1' }
+ end
+
+ context 'when in development env' do
+ let(:process_name) { "unicorn_rails worker[1] -c gitlab_dir/config/unicorn.rb -E development" }
+
+ it { is_expected.to eq 'unicorn_1' }
+ end
+ end
+
+ context 'when no specified unicorn master or worker id in process name' do
+ let(:process_name) { "bin/unknown_process"}
+
+ it { is_expected.to eq "process_#{Process.pid}" }
+ end
+ end
+
+ context 'when running in Puma mode' do
+ before do
+ stub_const('Puma', Module.new)
+ hide_const('Unicorn::Worker')
+
+ expect(described_class).to receive(:process_name)
+ .at_least(:once)
+ .and_return(process_name)
+ end
+
+ context 'when cluster worker id is specified in process name' do
+ let(:process_name) { 'puma: cluster worker 1: 17483 [gitlab-puma-worker]' }
+
+ it { is_expected.to eq 'puma_1' }
+ end
+
+ context 'when no worker id is specified in process name' do
+ let(:process_name) { 'bin/puma' }
+
+ it { is_expected.to eq 'puma_master' }
+ end
+ end
+
+ context 'when running in unknown mode' do
+ before do
+ hide_const('Puma')
+ hide_const('Unicorn::Worker')
+ end
+
+ it { is_expected.to eq "process_#{Process.pid}" }
+ end
+ end
+end
diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb
index 3465c3a050b..59870ce44a7 100644
--- a/spec/lib/quality/test_level_spec.rb
+++ b/spec/lib/quality/test_level_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a pattern' do
expect(subject.pattern(:unit))
- .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,helpers,initializers,javascripts,lib,migrations,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb")
+ .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,migrations,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb")
end
end
@@ -47,7 +47,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a regexp' do
expect(subject.regexp(:unit))
- .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|helpers|initializers|javascripts|lib|migrations|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)})
+ .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|migrations|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)})
end
end
diff --git a/spec/lib/rspec_flaky/config_spec.rb b/spec/lib/rspec_flaky/config_spec.rb
index 4a71b1feebd..13b2219267b 100644
--- a/spec/lib/rspec_flaky/config_spec.rb
+++ b/spec/lib/rspec_flaky/config_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RspecFlaky::Config, :aggregate_failures do
diff --git a/spec/lib/rspec_flaky/example_spec.rb b/spec/lib/rspec_flaky/example_spec.rb
index 5b4fd5ddf3e..4679dd818db 100644
--- a/spec/lib/rspec_flaky/example_spec.rb
+++ b/spec/lib/rspec_flaky/example_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RspecFlaky::Example do
diff --git a/spec/lib/rspec_flaky/flaky_example_spec.rb b/spec/lib/rspec_flaky/flaky_example_spec.rb
index d19c34bebb3..092bbc781a5 100644
--- a/spec/lib/rspec_flaky/flaky_example_spec.rb
+++ b/spec/lib/rspec_flaky/flaky_example_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RspecFlaky::FlakyExample, :aggregate_failures do
diff --git a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
index 6731a27ed17..2e224cda61b 100644
--- a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
+++ b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
diff --git a/spec/lib/rspec_flaky/listener_spec.rb b/spec/lib/rspec_flaky/listener_spec.rb
index ef085445081..44b8d99b74f 100644
--- a/spec/lib/rspec_flaky/listener_spec.rb
+++ b/spec/lib/rspec_flaky/listener_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RspecFlaky::Listener, :aggregate_failures do
diff --git a/spec/lib/rspec_flaky/report_spec.rb b/spec/lib/rspec_flaky/report_spec.rb
index 7d57d99f7e5..6a98a7a4e6b 100644
--- a/spec/lib/rspec_flaky/report_spec.rb
+++ b/spec/lib/rspec_flaky/report_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RspecFlaky::Report, :aggregate_failures do
diff --git a/spec/lib/safe_zip/entry_spec.rb b/spec/lib/safe_zip/entry_spec.rb
index 115e28c5994..0974f732188 100644
--- a/spec/lib/safe_zip/entry_spec.rb
+++ b/spec/lib/safe_zip/entry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SafeZip::Entry do
diff --git a/spec/lib/safe_zip/extract_params_spec.rb b/spec/lib/safe_zip/extract_params_spec.rb
index 85e22cfa495..f66d3de89ee 100644
--- a/spec/lib/safe_zip/extract_params_spec.rb
+++ b/spec/lib/safe_zip/extract_params_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SafeZip::ExtractParams do
diff --git a/spec/lib/safe_zip/extract_spec.rb b/spec/lib/safe_zip/extract_spec.rb
index b75a8fede00..3b8c64c1c9f 100644
--- a/spec/lib/safe_zip/extract_spec.rb
+++ b/spec/lib/safe_zip/extract_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SafeZip::Extract do
diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb
index cb14204b99a..ca2b17b44e0 100644
--- a/spec/lib/sentry/client_spec.rb
+++ b/spec/lib/sentry/client_spec.rb
@@ -63,7 +63,7 @@ describe Sentry::Client do
shared_examples 'maps exceptions' do
exceptions = {
- HTTParty::Error => 'Error when connecting to Sentry',
+ Gitlab::HTTP::Error => 'Error when connecting to Sentry',
Net::OpenTimeout => 'Connection to Sentry timed out',
SocketError => 'Received SocketError when trying to connect to Sentry',
OpenSSL::SSL::SSLError => 'Sentry returned invalid SSL data',
diff --git a/spec/lib/serializers/json_spec.rb b/spec/lib/serializers/json_spec.rb
index 5d59d66e8b8..a8d82d70e89 100644
--- a/spec/lib/serializers/json_spec.rb
+++ b/spec/lib/serializers/json_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Serializers::JSON do
@@ -6,24 +8,8 @@ describe Serializers::JSON do
subject { described_class.dump(obj) }
- context 'when MySQL is used' do
- before do
- allow(Gitlab::Database).to receive(:adapter_name) { 'mysql2' }
- end
-
- it 'encodes as string' do
- is_expected.to eq('{"key":"value"}')
- end
- end
-
- context 'when PostgreSQL is used' do
- before do
- allow(Gitlab::Database).to receive(:adapter_name) { 'postgresql' }
- end
-
- it 'returns a hash' do
- is_expected.to eq(obj)
- end
+ it 'returns a hash' do
+ is_expected.to eq(obj)
end
end
@@ -31,7 +17,13 @@ describe Serializers::JSON do
let(:data_string) { '{"key":"value","variables":[{"key":"VAR1","value":"VALUE1"}]}' }
let(:data_hash) { JSON.parse(data_string) }
- shared_examples 'having consistent accessor' do
+ context 'when loading a hash' do
+ subject { described_class.load(data_hash) }
+
+ it 'decodes a string' do
+ is_expected.to be_a(Hash)
+ end
+
it 'allows to access with symbols' do
expect(subject[:key]).to eq('value')
expect(subject[:variables].first[:key]).to eq('VAR1')
@@ -43,59 +35,11 @@ describe Serializers::JSON do
end
end
- context 'when MySQL is used' do
- before do
- allow(Gitlab::Database).to receive(:adapter_name) { 'mysql2' }
- end
-
- context 'when loading a string' do
- subject { described_class.load(data_string) }
-
- it 'decodes a string' do
- is_expected.to be_a(Hash)
- end
-
- it_behaves_like 'having consistent accessor'
- end
-
- context 'when loading a different type' do
- subject { described_class.load({ key: 'hash' }) }
-
- it 'raises an exception' do
- expect { subject }.to raise_error(TypeError)
- end
- end
-
- context 'when loading a nil' do
- subject { described_class.load(nil) }
-
- it 'returns nil' do
- is_expected.to be_nil
- end
- end
- end
-
- context 'when PostgreSQL is used' do
- before do
- allow(Gitlab::Database).to receive(:adapter_name) { 'postgresql' }
- end
-
- context 'when loading a hash' do
- subject { described_class.load(data_hash) }
-
- it 'decodes a string' do
- is_expected.to be_a(Hash)
- end
-
- it_behaves_like 'having consistent accessor'
- end
-
- context 'when loading a nil' do
- subject { described_class.load(nil) }
+ context 'when loading a nil' do
+ subject { described_class.load(nil) }
- it 'returns nil' do
- is_expected.to be_nil
- end
+ it 'returns nil' do
+ is_expected.to be_nil
end
end
end
diff --git a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
index a0fb86345f3..f132f608ab6 100644
--- a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
+++ b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SystemCheck::App::GitUserDefaultSSHConfigCheck do
diff --git a/spec/lib/system_check/base_check_spec.rb b/spec/lib/system_check/base_check_spec.rb
index faf8c99e772..ccb7b483bdc 100644
--- a/spec/lib/system_check/base_check_spec.rb
+++ b/spec/lib/system_check/base_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SystemCheck::BaseCheck do
diff --git a/spec/lib/system_check/orphans/namespace_check_spec.rb b/spec/lib/system_check/orphans/namespace_check_spec.rb
index 2a61ff3ad65..f7491e40438 100644
--- a/spec/lib/system_check/orphans/namespace_check_spec.rb
+++ b/spec/lib/system_check/orphans/namespace_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'rake_helper'
diff --git a/spec/lib/system_check/orphans/repository_check_spec.rb b/spec/lib/system_check/orphans/repository_check_spec.rb
index b0c2267d177..a5e06f30e75 100644
--- a/spec/lib/system_check/orphans/repository_check_spec.rb
+++ b/spec/lib/system_check/orphans/repository_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'rake_helper'
diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb
index e71e9da369d..94094343ec6 100644
--- a/spec/lib/system_check/simple_executor_spec.rb
+++ b/spec/lib/system_check/simple_executor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'rake_helper'
diff --git a/spec/lib/system_check_spec.rb b/spec/lib/system_check_spec.rb
index 4d9e17fa6ec..f3ed6ca31c9 100644
--- a/spec/lib/system_check_spec.rb
+++ b/spec/lib/system_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'rake_helper'
diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb
index a2f5c2e7121..2cb4727bd4b 100644
--- a/spec/lib/uploaded_file_spec.rb
+++ b/spec/lib/uploaded_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UploadedFile do
diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb
index eae83cd64d3..c52e3c2191d 100644
--- a/spec/mailers/emails/pages_domains_spec.rb
+++ b/spec/mailers/emails/pages_domains_spec.rb
@@ -23,7 +23,7 @@ describe Emails::PagesDomains do
is_expected.to have_body_text(domain.domain)
is_expected.to have_body_text domain.url
is_expected.to have_body_text project_pages_domain_url(project, domain)
- is_expected.to have_body_text help_page_url('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
+ is_expected.to have_body_text help_page_url('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: link_anchor)
end
end
end
@@ -50,6 +50,7 @@ describe Emails::PagesDomains do
describe '#pages_domain_enabled_email' do
let(:email_subject) { "#{project.path} | GitLab Pages domain '#{domain.domain}' has been enabled" }
+ let(:link_anchor) { 'steps' }
subject { Notify.pages_domain_enabled_email(domain, user) }
@@ -60,6 +61,7 @@ describe Emails::PagesDomains do
describe '#pages_domain_disabled_email' do
let(:email_subject) { "#{project.path} | GitLab Pages domain '#{domain.domain}' has been disabled" }
+ let(:link_anchor) { '4-verify-the-domains-ownership' }
subject { Notify.pages_domain_disabled_email(domain, user) }
@@ -72,6 +74,7 @@ describe Emails::PagesDomains do
describe '#pages_domain_verification_succeeded_email' do
let(:email_subject) { "#{project.path} | Verification succeeded for GitLab Pages domain '#{domain.domain}'" }
+ let(:link_anchor) { 'steps' }
subject { Notify.pages_domain_verification_succeeded_email(domain, user) }
@@ -82,6 +85,7 @@ describe Emails::PagesDomains do
describe '#pages_domain_verification_failed_email' do
let(:email_subject) { "#{project.path} | ACTION REQUIRED: Verification failed for GitLab Pages domain '#{domain.domain}'" }
+ let(:link_anchor) { 'steps' }
subject { Notify.pages_domain_verification_failed_email(domain, user) }
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index fa1343fe759..6cba7df114c 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -543,6 +543,73 @@ describe Notify do
end
end
+ describe '#mail_thread' do
+ set(:mail_thread_note) { create(:note) }
+
+ let(:headers) do
+ {
+ from: 'someone@test.com',
+ to: 'someone-else@test.com',
+ subject: 'something',
+ template_name: '_note_email' # re-use this for testing
+ }
+ end
+
+ let(:mailer) do
+ mailer = described_class.new
+ mailer.instance_variable_set(:@note, mail_thread_note)
+ mailer
+ end
+
+ context 'the model has no namespace' do
+ class TopLevelThing
+ include Referable
+ include Noteable
+
+ def to_reference(*_args)
+ 'tlt-ref'
+ end
+
+ def id
+ 'tlt-id'
+ end
+ end
+
+ subject do
+ mailer.send(:mail_thread, TopLevelThing.new, headers)
+ end
+
+ it 'has X-GitLab-Namespaced-Thing-ID header' do
+ expect(subject.header['X-GitLab-TopLevelThing-ID'].value).to eq('tlt-id')
+ end
+ end
+
+ context 'the model has a namespace' do
+ module Namespaced
+ class Thing
+ include Referable
+ include Noteable
+
+ def to_reference(*_args)
+ 'some-reference'
+ end
+
+ def id
+ 'some-id'
+ end
+ end
+ end
+
+ subject do
+ mailer.send(:mail_thread, Namespaced::Thing.new, headers)
+ end
+
+ it 'has X-GitLab-Namespaced-Thing-ID header' do
+ expect(subject.header['X-GitLab-Namespaced-Thing-ID'].value).to eq('some-id')
+ end
+ end
+ end
+
context 'for issue notes' do
let(:host) { Gitlab.config.gitlab.host }
@@ -606,7 +673,7 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'has the correct subject and body' do
- is_expected.to have_referable_subject(project_snippet, include_group: true, reply: true)
+ is_expected.to have_referable_subject(project_snippet, reply: true)
is_expected.to have_body_text project_snippet_note.note
end
end
@@ -640,7 +707,7 @@ describe Notify do
project.request_access(user)
project.requesters.find_by(user_id: user.id)
end
- subject { described_class.member_access_requested_email('project', project_member.id, recipient.notification_email) }
+ subject { described_class.member_access_requested_email('project', project_member.id, recipient.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
@@ -737,9 +804,9 @@ describe Notify do
describe 'project invitation accepted' do
let(:invited_user) { create(:user, name: 'invited user') }
- let(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }
+ let(:recipient) { create(:user).tap { |u| project.add_maintainer(u) } }
let(:project_member) do
- invitee = invite_to_project(project, inviter: maintainer)
+ invitee = invite_to_project(project, inviter: recipient)
invitee.accept_invite!(invited_user)
invitee
end
@@ -747,6 +814,7 @@ describe Notify do
subject { described_class.member_invite_accepted_email('project', project_member.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
@@ -762,16 +830,17 @@ describe Notify do
end
describe 'project invitation declined' do
- let(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }
+ let(:recipient) { create(:user).tap { |u| project.add_maintainer(u) } }
let(:project_member) do
- invitee = invite_to_project(project, inviter: maintainer)
+ invitee = invite_to_project(project, inviter: recipient)
invitee.decline_invite!
invitee
end
- subject { described_class.member_invite_declined_email('Project', project.id, project_member.invite_email, maintainer.id) }
+ subject { described_class.member_invite_declined_email('Project', project.id, project_member.invite_email, recipient.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
@@ -813,7 +882,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
- is_expected.to have_subject("Re: #{project.name} | #{project.group.name} | #{commit.title} (#{commit.short_id})")
+ is_expected.to have_subject("Re: #{project.name} | #{commit.title} (#{commit.short_id})")
is_expected.to have_body_text(commit.short_id)
end
end
@@ -839,7 +908,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
- is_expected.to have_referable_subject(merge_request, include_group: true, reply: true)
+ is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text note_on_merge_request_path
end
end
@@ -865,7 +934,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
- is_expected.to have_referable_subject(issue, include_group: true, reply: true)
+ is_expected.to have_referable_subject(issue, reply: true)
is_expected.to have_body_text(note_on_issue_path)
end
end
@@ -931,7 +1000,7 @@ describe Notify do
it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
- is_expected.to have_subject "Re: #{project.name} | #{project.group.name} | #{commit.title} (#{commit.short_id})"
+ is_expected.to have_subject "Re: #{project.name} | #{commit.title} (#{commit.short_id})"
end
it 'contains a link to the commit' do
@@ -959,7 +1028,7 @@ describe Notify do
it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
- is_expected.to have_referable_subject(merge_request, include_group: true, reply: true)
+ is_expected.to have_referable_subject(merge_request, reply: true)
end
it 'contains a link to the merge request note' do
@@ -987,7 +1056,7 @@ describe Notify do
it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
- is_expected.to have_referable_subject(issue, include_group: true, reply: true)
+ is_expected.to have_referable_subject(issue, reply: true)
end
it 'contains a link to the issue note' do
@@ -1087,9 +1156,10 @@ describe Notify do
group.request_access(user)
group.requesters.find_by(user_id: user.id)
end
- subject { described_class.member_access_requested_email('group', group_member.id, recipient.notification_email) }
+ subject { described_class.member_access_requested_email('group', group_member.id, recipient.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
@@ -1111,9 +1181,11 @@ describe Notify do
group.request_access(user)
group.requesters.find_by(user_id: user.id)
end
+ let(:recipient) { user }
subject { described_class.member_access_denied_email('group', group.id, user.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
@@ -1128,10 +1200,12 @@ describe Notify do
describe 'group access changed' do
let(:group_member) { create(:group_member, group: group, user: user) }
+ let(:recipient) { user }
subject { described_class.member_access_granted_email('group', group_member.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
diff --git a/spec/migrations/active_record/schema_spec.rb b/spec/migrations/active_record/schema_spec.rb
index fbf5d387d0e..bc246f88685 100644
--- a/spec/migrations/active_record/schema_spec.rb
+++ b/spec/migrations/active_record/schema_spec.rb
@@ -15,7 +15,7 @@ describe ActiveRecord::Schema do
it '> schema version equals last migration timestamp' do
defined_schema_version = File.open(Rails.root.join('db', 'schema.rb')) do |file|
file.find { |line| line =~ /ActiveRecord::Schema.define/ }
- end.match(/(\d+)/)[0].to_i
+ end.match(/(\d{4}_\d{2}_\d{2}_\d{6})/)[0].to_i
expect(defined_schema_version).to eq(latest_migration_timestamp)
end
diff --git a/spec/migrations/add_foreign_key_to_merge_requests_spec.rb b/spec/migrations/add_foreign_key_to_merge_requests_spec.rb
deleted file mode 100644
index d9ad9a585f0..00000000000
--- a/spec/migrations/add_foreign_key_to_merge_requests_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'migrate', '20170713104829_add_foreign_key_to_merge_requests.rb')
-
-describe AddForeignKeyToMergeRequests, :migration do
- let(:projects) { table(:projects) }
- let(:merge_requests) { table(:merge_requests) }
- let(:pipelines) { table(:ci_pipelines) }
-
- before do
- projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce')
- pipelines.create!(project_id: projects.first.id,
- ref: 'some-branch',
- sha: 'abc12345')
-
- # merge request without a pipeline
- create_merge_request(head_pipeline_id: nil)
-
- # merge request with non-existent pipeline
- create_merge_request(head_pipeline_id: 1234)
-
- # merge reqeust with existing pipeline assigned
- create_merge_request(head_pipeline_id: pipelines.first.id)
- end
-
- it 'correctly adds a foreign key to head_pipeline_id' do
- migrate!
-
- expect(merge_requests.first.head_pipeline_id).to be_nil
- expect(merge_requests.second.head_pipeline_id).to be_nil
- expect(merge_requests.third.head_pipeline_id).to eq pipelines.first.id
- end
-
- def create_merge_request(**opts)
- merge_requests.create!(source_project_id: projects.first.id,
- target_project_id: projects.first.id,
- source_branch: 'some-branch',
- target_branch: 'master', **opts)
- end
-end
diff --git a/spec/migrations/add_gitlab_instance_administration_project_spec.rb b/spec/migrations/add_gitlab_instance_administration_project_spec.rb
new file mode 100644
index 00000000000..08e20a4e8ff
--- /dev/null
+++ b/spec/migrations/add_gitlab_instance_administration_project_spec.rb
@@ -0,0 +1,252 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190801072937_add_gitlab_instance_administration_project.rb')
+
+describe AddGitlabInstanceAdministrationProject, :migration do
+ let(:application_settings) { table(:application_settings) }
+ let(:users) { table(:users) }
+ let(:projects) { table(:projects) }
+ let(:namespaces) { table(:namespaces) }
+ let(:members) { table(:members) }
+
+ let(:service_class) do
+ Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService
+ end
+
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ listen_address: 'localhost:9090'
+ }
+ end
+
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+
+ stub_config(prometheus: prometheus_settings)
+ end
+
+ describe 'down' do
+ let!(:application_setting) { application_settings.create! }
+ let!(:user) { users.create!(admin: true, email: 'admin1@example.com', projects_limit: 10, state: :active) }
+
+ it 'deletes group and project' do
+ migrate!
+
+ expect(Project.count).to eq(1)
+ expect(Group.count).to eq(1)
+
+ schema_migrate_down!
+
+ expect(Project.count).to eq(0)
+ expect(Group.count).to eq(0)
+ end
+ end
+
+ describe 'up' do
+ context 'without application_settings' do
+ it 'does not fail' do
+ migrate!
+
+ expect(Project.count).to eq(0)
+ end
+ end
+
+ context 'without admin users' do
+ let!(:application_setting) { application_settings.create! }
+
+ it 'does not fail' do
+ migrate!
+
+ expect(Project.count).to eq(0)
+ end
+ end
+
+ context 'with admin users' do
+ let(:project) { Project.last }
+ let(:group) { Group.last }
+ let!(:application_setting) { application_settings.create! }
+ let!(:user) { users.create!(admin: true, email: 'admin1@example.com', projects_limit: 10, state: :active) }
+
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ shared_examples 'has prometheus service' do |listen_address|
+ it do
+ migrate!
+
+ prometheus = project.prometheus_service
+ expect(prometheus).to be_persisted
+ expect(prometheus).not_to eq(nil)
+ expect(prometheus.api_url).to eq(listen_address)
+ expect(prometheus.active).to eq(true)
+ expect(prometheus.manual_configuration).to eq(true)
+ end
+ end
+
+ it_behaves_like 'has prometheus service', 'http://localhost:9090'
+
+ it 'creates GitLab Instance Administrator group' do
+ migrate!
+
+ expect(group).to be_persisted
+ expect(group.name).to eq('GitLab Instance Administrators')
+ expect(group.path).to start_with('gitlab-instance-administrators')
+ expect(group.path.split('-').last.length).to eq(8)
+ expect(group.visibility_level).to eq(service_class::VISIBILITY_LEVEL)
+ end
+
+ it 'creates project with internal visibility' do
+ migrate!
+
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ expect(project).to be_persisted
+ end
+
+ it 'creates project with correct name and description' do
+ migrate!
+
+ path = 'administration/monitoring/gitlab_instance_administration_project/index'
+ docs_path = Rails.application.routes.url_helpers.help_page_path(path)
+
+ expect(project.name).to eq(service_class::PROJECT_NAME)
+ expect(project.description).to eq(
+ 'This project is automatically generated and will be used to help monitor this GitLab instance. ' \
+ "[More information](#{docs_path})"
+ )
+ expect(File).to exist("doc/#{path}.md")
+ end
+
+ it 'adds all admins as maintainers' do
+ admin1 = users.create!(admin: true, email: 'admin2@example.com', projects_limit: 10, state: :active)
+ admin2 = users.create!(admin: true, email: 'admin3@example.com', projects_limit: 10, state: :active)
+ users.create!(email: 'nonadmin1@example.com', projects_limit: 10, state: :active)
+
+ migrate!
+
+ expect(project.owner).to eq(group)
+ expect(group.members.collect(&:user).collect(&:id)).to contain_exactly(user.id, admin1.id, admin2.id)
+ expect(group.members.collect(&:access_level)).to contain_exactly(
+ Gitlab::Access::OWNER,
+ Gitlab::Access::MAINTAINER,
+ Gitlab::Access::MAINTAINER
+ )
+ end
+
+ it 'saves the project id' do
+ migrate!
+
+ application_setting.reload
+ expect(application_setting.instance_administration_project_id).to eq(project.id)
+ end
+
+ it 'does not fail when a project already exists' do
+ group = namespaces.create!(
+ path: 'gitlab-instance-administrators',
+ name: 'GitLab Instance Administrators',
+ type: 'Group'
+ )
+ project = projects.create!(
+ namespace_id: group.id,
+ name: 'GitLab Instance Administration'
+ )
+
+ admin1 = users.create!(admin: true, email: 'admin4@example.com', projects_limit: 10, state: :active)
+ admin2 = users.create!(admin: true, email: 'admin5@example.com', projects_limit: 10, state: :active)
+
+ members.create!(
+ user_id: admin1.id,
+ source_id: group.id,
+ source_type: 'Namespace',
+ type: 'GroupMember',
+ access_level: GroupMember::MAINTAINER,
+ notification_level: NotificationSetting.levels[:global]
+ )
+ members.create!(
+ user_id: admin2.id,
+ source_id: group.id,
+ source_type: 'Namespace',
+ type: 'GroupMember',
+ access_level: GroupMember::MAINTAINER,
+ notification_level: NotificationSetting.levels[:global]
+ )
+
+ stub_application_setting(instance_administration_project: project)
+
+ migrate!
+
+ expect(Project.last.id).to eq(project.id)
+ expect(Group.last.id).to eq(group.id)
+ end
+
+ context 'when local requests from hooks and services are not allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+ end
+
+ it_behaves_like 'has prometheus service', 'http://localhost:9090'
+
+ it 'does not overwrite the existing whitelist' do
+ application_setting.update!(outbound_local_requests_whitelist: ['example.com'])
+
+ migrate!
+
+ application_setting.reload
+ expect(application_setting.outbound_local_requests_whitelist).to contain_exactly(
+ 'example.com', 'localhost'
+ )
+ end
+ end
+
+ context 'with non default prometheus address' do
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ listen_address: 'https://localhost:9090'
+ }
+ end
+
+ it_behaves_like 'has prometheus service', 'https://localhost:9090'
+ end
+
+ context 'when prometheus setting is not present in gitlab.yml' do
+ before do
+ allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting)
+ end
+
+ it 'does not fail' do
+ migrate!
+
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus setting is disabled in gitlab.yml' do
+ let(:prometheus_settings) do
+ {
+ enable: false,
+ listen_address: 'localhost:9090'
+ }
+ end
+
+ it 'does not configure prometheus' do
+ migrate!
+
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus listen address is blank in gitlab.yml' do
+ let(:prometheus_settings) { { enable: true, listen_address: '' } }
+
+ it 'does not configure prometheus' do
+ migrate!
+
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
deleted file mode 100644
index 13dc62595b5..00000000000
--- a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb')
-
-describe AddHeadPipelineForEachMergeRequest, :migration do
- let(:migration) { described_class.new }
-
- let!(:project) { table(:projects).create! }
- let!(:other_project) { table(:projects).create! }
-
- let!(:pipeline_1) { table(:ci_pipelines).create!(project_id: project.id, ref: "branch_1") }
- let!(:pipeline_2) { table(:ci_pipelines).create!(project_id: other_project.id, ref: "branch_1") }
- let!(:pipeline_3) { table(:ci_pipelines).create!(project_id: other_project.id, ref: "branch_1") }
- let!(:pipeline_4) { table(:ci_pipelines).create!(project_id: project.id, ref: "branch_2") }
-
- let!(:mr_1) { table(:merge_requests).create!(source_project_id: project.id, target_project_id: project.id, source_branch: "branch_1", target_branch: "target_1") }
- let!(:mr_2) { table(:merge_requests).create!(source_project_id: other_project.id, target_project_id: project.id, source_branch: "branch_1", target_branch: "target_2") }
- let!(:mr_3) { table(:merge_requests).create!(source_project_id: project.id, target_project_id: project.id, source_branch: "branch_2", target_branch: "master") }
- let!(:mr_4) { table(:merge_requests).create!(source_project_id: project.id, target_project_id: project.id, source_branch: "branch_3", target_branch: "master") }
-
- context "#up" do
- context "when source_project and source_branch of pipeline are the same of merge request" do
- it "sets head_pipeline_id of given merge requests" do
- migration.up
-
- expect(mr_1.reload.head_pipeline_id).to eq(pipeline_1.id)
- expect(mr_2.reload.head_pipeline_id).to eq(pipeline_3.id)
- expect(mr_3.reload.head_pipeline_id).to eq(pipeline_4.id)
- expect(mr_4.reload.head_pipeline_id).to be_nil
- end
- end
- end
-end
diff --git a/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb b/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb
new file mode 100644
index 00000000000..9cae1daacea
--- /dev/null
+++ b/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190628185004_backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table.rb')
+
+describe BackfillAndAddNotNullConstraintToReleasedAtColumnOnReleasesTable, :migration do
+ let(:releases) { table(:releases) }
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+
+ subject(:migration) { described_class.new }
+
+ it 'fills released_at with the value of created_at' do
+ created_at_a = Time.zone.parse('2019-02-10T08:00:00Z')
+ created_at_b = Time.zone.parse('2019-03-10T18:00:00Z')
+ namespace = namespaces.create(name: 'foo', path: 'foo')
+ project = projects.create!(namespace_id: namespace.id)
+ release_a = releases.create!(project_id: project.id, created_at: created_at_a)
+ release_b = releases.create!(project_id: project.id, created_at: created_at_b)
+
+ disable_migrations_output { migration.up }
+
+ release_a.reload
+ release_b.reload
+ expect(release_a.released_at).to eq(created_at_a)
+ expect(release_b.released_at).to eq(created_at_b)
+ end
+end
diff --git a/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb b/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb
index 34f4a36d63d..65a918d5440 100644
--- a/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb
+++ b/spec/migrations/backfill_store_project_full_path_in_repo_spec.rb
@@ -13,7 +13,7 @@ describe BackfillStoreProjectFullPathInRepo, :migration do
subject(:migration) { described_class.new }
around do |example|
- Sidekiq::Testing.inline! do
+ perform_enqueued_jobs do
example.run
end
end
diff --git a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
deleted file mode 100644
index 09c78d02890..00000000000
--- a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170803090603_calculate_conv_dev_index_percentages.rb')
-
-describe CalculateConvDevIndexPercentages, :migration do
- let(:migration) { described_class.new }
- let!(:conv_dev_index) do
- table(:conversational_development_index_metrics).create!(
- leader_issues: 9.256,
- leader_notes: 0,
- leader_milestones: 16.2456,
- leader_boards: 5.2123,
- leader_merge_requests: 1.2,
- leader_ci_pipelines: 12.1234,
- leader_environments: 3.3333,
- leader_deployments: 1.200,
- leader_projects_prometheus_active: 0.111,
- leader_service_desk_issues: 15.891,
- instance_issues: 1.234,
- instance_notes: 28.123,
- instance_milestones: 0,
- instance_boards: 3.254,
- instance_merge_requests: 0.6,
- instance_ci_pipelines: 2.344,
- instance_environments: 2.2222,
- instance_deployments: 0.771,
- instance_projects_prometheus_active: 0.109,
- instance_service_desk_issues: 13.345,
- percentage_issues: 0,
- percentage_notes: 0,
- percentage_milestones: 0,
- percentage_boards: 0,
- percentage_merge_requests: 0,
- percentage_ci_pipelines: 0,
- percentage_environments: 0,
- percentage_deployments: 0,
- percentage_projects_prometheus_active: 0,
- percentage_service_desk_issues: 0)
- end
-
- describe '#up' do
- it 'calculates percentages correctly' do
- migration.up
- conv_dev_index.reload
-
- expect(conv_dev_index.percentage_issues).to be_within(0.1).of(13.3)
- expect(conv_dev_index.percentage_notes).to be_zero # leader 0
- expect(conv_dev_index.percentage_milestones).to be_zero # instance 0
- expect(conv_dev_index.percentage_boards).to be_within(0.1).of(62.4)
- expect(conv_dev_index.percentage_merge_requests).to eq(50.0)
- expect(conv_dev_index.percentage_ci_pipelines).to be_within(0.1).of(19.3)
- expect(conv_dev_index.percentage_environments).to be_within(0.1).of(66.7)
- expect(conv_dev_index.percentage_deployments).to be_within(0.1).of(64.2)
- expect(conv_dev_index.percentage_projects_prometheus_active).to be_within(0.1).of(98.2)
- expect(conv_dev_index.percentage_service_desk_issues).to be_within(0.1).of(84.0)
- end
- end
-end
diff --git a/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb b/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb
new file mode 100644
index 00000000000..232f6f090c3
--- /dev/null
+++ b/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190725012225_change_outbound_local_requests_whitelist_default.rb')
+
+describe ChangeOutboundLocalRequestsWhitelistDefault, :migration do
+ let(:application_settings) { table(:application_settings) }
+
+ it 'defaults to empty array' do
+ setting = application_settings.create!
+ setting_with_value = application_settings.create!(outbound_local_requests_whitelist: '{a,b}')
+
+ expect(application_settings.where(outbound_local_requests_whitelist: nil).count).to eq(1)
+
+ migrate!
+
+ expect(application_settings.where(outbound_local_requests_whitelist: nil).count).to eq(0)
+ expect(setting.reload.outbound_local_requests_whitelist).to eq([])
+ expect(setting_with_value.reload.outbound_local_requests_whitelist).to eq(%w[a b])
+ end
+end
diff --git a/spec/migrations/clean_appearance_symlinks_spec.rb b/spec/migrations/clean_appearance_symlinks_spec.rb
deleted file mode 100644
index 9225dc0d894..00000000000
--- a/spec/migrations/clean_appearance_symlinks_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170613111224_clean_appearance_symlinks.rb')
-
-describe CleanAppearanceSymlinks do
- let(:migration) { described_class.new }
- let(:test_dir) { File.join(Rails.root, "tmp", "tests", "clean_appearance_test") }
- let(:uploads_dir) { File.join(test_dir, "public", "uploads") }
- let(:new_uploads_dir) { File.join(uploads_dir, "system") }
- let(:original_path) { File.join(new_uploads_dir, 'appearance') }
- let(:symlink_path) { File.join(uploads_dir, 'appearance') }
-
- before do
- FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
- FileUtils.mkdir_p(uploads_dir)
- allow(migration).to receive(:base_directory).and_return(test_dir)
- allow(migration).to receive(:say)
- end
-
- describe "#up" do
- before do
- FileUtils.mkdir_p(original_path)
- FileUtils.ln_s(original_path, symlink_path)
- end
-
- it 'removes the symlink' do
- migration.up
-
- expect(File.symlink?(symlink_path)).to be(false)
- end
- end
-
- describe '#down' do
- before do
- FileUtils.mkdir_p(File.join(original_path))
- FileUtils.touch(File.join(original_path, 'dummy.file'))
- end
-
- it 'creates a symlink' do
- expected_path = File.join(symlink_path, "dummy.file")
- migration.down
-
- expect(File.exist?(expected_path)).to be(true)
- expect(File.symlink?(symlink_path)).to be(true)
- end
- end
-end
diff --git a/spec/migrations/clean_stage_id_reference_migration_spec.rb b/spec/migrations/clean_stage_id_reference_migration_spec.rb
deleted file mode 100644
index 9a581df28a2..00000000000
--- a/spec/migrations/clean_stage_id_reference_migration_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'migrate', '20170710083355_clean_stage_id_reference_migration.rb')
-
-describe CleanStageIdReferenceMigration, :migration, :sidekiq, :redis do
- let(:migration_class) { 'MigrateBuildStageIdReference' }
- let(:migration) { spy('migration') }
-
- before do
- allow(Gitlab::BackgroundMigration.const_get(migration_class))
- .to receive(:new).and_return(migration)
- end
-
- context 'when there are pending background migrations' do
- it 'processes pending jobs synchronously' do
- Sidekiq::Testing.disable! do
- BackgroundMigrationWorker.perform_in(2.minutes, migration_class, [1, 1])
- BackgroundMigrationWorker.perform_async(migration_class, [1, 1])
-
- migrate!
-
- expect(migration).to have_received(:perform).with(1, 1).twice
- end
- end
- end
- context 'when there are no background migrations pending' do
- it 'does nothing' do
- Sidekiq::Testing.disable! do
- migrate!
-
- expect(migration).not_to have_received(:perform)
- end
- end
- end
-end
diff --git a/spec/migrations/clean_stages_statuses_migration_spec.rb b/spec/migrations/clean_stages_statuses_migration_spec.rb
deleted file mode 100644
index 38705f8eaae..00000000000
--- a/spec/migrations/clean_stages_statuses_migration_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'migrate', '20170912113435_clean_stages_statuses_migration.rb')
-
-describe CleanStagesStatusesMigration, :migration, :sidekiq, :redis do
- let(:migration) { spy('migration') }
-
- before do
- allow(Gitlab::BackgroundMigration::MigrateStageStatus)
- .to receive(:new).and_return(migration)
- end
-
- context 'when there are pending background migrations' do
- it 'processes pending jobs synchronously' do
- Sidekiq::Testing.disable! do
- BackgroundMigrationWorker
- .perform_in(2.minutes, 'MigrateStageStatus', [1, 1])
- BackgroundMigrationWorker
- .perform_async('MigrateStageStatus', [1, 1])
-
- migrate!
-
- expect(migration).to have_received(:perform).with(1, 1).twice
- end
- end
- end
-
- context 'when there are no background migrations pending' do
- it 'does nothing' do
- Sidekiq::Testing.disable! do
- migrate!
-
- expect(migration).not_to have_received(:perform)
- end
- end
- end
-
- context 'when there are still unmigrated stages afterwards' do
- let(:stages) { table('ci_stages') }
-
- before do
- stages.create!(status: nil, name: 'build')
- stages.create!(status: nil, name: 'test')
- end
-
- it 'migrates statuses sequentially in batches' do
- migrate!
-
- expect(migration).to have_received(:perform).once
- end
- end
-end
diff --git a/spec/migrations/clean_upload_symlinks_spec.rb b/spec/migrations/clean_upload_symlinks_spec.rb
deleted file mode 100644
index 26653b9c008..00000000000
--- a/spec/migrations/clean_upload_symlinks_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170406111121_clean_upload_symlinks.rb')
-
-describe CleanUploadSymlinks do
- let(:migration) { described_class.new }
- let(:test_dir) { File.join(Rails.root, "tmp", "tests", "move_uploads_test") }
- let(:uploads_dir) { File.join(test_dir, "public", "uploads") }
- let(:new_uploads_dir) { File.join(uploads_dir, "-", "system") }
- let(:original_path) { File.join(new_uploads_dir, 'user') }
- let(:symlink_path) { File.join(uploads_dir, 'user') }
-
- before do
- FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
- FileUtils.mkdir_p(uploads_dir)
- allow(migration).to receive(:base_directory).and_return(test_dir)
- allow(migration).to receive(:say)
- end
-
- describe "#up" do
- before do
- FileUtils.mkdir_p(original_path)
- FileUtils.ln_s(original_path, symlink_path)
- end
-
- it 'removes the symlink' do
- migration.up
-
- expect(File.symlink?(symlink_path)).to be(false)
- end
- end
-
- describe '#down' do
- before do
- FileUtils.mkdir_p(File.join(original_path))
- FileUtils.touch(File.join(original_path, 'dummy.file'))
- end
-
- it 'creates a symlink' do
- expected_path = File.join(symlink_path, "dummy.file")
- migration.down
-
- expect(File.exist?(expected_path)).to be(true)
- expect(File.symlink?(symlink_path)).to be(true)
- end
- end
-end
diff --git a/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb b/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb
deleted file mode 100644
index 3a9fa8c7113..00000000000
--- a/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'spec_helper'
-require Rails.root.join("db", "post_migrate", "20170717111152_cleanup_move_system_upload_folder_symlink.rb")
-
-describe CleanupMoveSystemUploadFolderSymlink do
- let(:migration) { described_class.new }
- let(:test_base) { File.join(Rails.root, 'tmp', 'tests', 'move-system-upload-folder') }
- let(:test_folder) { File.join(test_base, '-', 'system') }
-
- before do
- allow(migration).to receive(:base_directory).and_return(test_base)
- FileUtils.rm_rf(test_base)
- FileUtils.mkdir_p(test_folder)
- allow(migration).to receive(:say)
- end
-
- describe '#up' do
- before do
- FileUtils.ln_s(test_folder, File.join(test_base, 'system'))
- end
-
- it 'removes the symlink' do
- migration.up
-
- expect(File.exist?(File.join(test_base, 'system'))).to be_falsey
- end
- end
-
- describe '#down' do
- it 'creates the symlink' do
- migration.down
-
- expect(File.symlink?(File.join(test_base, 'system'))).to be_truthy
- end
- end
-end
diff --git a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb b/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
deleted file mode 100644
index 0e6bded29b4..00000000000
--- a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170816102555_cleanup_nonexisting_namespace_pending_delete_projects.rb')
-
-describe CleanupNonexistingNamespacePendingDeleteProjects, :migration do
- let(:projects) { table(:projects) }
- let(:namespaces) { table(:namespaces) }
-
- describe '#up' do
- let!(:some_project) { projects.create! }
- let(:namespace) { namespaces.create!(name: 'test', path: 'test') }
-
- it 'only cleans up when namespace does not exist' do
- projects.create!(pending_delete: true, namespace_id: namespace.id)
- project = projects.create!(pending_delete: true, namespace_id: 0)
-
- expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]])
-
- described_class.new.up
- end
-
- it 'does nothing when no pending delete projects without namespace found' do
- projects.create!(pending_delete: true, namespace_id: namespace.id)
-
- expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async)
-
- described_class.new.up
- end
- end
-end
diff --git a/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb b/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb
deleted file mode 100644
index d1bf6bdf9d6..00000000000
--- a/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170607121233_convert_custom_notification_settings_to_columns')
-
-describe ConvertCustomNotificationSettingsToColumns, :migration do
- let(:user_class) { table(:users) }
-
- let(:settings_params) do
- [
- { level: 0, events: [:new_note] }, # disabled, single event
- { level: 3, events: [:new_issue, :reopen_issue, :close_issue, :reassign_issue] }, # global, multiple events
- { level: 5, events: described_class::EMAIL_EVENTS }, # custom, all events
- { level: 5, events: [] } # custom, no events
- ]
- end
-
- let(:notification_settings_before) do
- settings_params.map do |params|
- events = {}
-
- params[:events].each do |event|
- events[event] = true
- end
-
- user = user_class.create!(email: "user-#{SecureRandom.hex}@example.org", username: "user-#{SecureRandom.hex}", encrypted_password: '12345678')
- create_params = { user_id: user.id, level: params[:level], events: events }
- notification_setting = described_class::NotificationSetting.create(create_params)
-
- [notification_setting, params]
- end
- end
-
- let(:notification_settings_after) do
- settings_params.map do |params|
- events = {}
-
- params[:events].each do |event|
- events[event] = true
- end
-
- user = user_class.create!(email: "user-#{SecureRandom.hex}@example.org", username: "user-#{SecureRandom.hex}", encrypted_password: '12345678')
- create_params = events.merge(user_id: user.id, level: params[:level])
- notification_setting = described_class::NotificationSetting.create(create_params)
-
- [notification_setting, params]
- end
- end
-
- describe '#up' do
- it 'migrates all settings where a custom event is enabled, even if they are not currently using the custom level' do
- notification_settings_before
-
- described_class.new.up
-
- notification_settings_before.each do |(notification_setting, params)|
- notification_setting.reload
-
- expect(notification_setting.read_attribute_before_type_cast(:events)).to be_nil
- expect(notification_setting.level).to eq(params[:level])
-
- described_class::EMAIL_EVENTS.each do |event|
- # We don't set the others to false, just let them default to nil
- expected = params[:events].include?(event) || nil
-
- expect(notification_setting.read_attribute(event)).to eq(expected)
- end
- end
- end
- end
-
- describe '#down' do
- it 'creates a custom events hash for all settings where at least one event is enabled' do
- notification_settings_after
-
- described_class.new.down
-
- notification_settings_after.each do |(notification_setting, params)|
- notification_setting.reload
-
- expect(notification_setting.level).to eq(params[:level])
-
- if params[:events].empty?
- # We don't migrate empty settings
- expect(notification_setting.events).to eq({})
- else
- described_class::EMAIL_EVENTS.each do |event|
- expected = params[:events].include?(event)
-
- expect(notification_setting.events[event]).to eq(expected)
- expect(notification_setting.read_attribute(event)).to be_nil
- end
- end
- end
- end
-
- it 'reverts the database to the state it was in before' do
- notification_settings_before
-
- described_class.new.up
- described_class.new.down
-
- notification_settings_before.each do |(notification_setting, params)|
- notification_setting.reload
-
- expect(notification_setting.level).to eq(params[:level])
-
- if params[:events].empty?
- # We don't migrate empty settings
- expect(notification_setting.events).to eq({})
- else
- described_class::EMAIL_EVENTS.each do |event|
- expected = params[:events].include?(event)
-
- expect(notification_setting.events[event]).to eq(expected)
- expect(notification_setting.read_attribute(event)).to be_nil
- end
- end
- end
- end
- end
-end
diff --git a/spec/migrations/delete_conflicting_redirect_routes_spec.rb b/spec/migrations/delete_conflicting_redirect_routes_spec.rb
deleted file mode 100644
index 8a191bd7139..00000000000
--- a/spec/migrations/delete_conflicting_redirect_routes_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170907170235_delete_conflicting_redirect_routes')
-
-describe DeleteConflictingRedirectRoutes, :migration, :sidekiq do
- let!(:redirect_routes) { table(:redirect_routes) }
- let!(:routes) { table(:routes) }
-
- around do |example|
- Timecop.freeze { example.run }
- end
-
- before do
- routes.create!(id: 1, source_id: 1, source_type: 'Namespace', path: 'foo1')
- routes.create!(id: 2, source_id: 2, source_type: 'Namespace', path: 'foo2')
- routes.create!(id: 3, source_id: 3, source_type: 'Namespace', path: 'foo3')
- routes.create!(id: 4, source_id: 4, source_type: 'Namespace', path: 'foo4')
- routes.create!(id: 5, source_id: 5, source_type: 'Namespace', path: 'foo5')
-
- # Valid redirects
- redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar')
- redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar2')
- redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'bar3')
-
- # Conflicting redirects
- redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'foo1')
- redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo2')
- redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo3')
- redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo4')
- redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo5')
- end
-
- # No-op. See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252
- it 'NO-OP: does not schedule any background migrations' do
- Sidekiq::Testing.fake! do
- Timecop.freeze do
- migrate!
-
- expect(BackgroundMigrationWorker.jobs.size).to eq 0
- end
- end
- end
-end
diff --git a/spec/migrations/encrypt_deploy_tokens_tokens_spec.rb b/spec/migrations/encrypt_deploy_tokens_tokens_spec.rb
new file mode 100644
index 00000000000..a398e079731
--- /dev/null
+++ b/spec/migrations/encrypt_deploy_tokens_tokens_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require Rails.root.join('db', 'post_migrate', '20190711201818_encrypt_deploy_tokens_tokens.rb')
+
+describe EncryptDeployTokensTokens, :migration do
+ let(:migration) { described_class.new }
+ let(:deployment_tokens) { table(:deploy_tokens) }
+ let(:plaintext) { "secret-token" }
+ let(:expires_at) { DateTime.now + 1.year }
+ let(:ciphertext) { Gitlab::CryptoHelper.aes256_gcm_encrypt(plaintext) }
+
+ describe '#up' do
+ it 'keeps plaintext token the same and populates token_encrypted if not present' do
+ deploy_token = deployment_tokens.create!(
+ name: 'test_token',
+ read_repository: true,
+ expires_at: expires_at,
+ username: 'gitlab-token-1',
+ token: plaintext
+ )
+
+ migration.up
+
+ expect(deploy_token.reload.token).to eq(plaintext)
+ expect(deploy_token.reload.token_encrypted).to eq(ciphertext)
+ end
+ end
+
+ describe '#down' do
+ it 'decrypts encrypted token and saves it' do
+ deploy_token = deployment_tokens.create!(
+ name: 'test_token',
+ read_repository: true,
+ expires_at: expires_at,
+ username: 'gitlab-token-1',
+ token_encrypted: ciphertext
+ )
+
+ migration.down
+
+ expect(deploy_token.reload.token).to eq(plaintext)
+ expect(deploy_token.reload.token_encrypted).to eq(ciphertext)
+ end
+ end
+end
diff --git a/spec/migrations/fix_wrong_pages_access_level_spec.rb b/spec/migrations/fix_wrong_pages_access_level_spec.rb
new file mode 100644
index 00000000000..75ac5d919b2
--- /dev/null
+++ b/spec/migrations/fix_wrong_pages_access_level_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190703185326_fix_wrong_pages_access_level.rb')
+
+describe FixWrongPagesAccessLevel, :migration, :sidekiq, schema: 20190628185004 do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:migration_class) { described_class::MIGRATION }
+ let(:migration_name) { migration_class.to_s.demodulize }
+
+ project_class = ::Gitlab::BackgroundMigration::FixPagesAccessLevel::Project
+ feature_class = ::Gitlab::BackgroundMigration::FixPagesAccessLevel::ProjectFeature
+
+ let(:namespaces_table) { table(:namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:features_table) { table(:project_features) }
+
+ let(:subgroup) do
+ root_group = namespaces_table.create(path: "group", name: "group")
+ namespaces_table.create!(path: "subgroup", name: "group", parent_id: root_group.id)
+ end
+
+ def create_project_feature(path, project_visibility, pages_access_level)
+ project = projects_table.create!(path: path, visibility_level: project_visibility,
+ namespace_id: subgroup.id)
+ features_table.create!(project_id: project.id, pages_access_level: pages_access_level)
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ first_id = create_project_feature("project1", project_class::PRIVATE, feature_class::PRIVATE).id
+ last_id = create_project_feature("project2", project_class::PRIVATE, feature_class::PUBLIC).id
+
+ migrate!
+
+ expect(migration_name).to be_scheduled_delayed_migration(2.minutes, first_id, last_id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(1)
+ end
+ end
+ end
+
+ def expect_migration
+ expect do
+ perform_enqueued_jobs do
+ migrate!
+ end
+ end
+ end
+
+ where(:project_visibility, :pages_access_level, :access_control_is_enabled,
+ :pages_deployed, :resulting_pages_access_level) do
+ # update settings for public projects regardless of access_control being enabled
+ project_class::PUBLIC | feature_class::PUBLIC | true | true | feature_class::ENABLED
+ project_class::PUBLIC | feature_class::PUBLIC | false | true | feature_class::ENABLED
+ # don't update public level for private and internal projects
+ project_class::PRIVATE | feature_class::PUBLIC | true | true | feature_class::PUBLIC
+ project_class::INTERNAL | feature_class::PUBLIC | true | true | feature_class::PUBLIC
+
+ # if access control is disabled but pages are deployed we make them public
+ project_class::INTERNAL | feature_class::ENABLED | false | true | feature_class::PUBLIC
+ # don't change anything if one of the conditions is not satisfied
+ project_class::INTERNAL | feature_class::ENABLED | true | true | feature_class::ENABLED
+ project_class::INTERNAL | feature_class::ENABLED | true | false | feature_class::ENABLED
+
+ # private projects
+ # if access control is enabled update pages_access_level to private regardless of deployment
+ project_class::PRIVATE | feature_class::ENABLED | true | true | feature_class::PRIVATE
+ project_class::PRIVATE | feature_class::ENABLED | true | false | feature_class::PRIVATE
+ # if access control is disabled and pages are deployed update pages_access_level to public
+ project_class::PRIVATE | feature_class::ENABLED | false | true | feature_class::PUBLIC
+ # if access control is disabled but pages aren't deployed update pages_access_level to private
+ project_class::PRIVATE | feature_class::ENABLED | false | false | feature_class::PRIVATE
+ end
+
+ with_them do
+ let!(:project_feature) do
+ create_project_feature("projectpath", project_visibility, pages_access_level)
+ end
+
+ before do
+ tested_path = File.join(Settings.pages.path, "group/subgroup/projectpath", "public")
+ allow(Dir).to receive(:exist?).with(tested_path).and_return(pages_deployed)
+
+ stub_pages_setting(access_control: access_control_is_enabled)
+ end
+
+ it "sets proper pages_access_level" do
+ expect(project_feature.reload.pages_access_level).to eq(pages_access_level)
+
+ perform_enqueued_jobs do
+ migrate!
+ end
+
+ expect(project_feature.reload.pages_access_level).to eq(resulting_pages_access_level)
+ end
+ end
+end
diff --git a/spec/migrations/fix_wrongly_renamed_routes_spec.rb b/spec/migrations/fix_wrongly_renamed_routes_spec.rb
deleted file mode 100644
index 543cf55f076..00000000000
--- a/spec/migrations/fix_wrongly_renamed_routes_spec.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170518231126_fix_wrongly_renamed_routes.rb')
-
-describe FixWronglyRenamedRoutes, :migration do
- let(:subject) { described_class.new }
- let(:namespaces_table) { table(:namespaces) }
- let(:projects_table) { table(:projects) }
- let(:routes_table) { table(:routes) }
- let(:broken_namespace) do
- namespaces_table.create!(name: 'apiis', path: 'apiis').tap do |namespace|
- routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'api0is', path: 'api0is')
- end
- end
- let(:broken_namespace_route) { routes_table.where(source_type: 'Namespace', source_id: broken_namespace.id).first }
-
- describe '#wrongly_renamed' do
- it "includes routes that have names that don't match their namespace" do
- broken_namespace
- other_namespace = namespaces_table.create!(name: 'api0', path: 'api0')
- routes_table.create!(source_type: 'Namespace', source_id: other_namespace.id, name: 'api0', path: 'api0')
-
- expect(subject.wrongly_renamed.map(&:id))
- .to contain_exactly(broken_namespace_route.id)
- end
- end
-
- describe "#paths_and_corrections" do
- it 'finds the wrong path and gets the correction from the namespace' do
- broken_namespace
- namespaces_table.create!(name: 'uploads-test', path: 'uploads-test').tap do |namespace|
- routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'uploads-test', path: 'uploads0-test')
- end
-
- expected_result = [
- { 'namespace_path' => 'apiis', 'path' => 'api0is' },
- { 'namespace_path' => 'uploads-test', 'path' => 'uploads0-test' }
- ]
-
- expect(subject.paths_and_corrections).to include(*expected_result)
- end
- end
-
- describe '#routes_in_namespace_query' do
- it 'includes only the required routes' do
- namespace = namespaces_table.create!(name: 'hello', path: 'hello')
- namespace_route = routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'hello', path: 'hello')
- project = projects_table.new(name: 'my-project', path: 'my-project', namespace_id: namespace.id).tap do |project|
- project.save!(validate: false)
- end
- routes_table.create!(source_type: 'Project', source_id: project.id, name: 'my-project', path: 'hello/my-project')
- _other_namespace = namespaces_table.create!(name: 'hello0', path: 'hello0')
-
- result = routes_table.where(subject.routes_in_namespace_query('hello'))
- project_route = routes_table.where(source_type: 'Project', source_id: project.id).first
-
- expect(result).to contain_exactly(namespace_route, project_route)
- end
- end
-
- describe '#up' do
- it 'renames incorrectly named routes' do
- broken_project =
- projects_table.new(name: 'broken-project', path: 'broken-project', namespace_id: broken_namespace.id).tap do |project|
- project.save!(validate: false)
- routes_table.create!(source_type: 'Project', source_id: project.id, name: 'broken-project', path: 'api0is/broken-project')
- end
-
- subject.up
-
- broken_project_route = routes_table.where(source_type: 'Project', source_id: broken_project.id).first
-
- expect(broken_project_route.path).to eq('apiis/broken-project')
- expect(broken_namespace_route.reload.path).to eq('apiis')
- end
-
- it "doesn't touch namespaces that look like something that should be renamed" do
- namespaces_table.create!(name: 'apiis', path: 'apiis')
- namespace = namespaces_table.create!(name: 'hello', path: 'api0')
- namespace_route = routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'hello', path: 'api0')
-
- subject.up
-
- expect(namespace_route.reload.path).to eq('api0')
- end
- end
-end
diff --git a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb b/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
deleted file mode 100644
index 71a4e71ac8a..00000000000
--- a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'migrate', '20171106151218_issues_moved_to_id_foreign_key.rb')
-
-describe IssuesMovedToIdForeignKey, :migration do
- let(:issues) { table(:issues) }
-
- let!(:issue_third) { issues.create! }
- let!(:issue_second) { issues.create!(moved_to_id: issue_third.id) }
- let!(:issue_first) { issues.create!(moved_to_id: issue_second.id) }
-
- subject { described_class.new }
-
- it 'removes the orphaned moved_to_id' do
- subject.down
-
- issue_third.update!(moved_to_id: 0)
-
- subject.up
-
- expect(issue_first.reload.moved_to_id).to eq(issue_second.id)
- expect(issue_second.reload.moved_to_id).to eq(issue_third.id)
- expect(issue_third.reload.moved_to_id).to be_nil
- end
-end
diff --git a/spec/migrations/migrate_build_stage_reference_again_spec.rb b/spec/migrations/migrate_build_stage_reference_again_spec.rb
deleted file mode 100644
index 6be480ce58e..00000000000
--- a/spec/migrations/migrate_build_stage_reference_again_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170526190000_migrate_build_stage_reference_again.rb')
-
-describe MigrateBuildStageReferenceAgain, :migration do
- ##
- # Create test data - pipeline and CI/CD jobs.
- #
-
- let(:jobs) { table(:ci_builds) }
- let(:stages) { table(:ci_stages) }
- let(:pipelines) { table(:ci_pipelines) }
- let(:projects) { table(:projects) }
-
- before do
- # Create projects
- #
- projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
- projects.create!(id: 456, name: 'gitlab2', path: 'gitlab2')
-
- # Create CI/CD pipelines
- #
- pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
- pipelines.create!(id: 2, project_id: 456, ref: 'feature', sha: '21a3deb')
-
- # Create CI/CD jobs
- #
- jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
- jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
- jobs.create!(id: 3, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test')
- jobs.create!(id: 4, commit_id: 1, project_id: 123, stage_idx: 3, stage: 'deploy')
- jobs.create!(id: 5, commit_id: 2, project_id: 456, stage_idx: 2, stage: 'test:2')
- jobs.create!(id: 6, commit_id: 2, project_id: 456, stage_idx: 1, stage: 'test:1')
- jobs.create!(id: 7, commit_id: 2, project_id: 456, stage_idx: 1, stage: 'test:1')
- jobs.create!(id: 8, commit_id: 3, project_id: 789, stage_idx: 3, stage: 'deploy')
-
- # Create CI/CD stages
- #
- stages.create(id: 101, pipeline_id: 1, project_id: 123, name: 'test')
- stages.create(id: 102, pipeline_id: 1, project_id: 123, name: 'build')
- stages.create(id: 103, pipeline_id: 1, project_id: 123, name: 'deploy')
- stages.create(id: 104, pipeline_id: 2, project_id: 456, name: 'test:1')
- stages.create(id: 105, pipeline_id: 2, project_id: 456, name: 'test:2')
- stages.create(id: 106, pipeline_id: 2, project_id: 456, name: 'deploy')
- end
-
- it 'correctly migrate build stage references' do
- expect(jobs.where(stage_id: nil).count).to eq 8
-
- migrate!
-
- expect(jobs.where(stage_id: nil).count).to eq 1
-
- expect(jobs.find(1).stage_id).to eq 102
- expect(jobs.find(2).stage_id).to eq 102
- expect(jobs.find(3).stage_id).to eq 101
- expect(jobs.find(4).stage_id).to eq 103
- expect(jobs.find(5).stage_id).to eq 105
- expect(jobs.find(6).stage_id).to eq 104
- expect(jobs.find(7).stage_id).to eq 104
- expect(jobs.find(8).stage_id).to eq nil
- end
-end
diff --git a/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb b/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb
deleted file mode 100644
index ba4c66057d4..00000000000
--- a/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb
+++ /dev/null
@@ -1,181 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20171013104327_migrate_gcp_clusters_to_new_clusters_architectures.rb')
-
-describe MigrateGcpClustersToNewClustersArchitectures, :migration do
- let(:projects) { table(:projects) }
- let(:project) { projects.create }
- let(:users) { table(:users) }
- let(:user) { users.create! }
- let(:service) { GcpMigrationSpec::KubernetesService.create!(project_id: project.id) }
-
- module GcpMigrationSpec
- class KubernetesService < ActiveRecord::Base
- self.table_name = 'services'
-
- serialize :properties, JSON
-
- default_value_for :active, true
- default_value_for :type, 'KubernetesService'
- default_value_for :properties, {
- api_url: 'https://kubernetes.example.com',
- token: 'a' * 40
- }
- end
- end
-
- context 'when cluster is being created' do
- let(:project_id) { project.id }
- let(:user_id) { user.id }
- let(:service_id) { service.id }
- let(:status) { 2 } # creating
- let(:gcp_cluster_size) { 1 }
- let(:created_at) { "'2017-10-17 20:24:02'" }
- let(:updated_at) { "'2017-10-17 20:28:44'" }
- let(:enabled) { true }
- let(:status_reason) { "''" }
- let(:project_namespace) { "'sample-app'" }
- let(:endpoint) { 'NULL' }
- let(:ca_cert) { 'NULL' }
- let(:encrypted_kubernetes_token) { 'NULL' }
- let(:encrypted_kubernetes_token_iv) { 'NULL' }
- let(:username) { 'NULL' }
- let(:encrypted_password) { 'NULL' }
- let(:encrypted_password_iv) { 'NULL' }
- let(:gcp_project_id) { "'gcp_project_id'" }
- let(:gcp_cluster_zone) { "'gcp_cluster_zone'" }
- let(:gcp_cluster_name) { "'gcp_cluster_name'" }
- let(:gcp_machine_type) { "'gcp_machine_type'" }
- let(:gcp_operation_id) { 'NULL' }
- let(:encrypted_gcp_token) { "'encrypted_gcp_token'" }
- let(:encrypted_gcp_token_iv) { "'encrypted_gcp_token_iv'" }
-
- let(:cluster) { described_class::Cluster.last }
- let(:cluster_id) { cluster.id }
-
- before do
- ActiveRecord::Base.connection.execute <<-SQL
- INSERT INTO gcp_clusters (project_id, user_id, service_id, status, gcp_cluster_size, created_at, updated_at, enabled, status_reason, project_namespace, endpoint, ca_cert, encrypted_kubernetes_token, encrypted_kubernetes_token_iv, username, encrypted_password, encrypted_password_iv, gcp_project_id, gcp_cluster_zone, gcp_cluster_name, gcp_machine_type, gcp_operation_id, encrypted_gcp_token, encrypted_gcp_token_iv)
- VALUES (#{project_id}, #{user_id}, #{service_id}, #{status}, #{gcp_cluster_size}, #{created_at}, #{updated_at}, #{enabled}, #{status_reason}, #{project_namespace}, #{endpoint}, #{ca_cert}, #{encrypted_kubernetes_token}, #{encrypted_kubernetes_token_iv}, #{username}, #{encrypted_password}, #{encrypted_password_iv}, #{gcp_project_id}, #{gcp_cluster_zone}, #{gcp_cluster_name}, #{gcp_machine_type}, #{gcp_operation_id}, #{encrypted_gcp_token}, #{encrypted_gcp_token_iv});
- SQL
- end
-
- it 'correctly migrate to new clusters architectures' do
- migrate!
-
- expect(described_class::Cluster.count).to eq(1)
- expect(described_class::ClustersProject.count).to eq(1)
- expect(described_class::ProvidersGcp.count).to eq(1)
- expect(described_class::PlatformsKubernetes.count).to eq(1)
-
- expect(cluster.user_id).to eq(user.id)
- expect(cluster.enabled).to be_truthy
- expect(cluster.name).to eq(gcp_cluster_name.delete!("'"))
- expect(cluster.provider_type).to eq('gcp')
- expect(cluster.platform_type).to eq('kubernetes')
-
- expect(cluster.project_ids).to include(project.id)
-
- expect(cluster.provider_gcp.cluster_id).to eq(cluster.id)
- expect(cluster.provider_gcp.status).to eq(status)
- expect(cluster.provider_gcp.status_reason).to eq(tr(status_reason))
- expect(cluster.provider_gcp.gcp_project_id).to eq(tr(gcp_project_id))
- expect(cluster.provider_gcp.zone).to eq(tr(gcp_cluster_zone))
- expect(cluster.provider_gcp.num_nodes).to eq(gcp_cluster_size)
- expect(cluster.provider_gcp.machine_type).to eq(tr(gcp_machine_type))
- expect(cluster.provider_gcp.operation_id).to be_nil
- expect(cluster.provider_gcp.endpoint).to be_nil
- expect(cluster.provider_gcp.encrypted_access_token).to eq(tr(encrypted_gcp_token))
- expect(cluster.provider_gcp.encrypted_access_token_iv).to eq(tr(encrypted_gcp_token_iv))
-
- expect(cluster.platform_kubernetes.cluster_id).to eq(cluster.id)
- expect(cluster.platform_kubernetes.api_url).to be_nil
- expect(cluster.platform_kubernetes.ca_cert).to be_nil
- expect(cluster.platform_kubernetes.namespace).to eq(tr(project_namespace))
- expect(cluster.platform_kubernetes.username).to be_nil
- expect(cluster.platform_kubernetes.encrypted_password).to be_nil
- expect(cluster.platform_kubernetes.encrypted_password_iv).to be_nil
- expect(cluster.platform_kubernetes.encrypted_token).to be_nil
- expect(cluster.platform_kubernetes.encrypted_token_iv).to be_nil
- end
- end
-
- context 'when cluster has been created' do
- let(:project_id) { project.id }
- let(:user_id) { user.id }
- let(:service_id) { service.id }
- let(:status) { 3 } # created
- let(:gcp_cluster_size) { 1 }
- let(:created_at) { "'2017-10-17 20:24:02'" }
- let(:updated_at) { "'2017-10-17 20:28:44'" }
- let(:enabled) { true }
- let(:status_reason) { "'general error'" }
- let(:project_namespace) { "'sample-app'" }
- let(:endpoint) { "'111.111.111.111'" }
- let(:ca_cert) { "'ca_cert'" }
- let(:encrypted_kubernetes_token) { "'encrypted_kubernetes_token'" }
- let(:encrypted_kubernetes_token_iv) { "'encrypted_kubernetes_token_iv'" }
- let(:username) { "'username'" }
- let(:encrypted_password) { "'encrypted_password'" }
- let(:encrypted_password_iv) { "'encrypted_password_iv'" }
- let(:gcp_project_id) { "'gcp_project_id'" }
- let(:gcp_cluster_zone) { "'gcp_cluster_zone'" }
- let(:gcp_cluster_name) { "'gcp_cluster_name'" }
- let(:gcp_machine_type) { "'gcp_machine_type'" }
- let(:gcp_operation_id) { "'gcp_operation_id'" }
- let(:encrypted_gcp_token) { "'encrypted_gcp_token'" }
- let(:encrypted_gcp_token_iv) { "'encrypted_gcp_token_iv'" }
-
- let(:cluster) { described_class::Cluster.last }
- let(:cluster_id) { cluster.id }
-
- before do
- ActiveRecord::Base.connection.execute <<-SQL
- INSERT INTO gcp_clusters (project_id, user_id, service_id, status, gcp_cluster_size, created_at, updated_at, enabled, status_reason, project_namespace, endpoint, ca_cert, encrypted_kubernetes_token, encrypted_kubernetes_token_iv, username, encrypted_password, encrypted_password_iv, gcp_project_id, gcp_cluster_zone, gcp_cluster_name, gcp_machine_type, gcp_operation_id, encrypted_gcp_token, encrypted_gcp_token_iv)
- VALUES (#{project_id}, #{user_id}, #{service_id}, #{status}, #{gcp_cluster_size}, #{created_at}, #{updated_at}, #{enabled}, #{status_reason}, #{project_namespace}, #{endpoint}, #{ca_cert}, #{encrypted_kubernetes_token}, #{encrypted_kubernetes_token_iv}, #{username}, #{encrypted_password}, #{encrypted_password_iv}, #{gcp_project_id}, #{gcp_cluster_zone}, #{gcp_cluster_name}, #{gcp_machine_type}, #{gcp_operation_id}, #{encrypted_gcp_token}, #{encrypted_gcp_token_iv});
- SQL
- end
-
- it 'correctly migrate to new clusters architectures' do
- migrate!
-
- expect(described_class::Cluster.count).to eq(1)
- expect(described_class::ClustersProject.count).to eq(1)
- expect(described_class::ProvidersGcp.count).to eq(1)
- expect(described_class::PlatformsKubernetes.count).to eq(1)
-
- expect(cluster.user_id).to eq(user.id)
- expect(cluster.enabled).to be_truthy
- expect(cluster.name).to eq(tr(gcp_cluster_name))
- expect(cluster.provider_type).to eq('gcp')
- expect(cluster.platform_type).to eq('kubernetes')
-
- expect(cluster.project_ids).to include(project.id)
-
- expect(cluster.provider_gcp.cluster_id).to eq(cluster.id)
- expect(cluster.provider_gcp.status).to eq(status)
- expect(cluster.provider_gcp.status_reason).to eq(tr(status_reason))
- expect(cluster.provider_gcp.gcp_project_id).to eq(tr(gcp_project_id))
- expect(cluster.provider_gcp.zone).to eq(tr(gcp_cluster_zone))
- expect(cluster.provider_gcp.num_nodes).to eq(gcp_cluster_size)
- expect(cluster.provider_gcp.machine_type).to eq(tr(gcp_machine_type))
- expect(cluster.provider_gcp.operation_id).to eq(tr(gcp_operation_id))
- expect(cluster.provider_gcp.endpoint).to eq(tr(endpoint))
- expect(cluster.provider_gcp.encrypted_access_token).to eq(tr(encrypted_gcp_token))
- expect(cluster.provider_gcp.encrypted_access_token_iv).to eq(tr(encrypted_gcp_token_iv))
-
- expect(cluster.platform_kubernetes.cluster_id).to eq(cluster.id)
- expect(cluster.platform_kubernetes.api_url).to eq('https://' + tr(endpoint))
- expect(cluster.platform_kubernetes.ca_cert).to eq(tr(ca_cert))
- expect(cluster.platform_kubernetes.namespace).to eq(tr(project_namespace))
- expect(cluster.platform_kubernetes.username).to eq(tr(username))
- expect(cluster.platform_kubernetes.encrypted_password).to eq(tr(encrypted_password))
- expect(cluster.platform_kubernetes.encrypted_password_iv).to eq(tr(encrypted_password_iv))
- expect(cluster.platform_kubernetes.encrypted_token).to eq(tr(encrypted_kubernetes_token))
- expect(cluster.platform_kubernetes.encrypted_token_iv).to eq(tr(encrypted_kubernetes_token_iv))
- end
- end
-
- def tr(str)
- str.delete("'")
- end
-end
diff --git a/spec/migrations/migrate_issues_to_ghost_user_spec.rb b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
deleted file mode 100644
index 0016f058a17..00000000000
--- a/spec/migrations/migrate_issues_to_ghost_user_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'migrate', '20170825104051_migrate_issues_to_ghost_user.rb')
-
-describe MigrateIssuesToGhostUser, :migration do
- describe '#up' do
- let(:projects) { table(:projects) }
- let(:issues) { table(:issues) }
- let(:users) { table(:users) }
-
- before do
- project = projects.create!(name: 'gitlab', namespace_id: 1)
- user = users.create(email: 'test@example.com')
- issues.create(title: 'Issue 1', author_id: nil, project_id: project.id)
- issues.create(title: 'Issue 2', author_id: user.id, project_id: project.id)
- end
-
- context 'when ghost user exists' do
- let!(:ghost) { users.create(ghost: true, email: 'ghost@example.com') }
-
- it 'does not create a new user' do
- expect { migrate! }.not_to change { User.count }
- end
-
- it 'migrates issues where author = nil to the ghost user' do
- migrate!
-
- expect(issues.first.reload.author_id).to eq(ghost.id)
- end
-
- it 'does not change issues authored by an existing user' do
- expect { migrate! }.not_to change { issues.second.reload.author_id}
- end
- end
-
- context 'when ghost user does not exist' do
- it 'creates a new user' do
- expect { migrate! }.to change { User.count }.by(1)
- end
-
- it 'migrates issues where author = nil to the ghost user' do
- migrate!
-
- expect(issues.first.reload.author_id).to eq(User.ghost.id)
- end
-
- it 'does not change issues authored by an existing user' do
- expect { migrate! }.not_to change { issues.second.reload.author_id}
- end
- end
- end
-end
diff --git a/spec/migrations/migrate_kubernetes_service_to_new_clusters_architectures_spec.rb b/spec/migrations/migrate_kubernetes_service_to_new_clusters_architectures_spec.rb
deleted file mode 100644
index df0015b6dd3..00000000000
--- a/spec/migrations/migrate_kubernetes_service_to_new_clusters_architectures_spec.rb
+++ /dev/null
@@ -1,312 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb')
-
-describe MigrateKubernetesServiceToNewClustersArchitectures, :migration do
- context 'when unique KubernetesService exists' do
- shared_examples 'KubernetesService migration' do
- let(:sample_num) { 2 }
-
- let(:projects) do
- (1..sample_num).each_with_object([]) do |n, array|
- array << MigrateKubernetesServiceToNewClustersArchitectures::Project.create!
- end
- end
-
- let!(:kubernetes_services) do
- projects.map do |project|
- MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
- project: project,
- active: active,
- category: 'deployment',
- type: 'KubernetesService',
- properties: "{\"namespace\":\"prod\",\"api_url\":\"https://kubernetes#{project.id}.com\",\"ca_pem\":\"ca_pem#{project.id}\",\"token\":\"token#{project.id}\"}")
- end
- end
-
- it 'migrates the KubernetesService to Platform::Kubernetes' do
- expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(sample_num)
-
- projects.each do |project|
- project.clusters.last.tap do |cluster|
- expect(cluster.enabled).to eq(active)
- expect(cluster.platform_kubernetes.api_url).to eq(project.kubernetes_service.api_url)
- expect(cluster.platform_kubernetes.ca_cert).to eq(project.kubernetes_service.ca_pem)
- expect(cluster.platform_kubernetes.token).to eq(project.kubernetes_service.token)
- expect(project.kubernetes_service).not_to be_active
- end
- end
- end
- end
-
- context 'when KubernetesService is active' do
- let(:active) { true }
-
- it_behaves_like 'KubernetesService migration'
- end
- end
-
- context 'when unique KubernetesService spawned from Service Template' do
- let(:sample_num) { 2 }
-
- let(:projects) do
- (1..sample_num).each_with_object([]) do |n, array|
- array << MigrateKubernetesServiceToNewClustersArchitectures::Project.create!
- end
- end
-
- let!(:kubernetes_service_template) do
- MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
- template: true,
- category: 'deployment',
- type: 'KubernetesService',
- properties: "{\"namespace\":\"prod\",\"api_url\":\"https://sample.kubernetes.com\",\"ca_pem\":\"ca_pem-sample\",\"token\":\"token-sample\"}")
- end
-
- let!(:kubernetes_services) do
- projects.map do |project|
- MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
- project: project,
- category: 'deployment',
- type: 'KubernetesService',
- properties: "{\"namespace\":\"prod\",\"api_url\":\"#{kubernetes_service_template.api_url}\",\"ca_pem\":\"#{kubernetes_service_template.ca_pem}\",\"token\":\"#{kubernetes_service_template.token}\"}")
- end
- end
-
- it 'migrates the KubernetesService to Platform::Kubernetes without template' do
- expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(sample_num)
-
- projects.each do |project|
- project.clusters.last.tap do |cluster|
- expect(cluster.platform_kubernetes.api_url).to eq(project.kubernetes_service.api_url)
- expect(cluster.platform_kubernetes.ca_cert).to eq(project.kubernetes_service.ca_pem)
- expect(cluster.platform_kubernetes.token).to eq(project.kubernetes_service.token)
- expect(project.kubernetes_service).not_to be_active
- end
- end
- end
- end
-
- context 'when managed KubernetesService exists' do
- let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
-
- let(:cluster) do
- MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!(
- projects: [project],
- name: 'sample-cluster',
- platform_type: :kubernetes,
- provider_type: :user,
- platform_kubernetes_attributes: {
- api_url: 'https://sample.kubernetes.com',
- ca_cert: 'ca_pem-sample',
- token: 'token-sample'
- } )
- end
-
- let!(:kubernetes_service) do
- MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
- project: project,
- active: cluster.enabled,
- category: 'deployment',
- type: 'KubernetesService',
- properties: "{\"api_url\":\"#{cluster.platform_kubernetes.api_url}\"}")
- end
-
- it 'does not migrate the KubernetesService and disables the kubernetes_service' do # Because the corresponding Platform::Kubernetes already exists
- expect { migrate! }.not_to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }
-
- kubernetes_service.reload
- expect(kubernetes_service).not_to be_active
- end
- end
-
- context 'when production cluster has already been existed' do # i.e. There are no environment_scope conflicts
- let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
-
- let(:cluster) do
- MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!(
- projects: [project],
- name: 'sample-cluster',
- platform_type: :kubernetes,
- provider_type: :user,
- environment_scope: 'production/*',
- platform_kubernetes_attributes: {
- api_url: 'https://sample.kubernetes.com',
- ca_cert: 'ca_pem-sample',
- token: 'token-sample'
- } )
- end
-
- let!(:kubernetes_service) do
- MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
- project: project,
- active: true,
- category: 'deployment',
- type: 'KubernetesService',
- properties: "{\"api_url\":\"https://debug.kube.com\"}")
- end
-
- it 'migrates the KubernetesService to Platform::Kubernetes' do
- expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(1)
-
- kubernetes_service.reload
- project.clusters.last.tap do |cluster|
- expect(cluster.environment_scope).to eq('*')
- expect(cluster.platform_kubernetes.api_url).to eq(kubernetes_service.api_url)
- expect(cluster.platform_kubernetes.ca_cert).to eq(kubernetes_service.ca_pem)
- expect(cluster.platform_kubernetes.token).to eq(kubernetes_service.token)
- expect(kubernetes_service).not_to be_active
- end
- end
- end
-
- context 'when default cluster has already been existed' do
- let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
-
- let!(:cluster) do
- MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!(
- projects: [project],
- name: 'sample-cluster',
- platform_type: :kubernetes,
- provider_type: :user,
- environment_scope: '*',
- platform_kubernetes_attributes: {
- api_url: 'https://sample.kubernetes.com',
- ca_cert: 'ca_pem-sample',
- token: 'token-sample'
- } )
- end
-
- let!(:kubernetes_service) do
- MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
- project: project,
- active: true,
- category: 'deployment',
- type: 'KubernetesService',
- properties: "{\"api_url\":\"https://debug.kube.com\"}")
- end
-
- it 'migrates the KubernetesService to Platform::Kubernetes with dedicated environment_scope' do # Because environment_scope is duplicated
- expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(1)
-
- kubernetes_service.reload
- project.clusters.last.tap do |cluster|
- expect(cluster.environment_scope).to eq('migrated/*')
- expect(cluster.platform_kubernetes.api_url).to eq(kubernetes_service.api_url)
- expect(cluster.platform_kubernetes.ca_cert).to eq(kubernetes_service.ca_pem)
- expect(cluster.platform_kubernetes.token).to eq(kubernetes_service.token)
- expect(kubernetes_service).not_to be_active
- end
- end
- end
-
- context 'when default cluster and migrated cluster has already been existed' do
- let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
-
- let!(:cluster) do
- MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!(
- projects: [project],
- name: 'sample-cluster',
- platform_type: :kubernetes,
- provider_type: :user,
- environment_scope: '*',
- platform_kubernetes_attributes: {
- api_url: 'https://sample.kubernetes.com',
- ca_cert: 'ca_pem-sample',
- token: 'token-sample'
- } )
- end
-
- let!(:migrated_cluster) do
- MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!(
- projects: [project],
- name: 'sample-cluster',
- platform_type: :kubernetes,
- provider_type: :user,
- environment_scope: 'migrated/*',
- platform_kubernetes_attributes: {
- api_url: 'https://sample.kubernetes.com',
- ca_cert: 'ca_pem-sample',
- token: 'token-sample'
- } )
- end
-
- let!(:kubernetes_service) do
- MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
- project: project,
- active: true,
- category: 'deployment',
- type: 'KubernetesService',
- properties: "{\"api_url\":\"https://debug.kube.com\"}")
- end
-
- it 'migrates the KubernetesService to Platform::Kubernetes with dedicated environment_scope' do # Because environment_scope is duplicated
- expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(1)
-
- kubernetes_service.reload
- project.clusters.last.tap do |cluster|
- expect(cluster.environment_scope).to eq('migrated0/*')
- expect(cluster.platform_kubernetes.api_url).to eq(kubernetes_service.api_url)
- expect(cluster.platform_kubernetes.ca_cert).to eq(kubernetes_service.ca_pem)
- expect(cluster.platform_kubernetes.token).to eq(kubernetes_service.token)
- expect(kubernetes_service).not_to be_active
- end
- end
- end
-
- context 'when KubernetesService has nullified parameters' do
- let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
-
- before do
- MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
- project: project,
- active: false,
- category: 'deployment',
- type: 'KubernetesService',
- properties: "{}")
- end
-
- it 'does not migrate the KubernetesService and disables the kubernetes_service' do
- expect { migrate! }.not_to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }
-
- expect(project.kubernetes_service).not_to be_active
- end
- end
-
- # Platforms::Kubernetes validates `token` reagdless of the activeness,
- # whereas KubernetesService validates `token` if only it's activated
- # However, in this migration file, there are no validations because of the re-defined model class
- # therefore, we should safely add this raw to Platform::Kubernetes
- context 'when KubernetesService has empty token' do
- let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
-
- before do
- MigrateKubernetesServiceToNewClustersArchitectures::Service.create!(
- project: project,
- active: false,
- category: 'deployment',
- type: 'KubernetesService',
- properties: "{\"namespace\":\"prod\",\"api_url\":\"http://111.111.111.111\",\"ca_pem\":\"a\",\"token\":\"\"}")
- end
-
- it 'does not migrate the KubernetesService and disables the kubernetes_service' do
- expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(1)
-
- project.clusters.last.tap do |cluster|
- expect(cluster.environment_scope).to eq('*')
- expect(cluster.platform_kubernetes.namespace).to eq('prod')
- expect(cluster.platform_kubernetes.api_url).to eq('http://111.111.111.111')
- expect(cluster.platform_kubernetes.ca_cert).to eq('a')
- expect(cluster.platform_kubernetes.token).to be_empty
- expect(project.kubernetes_service).not_to be_active
- end
- end
- end
-
- context 'when KubernetesService does not exist' do
- let!(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! }
-
- it 'does not migrate the KubernetesService' do
- expect { migrate! }.not_to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }
- end
- end
-end
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
deleted file mode 100644
index bc826d91471..00000000000
--- a/spec/migrations/migrate_old_artifacts_spec.rb
+++ /dev/null
@@ -1,140 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170523083112_migrate_old_artifacts.rb')
-
-# Adding the ci_job_artifacts table (from the 20170918072948 schema)
-# makes the use of model code below easier.
-describe MigrateOldArtifacts, :migration, schema: 20170918072948 do
- let(:migration) { described_class.new }
- let!(:directory) { Dir.mktmpdir }
-
- before do
- allow(Gitlab.config.artifacts).to receive(:path).and_return(directory)
- end
-
- after do
- FileUtils.remove_entry_secure(directory)
- end
-
- context 'with migratable data' do
- let(:projects) { table(:projects) }
- let(:ci_pipelines) { table(:ci_pipelines) }
- let(:ci_builds) { table(:ci_builds) }
-
- let!(:project1) { projects.create!(ci_id: 2) }
- let!(:project2) { projects.create!(ci_id: 3) }
- let!(:project3) { projects.create! }
-
- let!(:pipeline1) { ci_pipelines.create!(project_id: project1.id) }
- let!(:pipeline2) { ci_pipelines.create!(project_id: project2.id) }
- let!(:pipeline3) { ci_pipelines.create!(project_id: project3.id) }
-
- let!(:build_with_legacy_artifacts) { ci_builds.create!(commit_id: pipeline1.id, project_id: project1.id, type: 'Ci::Build').becomes(Ci::Build) }
- let!(:build_without_artifacts) { ci_builds.create!(commit_id: pipeline1.id, project_id: project1.id, type: 'Ci::Build').becomes(Ci::Build) }
- let!(:build2) { ci_builds.create!(commit_id: pipeline2.id, project_id: project2.id, type: 'Ci::Build').becomes(Ci::Build) }
- let!(:build3) { ci_builds.create!(commit_id: pipeline3.id, project_id: project3.id, type: 'Ci::Build').becomes(Ci::Build) }
-
- before do
- setup_builds(build2, build3)
-
- store_artifacts_in_legacy_path(build_with_legacy_artifacts)
- end
-
- it "legacy artifacts are not accessible" do
- expect(build_with_legacy_artifacts.artifacts?).to be_falsey
- end
-
- describe '#min_id' do
- subject { migration.send(:min_id) }
-
- it 'returns the newest build for which ci_id is not defined' do
- is_expected.to eq(build3.id)
- end
- end
-
- describe '#builds_with_artifacts' do
- subject { migration.send(:builds_with_artifacts).map(&:id) }
-
- it 'returns a list of builds that has artifacts and could be migrated' do
- is_expected.to contain_exactly(build_with_legacy_artifacts.id, build2.id)
- end
- end
-
- describe '#up' do
- context 'when migrating artifacts' do
- before do
- migration.up
- end
-
- it 'all files do have artifacts' do
- Ci::Build.with_artifacts_archive do |build|
- expect(build).to have_artifacts
- end
- end
-
- it 'artifacts are no longer present on legacy path' do
- expect(File.exist?(legacy_path(build_with_legacy_artifacts))).to eq(false)
- end
- end
-
- context 'when there are artifacts in old and new directory' do
- before do
- store_artifacts_in_legacy_path(build2)
-
- migration.up
- end
-
- it 'does not move old files' do
- expect(File.exist?(legacy_path(build2))).to eq(true)
- end
- end
- end
-
- private
-
- def store_artifacts_in_legacy_path(build)
- FileUtils.mkdir_p(legacy_path(build))
-
- FileUtils.copy(
- Rails.root.join('spec/fixtures/ci_build_artifacts.zip'),
- File.join(legacy_path(build), "ci_build_artifacts.zip"))
-
- FileUtils.copy(
- Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
- File.join(legacy_path(build), "ci_build_artifacts_metadata.gz"))
-
- build.update_columns(
- artifacts_file: 'ci_build_artifacts.zip',
- artifacts_metadata: 'ci_build_artifacts_metadata.gz')
-
- build.reload
- end
-
- def legacy_path(build)
- File.join(directory,
- build.created_at.utc.strftime('%Y_%m'),
- build.project.ci_id.to_s,
- build.id.to_s)
- end
-
- def new_legacy_path(build)
- File.join(directory,
- build.created_at.utc.strftime('%Y_%m'),
- build.project_id.to_s,
- build.id.to_s)
- end
-
- def setup_builds(*builds)
- builds.each do |build|
- FileUtils.mkdir_p(new_legacy_path(build))
-
- build.update_columns(
- artifacts_file: 'ci_build_artifacts.zip',
- artifacts_metadata: 'ci_build_artifacts_metadata.gz')
-
- build.reload
- end
- end
- end
-end
diff --git a/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb b/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb
deleted file mode 100644
index e38044ccceb..00000000000
--- a/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170822101017_migrate_pipeline_sidekiq_queues.rb')
-
-describe MigratePipelineSidekiqQueues, :sidekiq, :redis do
- include Gitlab::Database::MigrationHelpers
- include StubWorker
-
- context 'when there are jobs in the queues' do
- it 'correctly migrates queue when migrating up' do
- Sidekiq::Testing.disable! do
- stub_worker(queue: :pipeline).perform_async('Something', [1])
- stub_worker(queue: :build).perform_async('Something', [1])
-
- described_class.new.up
-
- expect(sidekiq_queue_length('pipeline')).to eq 0
- expect(sidekiq_queue_length('build')).to eq 0
- expect(sidekiq_queue_length('pipeline_default')).to eq 2
- end
- end
-
- it 'correctly migrates queue when migrating down' do
- Sidekiq::Testing.disable! do
- stub_worker(queue: :pipeline_default).perform_async('Class', [1])
- stub_worker(queue: :pipeline_processing).perform_async('Class', [2])
- stub_worker(queue: :pipeline_hooks).perform_async('Class', [3])
- stub_worker(queue: :pipeline_cache).perform_async('Class', [4])
-
- described_class.new.down
-
- expect(sidekiq_queue_length('pipeline')).to eq 4
- expect(sidekiq_queue_length('pipeline_default')).to eq 0
- expect(sidekiq_queue_length('pipeline_processing')).to eq 0
- expect(sidekiq_queue_length('pipeline_hooks')).to eq 0
- expect(sidekiq_queue_length('pipeline_cache')).to eq 0
- end
- end
- end
-
- context 'when there are no jobs in the queues' do
- it 'does not raise error when migrating up' do
- expect { described_class.new.up }.not_to raise_error
- end
-
- it 'does not raise error when migrating down' do
- expect { described_class.new.down }.not_to raise_error
- end
- end
-end
diff --git a/spec/migrations/migrate_pipeline_stages_spec.rb b/spec/migrations/migrate_pipeline_stages_spec.rb
deleted file mode 100644
index c47f2bb8ff9..00000000000
--- a/spec/migrations/migrate_pipeline_stages_spec.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170526185842_migrate_pipeline_stages.rb')
-
-describe MigratePipelineStages, :migration do
- ##
- # Create test data - pipeline and CI/CD jobs.
- #
-
- let(:jobs) { table(:ci_builds) }
- let(:stages) { table(:ci_stages) }
- let(:pipelines) { table(:ci_pipelines) }
- let(:projects) { table(:projects) }
-
- before do
- # Create projects
- #
- projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
- projects.create!(id: 456, name: 'gitlab2', path: 'gitlab2')
-
- # Create CI/CD pipelines
- #
- pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
- pipelines.create!(id: 2, project_id: 456, ref: 'feature', sha: '21a3deb')
-
- # Create CI/CD jobs
- #
- jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
- jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
- jobs.create!(id: 3, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test')
- jobs.create!(id: 4, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test')
- jobs.create!(id: 5, commit_id: 1, project_id: 123, stage_idx: 3, stage: 'deploy')
- jobs.create!(id: 6, commit_id: 2, project_id: 456, stage_idx: 3, stage: 'deploy')
- jobs.create!(id: 7, commit_id: 2, project_id: 456, stage_idx: 2, stage: 'test:2')
- jobs.create!(id: 8, commit_id: 2, project_id: 456, stage_idx: 1, stage: 'test:1')
- jobs.create!(id: 9, commit_id: 2, project_id: 456, stage_idx: 1, stage: 'test:1')
- jobs.create!(id: 10, commit_id: 2, project_id: 456, stage_idx: 2, stage: 'test:2')
- jobs.create!(id: 11, commit_id: 3, project_id: 456, stage_idx: 3, stage: 'deploy')
- jobs.create!(id: 12, commit_id: 2, project_id: 789, stage_idx: 3, stage: 'deploy')
- end
-
- it 'correctly migrates pipeline stages' do
- expect(stages.count).to be_zero
-
- migrate!
-
- expect(stages.count).to eq 6
- expect(stages.all.pluck(:name))
- .to match_array %w[test build deploy test:1 test:2 deploy]
- expect(stages.where(pipeline_id: 1).order(:id).pluck(:name))
- .to eq %w[test build deploy]
- expect(stages.where(pipeline_id: 2).order(:id).pluck(:name))
- .to eq %w[test:1 test:2 deploy]
- expect(stages.where(pipeline_id: 3).count).to be_zero
- expect(stages.where(project_id: 789).count).to be_zero
- end
-end
diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
deleted file mode 100644
index 6219a67c900..00000000000
--- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
+++ /dev/null
@@ -1,197 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_worker_jobs.rb')
-
-describe MigrateProcessCommitWorkerJobs do
- set(:project) { create(:project, :legacy_storage, :repository) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- set(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let(:commit) do
- Gitlab::Git::Commit.last(project.repository.raw)
- end
-
- describe 'Project' do
- describe 'find_including_path' do
- it 'returns Project instances' do
- expect(described_class::Project.find_including_path(project.id))
- .to be_an_instance_of(described_class::Project)
- end
-
- it 'selects the full path for every Project' do
- migration_project = described_class::Project
- .find_including_path(project.id)
-
- expect(migration_project[:path_with_namespace])
- .to eq(project.full_path)
- end
- end
-
- describe '#repository' do
- it 'returns a mock implemention of ::Repository' do
- migration_project = described_class::Project
- .find_including_path(project.id)
-
- expect(migration_project.repository).to respond_to(:storage)
- expect(migration_project.repository).to respond_to(:gitaly_repository)
- end
- end
- end
-
- describe '#up', :clean_gitlab_redis_shared_state do
- let(:migration) { described_class.new }
-
- def job_count
- Sidekiq.redis { |r| r.llen('queue:process_commit') }
- end
-
- def pop_job
- JSON.parse(Sidekiq.redis { |r| r.lpop('queue:process_commit') })
- end
-
- before do
- Sidekiq.redis do |redis|
- job = JSON.dump(args: [project.id, user.id, commit.id])
- redis.lpush('queue:process_commit', job)
- end
- end
-
- it 'skips jobs using a project that no longer exists' do
- allow(described_class::Project).to receive(:find_including_path)
- .with(project.id)
- .and_return(nil)
-
- migration.up
-
- expect(job_count).to eq(0)
- end
-
- it 'skips jobs using commits that no longer exist' do
- allow_any_instance_of(Gitlab::GitalyClient::CommitService)
- .to receive(:find_commit)
- .with(commit.id)
- .and_return(nil)
-
- migration.up
-
- expect(job_count).to eq(0)
- end
-
- it 'inserts migrated jobs back into the queue' do
- migration.up
-
- expect(job_count).to eq(1)
- end
-
- it 'encodes data to UTF-8' do
- allow(commit).to receive(:body)
- .and_return('김치'.force_encoding('BINARY'))
-
- migration.up
-
- job = pop_job
-
- # We don't care so much about what is being stored, instead we just want
- # to make sure the encoding is right so that JSON encoding the data
- # doesn't produce any errors.
- expect(job['args'][2]['message'].encoding).to eq(Encoding::UTF_8)
- end
-
- context 'a migrated job' do
- let(:job) do
- migration.up
- pop_job
- end
-
- let(:commit_hash) do
- job['args'][2]
- end
-
- it 'includes the project ID' do
- expect(job['args'][0]).to eq(project.id)
- end
-
- it 'includes the user ID' do
- expect(job['args'][1]).to eq(user.id)
- end
-
- it 'includes the commit ID' do
- expect(commit_hash['id']).to eq(commit.id)
- end
-
- it 'includes the commit message' do
- expect(commit_hash['message']).to eq(commit.message)
- end
-
- it 'includes the parent IDs' do
- expect(commit_hash['parent_ids']).to eq(commit.parent_ids)
- end
-
- it 'includes the author date' do
- expect(commit_hash['authored_date']).to eq(commit.authored_date.to_s)
- end
-
- it 'includes the author name' do
- expect(commit_hash['author_name']).to eq(commit.author_name)
- end
-
- it 'includes the author Email' do
- expect(commit_hash['author_email']).to eq(commit.author_email)
- end
-
- it 'includes the commit date' do
- expect(commit_hash['committed_date']).to eq(commit.committed_date.to_s)
- end
-
- it 'includes the committer name' do
- expect(commit_hash['committer_name']).to eq(commit.committer_name)
- end
-
- it 'includes the committer Email' do
- expect(commit_hash['committer_email']).to eq(commit.committer_email)
- end
- end
- end
-
- describe '#down', :clean_gitlab_redis_shared_state do
- let(:migration) { described_class.new }
-
- def job_count
- Sidekiq.redis { |r| r.llen('queue:process_commit') }
- end
-
- before do
- Sidekiq.redis do |redis|
- job = JSON.dump(args: [project.id, user.id, commit.id])
- redis.lpush('queue:process_commit', job)
-
- migration.up
- end
- end
-
- it 'pushes migrated jobs back into the queue' do
- migration.down
-
- expect(job_count).to eq(1)
- end
-
- context 'a migrated job' do
- let(:job) do
- migration.down
-
- JSON.parse(Sidekiq.redis { |r| r.lpop('queue:process_commit') })
- end
-
- it 'includes the project ID' do
- expect(job['args'][0]).to eq(project.id)
- end
-
- it 'includes the user ID' do
- expect(job['args'][1]).to eq(user.id)
- end
-
- it 'includes the commit SHA' do
- expect(job['args'][2]).to eq(commit.id)
- end
- end
- end
-end
diff --git a/spec/migrations/migrate_stage_id_reference_in_background_spec.rb b/spec/migrations/migrate_stage_id_reference_in_background_spec.rb
deleted file mode 100644
index dd6f5325750..00000000000
--- a/spec/migrations/migrate_stage_id_reference_in_background_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170628080858_migrate_stage_id_reference_in_background')
-
-describe MigrateStageIdReferenceInBackground, :migration, :sidekiq do
- let(:jobs) { table(:ci_builds) }
- let(:stages) { table(:ci_stages) }
- let(:pipelines) { table(:ci_pipelines) }
- let(:projects) { table(:projects) }
-
- before do
- stub_const("#{described_class.name}::BATCH_SIZE", 3)
- stub_const("#{described_class.name}::RANGE_SIZE", 2)
-
- projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
- projects.create!(id: 345, name: 'gitlab2', path: 'gitlab2')
-
- pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
- pipelines.create!(id: 2, project_id: 345, ref: 'feature', sha: 'cdf43c3c')
-
- jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
- jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
- jobs.create!(id: 3, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test')
- jobs.create!(id: 4, commit_id: 1, project_id: 123, stage_idx: 3, stage: 'deploy')
- jobs.create!(id: 5, commit_id: 2, project_id: 345, stage_idx: 1, stage: 'test')
-
- stages.create(id: 101, pipeline_id: 1, project_id: 123, name: 'test')
- stages.create(id: 102, pipeline_id: 1, project_id: 123, name: 'build')
- stages.create(id: 103, pipeline_id: 1, project_id: 123, name: 'deploy')
-
- jobs.create!(id: 6, commit_id: 2, project_id: 345, stage_id: 101, stage_idx: 1, stage: 'test')
- end
-
- it 'correctly schedules background migrations' do
- Sidekiq::Testing.fake! do
- Timecop.freeze do
- migrate!
-
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, 1, 2)
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, 3, 3)
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(4.minutes, 4, 5)
- expect(BackgroundMigrationWorker.jobs.size).to eq 3
- end
- end
- end
-
- it 'schedules background migrations' do
- perform_enqueued_jobs do
- expect(jobs.where(stage_id: nil).count).to eq 5
-
- migrate!
-
- expect(jobs.where(stage_id: nil).count).to eq 1
- end
- end
-end
diff --git a/spec/migrations/migrate_stages_statuses_spec.rb b/spec/migrations/migrate_stages_statuses_spec.rb
deleted file mode 100644
index 5483e24fce7..00000000000
--- a/spec/migrations/migrate_stages_statuses_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170711145558_migrate_stages_statuses.rb')
-
-describe MigrateStagesStatuses, :sidekiq, :migration do
- let(:jobs) { table(:ci_builds) }
- let(:stages) { table(:ci_stages) }
- let(:pipelines) { table(:ci_pipelines) }
- let(:projects) { table(:projects) }
-
- STATUSES = { created: 0, pending: 1, running: 2, success: 3,
- failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze
-
- before do
- stub_const("#{described_class.name}::BATCH_SIZE", 2)
- stub_const("#{described_class.name}::RANGE_SIZE", 1)
-
- projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1')
- projects.create!(id: 2, name: 'gitlab2', path: 'gitlab2')
-
- pipelines.create!(id: 1, project_id: 1, ref: 'master', sha: 'adf43c3a')
- pipelines.create!(id: 2, project_id: 2, ref: 'feature', sha: '21a3deb')
-
- create_job(project: 1, pipeline: 1, stage: 'test', status: 'success')
- create_job(project: 1, pipeline: 1, stage: 'test', status: 'running')
- create_job(project: 1, pipeline: 1, stage: 'build', status: 'success')
- create_job(project: 1, pipeline: 1, stage: 'build', status: 'failed')
- create_job(project: 2, pipeline: 2, stage: 'test', status: 'success')
- create_job(project: 2, pipeline: 2, stage: 'test', status: 'success')
- create_job(project: 2, pipeline: 2, stage: 'test', status: 'failed', retried: true)
-
- stages.create!(id: 1, pipeline_id: 1, project_id: 1, name: 'test', status: nil)
- stages.create!(id: 2, pipeline_id: 1, project_id: 1, name: 'build', status: nil)
- stages.create!(id: 3, pipeline_id: 2, project_id: 2, name: 'test', status: nil)
- end
-
- it 'correctly migrates stages statuses' do
- perform_enqueued_jobs do
- expect(stages.where(status: nil).count).to eq 3
-
- migrate!
-
- expect(stages.where(status: nil)).to be_empty
- expect(stages.all.order('id ASC').pluck(:status))
- .to eq [STATUSES[:running], STATUSES[:failed], STATUSES[:success]]
- end
- end
-
- it 'correctly schedules background migrations' do
- Sidekiq::Testing.fake! do
- Timecop.freeze do
- migrate!
-
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, 1, 1)
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, 2, 2)
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, 3, 3)
- expect(BackgroundMigrationWorker.jobs.size).to eq 3
- end
- end
- end
-
- def create_job(project:, pipeline:, stage:, status:, **opts)
- stages = { test: 1, build: 2, deploy: 3 }
-
- jobs.create!(project_id: project, commit_id: pipeline,
- stage_idx: stages[stage.to_sym], stage: stage,
- status: status, **opts)
- end
-end
diff --git a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
deleted file mode 100644
index 88aef3b70b4..00000000000
--- a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170324160416_migrate_user_activities_to_users_last_activity_on.rb')
-
-describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :migration do
- let(:migration) { described_class.new }
- let!(:user_active_1) { table(:users).create!(email: 'test1', username: 'test1') }
- let!(:user_active_2) { table(:users).create!(email: 'test2', username: 'test2') }
-
- def record_activity(user, time)
- Gitlab::Redis::SharedState.with do |redis|
- redis.zadd(described_class::USER_ACTIVITY_SET_KEY, time.to_i, user.username)
- end
- end
-
- around do |example|
- Timecop.freeze { example.run }
- end
-
- before do
- record_activity(user_active_1, described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 2.months)
- record_activity(user_active_2, described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 3.months)
- mute_stdout { migration.up }
- end
-
- describe '#up' do
- it 'fills last_activity_on from the legacy Redis Sorted Set' do
- expect(user_active_1.reload.last_activity_on).to eq((described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 2.months).to_date)
- expect(user_active_2.reload.last_activity_on).to eq((described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 3.months).to_date)
- end
- end
-
- describe '#down' do
- it 'sets last_activity_on to NULL for all users' do
- mute_stdout { migration.down }
-
- expect(user_active_1.reload.last_activity_on).to be_nil
- expect(user_active_2.reload.last_activity_on).to be_nil
- end
- end
-
- def mute_stdout
- orig_stdout = $stdout
- $stdout = StringIO.new
- yield
- $stdout = orig_stdout
- end
-end
diff --git a/spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb b/spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb
deleted file mode 100644
index b4834705011..00000000000
--- a/spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'migrate', '20171012125712_migrate_user_authentication_token_to_personal_access_token.rb')
-
-describe MigrateUserAuthenticationTokenToPersonalAccessToken, :migration do
- let(:users) { table(:users) }
- let(:personal_access_tokens) { table(:personal_access_tokens) }
-
- let!(:user) { users.create!(id: 1, email: 'user@example.com', authentication_token: 'user-token', admin: false) }
- let!(:admin) { users.create!(id: 2, email: 'admin@example.com', authentication_token: 'admin-token', admin: true) }
-
- it 'migrates private tokens to Personal Access Tokens' do
- migrate!
-
- expect(personal_access_tokens.count).to eq(2)
-
- user_token = personal_access_tokens.find_by(user_id: user.id)
- admin_token = personal_access_tokens.find_by(user_id: admin.id)
-
- expect(user_token.token).to eq('user-token')
- expect(admin_token.token).to eq('admin-token')
-
- expect(user_token.scopes).to eq(%w[api].to_yaml)
- expect(admin_token.scopes).to eq(%w[api sudo].to_yaml)
- end
-end
diff --git a/spec/migrations/migrate_user_project_view_spec.rb b/spec/migrations/migrate_user_project_view_spec.rb
deleted file mode 100644
index a0179ab3ceb..00000000000
--- a/spec/migrations/migrate_user_project_view_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170406142253_migrate_user_project_view.rb')
-
-describe MigrateUserProjectView, :migration do
- let(:migration) { described_class.new }
- let!(:user) { table(:users).create!(project_view: User.project_views['readme']) }
-
- describe '#up' do
- it 'updates project view setting with new value' do
- migration.up
-
- expect(user.reload.project_view).to eq(User.project_views['files'])
- end
- end
-end
diff --git a/spec/migrations/move_personal_snippets_files_spec.rb b/spec/migrations/move_personal_snippets_files_spec.rb
deleted file mode 100644
index d94ae1e52f5..00000000000
--- a/spec/migrations/move_personal_snippets_files_spec.rb
+++ /dev/null
@@ -1,197 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170612071012_move_personal_snippets_files.rb')
-
-describe MovePersonalSnippetsFiles, :migration do
- let(:migration) { described_class.new }
- let(:test_dir) { File.join(Rails.root, "tmp", "tests", "move_snippet_files_test") }
- let(:uploads_dir) { File.join(test_dir, 'uploads') }
- let(:new_uploads_dir) { File.join(uploads_dir, '-', 'system') }
-
- let(:notes) { table(:notes) }
- let(:snippets) { table(:snippets) }
- let(:uploads) { table(:uploads) }
-
- let(:user) { table(:users).create!(email: 'user@example.com', projects_limit: 10) }
- let(:project) { table(:projects).create!(name: 'gitlab', namespace_id: 1) }
-
- before do
- allow(CarrierWave).to receive(:root).and_return(test_dir)
- allow(migration).to receive(:base_directory).and_return(test_dir)
- FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
- allow(migration).to receive(:say)
- end
-
- describe "#up" do
- let(:snippet) do
- snippet = snippets.create!(author_id: user.id)
- create_upload('picture.jpg', snippet)
- snippet.update(description: markdown_linking_file('picture.jpg', snippet))
- snippet
- end
-
- let(:snippet_with_missing_file) do
- snippet = snippets.create!(author_id: user.id, project_id: project.id)
- create_upload('picture.jpg', snippet, create_file: false)
- snippet.update(description: markdown_linking_file('picture.jpg', snippet))
- snippet
- end
-
- it 'moves the files' do
- source_path = File.join(uploads_dir, model_file_path('picture.jpg', snippet))
- destination_path = File.join(new_uploads_dir, model_file_path('picture.jpg', snippet))
-
- migration.up
-
- expect(File.exist?(source_path)).to be_falsy
- expect(File.exist?(destination_path)).to be_truthy
- end
-
- describe 'updating the markdown' do
- it 'includes the new path when the file exists' do
- secret = "secret#{snippet.id}"
- file_location = "/uploads/-/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
-
- migration.up
-
- expect(snippet.reload.description).to include(file_location)
- end
-
- it 'does not update the markdown when the file is missing' do
- secret = "secret#{snippet_with_missing_file.id}"
- file_location = "/uploads/personal_snippet/#{snippet_with_missing_file.id}/#{secret}/picture.jpg"
-
- migration.up
-
- expect(snippet_with_missing_file.reload.description).to include(file_location)
- end
-
- it 'updates the note markdown' do
- secret = "secret#{snippet.id}"
- file_location = "/uploads/-/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
- markdown = markdown_linking_file('picture.jpg', snippet)
- note = notes.create!(noteable_id: snippet.id,
- noteable_type: Snippet,
- note: "with #{markdown}",
- author_id: user.id)
-
- migration.up
-
- expect(note.reload.note).to include(file_location)
- end
- end
- end
-
- describe "#down" do
- let(:snippet) do
- snippet = snippets.create!(author_id: user.id)
- create_upload('picture.jpg', snippet, in_new_path: true)
- snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true))
- snippet
- end
-
- let(:snippet_with_missing_file) do
- snippet = snippets.create!(author_id: user.id)
- create_upload('picture.jpg', snippet, create_file: false, in_new_path: true)
- snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true))
- snippet
- end
-
- it 'moves the files' do
- source_path = File.join(new_uploads_dir, model_file_path('picture.jpg', snippet))
- destination_path = File.join(uploads_dir, model_file_path('picture.jpg', snippet))
-
- migration.down
-
- expect(File.exist?(source_path)).to be_falsey
- expect(File.exist?(destination_path)).to be_truthy
- end
-
- describe 'updating the markdown' do
- it 'includes the new path when the file exists' do
- secret = "secret#{snippet.id}"
- file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
-
- migration.down
-
- expect(snippet.reload.description).to include(file_location)
- end
-
- it 'keeps the markdown as is when the file is missing' do
- secret = "secret#{snippet_with_missing_file.id}"
- file_location = "/uploads/-/system/personal_snippet/#{snippet_with_missing_file.id}/#{secret}/picture.jpg"
-
- migration.down
-
- expect(snippet_with_missing_file.reload.description).to include(file_location)
- end
-
- it 'updates the note markdown' do
- markdown = markdown_linking_file('picture.jpg', snippet, in_new_path: true)
- secret = "secret#{snippet.id}"
- file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
- note = notes.create!(noteable_id: snippet.id,
- noteable_type: Snippet,
- note: "with #{markdown}",
- author_id: user.id)
-
- migration.down
-
- expect(note.reload.note).to include(file_location)
- end
- end
- end
-
- describe '#update_markdown' do
- it 'escapes sql in the snippet description' do
- migration.instance_variable_set('@source_relative_location', '/uploads/personal_snippet')
- migration.instance_variable_set('@destination_relative_location', '/uploads/system/personal_snippet')
-
- secret = '123456789'
- filename = 'hello.jpg'
- snippet = snippets.create!(author_id: user.id)
-
- path_before = "/uploads/personal_snippet/#{snippet.id}/#{secret}/#{filename}"
- path_after = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/#{filename}"
- description_before = "Hello world; ![image](#{path_before})'; select * from users;"
- description_after = "Hello world; ![image](#{path_after})'; select * from users;"
-
- migration.update_markdown(snippet.id, secret, filename, description_before)
-
- expect(snippet.reload.description).to eq(description_after)
- end
- end
-
- def create_upload(filename, snippet, create_file: true, in_new_path: false)
- secret = "secret#{snippet.id}"
- absolute_path = if in_new_path
- File.join(new_uploads_dir, model_file_path(filename, snippet))
- else
- File.join(uploads_dir, model_file_path(filename, snippet))
- end
-
- if create_file
- FileUtils.mkdir_p(File.dirname(absolute_path))
- FileUtils.touch(absolute_path)
- end
-
- uploads.create!(model_id: snippet.id,
- model_type: snippet.class,
- path: "#{secret}/#{filename}",
- uploader: PersonalFileUploader,
- size: 100.kilobytes)
- end
-
- def markdown_linking_file(filename, snippet, in_new_path: false)
- markdown = "![#{filename.split('.')[0]}]"
- markdown += '(/uploads'
- markdown += '/-/system' if in_new_path
- markdown += "/#{model_file_path(filename, snippet)})"
- markdown
- end
-
- def model_file_path(filename, snippet)
- secret = "secret#{snippet.id}"
-
- File.join('personal_snippet', snippet.id.to_s, secret, filename)
- end
-end
diff --git a/spec/migrations/move_system_upload_folder_spec.rb b/spec/migrations/move_system_upload_folder_spec.rb
deleted file mode 100644
index d3180477db3..00000000000
--- a/spec/migrations/move_system_upload_folder_spec.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-require 'spec_helper'
-require Rails.root.join("db", "migrate", "20170717074009_move_system_upload_folder.rb")
-
-describe MoveSystemUploadFolder do
- let(:migration) { described_class.new }
- let(:test_base) { File.join(Rails.root, 'tmp', 'tests', 'move-system-upload-folder') }
-
- before do
- allow(migration).to receive(:base_directory).and_return(test_base)
- FileUtils.rm_rf(test_base)
- FileUtils.mkdir_p(test_base)
- allow(migration).to receive(:say)
- end
-
- describe '#up' do
- let(:test_folder) { File.join(test_base, 'system') }
- let(:test_file) { File.join(test_folder, 'file') }
-
- before do
- FileUtils.mkdir_p(test_folder)
- FileUtils.touch(test_file)
- end
-
- it 'moves the related folder' do
- migration.up
-
- expect(File.exist?(File.join(test_base, '-', 'system', 'file'))).to be_truthy
- end
-
- it 'creates a symlink linking making the new folder available on the old path' do
- migration.up
-
- expect(File.symlink?(File.join(test_base, 'system'))).to be_truthy
- expect(File.exist?(File.join(test_base, 'system', 'file'))).to be_truthy
- end
-
- it 'does not move if the target directory already exists' do
- FileUtils.mkdir_p(File.join(test_base, '-', 'system'))
-
- expect(FileUtils).not_to receive(:mv)
- expect(migration).to receive(:say).with(/already exists. No need to redo the move/)
-
- migration.up
- end
- end
-
- describe '#down' do
- let(:test_folder) { File.join(test_base, '-', 'system') }
- let(:test_file) { File.join(test_folder, 'file') }
-
- before do
- FileUtils.mkdir_p(test_folder)
- FileUtils.touch(test_file)
- end
-
- it 'moves the system folder back to the old location' do
- migration.down
-
- expect(File.exist?(File.join(test_base, 'system', 'file'))).to be_truthy
- end
-
- it 'removes the symlink if it existed' do
- FileUtils.ln_s(test_folder, File.join(test_base, 'system'))
-
- migration.down
-
- expect(File.directory?(File.join(test_base, 'system'))).to be_truthy
- expect(File.symlink?(File.join(test_base, 'system'))).to be_falsey
- end
-
- it 'does not move if the old directory already exists' do
- FileUtils.mkdir_p(File.join(test_base, 'system'))
-
- expect(FileUtils).not_to receive(:mv)
- expect(migration).to receive(:say).with(/already exists and is not a symlink, no need to revert/)
-
- migration.down
- end
- end
-end
diff --git a/spec/migrations/move_uploads_to_system_dir_spec.rb b/spec/migrations/move_uploads_to_system_dir_spec.rb
deleted file mode 100644
index ca11a2004c5..00000000000
--- a/spec/migrations/move_uploads_to_system_dir_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require "spec_helper"
-require Rails.root.join("db", "migrate", "20170316163845_move_uploads_to_system_dir.rb")
-
-describe MoveUploadsToSystemDir do
- let(:migration) { described_class.new }
- let(:test_dir) { File.join(Rails.root, "tmp", "move_uploads_test") }
- let(:uploads_dir) { File.join(test_dir, "public", "uploads") }
- let(:new_uploads_dir) { File.join(uploads_dir, "-", "system") }
-
- before do
- FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
- FileUtils.mkdir_p(uploads_dir)
- allow(migration).to receive(:base_directory).and_return(test_dir)
- allow(migration).to receive(:say)
- end
-
- describe "#up" do
- before do
- FileUtils.mkdir_p(File.join(uploads_dir, 'user'))
- FileUtils.touch(File.join(uploads_dir, 'user', 'dummy.file'))
- end
-
- it 'moves the directory to the new path' do
- expected_path = File.join(new_uploads_dir, 'user', 'dummy.file')
-
- migration.up
-
- expect(File.exist?(expected_path)).to be(true)
- end
-
- it 'creates a symlink in the old location' do
- symlink_path = File.join(uploads_dir, 'user')
- expected_path = File.join(symlink_path, 'dummy.file')
-
- migration.up
-
- expect(File.exist?(expected_path)).to be(true)
- expect(File.symlink?(symlink_path)).to be(true)
- end
- end
-
- describe "#down" do
- before do
- FileUtils.mkdir_p(File.join(new_uploads_dir, 'user'))
- FileUtils.touch(File.join(new_uploads_dir, 'user', 'dummy.file'))
- end
-
- it 'moves the directory to the old path' do
- expected_path = File.join(uploads_dir, 'user', 'dummy.file')
-
- migration.down
-
- expect(File.exist?(expected_path)).to be(true)
- end
-
- it 'removes the symlink if it existed' do
- FileUtils.ln_s(File.join(new_uploads_dir, 'user'), File.join(uploads_dir, 'user'))
-
- directory = File.join(uploads_dir, 'user')
- expected_path = File.join(directory, 'dummy.file')
-
- migration.down
-
- expect(File.exist?(expected_path)).to be(true)
- expect(File.symlink?(directory)).to be(false)
- end
- end
-end
diff --git a/spec/migrations/normalize_ldap_extern_uids_spec.rb b/spec/migrations/normalize_ldap_extern_uids_spec.rb
deleted file mode 100644
index a23a5d54e0a..00000000000
--- a/spec/migrations/normalize_ldap_extern_uids_spec.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170921101004_normalize_ldap_extern_uids')
-
-describe NormalizeLdapExternUids, :migration, :sidekiq do
- let!(:identities) { table(:identities) }
-
- around do |example|
- Timecop.freeze { example.run }
- end
-
- before do
- stub_const("Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_BATCH_SIZE", 2)
- stub_const("Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE", 2)
-
- # LDAP identities
- (1..4).each do |i|
- identities.create!(id: i, provider: 'ldapmain', extern_uid: " uid = foo #{i}, ou = People, dc = example, dc = com ", user_id: i)
- end
-
- # Non-LDAP identity
- identities.create!(id: 5, provider: 'foo', extern_uid: " uid = foo 5, ou = People, dc = example, dc = com ", user_id: 5)
- end
-
- it 'correctly schedules background migrations' do
- Sidekiq::Testing.fake! do
- Timecop.freeze do
- migrate!
-
- expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([described_class::MIGRATION, [1, 2]])
- expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(2.minutes.from_now.to_f)
- expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([described_class::MIGRATION, [3, 4]])
- expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(4.minutes.from_now.to_f)
- expect(BackgroundMigrationWorker.jobs[2]['args']).to eq([described_class::MIGRATION, [5, 5]])
- expect(BackgroundMigrationWorker.jobs[2]['at']).to eq(6.minutes.from_now.to_f)
- expect(BackgroundMigrationWorker.jobs.size).to eq 3
- end
- end
- end
-
- it 'migrates the LDAP identities' do
- perform_enqueued_jobs do
- migrate!
- identities.where(id: 1..4).each do |identity|
- expect(identity.extern_uid).to eq("uid=foo #{identity.id},ou=people,dc=example,dc=com")
- end
- end
- end
-
- it 'does not modify non-LDAP identities' do
- perform_enqueued_jobs do
- migrate!
- identity = identities.last
- expect(identity.extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ")
- end
- end
-end
diff --git a/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb b/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb
deleted file mode 100644
index 0ff98933d5c..00000000000
--- a/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'migrate', '20171215113714_populate_can_push_from_deploy_keys_projects.rb')
-
-describe PopulateCanPushFromDeployKeysProjects, :migration do
- let(:migration) { described_class.new }
- let(:deploy_keys) { table(:keys) }
- let(:deploy_keys_projects) { table(:deploy_keys_projects) }
- let(:projects) { table(:projects) }
-
- before do
- deploy_keys.inheritance_column = nil
-
- projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1')
- (1..10).each do |index|
- deploy_keys.create!(id: index, title: 'dummy', type: 'DeployKey', key: Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate + ' dummy@gitlab.com')
- deploy_keys_projects.create!(id: index, deploy_key_id: index, project_id: 1)
- end
- end
-
- describe '#up' do
- it 'migrates can_push from deploy_keys to deploy_keys_projects' do
- deploy_keys.limit(5).update_all(can_push: true)
-
- expected = deploy_keys.order(:id).pluck(:id, :can_push)
-
- migration.up
-
- expect(deploy_keys_projects.order(:id).pluck(:deploy_key_id, :can_push)).to eq expected
- end
- end
-
- describe '#down' do
- it 'migrates can_push from deploy_keys_projects to deploy_keys' do
- deploy_keys_projects.limit(5).update_all(can_push: true)
-
- expected = deploy_keys_projects.order(:id).pluck(:deploy_key_id, :can_push)
-
- migration.down
-
- expect(deploy_keys.order(:id).pluck(:id, :can_push)).to eq expected
- end
- end
-end
diff --git a/spec/migrations/remove_assignee_id_from_issue_spec.rb b/spec/migrations/remove_assignee_id_from_issue_spec.rb
deleted file mode 100644
index 2c6f992d3ae..00000000000
--- a/spec/migrations/remove_assignee_id_from_issue_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170523073948_remove_assignee_id_from_issue.rb')
-
-describe RemoveAssigneeIdFromIssue, :migration do
- let(:issues) { table(:issues) }
- let(:issue_assignees) { table(:issue_assignees) }
- let(:users) { table(:users) }
-
- let!(:user_1) { users.create(email: 'email1@example.com') }
- let!(:user_2) { users.create(email: 'email2@example.com') }
- let!(:user_3) { users.create(email: 'email3@example.com') }
-
- def create_issue(assignees:)
- issues.create.tap do |issue|
- assignees.each do |assignee|
- issue_assignees.create(issue_id: issue.id, user_id: assignee.id)
- end
- end
- end
-
- let!(:issue_single_assignee) { create_issue(assignees: [user_1]) }
- let!(:issue_no_assignee) { create_issue(assignees: []) }
- let!(:issue_multiple_assignees) { create_issue(assignees: [user_2, user_3]) }
-
- describe '#down' do
- it 'sets the assignee_id to a random matching assignee from the assignees table' do
- migrate!
- disable_migrations_output { described_class.new.down }
-
- expect(issue_single_assignee.reload.assignee_id).to eq(user_1.id)
- expect(issue_no_assignee.reload.assignee_id).to be_nil
- expect(issue_multiple_assignees.reload.assignee_id).to eq(user_2.id).or(user_3.id)
-
- disable_migrations_output { described_class.new.up }
- end
- end
-end
diff --git a/spec/migrations/remove_dot_git_from_usernames_spec.rb b/spec/migrations/remove_dot_git_from_usernames_spec.rb
deleted file mode 100644
index f11880a83e9..00000000000
--- a/spec/migrations/remove_dot_git_from_usernames_spec.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-require Rails.root.join('db', 'migrate', '20161226122833_remove_dot_git_from_usernames.rb')
-
-describe RemoveDotGitFromUsernames do
- let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let(:migration) { described_class.new }
-
- describe '#up' do
- before do
- update_namespace(user, 'test.git')
- end
-
- it 'renames user with .git in username' do
- migration.up
-
- expect(user.reload.username).to eq('test_git')
- expect(user.namespace.reload.path).to eq('test_git')
- expect(user.namespace.route.path).to eq('test_git')
- end
- end
-
- context 'when new path exists already' do
- describe '#up' do
- let(:user2) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
-
- before do
- update_namespace(user, 'test.git')
- update_namespace(user2, 'test_git')
-
- default_hash = Gitlab.config.repositories.storages.default.to_h
- default_hash['path'] = 'tmp/tests/custom_repositories'
- storages = { 'default' => Gitlab::GitalyClient::StorageSettings.new(default_hash) }
-
- allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
- allow(migration).to receive(:route_exists?).with('test_git').and_return(true)
- allow(migration).to receive(:route_exists?).with('test_git1').and_return(false)
- end
-
- it 'renames user with .git in username' do
- migration.up
-
- expect(user.reload.username).to eq('test_git1')
- expect(user.namespace.reload.path).to eq('test_git1')
- expect(user.namespace.route.path).to eq('test_git1')
- end
- end
- end
-
- def update_namespace(user, path)
- namespace = user.namespace
- namespace.path = path
- namespace.save!(validate: false)
-
- user.update_column(:username, path)
- end
-end
diff --git a/spec/migrations/remove_duplicate_mr_events_spec.rb b/spec/migrations/remove_duplicate_mr_events_spec.rb
deleted file mode 100644
index 2509ac6afd6..00000000000
--- a/spec/migrations/remove_duplicate_mr_events_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170815060945_remove_duplicate_mr_events.rb')
-
-describe RemoveDuplicateMrEvents, :delete do
- let(:migration) { described_class.new }
-
- describe '#up' do
- let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let(:merge_requests) { create_list(:merge_request, 2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let(:issue) { create(:issue) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:events) do
- [
- create(:event, :created, author: user, target: merge_requests.first), # rubocop:disable RSpec/FactoriesInMigrationSpecs
- create(:event, :created, author: user, target: merge_requests.first), # rubocop:disable RSpec/FactoriesInMigrationSpecs
- create(:event, :updated, author: user, target: merge_requests.first), # rubocop:disable RSpec/FactoriesInMigrationSpecs
- create(:event, :created, author: user, target: merge_requests.second), # rubocop:disable RSpec/FactoriesInMigrationSpecs
- create(:event, :created, author: user, target: issue), # rubocop:disable RSpec/FactoriesInMigrationSpecs
- create(:event, :created, author: user, target: issue) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- ]
- end
-
- it 'removes duplicated merge request create records' do
- expect { migration.up }.to change { Event.count }.from(6).to(5)
- end
- end
-end
diff --git a/spec/migrations/remove_empty_fork_networks_spec.rb b/spec/migrations/remove_empty_fork_networks_spec.rb
deleted file mode 100644
index f6d030ab25c..00000000000
--- a/spec/migrations/remove_empty_fork_networks_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20171114104051_remove_empty_fork_networks.rb')
-
-describe RemoveEmptyForkNetworks, :migration do
- let!(:fork_networks) { table(:fork_networks) }
- let!(:projects) { table(:projects) }
- let!(:fork_network_members) { table(:fork_network_members) }
-
- let(:deleted_project) { projects.create! }
- let!(:empty_network) { fork_networks.create!(id: 1, root_project_id: deleted_project.id) }
- let!(:other_network) { fork_networks.create!(id: 2, root_project_id: projects.create.id) }
-
- before do
- fork_network_members.create(fork_network_id: empty_network.id,
- project_id: empty_network.root_project_id)
- fork_network_members.create(fork_network_id: other_network.id,
- project_id: other_network.root_project_id)
-
- deleted_project.destroy!
- end
-
- after do
- Upload.reset_column_information
- end
-
- it 'deletes only the fork network without members' do
- expect(fork_networks.count).to eq(2)
-
- migrate!
-
- expect(fork_networks.find_by(id: empty_network.id)).to be_nil
- expect(fork_networks.find_by(id: other_network.id)).not_to be_nil
- expect(fork_networks.count).to eq(1)
- end
-end
diff --git a/spec/migrations/remove_project_labels_group_id_spec.rb b/spec/migrations/remove_project_labels_group_id_spec.rb
deleted file mode 100644
index 01b09e71d83..00000000000
--- a/spec/migrations/remove_project_labels_group_id_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20180202111106_remove_project_labels_group_id.rb')
-
-describe RemoveProjectLabelsGroupId, :delete do
- let(:migration) { described_class.new }
- let(:group) { create(:group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:project_label) { create(:label, group_id: group.id) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:group_label) { create(:group_label) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
-
- describe '#up' do
- it 'updates the project labels group ID' do
- expect { migration.up }.to change { project_label.reload.group_id }.to(nil)
- end
-
- it 'keeps the group labels group ID' do
- expect { migration.up }.not_to change { group_label.reload.group_id }
- end
- end
-end
diff --git a/spec/migrations/rename_duplicated_variable_key_spec.rb b/spec/migrations/rename_duplicated_variable_key_spec.rb
deleted file mode 100644
index 11096564dfa..00000000000
--- a/spec/migrations/rename_duplicated_variable_key_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'migrate', '20170622135451_rename_duplicated_variable_key.rb')
-
-describe RenameDuplicatedVariableKey, :migration do
- let(:variables) { table(:ci_variables) }
- let(:projects) { table(:projects) }
-
- before do
- projects.create!(id: 1)
- variables.create!(id: 1, key: 'key1', project_id: 1)
- variables.create!(id: 2, key: 'key2', project_id: 1)
- variables.create!(id: 3, key: 'keyX', project_id: 1)
- variables.create!(id: 4, key: 'keyX', project_id: 1)
- variables.create!(id: 5, key: 'keyY', project_id: 1)
- variables.create!(id: 6, key: 'keyX', project_id: 1)
- variables.create!(id: 7, key: 'key7', project_id: 1)
- variables.create!(id: 8, key: 'keyY', project_id: 1)
- end
-
- it 'correctly remove duplicated records with smaller id' do
- migrate!
-
- expect(variables.pluck(:id, :key)).to contain_exactly(
- [1, 'key1'],
- [2, 'key2'],
- [3, 'keyX_3'],
- [4, 'keyX_4'],
- [5, 'keyY_5'],
- [6, 'keyX'],
- [7, 'key7'],
- [8, 'keyY']
- )
- end
-end
diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb
deleted file mode 100644
index 80ae209e9d1..00000000000
--- a/spec/migrations/rename_more_reserved_project_names_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170313133418_rename_more_reserved_project_names.rb')
-
-# This migration uses multiple threads, and thus different transactions. This
-# means data created in this spec may not be visible to some threads. To work
-# around this we use the DELETE cleaning strategy.
-describe RenameMoreReservedProjectNames, :delete do
- let(:migration) { described_class.new }
- let!(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
-
- before do
- project.path = 'artifacts'
- project.save!(validate: false)
- end
-
- describe '#up' do
- context 'when project repository exists' do
- before do
- project.create_repository
- end
-
- context 'when no exception is raised' do
- it 'renames project with reserved names' do
- migration.up
-
- expect(project.reload.path).to eq('artifacts0')
- end
- end
-
- context 'when exception is raised during rename' do
- before do
- service = instance_double('service')
-
- allow(service)
- .to receive(:execute)
- .and_raise(Projects::AfterRenameService::RenameFailedError)
-
- expect(migration)
- .to receive(:after_rename_service)
- .and_return(service)
- end
-
- it 'captures exception from project rename' do
- expect { migration.up }.not_to raise_error
- end
- end
- end
-
- context 'when project repository does not exist' do
- it 'does not raise error' do
- expect { migration.up }.not_to raise_error
- end
- end
- end
-end
diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb
deleted file mode 100644
index 93e5c032287..00000000000
--- a/spec/migrations/rename_reserved_project_names_spec.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20161221153951_rename_reserved_project_names.rb')
-
-# This migration is using factories, which set fields that don't actually
-# exist in the DB schema previous to 20161221153951. Thus we just use the
-# latest schema when testing this migration.
-# This is ok-ish because:
-# 1. This migration is a data migration
-# 2. It only relies on very stable DB fields: routes.id, routes.path, namespaces.id, projects.namespace_id
-# Ideally, the test should not use factories and rely on the `table` helper instead.
-describe RenameReservedProjectNames, :migration, schema: :latest do
- let(:migration) { described_class.new }
- let!(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
-
- before do
- project.path = 'projects'
- project.save!(validate: false)
- end
-
- describe '#up' do
- context 'when project repository exists' do
- before do
- project.create_repository
- end
-
- context 'when no exception is raised' do
- it 'renames project with reserved names' do
- migration.up
-
- expect(project.reload.path).to eq('projects0')
- end
- end
-
- context 'when exception is raised during rename' do
- before do
- service = instance_double('service')
-
- allow(service)
- .to receive(:execute)
- .and_raise(Projects::AfterRenameService::RenameFailedError)
-
- expect(migration)
- .to receive(:after_rename_service)
- .and_return(service)
- end
-
- it 'captures exception from project rename' do
- expect { migration.up }.not_to raise_error
- end
- end
- end
-
- context 'when project repository does not exist' do
- it 'does not raise error' do
- expect { migration.up }.not_to raise_error
- end
- end
- end
-end
diff --git a/spec/migrations/rename_users_with_renamed_namespace_spec.rb b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
deleted file mode 100644
index b8a4dc2b2c0..00000000000
--- a/spec/migrations/rename_users_with_renamed_namespace_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170518200835_rename_users_with_renamed_namespace.rb')
-
-describe RenameUsersWithRenamedNamespace, :delete do
- it 'renames a user that had their namespace renamed to the namespace path' do
- other_user = create(:user, username: 'kodingu') # rubocop:disable RSpec/FactoriesInMigrationSpecs
- other_user1 = create(:user, username: 'api0') # rubocop:disable RSpec/FactoriesInMigrationSpecs
-
- user = create(:user, username: "Users0") # rubocop:disable RSpec/FactoriesInMigrationSpecs
- user.update_column(:username, 'Users')
- user1 = create(:user, username: "import0") # rubocop:disable RSpec/FactoriesInMigrationSpecs
- user1.update_column(:username, 'import')
-
- described_class.new.up
-
- expect(user.reload.username).to eq('Users0')
- expect(user1.reload.username).to eq('import0')
-
- expect(other_user.reload.username).to eq('kodingu')
- expect(other_user1.reload.username).to eq('api0')
- end
-end
diff --git a/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb b/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb
deleted file mode 100644
index 76fe16581ac..00000000000
--- a/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20171026082505_schedule_merge_request_latest_merge_request_diff_id_migrations')
-
-describe ScheduleMergeRequestLatestMergeRequestDiffIdMigrations, :migration, :sidekiq do
- let(:projects_table) { table(:projects) }
- let(:merge_requests_table) { table(:merge_requests) }
- let(:merge_request_diffs_table) { table(:merge_request_diffs) }
-
- let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') }
-
- let!(:merge_request_1) { create_mr!('mr_1', diffs: 1) }
- let!(:merge_request_2) { create_mr!('mr_2', diffs: 2) }
- let!(:merge_request_migrated) { create_mr!('merge_request_migrated', diffs: 3) }
- let!(:merge_request_4) { create_mr!('mr_4', diffs: 3) }
-
- def create_mr!(name, diffs: 0)
- merge_request =
- merge_requests_table.create!(target_project_id: project.id,
- target_branch: 'master',
- source_project_id: project.id,
- source_branch: name,
- title: name)
-
- diffs.times do
- merge_request_diffs_table.create!(merge_request_id: merge_request.id)
- end
-
- merge_request
- end
-
- def diffs_for(merge_request)
- merge_request_diffs_table.where(merge_request_id: merge_request.id)
- end
-
- before do
- stub_const("#{described_class.name}::BATCH_SIZE", 1)
-
- diff_id = diffs_for(merge_request_migrated).minimum(:id)
- merge_request_migrated.update!(latest_merge_request_diff_id: diff_id)
- end
-
- it 'correctly schedules background migrations' do
- Sidekiq::Testing.fake! do
- Timecop.freeze do
- migrate!
-
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, merge_request_1.id, merge_request_1.id)
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, merge_request_2.id, merge_request_2.id)
- expect(described_class::MIGRATION).to be_scheduled_delayed_migration(15.minutes, merge_request_4.id, merge_request_4.id)
- expect(BackgroundMigrationWorker.jobs.size).to eq 3
- end
- end
- end
-
- it 'schedules background migrations' do
- perform_enqueued_jobs do
- expect(merge_requests_table.where(latest_merge_request_diff_id: nil).count).to eq 3
-
- migrate!
-
- expect(merge_requests_table.where(latest_merge_request_diff_id: nil).count).to eq 0
- end
- end
-end
diff --git a/spec/migrations/set_issue_id_for_all_versions_spec.rb b/spec/migrations/set_issue_id_for_all_versions_spec.rb
new file mode 100644
index 00000000000..bfc2731181b
--- /dev/null
+++ b/spec/migrations/set_issue_id_for_all_versions_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190715043954_set_issue_id_for_all_versions.rb')
+
+describe SetIssueIdForAllVersions, :migration do
+ let(:projects) { table(:projects) }
+ let(:issues) { table(:issues) }
+ let(:designs) { table(:design_management_designs) }
+ let(:designs_versions) { table(:design_management_designs_versions) }
+ let(:versions) { table(:design_management_versions) }
+
+ before do
+ @project = projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1)
+
+ @issue_1 = issues.create!(description: 'first', project_id: @project.id)
+ @issue_2 = issues.create!(description: 'second', project_id: @project.id)
+
+ @design_1 = designs.create!(issue_id: @issue_1.id, filename: 'homepage-1.jpg', project_id: @project.id)
+ @design_2 = designs.create!(issue_id: @issue_2.id, filename: 'homepage-2.jpg', project_id: @project.id)
+
+ @version_1 = versions.create!(sha: 'foo')
+ @version_2 = versions.create!(sha: 'bar')
+
+ designs_versions.create!(version_id: @version_1.id, design_id: @design_1.id)
+ designs_versions.create!(version_id: @version_2.id, design_id: @design_2.id)
+ end
+
+ it 'correctly sets issue_id' do
+ expect(versions.where(issue_id: nil).count).to eq(2)
+
+ migrate!
+
+ expect(versions.where(issue_id: nil).count).to eq(0)
+ expect(versions.find(@version_1.id).issue_id).to eq(@issue_1.id)
+ expect(versions.find(@version_2.id).issue_id).to eq(@issue_2.id)
+ end
+end
diff --git a/spec/migrations/track_untracked_uploads_spec.rb b/spec/migrations/track_untracked_uploads_spec.rb
deleted file mode 100644
index 2fccfb3f12c..00000000000
--- a/spec/migrations/track_untracked_uploads_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20171103140253_track_untracked_uploads')
-
-describe TrackUntrackedUploads, :migration, :sidekiq do
- include MigrationsHelpers::TrackUntrackedUploadsHelpers
-
- it 'correctly schedules the follow-up background migration' do
- Sidekiq::Testing.fake! do
- migrate!
-
- expect(described_class::MIGRATION).to be_scheduled_migration
- expect(BackgroundMigrationWorker.jobs.size).to eq(1)
- end
- end
-end
diff --git a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
deleted file mode 100644
index 5f5ba426d69..00000000000
--- a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'migrate', '20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb')
-
-describe TurnNestedGroupsIntoRegularGroupsForMysql do
- let!(:parent_group) { create(:group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:child_group) { create(:group, parent: parent_group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:project) { create(:project, :legacy_storage, :empty_repo, namespace: child_group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:member) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let(:migration) { described_class.new }
-
- before do
- parent_group.add_developer(member)
-
- allow(migration).to receive(:run_migration?).and_return(true)
- allow(migration).to receive(:verbose).and_return(false)
- end
-
- describe '#up' do
- let(:updated_project) do
- # path_with_namespace is memoized in an instance variable so we retrieve a
- # new row here to work around that.
- Project.find(project.id)
- end
-
- before do
- migration.up
- end
-
- it 'unsets the parent_id column' do
- expect(Namespace.where('parent_id IS NOT NULL').any?).to eq(false)
- end
-
- it 'adds members of parent groups as members to the migrated group' do
- is_member = child_group.members
- .where(user_id: member, access_level: Gitlab::Access::DEVELOPER).any?
-
- expect(is_member).to eq(true)
- end
-
- it 'update the path of the nested group' do
- child_group.reload
-
- expect(child_group.path).to eq("#{parent_group.name}-#{child_group.name}")
- end
-
- it 'renames projects of the nested group' do
- expect(updated_project.full_path)
- .to eq("#{parent_group.name}-#{child_group.name}/#{updated_project.path}")
- end
-
- it 'renames the repository of any projects' do
- repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- updated_project.repository.path
- end
-
- expect(repo_path)
- .to end_with("#{parent_group.name}-#{child_group.name}/#{updated_project.path}.git")
-
- expect(File.directory?(repo_path)).to eq(true)
- end
-
- it 'creates a redirect route for renamed projects' do
- exists = RedirectRoute
- .where(source_type: 'Project', source_id: project.id)
- .any?
-
- expect(exists).to eq(true)
- end
- end
-end
diff --git a/spec/migrations/update_legacy_diff_notes_type_for_import_spec.rb b/spec/migrations/update_legacy_diff_notes_type_for_import_spec.rb
deleted file mode 100644
index d625b60ff50..00000000000
--- a/spec/migrations/update_legacy_diff_notes_type_for_import_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170927112318_update_legacy_diff_notes_type_for_import.rb')
-
-describe UpdateLegacyDiffNotesTypeForImport, :migration do
- let(:notes) { table(:notes) }
-
- before do
- notes.inheritance_column = nil
-
- notes.create(type: 'Note')
- notes.create(type: 'LegacyDiffNote')
- notes.create(type: 'Github::Import::Note')
- notes.create(type: 'Github::Import::LegacyDiffNote')
- end
-
- it 'updates the notes type' do
- migrate!
-
- expect(notes.pluck(:type))
- .to contain_exactly('Note', 'Github::Import::Note', 'LegacyDiffNote', 'LegacyDiffNote')
- end
-end
diff --git a/spec/migrations/update_notes_type_for_import_spec.rb b/spec/migrations/update_notes_type_for_import_spec.rb
deleted file mode 100644
index 06195d970d8..00000000000
--- a/spec/migrations/update_notes_type_for_import_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170927112319_update_notes_type_for_import.rb')
-
-describe UpdateNotesTypeForImport, :migration do
- let(:notes) { table(:notes) }
-
- before do
- notes.inheritance_column = nil
-
- notes.create(type: 'Note')
- notes.create(type: 'LegacyDiffNote')
- notes.create(type: 'Github::Import::Note')
- notes.create(type: 'Github::Import::LegacyDiffNote')
- end
-
- it 'updates the notes type' do
- migrate!
-
- expect(notes.pluck(:type))
- .to contain_exactly('Note', 'Note', 'LegacyDiffNote', 'Github::Import::LegacyDiffNote')
- end
-end
diff --git a/spec/migrations/update_retried_for_ci_build_spec.rb b/spec/migrations/update_retried_for_ci_build_spec.rb
deleted file mode 100644
index 637dcbb8e01..00000000000
--- a/spec/migrations/update_retried_for_ci_build_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170503004427_update_retried_for_ci_build.rb')
-
-describe UpdateRetriedForCiBuild, :delete do
- let(:pipeline) { create(:ci_pipeline) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:build_old) { create(:ci_build, pipeline: pipeline, name: 'test') } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:build_new) { create(:ci_build, pipeline: pipeline, name: 'test') } # rubocop:disable RSpec/FactoriesInMigrationSpecs
-
- before do
- described_class.new.up
- end
-
- it 'updates ci_builds.is_retried' do
- expect(build_old.reload).to be_retried
- expect(build_new.reload).not_to be_retried
- end
-end
diff --git a/spec/migrations/update_upload_paths_to_system_spec.rb b/spec/migrations/update_upload_paths_to_system_spec.rb
deleted file mode 100644
index 984b428a020..00000000000
--- a/spec/migrations/update_upload_paths_to_system_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170317162059_update_upload_paths_to_system.rb')
-
-describe UpdateUploadPathsToSystem, :migration do
- let(:migration) { described_class.new }
- let(:uploads_table) { table(:uploads) }
- let(:base_upload_attributes) { { size: 42, uploader: 'John Doe' } }
-
- before do
- allow(migration).to receive(:say)
- end
-
- describe '#uploads_to_switch_to_new_path' do
- it 'contains only uploads with the old path for the correct models' do
- _upload_for_other_type = create_upload('Pipeline', 'uploads/ci_pipeline/avatar.jpg')
- _upload_with_system_path = create_upload('Project', 'uploads/-/system/project/avatar.jpg')
- _upload_with_other_path = create_upload('Project', 'thelongsecretforafileupload/avatar.jpg')
- old_upload = create_upload('Project', 'uploads/project/avatar.jpg')
- group_upload = create_upload('Namespace', 'uploads/group/avatar.jpg')
-
- expect(uploads_table.where(migration.uploads_to_switch_to_new_path)).to contain_exactly(old_upload, group_upload)
- end
- end
-
- describe '#uploads_to_switch_to_old_path' do
- it 'contains only uploads with the new path for the correct models' do
- _upload_for_other_type = create_upload('Pipeline', 'uploads/ci_pipeline/avatar.jpg')
- upload_with_system_path = create_upload('Project', 'uploads/-/system/project/avatar.jpg')
- _upload_with_other_path = create_upload('Project', 'thelongsecretforafileupload/avatar.jpg')
- _old_upload = create_upload('Project', 'uploads/project/avatar.jpg')
-
- expect(uploads_table.where(migration.uploads_to_switch_to_old_path)).to contain_exactly(upload_with_system_path)
- end
- end
-
- describe '#up' do
- it 'updates old upload records to the new path' do
- old_upload = create_upload('Project', 'uploads/project/avatar.jpg')
-
- migration.up
-
- expect(old_upload.reload.path).to eq('uploads/-/system/project/avatar.jpg')
- end
- end
-
- describe '#down' do
- it 'updates the new system patsh to the old paths' do
- new_upload = create_upload('Project', 'uploads/-/system/project/avatar.jpg')
-
- migration.down
-
- expect(new_upload.reload.path).to eq('uploads/project/avatar.jpg')
- end
- end
-
- def create_upload(type, path)
- uploads_table.create(base_upload_attributes.merge(model_type: type, path: path))
- end
-end
diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb
index 2762eaeccd3..2a689754ee0 100644
--- a/spec/models/active_session_spec.rb
+++ b/spec/models/active_session_spec.rb
@@ -114,7 +114,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
redis.sadd("session:lookup:user:gitlab:#{user.id}", session_ids)
end
- expect(ActiveSession.session_ids_for_user(user)).to eq(session_ids)
+ expect(ActiveSession.session_ids_for_user(user.id)).to eq(session_ids)
end
end
@@ -132,6 +132,19 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
expect(ActiveSession.sessions_from_ids([])).to eq([])
end
+
+ it 'uses redis lookup in batches' do
+ stub_const('ActiveSession::SESSION_BATCH_SIZE', 1)
+
+ redis = double(:redis)
+ expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
+
+ sessions = ['session-a', 'session-b']
+ mget_responses = sessions.map { |session| [Marshal.dump(session)]}
+ expect(redis).to receive(:mget).twice.and_return(*mget_responses)
+
+ expect(ActiveSession.sessions_from_ids([1, 2])).to eql(sessions)
+ end
end
describe '.set' do
diff --git a/spec/models/analytics/cycle_analytics/project_stage_spec.rb b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
new file mode 100644
index 00000000000..83d6ff754c5
--- /dev/null
+++ b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Analytics::CycleAnalytics::ProjectStage do
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ end
+
+ it 'default stages must be valid' do
+ project = create(:project)
+
+ Gitlab::Analytics::CycleAnalytics::DefaultStages.all.each do |params|
+ stage = described_class.new(params.merge(project: project))
+ expect(stage).to be_valid
+ end
+ end
+
+ it_behaves_like "cycle analytics stage" do
+ let(:parent) { create(:project) }
+ let(:parent_name) { :project }
+ end
+end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index ab6f6dfe720..db80b85360f 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -37,6 +37,17 @@ describe ApplicationSetting do
it { is_expected.not_to allow_value("myemail@example.com").for(:lets_encrypt_notification_email) }
it { is_expected.to allow_value("myemail@test.example.com").for(:lets_encrypt_notification_email) }
+ it { is_expected.to allow_value(['192.168.1.1'] * 1_000).for(:outbound_local_requests_whitelist) }
+ it { is_expected.not_to allow_value(['192.168.1.1'] * 1_001).for(:outbound_local_requests_whitelist) }
+ it { is_expected.to allow_value(['1' * 255]).for(:outbound_local_requests_whitelist) }
+ it { is_expected.not_to allow_value(['1' * 256]).for(:outbound_local_requests_whitelist) }
+ it { is_expected.not_to allow_value(['ğitlab.com']).for(:outbound_local_requests_whitelist) }
+ it { is_expected.to allow_value(['xn--itlab-j1a.com']).for(:outbound_local_requests_whitelist) }
+ it { is_expected.not_to allow_value(['<h1></h1>']).for(:outbound_local_requests_whitelist) }
+ it { is_expected.to allow_value(['gitlab.com']).for(:outbound_local_requests_whitelist) }
+ it { is_expected.not_to allow_value(nil).for(:outbound_local_requests_whitelist) }
+ it { is_expected.to allow_value([]).for(:outbound_local_requests_whitelist) }
+
context "when user accepted let's encrypt terms of service" do
before do
setting.update(lets_encrypt_terms_of_service_accepted: true)
diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb
index 8452ac69734..b15b26b1630 100644
--- a/spec/models/award_emoji_spec.rb
+++ b/spec/models/award_emoji_spec.rb
@@ -44,6 +44,29 @@ describe AwardEmoji do
end
end
+ describe 'scopes' do
+ set(:thumbsup) { create(:award_emoji, name: 'thumbsup') }
+ set(:thumbsdown) { create(:award_emoji, name: 'thumbsdown') }
+
+ describe '.upvotes' do
+ it { expect(described_class.upvotes).to contain_exactly(thumbsup) }
+ end
+
+ describe '.downvotes' do
+ it { expect(described_class.downvotes).to contain_exactly(thumbsdown) }
+ end
+
+ describe '.named' do
+ it { expect(described_class.named('thumbsup')).to contain_exactly(thumbsup) }
+ it { expect(described_class.named(%w[thumbsup thumbsdown])).to contain_exactly(thumbsup, thumbsdown) }
+ end
+
+ describe '.awarded_by' do
+ it { expect(described_class.awarded_by(thumbsup.user)).to contain_exactly(thumbsup) }
+ it { expect(described_class.awarded_by([thumbsup.user, thumbsdown.user])).to contain_exactly(thumbsup, thumbsdown) }
+ end
+ end
+
describe 'expiring ETag cache' do
context 'on a note' do
let(:note) { create(:note_on_issue) }
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index eb32198265b..a871f9b3fe6 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -23,7 +23,7 @@ describe Ci::Bridge do
let(:status) { bridge.detailed_status(user) }
it 'returns detailed status object' do
- expect(status).to be_a Gitlab::Ci::Status::Success
+ expect(status).to be_a Gitlab::Ci::Status::Created
end
end
diff --git a/spec/models/ci/build_need_spec.rb b/spec/models/ci/build_need_spec.rb
new file mode 100644
index 00000000000..450dd550a8f
--- /dev/null
+++ b/spec/models/ci/build_need_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::BuildNeed, model: true do
+ let(:build_need) { build(:ci_build_need) }
+
+ it { is_expected.to belong_to(:build) }
+
+ it { is_expected.to validate_presence_of(:build) }
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_length_of(:name).is_at_most(128) }
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index d98db024f73..bc853d45085 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -19,9 +19,11 @@ describe Ci::Build do
it { is_expected.to belong_to(:runner) }
it { is_expected.to belong_to(:trigger_request) }
it { is_expected.to belong_to(:erased_by) }
- it { is_expected.to have_many(:trace_sections)}
+ it { is_expected.to have_many(:trace_sections) }
+ it { is_expected.to have_many(:needs) }
it { is_expected.to have_one(:deployment) }
- it { is_expected.to have_one(:runner_session)}
+ it { is_expected.to have_one(:runner_session) }
+ it { is_expected.to have_many(:job_variables) }
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) }
@@ -147,6 +149,56 @@ describe Ci::Build do
end
end
+ describe '.with_stale_live_trace' do
+ subject { described_class.with_stale_live_trace }
+
+ context 'when build has a stale live trace' do
+ let!(:build) { create(:ci_build, :success, :trace_live, finished_at: 1.day.ago) }
+
+ it 'selects the build' do
+ is_expected.to eq([build])
+ end
+ end
+
+ context 'when build does not have a stale live trace' do
+ let!(:build) { create(:ci_build, :success, :trace_live, finished_at: 1.hour.ago) }
+
+ it 'does not select the build' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.finished_before' do
+ subject { described_class.finished_before(date) }
+
+ let(:date) { 1.hour.ago }
+
+ context 'when build has finished one day ago' do
+ let!(:build) { create(:ci_build, :success, finished_at: 1.day.ago) }
+
+ it 'selects the build' do
+ is_expected.to eq([build])
+ end
+ end
+
+ context 'when build has finished 30 minutes ago' do
+ let!(:build) { create(:ci_build, :success, finished_at: 30.minutes.ago) }
+
+ it 'returns an empty array' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when build is still running' do
+ let!(:build) { create(:ci_build, :running) }
+
+ it 'returns an empty array' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe '.with_reports' do
subject { described_class.with_reports(Ci::JobArtifact.test_reports) }
@@ -181,6 +233,47 @@ describe Ci::Build do
end
end
+ describe '.with_needs' do
+ let!(:build) { create(:ci_build) }
+ let!(:build_b) { create(:ci_build) }
+ let!(:build_need_a) { create(:ci_build_need, build: build) }
+ let!(:build_need_b) { create(:ci_build_need, build: build_b) }
+
+ context 'when passing build name' do
+ subject { described_class.with_needs(build_need_a.name) }
+
+ it { is_expected.to contain_exactly(build) }
+ end
+
+ context 'when not passing any build name' do
+ subject { described_class.with_needs }
+
+ it { is_expected.to contain_exactly(build, build_b) }
+ end
+
+ context 'when not matching build name' do
+ subject { described_class.with_needs('undefined') }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ describe '.without_needs' do
+ let!(:build) { create(:ci_build) }
+
+ subject { described_class.without_needs }
+
+ context 'when no build_need is created' do
+ it { is_expected.to contain_exactly(build) }
+ end
+
+ context 'when a build_need is created' do
+ let!(:need_a) { create(:ci_build_need, build: build) }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
describe '#enqueue' do
let(:build) { create(:ci_build, :created) }
@@ -594,6 +687,59 @@ describe Ci::Build do
expect(staging.depends_on_builds.map(&:id))
.to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
end
+
+ describe '#dependencies' do
+ let(:dependencies) { }
+ let(:needs) { }
+
+ let!(:final) do
+ create(:ci_build,
+ pipeline: pipeline, name: 'final',
+ stage_idx: 3, stage: 'deploy', options: {
+ dependencies: dependencies
+ }
+ )
+ end
+
+ before do
+ needs.to_a.each do |need|
+ create(:ci_build_need, build: final, name: need)
+ end
+ end
+
+ subject { final.dependencies }
+
+ context 'when depedencies are defined' do
+ let(:dependencies) { %w(rspec staging) }
+
+ it { is_expected.to contain_exactly(rspec_test, staging) }
+ end
+
+ context 'when needs are defined' do
+ let(:needs) { %w(build rspec staging) }
+
+ it { is_expected.to contain_exactly(build, rspec_test, staging) }
+
+ context 'when ci_dag_support is disabled' do
+ before do
+ stub_feature_flags(ci_dag_support: false)
+ end
+
+ it { is_expected.to contain_exactly(build, rspec_test, rubocop_test, staging) }
+ end
+ end
+
+ context 'when needs and dependencies are defined' do
+ let(:dependencies) { %w(rspec staging) }
+ let(:needs) { %w(build rspec staging) }
+
+ it { is_expected.to contain_exactly(rspec_test, staging) }
+ end
+
+ context 'when nor dependencies or needs are defined' do
+ it { is_expected.to contain_exactly(build, rspec_test, rubocop_test, staging) }
+ end
+ end
end
describe '#triggered_by?' do
@@ -692,6 +838,34 @@ describe Ci::Build do
end
end
+ describe '#has_live_trace?' do
+ subject { build.has_live_trace? }
+
+ let(:build) { create(:ci_build, :trace_live) }
+
+ it { is_expected.to be_truthy }
+
+ context 'when build does not have live trace' do
+ let(:build) { create(:ci_build) }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#has_archived_trace?' do
+ subject { build.has_archived_trace? }
+
+ let(:build) { create(:ci_build, :trace_artifact) }
+
+ it { is_expected.to be_truthy }
+
+ context 'when build does not have archived trace' do
+ let(:build) { create(:ci_build) }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
describe '#has_job_artifacts?' do
subject { build.has_job_artifacts? }
@@ -2013,6 +2187,7 @@ describe Ci::Build do
{ key: 'CI', value: 'true', public: true, masked: false },
{ key: 'GITLAB_CI', value: 'true', public: true, masked: false },
{ key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true, masked: false },
+ { key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host, public: true, masked: false },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false },
{ key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false },
@@ -2215,6 +2390,32 @@ describe Ci::Build do
it_behaves_like 'containing environment variables'
end
end
+
+ context 'when project has an environment specific variable' do
+ let(:environment_specific_variable) do
+ { key: 'MY_STAGING_ONLY_VARIABLE', value: 'environment_specific_variable', public: false, masked: false }
+ end
+
+ before do
+ create(:ci_variable, environment_specific_variable.slice(:key, :value)
+ .merge(project: project, environment_scope: 'stag*'))
+ end
+
+ it_behaves_like 'containing environment variables'
+
+ context 'when environment scope does not match build environment' do
+ it { is_expected.not_to include(environment_specific_variable) }
+ end
+
+ context 'when environment scope matches build environment' do
+ before do
+ create(:environment, name: 'staging', project: project)
+ build.update!(environment: 'staging')
+ end
+
+ it { is_expected.to include(environment_specific_variable) }
+ end
+ end
end
context 'when build started manually' do
@@ -2229,6 +2430,16 @@ describe Ci::Build do
it { is_expected.to include(manual_variable) }
end
+ context 'when job variable is defined' do
+ let(:job_variable) { { key: 'first', value: 'first', public: false, masked: false } }
+
+ before do
+ create(:ci_job_variable, job_variable.slice(:key, :value).merge(job: build))
+ end
+
+ it { is_expected.to include(job_variable) }
+ end
+
context 'when build is for tag' do
let(:tag_variable) do
{ key: 'CI_COMMIT_TAG', value: 'master', public: true, masked: false }
@@ -3574,6 +3785,7 @@ describe Ci::Build do
before do
build.ensure_metadata
+ build.needs.create!(name: 'another-job')
end
it 'drops metadata' do
@@ -3581,6 +3793,7 @@ describe Ci::Build do
expect(build.reload).to be_degenerated
expect(build.metadata).to be_nil
+ expect(build.needs).to be_empty
end
end
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index 1ba66565e03..1413da231e0 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -70,6 +70,31 @@ describe Ci::JobArtifact do
end
end
+ describe '.archived_trace_exists_for?' do
+ subject { described_class.archived_trace_exists_for?(job_id) }
+
+ let!(:artifact) { create(:ci_job_artifact, :trace, job: job) }
+ let(:job) { create(:ci_build) }
+
+ context 'when the specified job_id exists' do
+ let(:job_id) { job.id }
+
+ it { is_expected.to be_truthy }
+
+ context 'when the job does have archived trace' do
+ let!(:artifact) { }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'when the specified job_id does not exist' do
+ let(:job_id) { 10000 }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
describe 'callbacks' do
subject { create(:ci_job_artifact, :archive) }
diff --git a/spec/models/ci/job_variable_spec.rb b/spec/models/ci/job_variable_spec.rb
new file mode 100644
index 00000000000..b94a914c784
--- /dev/null
+++ b/spec/models/ci/job_variable_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::JobVariable do
+ subject { build(:ci_job_variable) }
+
+ it_behaves_like "CI variable"
+
+ it { is_expected.to belong_to(:job) }
+ it { is_expected.to validate_uniqueness_of(:key).scoped_to(:job_id) }
+end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 55cea48b641..7d84d094bdf 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Ci::Pipeline, :mailer do
include ProjectForksHelper
+ include StubRequests
let(:user) { create(:user) }
set(:project) { create(:project) }
@@ -1799,7 +1800,7 @@ describe Ci::Pipeline, :mailer do
end
end
- describe '.latest_successful_for' do
+ describe '.latest_successful_for_ref' do
include_context 'with some outdated pipelines'
let!(:latest_successful_pipeline) do
@@ -1807,7 +1808,20 @@ describe Ci::Pipeline, :mailer do
end
it 'returns the latest successful pipeline' do
- expect(described_class.latest_successful_for('ref'))
+ expect(described_class.latest_successful_for_ref('ref'))
+ .to eq(latest_successful_pipeline)
+ end
+ end
+
+ describe '.latest_successful_for_sha' do
+ include_context 'with some outdated pipelines'
+
+ let!(:latest_successful_pipeline) do
+ create_pipeline(:success, 'ref', 'awesomesha', project)
+ end
+
+ it 'returns the latest successful pipeline' do
+ expect(described_class.latest_successful_for_sha('awesomesha'))
.to eq(latest_successful_pipeline)
end
end
@@ -1916,6 +1930,13 @@ describe Ci::Pipeline, :mailer do
it { is_expected.to be_an(Array) }
end
+ describe '.bridgeable_statuses' do
+ subject { described_class.bridgeable_statuses }
+
+ it { is_expected.to be_an(Array) }
+ it { is_expected.not_to include('created', 'preparing', 'pending') }
+ end
+
describe '#status' do
let(:build) do
create(:ci_build, :created, pipeline: pipeline, name: 'test')
@@ -2484,7 +2505,7 @@ describe Ci::Pipeline, :mailer do
let(:enabled) { true }
before do
- WebMock.stub_request(:post, hook.url)
+ stub_full_request(hook.url, method: :post)
end
context 'with multiple builds' do
@@ -2538,7 +2559,7 @@ describe Ci::Pipeline, :mailer do
end
def have_requested_pipeline_hook(status)
- have_requested(:post, hook.url).with do |req|
+ have_requested(:post, stubbed_hostname(hook.url)).with do |req|
json_body = JSON.parse(req.body)
json_body['object_attributes']['status'] == status &&
json_body['builds'].length == 2
@@ -2998,4 +3019,28 @@ describe Ci::Pipeline, :mailer do
end
end
end
+
+ describe '#error_messages' do
+ subject { pipeline.error_messages }
+
+ before do
+ pipeline.valid?
+ end
+
+ context 'when pipeline has errors' do
+ let(:pipeline) { build(:ci_pipeline, sha: nil, ref: nil) }
+
+ it 'returns the full error messages' do
+ is_expected.to eq("Sha can't be blank and Ref can't be blank")
+ end
+ end
+
+ context 'when pipeline does not have errors' do
+ let(:pipeline) { build(:ci_pipeline) }
+
+ it 'returns empty string' do
+ is_expected.to be_empty
+ end
+ end
+ end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index f735a89f69f..70ff3cf5dc4 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -80,6 +80,13 @@ describe Ci::Runner do
end
end
+ describe 'constraints' do
+ it '.UPDATE_CONTACT_COLUMN_EVERY' do
+ expect(described_class::UPDATE_CONTACT_COLUMN_EVERY.max)
+ .to be <= described_class::ONLINE_CONTACT_TIMEOUT
+ end
+ end
+
describe '#access_level' do
context 'when creating new runner and access_level is nil' do
let(:runner) do
@@ -146,7 +153,7 @@ describe Ci::Runner do
expect(described_class.belonging_to_parent_group_of_project(project.id)).to contain_exactly(runner)
end
- context 'with a parent group with a runner', :nested_groups do
+ context 'with a parent group with a runner' do
let(:runner) { create(:ci_runner, :group, groups: [parent_group]) }
let(:project) { create(:project, group: group) }
let(:group) { create(:group, parent: parent_group) }
@@ -554,7 +561,7 @@ describe Ci::Runner do
end
def expect_value_in_queues
- Gitlab::Redis::Queues.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
runner_queue_key = runner.send(:runner_queue_key)
expect(redis.get(runner_queue_key))
end
@@ -627,7 +634,7 @@ describe Ci::Runner do
end
it 'cleans up the queue' do
- Gitlab::Redis::Queues.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
expect(redis.get(queue_key)).to be_nil
end
end
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index fde8375f2a5..5b5d6f51b33 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -54,19 +54,31 @@ describe Ci::Trigger do
end
describe '#can_access_project?' do
+ let(:owner) { create(:user) }
let(:trigger) { create(:ci_trigger, owner: owner, project: project) }
context 'when owner is blank' do
- let(:owner) { nil }
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ trigger.update_attribute(:owner, nil)
+ end
subject { trigger.can_access_project? }
- it { is_expected.to eq(true) }
+ it { is_expected.to eq(false) }
+
+ context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: true)
+ end
+
+ subject { trigger.can_access_project? }
+
+ it { is_expected.to eq(true) }
+ end
end
context 'when owner is set' do
- let(:owner) { create(:user) }
-
subject { trigger.can_access_project? }
context 'and is member of the project' do
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index a231c7eaed8..3ff547456c6 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -10,6 +10,7 @@ describe Ci::Variable do
describe 'validations' do
it { is_expected.to include_module(Presentable) }
it { is_expected.to include_module(Maskable) }
+ it { is_expected.to include_module(HasEnvironmentScope) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope).with_message(/\(\w+\) has already been taken/) }
end
diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb
index 8d853a04e33..93050e80b07 100644
--- a/spec/models/clusters/applications/cert_manager_spec.rb
+++ b/spec/models/clusters/applications/cert_manager_spec.rb
@@ -3,17 +3,17 @@
require 'rails_helper'
describe Clusters::Applications::CertManager do
- let(:cert_manager) { create(:clusters_applications_cert_managers) }
+ let(:cert_manager) { create(:clusters_applications_cert_manager) }
- include_examples 'cluster application core specs', :clusters_applications_cert_managers
- include_examples 'cluster application status specs', :clusters_applications_cert_managers
- include_examples 'cluster application version specs', :clusters_applications_cert_managers
+ include_examples 'cluster application core specs', :clusters_applications_cert_manager
+ include_examples 'cluster application status specs', :clusters_applications_cert_manager
+ include_examples 'cluster application version specs', :clusters_applications_cert_manager
include_examples 'cluster application initial status specs'
describe '#can_uninstall?' do
subject { cert_manager.can_uninstall? }
- it { is_expected.to be_falsey }
+ it { is_expected.to be_truthy }
end
describe '#install_command' do
@@ -48,7 +48,7 @@ describe Clusters::Applications::CertManager do
expect(subject.version).to eq('v0.5.2')
expect(subject).to be_rbac
expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file))
- expect(subject.postinstall).to eq(['/usr/bin/kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml'])
+ expect(subject.postinstall).to eq(['kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml'])
end
context 'for a specific user' do
@@ -72,7 +72,7 @@ describe Clusters::Applications::CertManager do
end
context 'application failed to install previously' do
- let(:cert_manager) { create(:clusters_applications_cert_managers, :errored, version: '0.0.1') }
+ let(:cert_manager) { create(:clusters_applications_cert_manager, :errored, version: '0.0.1') }
it 'is initialized with the locked version' do
expect(subject.version).to eq('v0.5.2')
@@ -80,6 +80,44 @@ describe Clusters::Applications::CertManager do
end
end
+ describe '#uninstall_command' do
+ subject { cert_manager.uninstall_command }
+
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
+
+ it 'is initialized with cert_manager arguments' do
+ expect(subject.name).to eq('certmanager')
+ expect(subject).to be_rbac
+ expect(subject.files).to eq(cert_manager.files)
+ end
+
+ it 'specifies a post delete command to remove custom resource definitions' do
+ expect(subject.postdelete).to eq([
+ "kubectl delete secret -n gitlab-managed-apps letsencrypt-prod --ignore-not-found",
+ 'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found'
+ ])
+ end
+
+ context 'secret key name is not found' do
+ before do
+ allow(File).to receive(:read).and_call_original
+ expect(File).to receive(:read)
+ .with(Rails.root.join('vendor', 'cert_manager', 'cluster_issuer.yaml'))
+ .and_return('key: value')
+ end
+
+ it 'does not try and delete the secret' do
+ expect(subject.postdelete).to eq([
+ 'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found'
+ ])
+ end
+ end
+ end
+
describe '#files' do
let(:application) { cert_manager }
let(:values) { subject[:'values.yaml'] }
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index 6ea6c110d62..00b5c72a3d3 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -19,11 +19,35 @@ describe Clusters::Applications::Helm do
end
describe '#can_uninstall?' do
- let(:helm) { create(:clusters_applications_helm) }
+ context "with other existing applications" do
+ Clusters::Cluster::APPLICATIONS.keys.each do |application_name|
+ next if application_name == 'helm'
+
+ it "is false when #{application_name} is installed" do
+ cluster_application = create("clusters_applications_#{application_name}".to_sym)
+
+ helm = cluster_application.cluster.application_helm
+
+ expect(helm.allowed_to_uninstall?).to be_falsy
+ end
+ end
- subject { helm.can_uninstall? }
+ it 'executes a single query only' do
+ cluster_application = create(:clusters_applications_ingress)
+ helm = cluster_application.cluster.application_helm
- it { is_expected.to be_falsey }
+ query_count = ActiveRecord::QueryRecorder.new { helm.allowed_to_uninstall? }.count
+ expect(query_count).to eq(1)
+ end
+ end
+
+ context "without other existing applications" do
+ subject { helm.can_uninstall? }
+
+ let(:helm) { create(:clusters_applications_helm) }
+
+ it { is_expected.to be_truthy }
+ end
end
describe '#issue_client_cert' do
@@ -73,4 +97,41 @@ describe Clusters::Applications::Helm do
end
end
end
+
+ describe '#uninstall_command' do
+ let(:helm) { create(:clusters_applications_helm) }
+
+ subject { helm.uninstall_command }
+
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::ResetCommand) }
+
+ it 'has name' do
+ expect(subject.name).to eq('helm')
+ end
+
+ it 'has cert files' do
+ expect(subject.files[:'ca.pem']).to be_present
+ expect(subject.files[:'ca.pem']).to eq(helm.ca_cert)
+
+ expect(subject.files[:'cert.pem']).to be_present
+ expect(subject.files[:'key.pem']).to be_present
+
+ cert = OpenSSL::X509::Certificate.new(subject.files[:'cert.pem'])
+ expect(cert.not_after).to be > 999.years.from_now
+ end
+
+ describe 'rbac' do
+ context 'rbac cluster' do
+ it { expect(subject).to be_rbac }
+ end
+
+ context 'non rbac cluster' do
+ before do
+ helm.cluster.platform_kubernetes.abac!
+ end
+
+ it { expect(subject).not_to be_rbac }
+ end
+ end
+ end
end
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index 292ddabd2d8..057517d3820 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -21,7 +21,21 @@ describe Clusters::Applications::Ingress do
describe '#can_uninstall?' do
subject { ingress.can_uninstall? }
- it { is_expected.to be_falsey }
+ it 'returns true if application_jupyter_nil_or_installable? AND external_ip_or_hostname? are true' do
+ ingress.external_ip = 'IP'
+
+ is_expected.to be_truthy
+ end
+
+ it 'returns false if application_jupyter_nil_or_installable? is false' do
+ create(:clusters_applications_jupyter, :installed, cluster: ingress.cluster)
+
+ is_expected.to be_falsey
+ end
+
+ it 'returns false if external_ip_or_hostname? is false' do
+ is_expected.to be_falsey
+ end
end
describe '#make_installed!' do
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index 43fa1010b2b..3ff66a074e4 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -16,7 +16,7 @@ describe Clusters::Applications::Jupyter do
subject { jupyter.can_uninstall? }
- it { is_expected.to be_falsey }
+ it { is_expected.to be_truthy }
end
describe '#set_initial_status' do
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index 7f4819cbb9a..334f10526cb 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -39,7 +39,7 @@ describe Clusters::Applications::Knative do
describe '#can_uninstall?' do
subject { knative.can_uninstall? }
- it { is_expected.to be_falsey }
+ it { is_expected.to be_truthy }
end
describe '#schedule_status_update with external_ip' do
@@ -91,7 +91,7 @@ describe Clusters::Applications::Knative do
end
it 'does not install metrics for prometheus' do
- expect(subject.postinstall).to be_nil
+ expect(subject.postinstall).to be_empty
end
context 'with prometheus installed' do
@@ -101,7 +101,7 @@ describe Clusters::Applications::Knative do
subject { knative.install_command }
it 'installs metrics' do
- expect(subject.postinstall).not_to be_nil
+ expect(subject.postinstall).not_to be_empty
expect(subject.postinstall.length).to be(1)
expect(subject.postinstall[0]).to eql("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}")
end
@@ -129,6 +129,46 @@ describe Clusters::Applications::Knative do
it_behaves_like 'a command'
end
+ describe '#uninstall_command' do
+ subject { knative.uninstall_command }
+
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
+
+ it "removes knative deployed services before uninstallation" do
+ 2.times do |i|
+ cluster_project = create(:cluster_project, cluster: knative.cluster)
+
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster_project.cluster,
+ cluster_project: cluster_project,
+ project: cluster_project.project,
+ namespace: "namespace_#{i}")
+ end
+
+ remove_namespaced_services_script = [
+ "kubectl delete ksvc --all -n #{knative.cluster.kubernetes_namespaces.first.namespace}",
+ "kubectl delete ksvc --all -n #{knative.cluster.kubernetes_namespaces.second.namespace}"
+ ]
+
+ expect(subject.predelete).to match_array(remove_namespaced_services_script)
+ end
+
+ it "initializes command with all necessary postdelete script" do
+ api_resources = YAML.safe_load(File.read(Rails.root.join(Clusters::Applications::Knative::API_RESOURCES_PATH)))
+
+ remove_knative_istio_leftovers_script = [
+ "kubectl delete --ignore-not-found ns knative-serving",
+ "kubectl delete --ignore-not-found ns knative-build"
+ ]
+
+ full_delete_commands_size = api_resources.size + remove_knative_istio_leftovers_script.size
+
+ expect(subject.postdelete).to include(*remove_knative_istio_leftovers_script)
+ expect(subject.postdelete.size).to eq(full_delete_commands_size)
+ expect(subject.postdelete[2]).to eq("kubectl delete --ignore-not-found crd #{api_resources[0]}")
+ end
+ end
+
describe '#files' do
let(:application) { knative }
let(:values) { subject[:'values.yaml'] }
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index 26267c64112..eb6ccba5584 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -86,16 +86,15 @@ describe Clusters::Applications::Prometheus do
project: cluster.cluster_project.project)
end
- it 'creates proxy prometheus rest client' do
- expect(subject.prometheus_client).to be_instance_of(RestClient::Resource)
+ it 'creates proxy prometheus_client' do
+ expect(subject.prometheus_client).to be_instance_of(Gitlab::PrometheusClient)
end
- it 'creates proper url' do
- expect(subject.prometheus_client.url).to eq("#{kubernetes_url}/api/v1/namespaces/gitlab-managed-apps/services/prometheus-prometheus-server:80/proxy")
- end
-
- it 'copies options and headers from kube client to proxy client' do
- expect(subject.prometheus_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers))
+ it 'copies proxy_url, options and headers from kube client to prometheus_client' do
+ expect(Gitlab::PrometheusClient)
+ .to(receive(:new))
+ .with(a_valid_url, kube_client.rest_client.options.merge(headers: kube_client.headers))
+ subject.prometheus_client
end
context 'when cluster is not reachable' do
@@ -142,7 +141,7 @@ describe Clusters::Applications::Prometheus do
end
it 'does not install knative metrics' do
- expect(subject.postinstall).to be_nil
+ expect(subject.postinstall).to be_empty
end
context 'with knative installed' do
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 4f0cd0efe9c..4abe45a2152 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -18,7 +18,7 @@ describe Clusters::Applications::Runner do
subject { gitlab_runner.can_uninstall? }
- it { is_expected.to be_falsey }
+ it { is_expected.to be_truthy }
end
describe '#install_command' do
@@ -156,4 +156,35 @@ describe Clusters::Applications::Runner do
end
end
end
+
+ describe '#prepare_uninstall' do
+ it 'pauses associated runner' do
+ active_runner = create(:ci_runner, contacted_at: 1.second.ago)
+
+ expect(active_runner.status).to eq(:online)
+
+ application_runner = create(:clusters_applications_runner, :scheduled, runner: active_runner)
+ application_runner.prepare_uninstall
+
+ expect(active_runner.status).to eq(:paused)
+ end
+ end
+
+ describe '#make_uninstalling!' do
+ subject { create(:clusters_applications_runner, :scheduled, runner: ci_runner) }
+
+ it 'calls prepare_uninstall' do
+ expect_any_instance_of(described_class).to receive(:prepare_uninstall).and_call_original
+
+ subject.make_uninstalling!
+ end
+ end
+
+ describe '#post_uninstall' do
+ it 'destroys its runner' do
+ application_runner = create(:clusters_applications_runner, :scheduled, runner: ci_runner)
+
+ expect { application_runner.post_uninstall }.to change { Ci::Runner.count }.by(-1)
+ end
+ end
end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 52661178d76..9afbe6328ca 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -38,11 +38,6 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
it { is_expected.to respond_to :project }
- it do
- expect(subject.knative_services_finder(subject.project))
- .to be_instance_of(Clusters::KnativeServicesFinder)
- end
-
describe '.enabled' do
subject { described_class.enabled }
@@ -121,26 +116,6 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
- describe '.missing_kubernetes_namespace' do
- let!(:cluster) { create(:cluster, :provided_by_gcp, :project) }
- let(:project) { cluster.project }
- let(:kubernetes_namespaces) { project.kubernetes_namespaces }
-
- subject do
- described_class.joins(:projects).where(projects: { id: project.id }).missing_kubernetes_namespace(kubernetes_namespaces)
- end
-
- it { is_expected.to contain_exactly(cluster) }
-
- context 'kubernetes namespace exists' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- it { is_expected.to be_empty }
- end
- end
-
describe 'validations' do
subject { cluster.valid? }
@@ -342,7 +317,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
- context 'when sub-group has configured kubernetes cluster', :nested_groups do
+ context 'when sub-group has configured kubernetes cluster' do
let(:sub_group_cluster) { create(:cluster, :provided_by_gcp, :group) }
let(:sub_group) { sub_group_cluster.group }
let(:project) { create(:project, group: sub_group) }
@@ -423,31 +398,6 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
- describe '#all_projects' do
- let(:project) { create(:project) }
- let(:cluster) { create(:cluster, projects: [project]) }
-
- subject { cluster.all_projects }
-
- context 'project cluster' do
- it 'returns project' do
- is_expected.to eq([project])
- end
- end
-
- context 'group cluster' do
- let(:cluster) { create(:cluster, :group) }
- let(:group) { cluster.group }
- let(:project) { create(:project, group: group) }
- let(:subgroup) { create(:group, parent: group) }
- let(:subproject) { create(:project, group: subgroup) }
-
- it 'returns all projects for group' do
- is_expected.to contain_exactly(project, subproject)
- end
- end
- end
-
describe '#first_project' do
subject { cluster.first_project }
@@ -496,7 +446,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
context 'when applications are created' do
let!(:helm) { create(:clusters_applications_helm, cluster: cluster) }
let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) }
- let!(:cert_manager) { create(:clusters_applications_cert_managers, cluster: cluster) }
+ let!(:cert_manager) { create(:clusters_applications_cert_manager, cluster: cluster) }
let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) }
let!(:runner) { create(:clusters_applications_runner, cluster: cluster) }
let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) }
@@ -579,60 +529,39 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
- describe '#find_or_initialize_kubernetes_namespace_for_project' do
- let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:project) { cluster.projects.first }
-
- subject { cluster.find_or_initialize_kubernetes_namespace_for_project(project) }
-
- context 'kubernetes namespace exists' do
- context 'with no service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster) }
+ describe '#kubernetes_namespace_for' do
+ let(:cluster) { create(:cluster, :group) }
+ let(:environment) { create(:environment) }
- it { is_expected.to eq kubernetes_namespace }
- end
-
- context 'with a service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, project: project, cluster: cluster) }
-
- it { is_expected.to eq kubernetes_namespace }
- end
- end
+ subject { cluster.kubernetes_namespace_for(environment) }
- context 'kubernetes namespace does not exist' do
- it 'initializes a new namespace and sets default values' do
- expect(subject).to be_new_record
- expect(subject.project).to eq project
- expect(subject.cluster).to eq cluster
- expect(subject.namespace).to be_present
- expect(subject.service_account_name).to be_present
- end
+ before do
+ expect(Clusters::KubernetesNamespaceFinder).to receive(:new)
+ .with(cluster, project: environment.project, environment_slug: environment.slug)
+ .and_return(double(execute: persisted_namespace))
end
- context 'a custom scope is provided' do
- let(:scope) { cluster.kubernetes_namespaces.has_service_account_token }
-
- subject { cluster.find_or_initialize_kubernetes_namespace_for_project(project, scope: scope) }
+ context 'a persisted namespace exists' do
+ let(:persisted_namespace) { create(:cluster_kubernetes_namespace) }
- context 'kubernetes namespace exists' do
- context 'with no service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster) }
-
- it 'initializes a new namespace and sets default values' do
- expect(subject).to be_new_record
- expect(subject.project).to eq project
- expect(subject.cluster).to eq cluster
- expect(subject.namespace).to be_present
- expect(subject.service_account_name).to be_present
- end
- end
+ it { is_expected.to eq persisted_namespace.namespace }
+ end
- context 'with a service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, project: project, cluster: cluster) }
+ context 'no persisted namespace exists' do
+ let(:persisted_namespace) { nil }
+ let(:namespace_generator) { double }
+ let(:default_namespace) { 'a-default-namespace' }
- it { is_expected.to eq kubernetes_namespace }
- end
+ before do
+ expect(Gitlab::Kubernetes::DefaultNamespace).to receive(:new)
+ .with(cluster, project: environment.project)
+ .and_return(namespace_generator)
+ expect(namespace_generator).to receive(:from_environment_slug)
+ .with(environment.slug)
+ .and_return(default_namespace)
end
+
+ it { is_expected.to eq default_namespace }
end
end
diff --git a/spec/models/clusters/clusters_hierarchy_spec.rb b/spec/models/clusters/clusters_hierarchy_spec.rb
new file mode 100644
index 00000000000..0470ebe17ea
--- /dev/null
+++ b/spec/models/clusters/clusters_hierarchy_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::ClustersHierarchy do
+ describe '#base_and_ancestors' do
+ def base_and_ancestors(clusterable)
+ described_class.new(clusterable).base_and_ancestors
+ end
+
+ context 'project in nested group with clusters at every level' do
+ let!(:cluster) { create(:cluster, :project, projects: [project]) }
+ let!(:child) { create(:cluster, :group, groups: [child_group]) }
+ let!(:parent) { create(:cluster, :group, groups: [parent_group]) }
+ let!(:ancestor) { create(:cluster, :group, groups: [ancestor_group]) }
+
+ let(:ancestor_group) { create(:group) }
+ let(:parent_group) { create(:group, parent: ancestor_group) }
+ let(:child_group) { create(:group, parent: parent_group) }
+ let(:project) { create(:project, group: child_group) }
+
+ it 'returns clusters for project' do
+ expect(base_and_ancestors(project)).to eq([cluster, child, parent, ancestor])
+ end
+
+ it 'returns clusters for child_group' do
+ expect(base_and_ancestors(child_group)).to eq([child, parent, ancestor])
+ end
+
+ it 'returns clusters for parent_group' do
+ expect(base_and_ancestors(parent_group)).to eq([parent, ancestor])
+ end
+
+ it 'returns clusters for ancestor_group' do
+ expect(base_and_ancestors(ancestor_group)).to eq([ancestor])
+ end
+ end
+
+ context 'project in a namespace' do
+ let!(:cluster) { create(:cluster, :project) }
+
+ it 'returns clusters for project' do
+ expect(base_and_ancestors(cluster.project)).to eq([cluster])
+ end
+ end
+
+ context 'project in nested group with clusters at some levels' do
+ let!(:child) { create(:cluster, :group, groups: [child_group]) }
+ let!(:ancestor) { create(:cluster, :group, groups: [ancestor_group]) }
+
+ let(:ancestor_group) { create(:group) }
+ let(:parent_group) { create(:group, parent: ancestor_group) }
+ let(:child_group) { create(:group, parent: parent_group) }
+ let(:project) { create(:project, group: child_group) }
+
+ it 'returns clusters for project' do
+ expect(base_and_ancestors(project)).to eq([child, ancestor])
+ end
+
+ it 'returns clusters for child_group' do
+ expect(base_and_ancestors(child_group)).to eq([child, ancestor])
+ end
+
+ it 'returns clusters for parent_group' do
+ expect(base_and_ancestors(parent_group)).to eq([ancestor])
+ end
+
+ it 'returns clusters for ancestor_group' do
+ expect(base_and_ancestors(ancestor_group)).to eq([ancestor])
+ end
+ end
+ end
+end
diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb
index b5cba80b806..d4e3a0ac84d 100644
--- a/spec/models/clusters/kubernetes_namespace_spec.rb
+++ b/spec/models/clusters/kubernetes_namespace_spec.rb
@@ -24,70 +24,60 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
end
end
- describe 'namespace uniqueness validation' do
- let(:cluster_project) { create(:cluster_project) }
- let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') }
+ describe '.with_environment_slug' do
+ let(:cluster) { create(:cluster, :group) }
+ let(:environment) { create(:environment, slug: slug) }
- subject { kubernetes_namespace }
+ let(:slug) { 'production' }
- context 'when cluster is using the namespace' do
- before do
- create(:cluster_kubernetes_namespace,
- cluster: kubernetes_namespace.cluster,
- namespace: 'my-namespace')
- end
+ subject { described_class.with_environment_slug(slug) }
- it { is_expected.not_to be_valid }
- end
+ context 'there is no associated environment' do
+ let!(:namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, project: environment.project) }
- context 'when cluster is not using the namespace' do
- it { is_expected.to be_valid }
+ it { is_expected.to be_empty }
end
- end
- describe '#set_defaults' do
- let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace) }
- let(:cluster) { kubernetes_namespace.cluster }
- let(:platform) { kubernetes_namespace.platform_kubernetes }
-
- subject { kubernetes_namespace.set_defaults }
-
- describe '#namespace' do
- before do
- platform.update_column(:namespace, namespace)
+ context 'there is an assicated environment' do
+ let!(:namespace) do
+ create(
+ :cluster_kubernetes_namespace,
+ cluster: cluster,
+ project: environment.project,
+ environment: environment
+ )
end
- context 'when platform has a namespace assigned' do
- let(:namespace) { 'platform-namespace' }
-
- it 'copies the namespace' do
- subject
-
- expect(kubernetes_namespace.namespace).to eq('platform-namespace')
- end
+ context 'with a matching slug' do
+ it { is_expected.to eq [namespace] }
end
- context 'when platform does not have namespace assigned' do
- let(:project) { kubernetes_namespace.project }
- let(:namespace) { nil }
- let(:project_slug) { "#{project.path}-#{project.id}" }
-
- it 'fallbacks to project namespace' do
- subject
+ context 'without a matching slug' do
+ let(:environment) { create(:environment, slug: 'staging') }
- expect(kubernetes_namespace.namespace).to eq(project_slug)
- end
+ it { is_expected.to be_empty }
end
end
+ end
- describe '#service_account_name' do
- let(:service_account_name) { "#{kubernetes_namespace.namespace}-service-account" }
+ describe 'namespace uniqueness validation' do
+ let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') }
- it 'sets a service account name based on namespace' do
- subject
+ subject { kubernetes_namespace }
- expect(kubernetes_namespace.service_account_name).to eq(service_account_name)
+ context 'when cluster is using the namespace' do
+ before do
+ create(:cluster_kubernetes_namespace,
+ cluster: kubernetes_namespace.cluster,
+ environment: kubernetes_namespace.environment,
+ namespace: 'my-namespace')
end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when cluster is not using the namespace' do
+ it { is_expected.to be_valid }
end
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 471769e4aab..0c4cf291d20 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -106,7 +106,7 @@ describe Clusters::Platforms::Kubernetes do
before do
allow(ApplicationSetting)
.to receive(:current)
- .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true))
+ .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_web_hooks_and_services: true))
end
it { expect(kubernetes.save).to be_truthy }
@@ -205,192 +205,77 @@ describe Clusters::Platforms::Kubernetes do
it { is_expected.to be_truthy }
end
- describe '#kubernetes_namespace_for' do
- let(:cluster) { create(:cluster, :project) }
- let(:project) { cluster.project }
-
- let(:platform) do
- create(:cluster_platform_kubernetes,
- cluster: cluster,
- namespace: namespace)
- end
-
- subject { platform.kubernetes_namespace_for(project) }
-
- context 'with a namespace assigned' do
- let(:namespace) { 'namespace-123' }
-
- it { is_expected.to eq(namespace) }
-
- context 'kubernetes namespace is present but has no service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }
-
- it { is_expected.to eq(namespace) }
- end
- end
-
- context 'with no namespace assigned' do
- let(:namespace) { nil }
-
- context 'when kubernetes namespace is present' do
- let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster) }
-
- before do
- kubernetes_namespace
- end
-
- it { is_expected.to eq(kubernetes_namespace.namespace) }
-
- context 'kubernetes namespace has no service account token' do
- before do
- kubernetes_namespace.update!(namespace: 'old-namespace', service_account_token: nil)
- end
+ describe '#predefined_variables' do
+ let(:project) { create(:project) }
+ let(:cluster) { create(:cluster, :group, platform_kubernetes: platform) }
+ let(:platform) { create(:cluster_platform_kubernetes) }
+ let(:persisted_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster) }
- it { is_expected.to eq("#{project.path}-#{project.id}") }
- end
- end
+ let(:environment_name) { 'env/production' }
+ let(:environment_slug) { Gitlab::Slug::Environment.new(environment_name).generate }
- context 'when kubernetes namespace is not present' do
- it { is_expected.to eq("#{project.path}-#{project.id}") }
- end
- end
- end
+ subject { platform.predefined_variables(project: project, environment_name: environment_name) }
- describe '#predefined_variables' do
- let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
- let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem) }
- let(:api_url) { 'https://kube.domain.com' }
- let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) }
-
- subject { kubernetes.predefined_variables(project: cluster.project) }
-
- shared_examples 'setting variables' do
- it 'sets the variables' do
- expect(subject).to include(
- { key: 'KUBE_URL', value: api_url, public: true },
- { key: 'KUBE_CA_PEM', value: ca_pem, public: true },
- { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
- )
- end
+ before do
+ allow(Clusters::KubernetesNamespaceFinder).to receive(:new)
+ .with(cluster, project: project, environment_slug: environment_slug)
+ .and_return(double(execute: persisted_namespace))
end
- context 'kubernetes namespace is created with no service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }
+ it { is_expected.to include(key: 'KUBE_URL', value: platform.api_url, public: true) }
- it_behaves_like 'setting variables'
+ context 'platform has a CA certificate' do
+ let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) }
+ let(:platform) { create(:cluster_platform_kubernetes, ca_cert: ca_pem) }
- it 'does not set KUBE_TOKEN' do
- expect(subject).not_to include(
- { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true }
- )
- end
+ it { is_expected.to include(key: 'KUBE_CA_PEM', value: ca_pem, public: true) }
+ it { is_expected.to include(key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true) }
end
- context 'kubernetes namespace is created with service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster) }
-
- it_behaves_like 'setting variables'
+ context 'kubernetes namespace exists' do
+ let(:variable) { Hash(key: :fake_key, value: 'fake_value') }
+ let(:namespace_variables) { Gitlab::Ci::Variables::Collection.new([variable]) }
- it 'sets KUBE_TOKEN' do
- expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true }
- )
+ before do
+ expect(persisted_namespace).to receive(:predefined_variables).and_return(namespace_variables)
end
- context 'the cluster has been set to unmanaged after the namespace was created' do
- before do
- cluster.update!(managed: false)
- end
-
- it_behaves_like 'setting variables'
-
- it 'sets KUBE_TOKEN from the platform' do
- expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true }
- )
- end
-
- context 'the platform has a custom namespace set' do
- before do
- kubernetes.update!(namespace: 'custom-namespace')
- end
-
- it 'sets KUBE_NAMESPACE from the platform' do
- expect(subject).to include(
- { key: 'KUBE_NAMESPACE', value: kubernetes.namespace, public: true, masked: false }
- )
- end
- end
-
- context 'there is no namespace specified on the platform' do
- let(:project) { cluster.project }
-
- before do
- kubernetes.update!(namespace: nil)
- end
-
- it 'sets KUBE_NAMESPACE to a default for the project' do
- expect(subject).to include(
- { key: 'KUBE_NAMESPACE', value: "#{project.path}-#{project.id}", public: true, masked: false }
- )
- end
- end
- end
+ it { is_expected.to include(variable) }
end
- context 'group level cluster' do
- let!(:cluster) { create(:cluster, :group, platform_kubernetes: kubernetes) }
-
- let(:project) { create(:project, group: cluster.group) }
-
- subject { kubernetes.predefined_variables(project: project) }
-
- context 'no kubernetes namespace for the project' do
- it_behaves_like 'setting variables'
-
- it 'does not return KUBE_TOKEN' do
- expect(subject).not_to include(
- { key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
- )
- end
-
- context 'the cluster is not managed' do
- let!(:cluster) { create(:cluster, :group, :not_managed, platform_kubernetes: kubernetes) }
+ context 'kubernetes namespace does not exist' do
+ let(:persisted_namespace) { nil }
+ let(:namespace) { 'kubernetes-namespace' }
+ let(:kubeconfig) { 'kubeconfig' }
- it_behaves_like 'setting variables'
-
- it 'sets KUBE_TOKEN' do
- expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true }
- )
- end
- end
+ before do
+ allow(Gitlab::Kubernetes::DefaultNamespace).to receive(:new)
+ .with(cluster, project: project).and_return(double(from_environment_name: namespace))
+ allow(platform).to receive(:kubeconfig).with(namespace).and_return(kubeconfig)
end
- context 'kubernetes namespace exists for the project' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster, project: project) }
+ it { is_expected.not_to include(key: 'KUBE_TOKEN', value: platform.token, public: false, masked: true) }
+ it { is_expected.not_to include(key: 'KUBE_NAMESPACE', value: namespace) }
+ it { is_expected.not_to include(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true) }
- it_behaves_like 'setting variables'
+ context 'cluster is unmanaged' do
+ let(:cluster) { create(:cluster, :group, :not_managed, platform_kubernetes: platform) }
- it 'sets KUBE_TOKEN' do
- expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true }
- )
- end
+ it { is_expected.to include(key: 'KUBE_TOKEN', value: platform.token, public: false, masked: true) }
+ it { is_expected.to include(key: 'KUBE_NAMESPACE', value: namespace) }
+ it { is_expected.to include(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true) }
end
end
- context 'with a domain' do
- let!(:cluster) do
- create(:cluster, :provided_by_gcp, :with_domain,
- platform_kubernetes: kubernetes)
- end
+ context 'cluster variables' do
+ let(:variable) { Hash(key: :fake_key, value: 'fake_value') }
+ let(:cluster_variables) { Gitlab::Ci::Variables::Collection.new([variable]) }
- it 'sets KUBE_INGRESS_BASE_DOMAIN' do
- expect(subject).to include(
- { key: 'KUBE_INGRESS_BASE_DOMAIN', value: cluster.domain, public: true }
- )
+ before do
+ expect(cluster).to receive(:predefined_variables).and_return(cluster_variables)
end
+
+ it { is_expected.to include(variable) }
end
end
@@ -410,7 +295,7 @@ describe Clusters::Platforms::Kubernetes do
end
context 'with valid pods' do
- let(:pod) { kube_pod(environment_slug: environment.slug, namespace: cluster.kubernetes_namespace_for(project), project_slug: project.full_path_slug) }
+ let(:pod) { kube_pod(environment_slug: environment.slug, namespace: cluster.kubernetes_namespace_for(environment), project_slug: project.full_path_slug) }
let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") }
let(:terminals) { kube_terminals(service, pod) }
let(:pods) { [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")] }
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index b96ca89c893..4a524b585e1 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -139,8 +139,8 @@ describe CommitRange do
end
describe '#has_been_reverted?' do
- let(:issue) { create(:issue) }
- let(:user) { issue.author }
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, author: user, project: project) }
it 'returns true if the commit has been reverted' do
create(:note_on_issue,
@@ -149,9 +149,11 @@ describe CommitRange do
note: commit1.revert_description(user),
project: issue.project)
- expect_any_instance_of(Commit).to receive(:reverts_commit?)
- .with(commit1, user)
- .and_return(true)
+ expect_next_instance_of(Commit) do |commit|
+ expect(commit).to receive(:reverts_commit?)
+ .with(commit1, user)
+ .and_return(true)
+ end
expect(commit1.has_been_reverted?(user, issue.notes_with_associations)).to eq(true)
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index e76186fb280..7b35c2ffd36 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -556,6 +556,7 @@ eos
it 'returns the URI type at the given path' do
expect(commit.uri_type('files/html')).to be(:tree)
expect(commit.uri_type('files/images/logo-black.png')).to be(:raw)
+ expect(commit.uri_type('files/images/wm.svg')).to be(:raw)
expect(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw)
expect(commit.uri_type('files/js/application.js')).to be(:blob)
end
diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb
index 9e7106281ee..76da42cf243 100644
--- a/spec/models/concerns/awardable_spec.rb
+++ b/spec/models/concerns/awardable_spec.rb
@@ -82,16 +82,6 @@ describe Awardable do
end
end
- describe "#toggle_award_emoji" do
- it "adds an emoji if it isn't awarded yet" do
- expect { issue.toggle_award_emoji("thumbsup", award_emoji.user) }.to change { AwardEmoji.count }.by(1)
- end
-
- it "toggles already awarded emoji" do
- expect { issue.toggle_award_emoji("thumbsdown", award_emoji.user) }.to change { AwardEmoji.count }.by(-1)
- end
- end
-
describe 'querying award_emoji on an Awardable' do
let(:issue) { create(:issue) }
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 0e5fb2b5153..9a12c3d6965 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -198,6 +198,36 @@ describe CacheMarkdownField, :clean_gitlab_redis_cache do
end
end
end
+
+ describe '#updated_cached_html_for' do
+ let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) }
+
+ context 'when the markdown cache is outdated' do
+ before do
+ thing.cached_markdown_version += 1
+ end
+
+ it 'calls #refresh_markdown_cache' do
+ expect(thing).to receive(:refresh_markdown_cache)
+
+ expect(thing.updated_cached_html_for(:description)).to eq(html)
+ end
+ end
+
+ context 'when the markdown field does not exist' do
+ it 'returns nil' do
+ expect(thing.updated_cached_html_for(:something)).to eq(nil)
+ end
+ end
+
+ context 'when the markdown cache is up to date' do
+ it 'does not call #refresh_markdown_cache' do
+ expect(thing).not_to receive(:refresh_markdown_cache)
+
+ expect(thing.updated_cached_html_for(:description)).to eq(html)
+ end
+ end
+ end
end
context 'for Active record classes' do
diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb
index 394fac52aa7..da46effe411 100644
--- a/spec/models/concerns/cacheable_attributes_spec.rb
+++ b/spec/models/concerns/cacheable_attributes_spec.rb
@@ -7,6 +7,7 @@ describe CacheableAttributes do
Class.new do
include ActiveModel::Model
extend ActiveModel::Callbacks
+ include ActiveModel::AttributeMethods
define_model_callbacks :commit
include CacheableAttributes
@@ -34,44 +35,60 @@ describe CacheableAttributes do
end
end
+ before do
+ stub_const("MinimalTestClass", minimal_test_class)
+ end
+
shared_context 'with defaults' do
before do
- minimal_test_class.define_singleton_method(:defaults) do
+ MinimalTestClass.define_singleton_method(:defaults) do
{ foo: 'a', bar: 'b', baz: 'c' }
end
end
end
+ describe '.expire', :use_clean_rails_memory_store_caching, :request_store do
+ it 'wipes the cache' do
+ obj = MinimalTestClass.new
+ obj.cache!
+ expect(MinimalTestClass.cached).not_to eq(nil)
+
+ MinimalTestClass.expire
+
+ expect(MinimalTestClass.cached).to eq(nil)
+ end
+ end
+
describe '.current_without_cache' do
it 'defaults to last' do
- expect(minimal_test_class.current_without_cache).to eq(minimal_test_class.last)
+ expect(MinimalTestClass.current_without_cache).to eq(MinimalTestClass.last)
end
it 'can be overridden' do
- minimal_test_class.define_singleton_method(:current_without_cache) do
+ MinimalTestClass.define_singleton_method(:current_without_cache) do
first
end
- expect(minimal_test_class.current_without_cache).to eq(minimal_test_class.first)
+ expect(MinimalTestClass.current_without_cache).to eq(MinimalTestClass.first)
end
end
describe '.cache_key' do
it 'excludes cache attributes' do
- expect(minimal_test_class.cache_key).to eq("TestClass:#{Gitlab::VERSION}:#{Rails.version}")
+ expect(MinimalTestClass.cache_key).to eq("TestClass:#{Gitlab::VERSION}:#{Rails.version}")
end
end
describe '.defaults' do
it 'defaults to {}' do
- expect(minimal_test_class.defaults).to eq({})
+ expect(MinimalTestClass.defaults).to eq({})
end
context 'with defaults defined' do
include_context 'with defaults'
it 'can be overridden' do
- expect(minimal_test_class.defaults).to eq({ foo: 'a', bar: 'b', baz: 'c' })
+ expect(MinimalTestClass.defaults).to eq({ foo: 'a', bar: 'b', baz: 'c' })
end
end
end
@@ -81,13 +98,13 @@ describe CacheableAttributes do
context 'without any attributes given' do
it 'intializes a new object with the defaults' do
- expect(minimal_test_class.build_from_defaults.attributes).to eq(minimal_test_class.defaults.stringify_keys)
+ expect(MinimalTestClass.build_from_defaults.attributes).to eq(MinimalTestClass.defaults.stringify_keys)
end
end
context 'with attributes given' do
it 'intializes a new object with the given attributes merged into the defaults' do
- expect(minimal_test_class.build_from_defaults(foo: 'd').attributes['foo']).to eq('d')
+ expect(MinimalTestClass.build_from_defaults(foo: 'd').attributes['foo']).to eq('d')
end
end
@@ -108,8 +125,8 @@ describe CacheableAttributes do
describe '.current', :use_clean_rails_memory_store_caching do
context 'redis unavailable' do
before do
- allow(minimal_test_class).to receive(:last).and_return(:last)
- expect(Rails.cache).to receive(:read).with(minimal_test_class.cache_key).and_raise(Redis::BaseError)
+ allow(MinimalTestClass).to receive(:last).and_return(:last)
+ expect(Rails.cache).to receive(:read).with(MinimalTestClass.cache_key).and_raise(Redis::BaseError)
end
context 'in production environment' do
@@ -120,7 +137,7 @@ describe CacheableAttributes do
it 'returns an uncached record and logs a warning' do
expect(Rails.logger).to receive(:warn).with("Cached record for TestClass couldn't be loaded, falling back to uncached record: Redis::BaseError")
- expect(minimal_test_class.current).to eq(:last)
+ expect(MinimalTestClass.current).to eq(:last)
end
end
@@ -132,7 +149,7 @@ describe CacheableAttributes do
it 'returns an uncached record and logs a warning' do
expect(Rails.logger).not_to receive(:warn)
- expect { minimal_test_class.current }.to raise_error(Redis::BaseError)
+ expect { MinimalTestClass.current }.to raise_error(Redis::BaseError)
end
end
end
@@ -143,14 +160,14 @@ describe CacheableAttributes do
allow(ApplicationSetting).to receive(:current_without_cache).twice.and_return(nil)
expect(ApplicationSetting.current).to be_nil
- expect(Rails.cache.exist?(ApplicationSetting.cache_key)).to be(false)
+ expect(ApplicationSetting.cache_backend.exist?(ApplicationSetting.cache_key)).to be(false)
end
it 'caches non-nil object' do
create(:application_setting)
expect(ApplicationSetting.current).to eq(ApplicationSetting.last)
- expect(Rails.cache.exist?(ApplicationSetting.cache_key)).to be(true)
+ expect(ApplicationSetting.cache_backend.exist?(ApplicationSetting.cache_key)).to be(true)
# subsequent calls retrieve the record from the cache
last_record = ApplicationSetting.last
@@ -188,11 +205,12 @@ describe CacheableAttributes do
end
end
- it 'uses RequestStore in addition to Rails.cache', :request_store do
+ it 'uses RequestStore in addition to Thread memory cache', :request_store do
# Warm up the cache
create(:application_setting).cache!
- expect(Rails.cache).to receive(:read).with(ApplicationSetting.cache_key).once.and_call_original
+ expect(ApplicationSetting.cache_backend).to eq(Gitlab::ThreadMemoryCache.cache_backend)
+ expect(ApplicationSetting.cache_backend).to receive(:read).with(ApplicationSetting.cache_key).once.and_call_original
2.times { ApplicationSetting.current }
end
@@ -201,7 +219,7 @@ describe CacheableAttributes do
describe '.cached', :use_clean_rails_memory_store_caching do
context 'when cache is cold' do
it 'returns nil' do
- expect(minimal_test_class.cached).to be_nil
+ expect(MinimalTestClass.cached).to be_nil
end
end
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index d6d41a25eac..9819f656f0d 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -28,28 +28,13 @@ describe CaseSensitivity do
.to contain_exactly(model_1)
end
- # Using `mysql` & `postgresql` metadata-tags here because both adapters build
- # the query slightly differently
- context 'for MySQL', :mysql do
- it 'builds a simple query' do
- query = model.iwhere(path: %w(MODEL-1 model-2), name: 'model 1').to_sql
- expected_query = <<~QRY.strip
- SELECT `namespaces`.* FROM `namespaces` WHERE (`namespaces`.`path` IN ('MODEL-1', 'model-2')) AND (`namespaces`.`name` = 'model 1')
- QRY
-
- expect(query).to eq(expected_query)
- end
- end
+ it 'builds a query using LOWER' do
+ query = model.iwhere(path: %w(MODEL-1 model-2), name: 'model 1').to_sql
+ expected_query = <<~QRY.strip
+ SELECT \"namespaces\".* FROM \"namespaces\" WHERE (LOWER(\"namespaces\".\"path\") IN (LOWER('MODEL-1'), LOWER('model-2'))) AND (LOWER(\"namespaces\".\"name\") = LOWER('model 1'))
+ QRY
- context 'for PostgreSQL', :postgresql do
- it 'builds a query using LOWER' do
- query = model.iwhere(path: %w(MODEL-1 model-2), name: 'model 1').to_sql
- expected_query = <<~QRY.strip
- SELECT \"namespaces\".* FROM \"namespaces\" WHERE (LOWER(\"namespaces\".\"path\") IN (LOWER('MODEL-1'), LOWER('model-2'))) AND (LOWER(\"namespaces\".\"name\") = LOWER('model 1'))
- QRY
-
- expect(query).to eq(expected_query)
- end
+ expect(query).to eq(expected_query)
end
end
end
diff --git a/spec/models/concerns/deployable_spec.rb b/spec/models/concerns/deployable_spec.rb
index 42bed9434f5..bb73dd8ade0 100644
--- a/spec/models/concerns/deployable_spec.rb
+++ b/spec/models/concerns/deployable_spec.rb
@@ -7,10 +7,6 @@ describe Deployable do
let(:deployment) { job.deployment }
let(:environment) { deployment&.environment }
- before do
- job.reload
- end
-
context 'when the deployable object will deploy to production' do
let!(:job) { create(:ci_build, :start_review_app) }
@@ -26,6 +22,16 @@ describe Deployable do
end
end
+ context 'when the deployable object will deploy to a cluster' do
+ let(:project) { create(:project) }
+ let!(:cluster) { create(:cluster, :provided_by_user, projects: [project]) }
+ let!(:job) { create(:ci_build, :start_review_app, project: project) }
+
+ it 'creates a deployment with cluster association' do
+ expect(deployment.cluster).to eq(cluster)
+ end
+ end
+
context 'when the deployable object will stop an environment' do
let!(:job) { create(:ci_build, :stop_review_app) }
diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb
index 2378f400540..27f535487c8 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -45,13 +45,12 @@ describe DeploymentPlatform do
is_expected.to eq(group_cluster.platform_kubernetes)
end
- context 'when child group has configured kubernetes cluster', :nested_groups do
- let!(:child_group1_cluster) { create(:cluster, :provided_by_gcp, :group) }
- let(:child_group1) { child_group1_cluster.group }
+ context 'when child group has configured kubernetes cluster' do
+ let(:child_group1) { create(:group, parent: group) }
+ let!(:child_group1_cluster) { create(:cluster_for_group, groups: [child_group1]) }
before do
project.update!(group: child_group1)
- child_group1.update!(parent: group)
end
it 'returns the Kubernetes platform for the child group' do
@@ -59,11 +58,10 @@ describe DeploymentPlatform do
end
context 'deeply nested group' do
- let!(:child_group2_cluster) { create(:cluster, :provided_by_gcp, :group) }
- let(:child_group2) { child_group2_cluster.group }
+ let(:child_group2) { create(:group, parent: child_group1) }
+ let!(:child_group2_cluster) { create(:cluster_for_group, groups: [child_group2]) }
before do
- child_group2.update!(parent: child_group1)
project.update!(group: child_group2)
end
diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb
index 194caac3fce..192e884f3e8 100644
--- a/spec/models/concerns/group_descendant_spec.rb
+++ b/spec/models/concerns/group_descendant_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupDescendant, :nested_groups do
+describe GroupDescendant do
let(:parent) { create(:group) }
let(:subgroup) { create(:group, parent: parent) }
let(:subsub_group) { create(:group, parent: subgroup) }
@@ -84,7 +84,7 @@ describe GroupDescendant, :nested_groups do
it 'tracks the exception when a parent was not preloaded' do
expect(Gitlab::Sentry).to receive(:track_exception).and_call_original
- expect { GroupDescendant.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
+ expect { described_class.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
end
it 'recovers if a parent was not reloaded by querying for the parent' do
@@ -93,7 +93,7 @@ describe GroupDescendant, :nested_groups do
# this does not raise in production, so stubbing it here.
allow(Gitlab::Sentry).to receive(:track_exception)
- expect(GroupDescendant.build_hierarchy([subsub_group])).to eq(expected_hierarchy)
+ expect(described_class.build_hierarchy([subsub_group])).to eq(expected_hierarchy)
end
it 'raises an error if not all elements were preloaded' do
diff --git a/spec/models/concerns/has_environment_scope_spec.rb b/spec/models/concerns/has_environment_scope_spec.rb
new file mode 100644
index 00000000000..a6e1ba59263
--- /dev/null
+++ b/spec/models/concerns/has_environment_scope_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe HasEnvironmentScope do
+ subject { build(:ci_variable) }
+
+ it { is_expected.to allow_value('*').for(:environment_scope) }
+ it { is_expected.to allow_value('review/*').for(:environment_scope) }
+ it { is_expected.not_to allow_value('').for(:environment_scope) }
+ it { is_expected.not_to allow_value('!!()()').for(:environment_scope) }
+
+ it do
+ is_expected.to validate_uniqueness_of(:key)
+ .scoped_to(:project_id, :environment_scope)
+ .with_message(/\(\w+\) has already been taken/)
+ end
+
+ describe '.on_environment' do
+ let(:project) { create(:project) }
+
+ it 'returns scoped objects' do
+ variable1 = create(:ci_variable, project: project, environment_scope: '*')
+ variable2 = create(:ci_variable, project: project, environment_scope: 'product/*')
+ create(:ci_variable, project: project, environment_scope: 'staging/*')
+
+ expect(project.variables.on_environment('product/canary-1')).to eq([variable1, variable2])
+ end
+
+ it 'returns only the most relevant object if relevant_only is true' do
+ create(:ci_variable, project: project, environment_scope: '*')
+ variable2 = create(:ci_variable, project: project, environment_scope: 'product/*')
+ create(:ci_variable, project: project, environment_scope: 'staging/*')
+
+ expect(project.variables.on_environment('product/canary-1', relevant_only: true)).to eq([variable2])
+ end
+
+ it 'returns scopes ordered by lowest precedence first' do
+ create(:ci_variable, project: project, environment_scope: '*')
+ create(:ci_variable, project: project, environment_scope: 'production*')
+ create(:ci_variable, project: project, environment_scope: 'production')
+
+ result = project.variables.on_environment('production').map(&:environment_scope)
+
+ expect(result).to eq(['*', 'production*', 'production'])
+ end
+ end
+
+ describe '#environment_scope=' do
+ context 'when the new environment_scope is nil' do
+ it 'strips leading and trailing whitespaces' do
+ subject.environment_scope = nil
+
+ expect(subject.environment_scope).to eq('')
+ end
+ end
+
+ context 'when the new environment_scope has leadind and trailing whitespaces' do
+ it 'strips leading and trailing whitespaces' do
+ subject.environment_scope = ' * '
+
+ expect(subject.environment_scope).to eq('*')
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 64f02978d79..39680c0e51a 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -128,7 +128,7 @@ describe Issuable do
expect(build_issuable(milestone.id).milestone_available?).to be_truthy
end
- it 'returns true with a milestone from the the parent of the issue project group', :nested_groups do
+ it 'returns true with a milestone from the the parent of the issue project group' do
parent = create(:group)
group.update(parent: parent)
milestone = create(:milestone, group: parent)
@@ -223,6 +223,16 @@ describe Issuable do
expect(issuable_class.full_search(searchable_issue2.description.downcase)).to eq([searchable_issue2])
end
+ it 'returns issues with a fuzzy matching description for a query shorter than 3 chars if told to do so' do
+ search = searchable_issue2.description.downcase.scan(/\w+/).sample[-1]
+
+ expect(issuable_class.full_search(search, use_minimum_char_limit: false)).to include(searchable_issue2)
+ end
+
+ it 'returns issues with a fuzzy matching title for a query shorter than 3 chars if told to do so' do
+ expect(issuable_class.full_search('i', use_minimum_char_limit: false)).to include(searchable_issue)
+ end
+
context 'when matching columns is "title"' do
it 'returns issues with a matching title' do
expect(issuable_class.full_search(searchable_issue.title, matched_columns: 'title'))
@@ -764,4 +774,25 @@ describe Issuable do
end
end
end
+
+ describe '#supports_milestone?' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+
+ context "for issues" do
+ let(:issue) { build(:issue, project: project) }
+
+ it 'returns true' do
+ expect(issue.supports_milestone?).to be_truthy
+ end
+ end
+
+ context "for merge requests" do
+ let(:merge_request) { build(:merge_request, target_project: project, source_project: project) }
+
+ it 'returns true' do
+ expect(merge_request.supports_milestone?).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/project_api_compatibility_spec.rb b/spec/models/concerns/project_api_compatibility_spec.rb
new file mode 100644
index 00000000000..f5722f88aac
--- /dev/null
+++ b/spec/models/concerns/project_api_compatibility_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectAPICompatibility do
+ let(:project) { create(:project) }
+
+ # git_strategy
+ it "converts build_git_strategy=fetch to build_allow_git_fetch=true" do
+ project.update!(build_git_strategy: 'fetch')
+ expect(project.build_allow_git_fetch).to eq(true)
+ end
+
+ it "converts build_git_strategy=clone to build_allow_git_fetch=false" do
+ project.update!(build_git_strategy: 'clone')
+ expect(project.build_allow_git_fetch).to eq(false)
+ end
+
+ describe '#auto_devops_enabled' do
+ where(
+ initial: [:missing, nil, false, true],
+ final: [nil, false, true]
+ )
+
+ with_them do
+ before do
+ project.build_auto_devops(enabled: initial) unless initial == :missing
+ end
+
+ # Implicit auto devops when enabled is nil
+ let(:expected) { final.nil? ? true : final }
+
+ it 'sets the correct value' do
+ project.update!(auto_devops_enabled: final)
+
+ expect(project.auto_devops_enabled?).to eq(expected)
+ end
+ end
+ end
+
+ describe '#auto_devops_deploy_strategy' do
+ where(
+ initial: [:missing, *ProjectAutoDevops.deploy_strategies.keys],
+ final: ProjectAutoDevops.deploy_strategies.keys
+ )
+
+ with_them do
+ before do
+ project.build_auto_devops(deploy_strategy: initial) unless initial == :missing
+ end
+
+ it 'sets the correct value' do
+ project.update!(auto_devops_deploy_strategy: final)
+
+ expect(project.auto_devops.deploy_strategy).to eq(final)
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb
index 5aa43b58217..1fe176ab5af 100644
--- a/spec/models/concerns/project_features_compatibility_spec.rb
+++ b/spec/models/concerns/project_features_compatibility_spec.rb
@@ -4,7 +4,8 @@ require 'spec_helper'
describe ProjectFeaturesCompatibility do
let(:project) { create(:project) }
- let(:features) { %w(issues wiki builds merge_requests snippets) }
+ let(:features_except_repository) { %w(issues wiki builds merge_requests snippets) }
+ let(:features) { features_except_repository + ['repository'] }
# We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table
# All those fields got moved to a new table called project_feature and are now integers instead of booleans
@@ -12,30 +13,37 @@ describe ProjectFeaturesCompatibility do
# So we can keep it compatible
it "converts fields from 'true' to ProjectFeature::ENABLED" do
- features.each do |feature|
+ features_except_repository.each do |feature|
project.update_attribute("#{feature}_enabled".to_sym, "true")
expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::ENABLED)
end
end
it "converts fields from 'false' to ProjectFeature::DISABLED" do
- features.each do |feature|
+ features_except_repository.each do |feature|
project.update_attribute("#{feature}_enabled".to_sym, "false")
expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::DISABLED)
end
end
it "converts fields from true to ProjectFeature::ENABLED" do
- features.each do |feature|
+ features_except_repository.each do |feature|
project.update_attribute("#{feature}_enabled".to_sym, true)
expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::ENABLED)
end
end
it "converts fields from false to ProjectFeature::DISABLED" do
- features.each do |feature|
+ features_except_repository.each do |feature|
project.update_attribute("#{feature}_enabled".to_sym, false)
expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::DISABLED)
end
end
+
+ it "accepts private as ProjectFeature::PRIVATE" do
+ features.each do |feature|
+ project.update!("#{feature}_access_level".to_sym => 'private')
+ expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::PRIVATE)
+ end
+ end
end
diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb
index 25a2d290f76..3d26ba95192 100644
--- a/spec/models/concerns/prometheus_adapter_spec.rb
+++ b/spec/models/concerns/prometheus_adapter_spec.rb
@@ -40,13 +40,13 @@ describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
describe 'matched_metrics' do
let(:matched_metrics_query) { Gitlab::Prometheus::Queries::MatchedMetricQuery }
- let(:prometheus_client_wrapper) { double(:prometheus_client_wrapper, label_values: nil) }
+ let(:prometheus_client) { double(:prometheus_client, label_values: nil) }
context 'with valid data' do
subject { service.query(:matched_metrics) }
before do
- allow(service).to receive(:prometheus_client_wrapper).and_return(prometheus_client_wrapper)
+ allow(service).to receive(:prometheus_client).and_return(prometheus_client)
synchronous_reactive_cache(service)
end
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index 7faa196623f..3d026932f59 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -47,30 +47,12 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
subject(:go!) { instance.result }
- context 'when cache is empty' do
- it { is_expected.to be_nil }
-
- it 'enqueues a background worker to bootstrap the cache' do
- expect(ReactiveCachingWorker).to receive(:perform_async).with(CacheTest, 666)
-
- go!
- end
-
- it 'updates the cache lifespan' do
- expect(reactive_cache_alive?(instance)).to be_falsy
-
- go!
-
- expect(reactive_cache_alive?(instance)).to be_truthy
- end
- end
-
- context 'when the cache is full' do
+ shared_examples 'a cacheable value' do |cached_value|
before do
- stub_reactive_cache(instance, 4)
+ stub_reactive_cache(instance, cached_value)
end
- it { is_expected.to eq(4) }
+ it { is_expected.to eq(cached_value) }
it 'does not enqueue a background worker' do
expect(ReactiveCachingWorker).not_to receive(:perform_async)
@@ -90,9 +72,7 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
end
it { is_expected.to be_nil }
- end
- context 'when cache was invalidated' do
it 'refreshes cache' do
expect(ReactiveCachingWorker).to receive(:perform_async).with(CacheTest, 666)
@@ -101,12 +81,34 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
end
end
- context 'when cache contains non-nil but blank value' do
- before do
- stub_reactive_cache(instance, false)
+ context 'when cache is empty' do
+ it { is_expected.to be_nil }
+
+ it 'enqueues a background worker to bootstrap the cache' do
+ expect(ReactiveCachingWorker).to receive(:perform_async).with(CacheTest, 666)
+
+ go!
end
- it { is_expected.to eq(false) }
+ it 'updates the cache lifespan' do
+ expect(reactive_cache_alive?(instance)).to be_falsy
+
+ go!
+
+ expect(reactive_cache_alive?(instance)).to be_truthy
+ end
+ end
+
+ context 'when the cache is full' do
+ it_behaves_like 'a cacheable value', 4
+ end
+
+ context 'when the cache contains non-nil but blank value' do
+ it_behaves_like 'a cacheable value', false
+ end
+
+ context 'when the cache contains nil value' do
+ it_behaves_like 'a cacheable value', nil
end
end
@@ -206,8 +208,9 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
expect(read_reactive_cache(instance)).to eq("preexisting")
end
- it 'enqueues a repeat worker' do
- expect_reactive_cache_update_queued(instance)
+ it 'does not enqueue a repeat worker' do
+ expect(ReactiveCachingWorker)
+ .not_to receive(:perform_in)
expect { go! }.to raise_error("foo")
end
diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb
deleted file mode 100644
index d0ae45f7871..00000000000
--- a/spec/models/concerns/relative_positioning_spec.rb
+++ /dev/null
@@ -1,242 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe RelativePositioning do
- let(:project) { create(:project) }
- let(:issue) { create(:issue, project: project) }
- let(:issue1) { create(:issue, project: project) }
- let(:new_issue) { create(:issue, project: project) }
-
- describe '.move_to_end' do
- it 'moves the object to the end' do
- Issue.move_to_end([issue, issue1])
-
- expect(issue1.prev_relative_position).to eq issue.relative_position
- expect(issue.prev_relative_position).to eq nil
- expect(issue1.next_relative_position).to eq nil
- end
-
- it 'does not perform any moves if all issues have their relative_position set' do
- issue.update!(relative_position: 1)
-
- expect(issue).not_to receive(:save)
-
- Issue.move_to_end([issue])
- end
- end
-
- describe '#max_relative_position' do
- it 'returns maximum position' do
- expect(issue.max_relative_position).to eq issue1.relative_position
- end
- end
-
- describe '#prev_relative_position' do
- it 'returns previous position if there is an issue above' do
- expect(issue1.prev_relative_position).to eq issue.relative_position
- end
-
- it 'returns nil if there is no issue above' do
- expect(issue.prev_relative_position).to eq nil
- end
- end
-
- describe '#next_relative_position' do
- it 'returns next position if there is an issue below' do
- expect(issue.next_relative_position).to eq issue1.relative_position
- end
-
- it 'returns nil if there is no issue below' do
- expect(issue1.next_relative_position).to eq nil
- end
- end
-
- describe '#move_before' do
- it 'moves issue before' do
- [issue1, issue].each(&:move_to_end)
-
- issue.move_before(issue1)
-
- expect(issue.relative_position).to be < issue1.relative_position
- end
- end
-
- describe '#move_after' do
- it 'moves issue after' do
- [issue, issue1].each(&:move_to_end)
-
- issue.move_after(issue1)
-
- expect(issue.relative_position).to be > issue1.relative_position
- end
- end
-
- describe '#move_to_end' do
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
- end
- end
-
- it 'moves issue to the end' do
- new_issue.move_to_end
-
- expect(new_issue.relative_position).to be > issue1.relative_position
- end
- end
-
- describe '#shift_after?' do
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
- end
- end
-
- it 'returns true' do
- issue.update(relative_position: issue1.relative_position - 1)
-
- expect(issue.shift_after?).to be_truthy
- end
-
- it 'returns false' do
- issue.update(relative_position: issue1.relative_position - 2)
-
- expect(issue.shift_after?).to be_falsey
- end
- end
-
- describe '#shift_before?' do
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
- end
- end
-
- it 'returns true' do
- issue.update(relative_position: issue1.relative_position + 1)
-
- expect(issue.shift_before?).to be_truthy
- end
-
- it 'returns false' do
- issue.update(relative_position: issue1.relative_position + 2)
-
- expect(issue.shift_before?).to be_falsey
- end
- end
-
- describe '#move_between' do
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
- end
- end
-
- it 'positions issue between two other' do
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to be > issue.relative_position
- expect(new_issue.relative_position).to be < issue1.relative_position
- end
-
- it 'positions issue between on top' do
- new_issue.move_between(nil, issue)
-
- expect(new_issue.relative_position).to be < issue.relative_position
- end
-
- it 'positions issue between to end' do
- new_issue.move_between(issue1, nil)
-
- expect(new_issue.relative_position).to be > issue1.relative_position
- end
-
- it 'positions issues even when after and before positions are the same' do
- issue1.update relative_position: issue.relative_position
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to be > issue.relative_position
- expect(issue.relative_position).to be < issue1.relative_position
- end
-
- it 'positions issues between other two if distance is 1' do
- issue1.update relative_position: issue.relative_position + 1
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to be > issue.relative_position
- expect(issue.relative_position).to be < issue1.relative_position
- end
-
- it 'positions issue in the middle of other two if distance is big enough' do
- issue.update relative_position: 6000
- issue1.update relative_position: 10000
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to eq(8000)
- end
-
- it 'positions issue closer to the middle if we are at the very top' do
- issue1.update relative_position: 6000
-
- new_issue.move_between(nil, issue1)
-
- expect(new_issue.relative_position).to eq(6000 - RelativePositioning::IDEAL_DISTANCE)
- end
-
- it 'positions issue closer to the middle if we are at the very bottom' do
- issue.update relative_position: 6000
- issue1.update relative_position: nil
-
- new_issue.move_between(issue, nil)
-
- expect(new_issue.relative_position).to eq(6000 + RelativePositioning::IDEAL_DISTANCE)
- end
-
- it 'positions issue in the middle of other two if distance is not big enough' do
- issue.update relative_position: 100
- issue1.update relative_position: 400
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to eq(250)
- end
-
- it 'positions issue in the middle of other two is there is no place' do
- issue.update relative_position: 100
- issue1.update relative_position: 101
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to be_between(issue.relative_position, issue1.relative_position)
- end
-
- it 'uses rebalancing if there is no place' do
- issue.update relative_position: 100
- issue1.update relative_position: 101
- issue2 = create(:issue, relative_position: 102, project: project)
- new_issue.update relative_position: 103
-
- new_issue.move_between(issue1, issue2)
- new_issue.save!
-
- expect(new_issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
- expect(issue.reload.relative_position).not_to eq(100)
- end
-
- it 'positions issue right if we pass none-sequential parameters' do
- issue.update relative_position: 99
- issue1.update relative_position: 101
- issue2 = create(:issue, relative_position: 102, project: project)
- new_issue.update relative_position: 103
-
- new_issue.move_between(issue, issue2)
- new_issue.save!
-
- expect(new_issue.relative_position).to be(100)
- end
- end
-end
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 1fb0dd5030c..31163a5bb5c 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -15,23 +15,46 @@ describe Group, 'Routable' do
end
describe 'Callbacks' do
- it 'creates route record on create' do
- expect(group.route.path).to eq(group.path)
- expect(group.route.name).to eq(group.name)
- end
+ context 'for a group' do
+ it 'creates route record on create' do
+ expect(group.route.path).to eq(group.path)
+ expect(group.route.name).to eq(group.name)
+ end
+
+ it 'updates route record on path change' do
+ group.update(path: 'wow', name: 'much')
- it 'updates route record on path change' do
- group.update(path: 'wow', name: 'much')
+ expect(group.route.path).to eq('wow')
+ expect(group.route.name).to eq('much')
+ end
+
+ it 'ensure route path uniqueness across different objects' do
+ create(:group, parent: group, path: 'xyz')
+ duplicate = build(:project, namespace: group, path: 'xyz')
- expect(group.route.path).to eq('wow')
- expect(group.route.name).to eq('much')
+ expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Path has already been taken')
+ end
end
- it 'ensure route path uniqueness across different objects' do
- create(:group, parent: group, path: 'xyz')
- duplicate = build(:project, namespace: group, path: 'xyz')
+ context 'for a user' do
+ let(:user) { create(:user, username: 'jane', name: "Jane Doe") }
- expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Path has already been taken')
+ it 'creates the route for a record on create' do
+ expect(user.namespace.name).to eq('Jane Doe')
+ expect(user.namespace.path).to eq('jane')
+ end
+
+ it 'updates routes and nested routes on name change' do
+ project = create(:project, path: 'work-stuff', name: 'Work stuff', namespace: user.namespace)
+
+ user.update!(username: 'jaen', name: 'Jaen Did')
+ project.reload
+
+ expect(user.namespace.name).to eq('Jaen Did')
+ expect(user.namespace.path).to eq('jaen')
+ expect(project.full_name).to eq('Jaen Did / Work stuff')
+ expect(project.full_path).to eq('jaen/work-stuff')
+ end
end
end
diff --git a/spec/models/concerns/stepable_spec.rb b/spec/models/concerns/stepable_spec.rb
new file mode 100644
index 00000000000..5685de6a9bf
--- /dev/null
+++ b/spec/models/concerns/stepable_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Stepable do
+ let(:described_class) do
+ Class.new do
+ include Stepable
+
+ steps :method1, :method2, :method3
+
+ def execute
+ execute_steps
+ end
+
+ private
+
+ def method1
+ { status: :success }
+ end
+
+ def method2
+ return { status: :error } unless @pass
+
+ { status: :success, variable1: 'var1' }
+ end
+
+ def method3
+ { status: :success, variable2: 'var2' }
+ end
+ end
+ end
+
+ let(:prepended_module) do
+ Module.new do
+ extend ActiveSupport::Concern
+
+ prepended do
+ steps :appended_method1
+ end
+
+ private
+
+ def appended_method1
+ { status: :success }
+ end
+ end
+ end
+
+ before do
+ described_class.prepend(prepended_module)
+ end
+
+ it 'stops after the first error' do
+ expect(subject).not_to receive(:method3)
+ expect(subject).not_to receive(:appended_method1)
+
+ expect(subject.execute).to eq(
+ status: :error,
+ failed_step: :method2
+ )
+ end
+
+ context 'when all methods return success' do
+ before do
+ subject.instance_variable_set(:@pass, true)
+ end
+
+ it 'calls all methods in order' do
+ expect(subject).to receive(:method1).and_call_original.ordered
+ expect(subject).to receive(:method2).and_call_original.ordered
+ expect(subject).to receive(:method3).and_call_original.ordered
+ expect(subject).to receive(:appended_method1).and_call_original.ordered
+
+ subject.execute
+ end
+
+ it 'merges variables returned by all steps' do
+ expect(subject.execute).to eq(
+ status: :success,
+ variable1: 'var1',
+ variable2: 'var2'
+ )
+ end
+ end
+
+ context 'with multiple stepable classes' do
+ let(:other_class) do
+ Class.new do
+ include Stepable
+
+ steps :other_method1, :other_method2
+
+ private
+
+ def other_method1
+ { status: :success }
+ end
+
+ def other_method2
+ { status: :success }
+ end
+ end
+ end
+
+ it 'does not leak steps' do
+ expect(other_class.new.steps).to contain_exactly(:other_method1, :other_method2)
+ expect(subject.steps).to contain_exactly(:method1, :method2, :method3, :appended_method1)
+ end
+ end
+end
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 013112d1d51..935838ce294 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -16,7 +16,7 @@ describe ContainerRepository do
host_port: 'registry.gitlab')
stub_request(:get, 'http://registry.gitlab/v2/group/test/my_image/tags/list')
- .with(headers: { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' })
+ .with(headers: { 'Accept' => ContainerRegistry::Client::ACCEPTED_TYPES.join(', ') })
.to_return(
status: 200,
body: JSON.dump(tags: ['test_tag']),
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index b22a0340015..808659552ff 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#code' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
context 'with deployment' do
generate_cycle_analytics_spec(
@@ -37,7 +38,7 @@ describe 'CycleAnalytics#code' do
merge_merge_requests_closing_issue(user, project, issue)
deploy_master(user, project)
- expect(subject[:code].median).to be_nil
+ expect(subject[:code].project_median).to be_nil
end
end
end
@@ -67,7 +68,7 @@ describe 'CycleAnalytics#code' do
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:code].median).to be_nil
+ expect(subject[:code].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/group_level_spec.rb b/spec/models/cycle_analytics/group_level_spec.rb
new file mode 100644
index 00000000000..154c1b9c0f8
--- /dev/null
+++ b/spec/models/cycle_analytics/group_level_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe CycleAnalytics::GroupLevel do
+ let(:group) { create(:group)}
+ let(:project) { create(:project, :repository, namespace: group) }
+ let(:from_date) { 10.days.ago }
+ let(:user) { create(:user, :admin) }
+ let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
+ let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
+
+ subject { described_class.new(group: group, options: { from: from_date, current_user: user }) }
+
+ describe '#permissions' do
+ it 'returns true for all stages' do
+ expect(subject.permissions.values.uniq).to eq([true])
+ end
+ end
+
+ describe '#stats' do
+ before do
+ allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
+
+ create_cycle(user, project, issue, mr, milestone, pipeline)
+ deploy_master(user, project)
+ end
+
+ it 'returns medians for each stage for a specific group' do
+ expect(subject.no_stats?).to eq(false)
+ end
+ end
+
+ describe '#summary' do
+ before do
+ create_cycle(user, project, issue, mr, milestone, pipeline)
+ deploy_master(user, project)
+ end
+
+ it 'returns medians for each stage for a specific group' do
+ expect(subject.summary.map { |summary| summary[:value] }).to contain_exactly(1, 1)
+ end
+ end
+end
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 07d60be091a..8cdf83b1292 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#issue' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :issue,
@@ -23,7 +24,7 @@ describe 'CycleAnalytics#issue' do
["list label added to issue",
-> (context, data) do
if data[:issue].persisted?
- data[:issue].update(label_ids: [context.create(:label, lists: [context.create(:list)]).id])
+ data[:issue].update(label_ids: [context.create(:list).label_id])
end
end]],
post_fn: -> (context, data) do
@@ -42,7 +43,7 @@ describe 'CycleAnalytics#issue' do
create_merge_request_closing_issue(user, project, issue)
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:issue].median).to be_nil
+ expect(subject[:issue].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index 3d22a284264..28ad9bd194d 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#plan' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :plan,
@@ -24,7 +25,7 @@ describe 'CycleAnalytics#plan' do
end],
["list label added to issue",
-> (context, data) do
- data[:issue].update(label_ids: [context.create(:label, lists: [context.create(:list)]).id])
+ data[:issue].update(label_ids: [context.create(:list).label_id])
end]],
end_time_conditions: [["issue mentioned in a commit",
-> (context, data) do
@@ -46,7 +47,7 @@ describe 'CycleAnalytics#plan' do
create_merge_request_closing_issue(user, project, issue, source_branch: branch_name)
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:issue].median).to be_nil
+ expect(subject[:issue].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 383727cd8f7..613c1786540 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#production' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :production,
@@ -40,7 +41,7 @@ describe 'CycleAnalytics#production' do
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project)
- expect(subject[:production].median).to be_nil
+ expect(subject[:production].project_median).to be_nil
end
end
@@ -51,7 +52,7 @@ describe 'CycleAnalytics#production' do
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project, environment: 'staging')
- expect(subject[:production].median).to be_nil
+ expect(subject[:production].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics_spec.rb b/spec/models/cycle_analytics/project_level_spec.rb
index 5d8b5b573cf..4de01b1c679 100644
--- a/spec/models/cycle_analytics_spec.rb
+++ b/spec/models/cycle_analytics/project_level_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CycleAnalytics do
+describe CycleAnalytics::ProjectLevel do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
@@ -11,9 +11,9 @@ describe CycleAnalytics do
let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
- subject { described_class.new(project, from: from_date) }
+ subject { described_class.new(project, options: { from: from_date }) }
- describe '#all_medians_per_stage' do
+ describe '#all_medians_by_stage' do
before do
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
@@ -23,10 +23,10 @@ describe CycleAnalytics do
it 'returns every median for each stage for a specific project' do
values = described_class::STAGES.each_with_object({}) do |stage_name, hsh|
- hsh[stage_name] = subject[stage_name].median.presence
+ hsh[stage_name] = subject[stage_name].project_median.presence
end
- expect(subject.all_medians_per_stage).to eq(values)
+ expect(subject.all_medians_by_stage).to eq(values)
end
end
end
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 1af5f9cc1f4..ef88fd86340 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#review' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :review,
@@ -27,7 +28,7 @@ describe 'CycleAnalytics#review' do
it "returns nil" do
MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
- expect(subject[:review].median).to be_nil
+ expect(subject[:review].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 8375944f03c..571792559d8 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -9,7 +9,7 @@ describe 'CycleAnalytics#staging' do
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :staging,
@@ -45,7 +45,7 @@ describe 'CycleAnalytics#staging' do
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project)
- expect(subject[:staging].median).to be_nil
+ expect(subject[:staging].project_median).to be_nil
end
end
@@ -56,7 +56,7 @@ describe 'CycleAnalytics#staging' do
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project, environment: 'staging')
- expect(subject[:staging].median).to be_nil
+ expect(subject[:staging].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index b78258df564..7b3001d2bd8 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#test' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :test,
@@ -35,7 +36,7 @@ describe 'CycleAnalytics#test' do
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:test].median).to be_nil
+ expect(subject[:test].project_median).to be_nil
end
end
@@ -46,7 +47,7 @@ describe 'CycleAnalytics#test' do
pipeline.run!
pipeline.succeed!
- expect(subject[:test].median).to be_nil
+ expect(subject[:test].project_median).to be_nil
end
end
@@ -61,7 +62,7 @@ describe 'CycleAnalytics#test' do
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:test].median).to be_nil
+ expect(subject[:test].project_median).to be_nil
end
end
@@ -76,7 +77,7 @@ describe 'CycleAnalytics#test' do
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:test].median).to be_nil
+ expect(subject[:test].project_median).to be_nil
end
end
end
diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb
index 2fe82eaa778..8d951ab6f0f 100644
--- a/spec/models/deploy_token_spec.rb
+++ b/spec/models/deploy_token_spec.rb
@@ -8,6 +8,15 @@ describe DeployToken do
it { is_expected.to have_many :project_deploy_tokens }
it { is_expected.to have_many(:projects).through(:project_deploy_tokens) }
+ describe 'validations' do
+ let(:username_format_message) { "can contain only letters, digits, '_', '-', '+', and '.'" }
+
+ it { is_expected.to validate_length_of(:username).is_at_most(255) }
+ it { is_expected.to allow_value('GitLab+deploy_token-3.14').for(:username) }
+ it { is_expected.not_to allow_value('<script>').for(:username).with_message(username_format_message) }
+ it { is_expected.not_to allow_value('').for(:username).with_message(username_format_message) }
+ end
+
describe '#ensure_token' do
it 'ensures a token' do
deploy_token.token = nil
@@ -87,8 +96,30 @@ describe DeployToken do
end
describe '#username' do
- it 'returns a harcoded username' do
- expect(deploy_token.username).to eq("gitlab+deploy-token-#{deploy_token.id}")
+ context 'persisted records' do
+ it 'returns a default username if none is set' do
+ expect(deploy_token.username).to eq("gitlab+deploy-token-#{deploy_token.id}")
+ end
+
+ it 'returns the username provided if one is set' do
+ deploy_token = create(:deploy_token, username: 'deployer')
+
+ expect(deploy_token.username).to eq('deployer')
+ end
+ end
+
+ context 'new records' do
+ it 'returns nil if no username is set' do
+ deploy_token = build(:deploy_token)
+
+ expect(deploy_token.username).to be_nil
+ end
+
+ it 'returns the username provided if one is set' do
+ deploy_token = build(:deploy_token, username: 'deployer')
+
+ expect(deploy_token.username).to eq('deployer')
+ end
end
end
diff --git a/spec/models/deployment_metrics_spec.rb b/spec/models/deployment_metrics_spec.rb
new file mode 100644
index 00000000000..7c574a8b6c8
--- /dev/null
+++ b/spec/models/deployment_metrics_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DeploymentMetrics do
+ describe '#has_metrics?' do
+ subject { described_class.new(deployment.project, deployment).has_metrics? }
+
+ context 'when deployment is failed' do
+ let(:deployment) { create(:deployment, :failed) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when deployment is success' do
+ let(:deployment) { create(:deployment, :success) }
+
+ context 'without a monitoring service' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'with a Prometheus Service' do
+ let(:prometheus_service) { instance_double(PrometheusService, can_query?: true) }
+
+ before do
+ allow(deployment.project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'with a Prometheus Service that cannot query' do
+ let(:prometheus_service) { instance_double(PrometheusService, can_query?: false) }
+
+ before do
+ allow(deployment.project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'with a cluster Prometheus' do
+ let(:deployment) { create(:deployment, :success, :on_cluster) }
+ let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: deployment.cluster) }
+
+ before do
+ expect(deployment.cluster.application_prometheus).to receive(:can_query?).and_return(true)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+
+ describe '#metrics' do
+ let(:deployment) { create(:deployment, :success) }
+ let(:prometheus_adapter) { instance_double(PrometheusService, can_query?: true) }
+ let(:deployment_metrics) { described_class.new(deployment.project, deployment) }
+
+ subject { deployment_metrics.metrics }
+
+ context 'metrics are disabled' do
+ it { is_expected.to eq({}) }
+ end
+
+ context 'metrics are enabled' do
+ let(:simple_metrics) do
+ {
+ success: true,
+ metrics: {},
+ last_update: 42
+ }
+ end
+
+ before do
+ allow(deployment_metrics).to receive(:prometheus_adapter).and_return(prometheus_adapter)
+ expect(prometheus_adapter).to receive(:query).with(:deployment, deployment).and_return(simple_metrics)
+ end
+
+ it { is_expected.to eq(simple_metrics.merge({ deployment_time: deployment.created_at.to_i })) }
+ end
+ end
+
+ describe '#additional_metrics' do
+ let(:project) { create(:project, :repository) }
+ let(:deployment) { create(:deployment, :succeed, project: project) }
+ let(:deployment_metrics) { described_class.new(deployment.project, deployment) }
+
+ subject { deployment_metrics.additional_metrics }
+
+ context 'metrics are disabled' do
+ it { is_expected.to eq({}) }
+ end
+
+ context 'metrics are enabled' do
+ let(:simple_metrics) do
+ {
+ success: true,
+ metrics: {},
+ last_update: 42
+ }
+ end
+
+ let(:prometheus_adapter) { instance_double('prometheus_adapter', can_query?: true) }
+
+ before do
+ allow(deployment_metrics).to receive(:prometheus_adapter).and_return(prometheus_adapter)
+ expect(prometheus_adapter).to receive(:query).with(:additional_metrics_deployment, deployment).and_return(simple_metrics)
+ end
+
+ it { is_expected.to eq(simple_metrics.merge({ deployment_time: deployment.created_at.to_i })) }
+ end
+ end
+end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index a433878f3bc..51ed8e9421b 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -7,6 +7,7 @@ describe Deployment do
it { is_expected.to belong_to(:project).required }
it { is_expected.to belong_to(:environment).required }
+ it { is_expected.to belong_to(:cluster).class_name('Clusters::Cluster') }
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:deployable) }
@@ -294,64 +295,6 @@ describe Deployment do
end
end
- describe '#metrics' do
- let(:deployment) { create(:deployment, :success) }
- let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) }
-
- subject { deployment.metrics }
-
- context 'metrics are disabled' do
- it { is_expected.to eq({}) }
- end
-
- context 'metrics are enabled' do
- let(:simple_metrics) do
- {
- success: true,
- metrics: {},
- last_update: 42
- }
- end
-
- before do
- allow(deployment).to receive(:prometheus_adapter).and_return(prometheus_adapter)
- allow(prometheus_adapter).to receive(:query).with(:deployment, deployment).and_return(simple_metrics)
- end
-
- it { is_expected.to eq(simple_metrics.merge({ deployment_time: deployment.created_at.to_i })) }
- end
- end
-
- describe '#additional_metrics' do
- let(:project) { create(:project, :repository) }
- let(:deployment) { create(:deployment, :succeed, project: project) }
-
- subject { deployment.additional_metrics }
-
- context 'metrics are disabled' do
- it { is_expected.to eq({}) }
- end
-
- context 'metrics are enabled' do
- let(:simple_metrics) do
- {
- success: true,
- metrics: {},
- last_update: 42
- }
- end
-
- let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) }
-
- before do
- allow(deployment).to receive(:prometheus_adapter).and_return(prometheus_adapter)
- allow(prometheus_adapter).to receive(:query).with(:additional_metrics_deployment, deployment).and_return(simple_metrics)
- end
-
- it { is_expected.to eq(simple_metrics.merge({ deployment_time: deployment.created_at.to_i })) }
- end
- end
-
describe '#stop_action' do
let(:build) { create(:ci_build) }
@@ -380,37 +323,29 @@ describe Deployment do
end
end
- describe '#deployment_platform_cluster' do
- let(:deployment) { create(:deployment) }
- let(:project) { deployment.project }
- let(:environment) { deployment.environment }
+ describe '#deployed_by' do
+ it 'returns the deployment user if there is no deployable' do
+ deployment_user = create(:user)
+ deployment = create(:deployment, deployable: nil, user: deployment_user)
- subject { deployment.deployment_platform_cluster }
-
- before do
- expect(project).to receive(:deployment_platform)
- .with(environment: environment.name).and_call_original
- end
-
- context 'project has no deployment platform' do
- before do
- expect(project.clusters).to be_empty
- end
-
- it { is_expected.to be_nil }
+ expect(deployment.deployed_by).to eq(deployment_user)
end
- context 'project uses the kubernetes service for deployments' do
- let!(:service) { create(:kubernetes_service, project: project) }
+ it 'returns the deployment user if the deployable have no user' do
+ deployment_user = create(:user)
+ build = create(:ci_build, user: nil)
+ deployment = create(:deployment, deployable: build, user: deployment_user)
- it { is_expected.to be_nil }
+ expect(deployment.deployed_by).to eq(deployment_user)
end
- context 'project has a deployment platform' do
- let!(:cluster) { create(:cluster, projects: [project]) }
- let!(:platform) { create(:cluster_platform_kubernetes, cluster: cluster) }
+ it 'returns the deployable user if there is one' do
+ build_user = create(:user)
+ deployment_user = create(:user)
+ build = create(:ci_build, user: build_user)
+ deployment = create(:deployment, deployable: build, user: deployment_user)
- it { is_expected.to eq cluster }
+ expect(deployment.deployed_by).to eq(build_user)
end
end
end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index d9e1fe4b165..8d7dafc523d 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -34,6 +34,10 @@ describe DiffNote do
subject { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) }
+ describe 'validations' do
+ it_behaves_like 'a valid diff positionable note', :diff_note_on_commit
+ end
+
describe "#position=" do
context "when provided a string" do
it "sets the position" do
diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb
index 22d4dab0617..950bdec4d00 100644
--- a/spec/models/discussion_spec.rb
+++ b/spec/models/discussion_spec.rb
@@ -10,6 +10,20 @@ describe Discussion do
let(:second_note) { create(:diff_note_on_merge_request, in_reply_to: first_note) }
let(:third_note) { create(:diff_note_on_merge_request) }
+ describe '.lazy_find' do
+ let!(:note1) { create(:discussion_note_on_merge_request).to_discussion }
+ let!(:note2) { create(:discussion_note_on_merge_request, in_reply_to: note1).to_discussion }
+
+ subject { [note1, note2].map { |note| described_class.lazy_find(note.discussion_id) } }
+
+ it 'batches requests' do
+ expect do
+ [described_class.lazy_find(note1.id),
+ described_class.lazy_find(note2.id)].map(&:__sync)
+ end.not_to exceed_query_limit(1)
+ end
+ end
+
describe '.build' do
it 'returns a discussion of the right type' do
discussion = described_class.build([first_note, second_note], merge_request)
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index fe4d64818b4..521c4704c87 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Environment, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
+ using RSpec::Parameterized::TableSyntax
let(:project) { create(:project, :stubbed_repository) }
subject(:environment) { create(:environment, project: project) }
@@ -574,6 +575,34 @@ describe Environment, :use_clean_rails_memory_store_caching do
end
end
+ describe '#deployment_namespace' do
+ let(:environment) { create(:environment) }
+
+ subject { environment.deployment_namespace }
+
+ before do
+ allow(environment).to receive(:deployment_platform).and_return(deployment_platform)
+ end
+
+ context 'no deployment platform available' do
+ let(:deployment_platform) { nil }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'deployment platform is available' do
+ let(:cluster) { create(:cluster, :provided_by_user, :project, projects: [environment.project]) }
+ let(:deployment_platform) { cluster.platform }
+
+ it 'retrieves a namespace from the cluster' do
+ expect(cluster).to receive(:kubernetes_namespace_for)
+ .with(environment).and_return('mock-namespace')
+
+ expect(subject).to eq 'mock-namespace'
+ end
+ end
+ end
+
describe '#terminals' do
subject { environment.terminals }
@@ -762,32 +791,6 @@ describe Environment, :use_clean_rails_memory_store_caching do
end
end
- describe '#generate_slug' do
- SUFFIX = "-[a-z0-9]{6}".freeze
- {
- "staging-12345678901234567" => "staging-123456789" + SUFFIX,
- "9-staging-123456789012345" => "env-9-staging-123" + SUFFIX,
- "staging-1234567890123456" => "staging-1234567890123456",
- "production" => "production",
- "PRODUCTION" => "production" + SUFFIX,
- "review/1-foo" => "review-1-foo" + SUFFIX,
- "1-foo" => "env-1-foo" + SUFFIX,
- "1/foo" => "env-1-foo" + SUFFIX,
- "foo-" => "foo" + SUFFIX,
- "foo--bar" => "foo-bar" + SUFFIX,
- "foo**bar" => "foo-bar" + SUFFIX,
- "*-foo" => "env-foo" + SUFFIX,
- "staging-12345678-" => "staging-12345678" + SUFFIX,
- "staging-12345678-01234567" => "staging-12345678" + SUFFIX
- }.each do |name, matcher|
- it "returns a slug matching #{matcher}, given #{name}" do
- slug = described_class.new(name: name).generate_slug
-
- expect(slug).to match(/\A#{matcher}\z/)
- end
- end
- end
-
describe '#ref_path' do
subject(:environment) do
create(:environment, name: 'staging / review-1')
@@ -808,12 +811,9 @@ describe Environment, :use_clean_rails_memory_store_caching do
let(:source_path) { 'source/file.html' }
let(:sha) { RepoHelpers.sample_commit.id }
- before do
- environment.external_url = 'http://example.com'
- end
-
context 'when the public path is not known' do
before do
+ environment.external_url = 'http://example.com'
allow(project).to receive(:public_path_for_source_path).with(source_path, sha).and_return(nil)
end
@@ -823,12 +823,23 @@ describe Environment, :use_clean_rails_memory_store_caching do
end
context 'when the public path is known' do
- before do
- allow(project).to receive(:public_path_for_source_path).with(source_path, sha).and_return('file.html')
- end
-
- it 'returns the full external URL' do
- expect(environment.external_url_for(source_path, sha)).to eq('http://example.com/file.html')
+ where(:external_url, :public_path, :full_url) do
+ 'http://example.com' | 'file.html' | 'http://example.com/file.html'
+ 'http://example.com/' | 'file.html' | 'http://example.com/file.html'
+ 'http://example.com' | '/file.html' | 'http://example.com/file.html'
+ 'http://example.com/' | '/file.html' | 'http://example.com/file.html'
+ 'http://example.com/subpath' | 'public/file.html' | 'http://example.com/subpath/public/file.html'
+ 'http://example.com/subpath/' | 'public/file.html' | 'http://example.com/subpath/public/file.html'
+ 'http://example.com/subpath' | '/public/file.html' | 'http://example.com/subpath/public/file.html'
+ 'http://example.com/subpath/' | '/public/file.html' | 'http://example.com/subpath/public/file.html'
+ end
+ with_them do
+ it 'returns the full external URL' do
+ environment.external_url = external_url
+ allow(project).to receive(:public_path_for_source_path).with(source_path, sha).and_return(public_path)
+
+ expect(environment.external_url_for(source_path, sha)).to eq(full_url)
+ end
end
end
end
@@ -840,4 +851,35 @@ describe Environment, :use_clean_rails_memory_store_caching do
subject.prometheus_adapter
end
end
+
+ describe '#knative_services_finder' do
+ let(:environment) { create(:environment) }
+
+ subject { environment.knative_services_finder }
+
+ context 'environment has no deployments' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'environment has a deployment' do
+ let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
+
+ context 'with no cluster associated' do
+ let(:cluster) { nil }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'with a cluster associated' do
+ let(:cluster) { create(:cluster) }
+
+ it 'calls the service finder' do
+ expect(Clusters::KnativeServicesFinder).to receive(:new)
+ .with(cluster, environment).and_return(:finder)
+
+ is_expected.to eq :finder
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
index c503c35305f..e2836420df9 100644
--- a/spec/models/environment_status_spec.rb
+++ b/spec/models/environment_status_spec.rb
@@ -11,11 +11,10 @@ describe EnvironmentStatus do
let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) }
let(:sha) { deployment.sha }
- subject(:environment_status) { described_class.new(environment, merge_request, sha) }
+ subject(:environment_status) { described_class.new(project, environment, merge_request, sha) }
it { is_expected.to delegate_method(:id).to(:environment) }
it { is_expected.to delegate_method(:name).to(:environment) }
- it { is_expected.to delegate_method(:project).to(:environment) }
it { is_expected.to delegate_method(:deployed_at).to(:deployment) }
it { is_expected.to delegate_method(:status).to(:deployment) }
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 470ce65707d..1c41ceb7deb 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -23,6 +23,7 @@ describe Group do
it { is_expected.to have_many(:badges).class_name('GroupBadge') }
it { is_expected.to have_many(:cluster_groups).class_name('Clusters::Group') }
it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') }
+ it { is_expected.to have_many(:container_repositories) }
describe '#members & #requesters' do
let(:requester) { create(:user) }
@@ -71,7 +72,7 @@ describe Group do
end
end
- describe '#notification_settings', :nested_groups do
+ describe '#notification_settings' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:sub_group) { create(:group, parent_id: group.id) }
@@ -95,6 +96,43 @@ describe Group do
end
end
+ describe '#notification_email_for' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:subgroup) { create(:group, parent: group) }
+
+ let(:group_notification_email) { 'user+group@example.com' }
+ let(:subgroup_notification_email) { 'user+subgroup@example.com' }
+
+ subject { subgroup.notification_email_for(user) }
+
+ context 'when both group notification emails are set' do
+ it 'returns subgroup notification email' do
+ create(:notification_setting, user: user, source: group, notification_email: group_notification_email)
+ create(:notification_setting, user: user, source: subgroup, notification_email: subgroup_notification_email)
+
+ is_expected.to eq(subgroup_notification_email)
+ end
+ end
+
+ context 'when subgroup notification email is blank' do
+ it 'returns parent group notification email' do
+ create(:notification_setting, user: user, source: group, notification_email: group_notification_email)
+ create(:notification_setting, user: user, source: subgroup, notification_email: '')
+
+ is_expected.to eq(group_notification_email)
+ end
+ end
+
+ context 'when only the parent group notification email is set' do
+ it 'returns parent group notification email' do
+ create(:notification_setting, user: user, source: group, notification_email: group_notification_email)
+
+ is_expected.to eq(group_notification_email)
+ end
+ end
+ end
+
describe '#visibility_level_allowed_by_parent' do
let(:parent) { create(:group, :internal) }
let(:sub_group) { build(:group, parent_id: parent.id) }
@@ -200,7 +238,7 @@ describe Group do
it { is_expected.to match_array([private_group, internal_group, group]) }
end
- context 'when user is a member of private subgroup', :postgresql do
+ context 'when user is a member of private subgroup' do
let!(:private_subgroup) { create(:group, :private, parent: private_group) }
before do
@@ -379,7 +417,7 @@ describe Group do
it { expect(group.last_owner?(@members[:owner])).to be_falsy }
end
- context 'with owners from a parent', :postgresql do
+ context 'with owners from a parent' do
before do
parent_group = create(:group)
create(:group_member, :owner, group: parent_group)
@@ -487,7 +525,7 @@ describe Group do
it { expect(subject.parent).to be_kind_of(described_class) }
end
- describe '#members_with_parents', :nested_groups do
+ describe '#members_with_parents' do
let!(:group) { create(:group, :nested) }
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
@@ -498,7 +536,7 @@ describe Group do
end
end
- describe '#direct_and_indirect_members', :nested_groups do
+ describe '#direct_and_indirect_members' do
let!(:group) { create(:group, :nested) }
let!(:sub_group) { create(:group, parent: group) }
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
@@ -515,7 +553,7 @@ describe Group do
end
end
- describe '#users_with_descendants', :nested_groups do
+ describe '#users_with_descendants' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
@@ -534,7 +572,7 @@ describe Group do
end
end
- describe '#direct_and_indirect_users', :nested_groups do
+ describe '#direct_and_indirect_users' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
let(:user_c) { create(:user) }
@@ -564,7 +602,7 @@ describe Group do
end
end
- describe '#project_users_with_descendants', :nested_groups do
+ describe '#project_users_with_descendants' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
let(:user_c) { create(:user) }
@@ -641,7 +679,7 @@ describe Group do
end
end
- context 'sub groups and projects', :nested_groups do
+ context 'sub groups and projects' do
it 'enables two_factor_requirement for group member' do
group.add_user(user, GroupMember::OWNER)
@@ -650,7 +688,7 @@ describe Group do
expect(user.reload.require_two_factor_authentication_from_group).to be_truthy
end
- context 'expanded group members', :nested_groups do
+ context 'expanded group members' do
let(:indirect_user) { create(:user) }
it 'enables two_factor_requirement for subgroup member' do
@@ -683,7 +721,7 @@ describe Group do
expect(user.reload.require_two_factor_authentication_from_group).to be_falsey
end
- it 'does not enable two_factor_requirement for subgroup child project member', :nested_groups do
+ it 'does not enable two_factor_requirement for subgroup child project member' do
subgroup = create(:group, :nested, parent: group)
project = create(:project, group: subgroup)
project.add_maintainer(user)
@@ -783,7 +821,7 @@ describe Group do
it_behaves_like 'ref is protected'
end
- context 'when group has children', :postgresql do
+ context 'when group has children' do
let(:group_child) { create(:group, parent: group) }
let(:group_child_2) { create(:group, parent: group_child) }
let(:group_child_3) { create(:group, parent: group_child_2) }
@@ -806,7 +844,7 @@ describe Group do
end
end
- describe '#highest_group_member', :nested_groups do
+ describe '#highest_group_member' do
let(:nested_group) { create(:group, parent: group) }
let(:nested_group_2) { create(:group, parent: nested_group) }
let(:user) { create(:user) }
@@ -895,7 +933,7 @@ describe Group do
it { is_expected.to eq(config) }
end
- context 'with parent groups', :nested_groups do
+ context 'with parent groups' do
where(:instance_value, :parent_value, :group_value, :config) do
# Instance level enabled
true | nil | nil | { status: true, scope: :instance }
@@ -994,4 +1032,11 @@ describe Group do
expect(group.project_creation_level).to eq(Gitlab::CurrentSettings.default_project_creation)
end
end
+
+ describe 'subgroup_creation_level' do
+ it 'defaults to maintainers' do
+ expect(group.subgroup_creation_level)
+ .to eq(Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS)
+ end
+ end
end
diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb
index 07858fe8a70..7aa0d97b194 100644
--- a/spec/models/issue/metrics_spec.rb
+++ b/spec/models/issue/metrics_spec.rb
@@ -32,7 +32,7 @@ describe Issue::Metrics do
context "list labels" do
it "records the first time an issue is associated with a list label" do
- list_label = create(:label, lists: [create(:list)])
+ list_label = create(:list).label
time = Time.now
Timecop.freeze(time) { subject.update(label_ids: [list_label.id]) }
metrics = subject.metrics
@@ -43,9 +43,9 @@ describe Issue::Metrics do
it "does not record the second time an issue is associated with a list label" do
time = Time.now
- first_list_label = create(:label, lists: [create(:list)])
+ first_list_label = create(:list).label
Timecop.freeze(time) { subject.update(label_ids: [first_list_label.id]) }
- second_list_label = create(:label, lists: [create(:list)])
+ second_list_label = create(:list).label
Timecop.freeze(time + 5.hours) { subject.update(label_ids: [second_list_label.id]) }
metrics = subject.metrics
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index d5b016dc8f6..2e7d78d77a8 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -871,4 +871,12 @@ describe Issue do
expect(issue.labels_hook_attrs).to eq([label.hook_attrs])
end
end
+
+ context "relative positioning" do
+ it_behaves_like "a class that supports relative positioning" do
+ let(:project) { create(:project) }
+ let(:factory) { :issue }
+ let(:default_params) { { project: project } }
+ end
+ end
end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 5174c590a10..c2e2298823e 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -193,4 +193,17 @@ describe Label do
expect(described_class.optionally_subscribed_by(nil)).to match_array([label, label2])
end
end
+
+ describe '#templates' do
+ context 'with invalid template labels' do
+ it 'returns only valid template labels' do
+ create(:label)
+ # Project labels should not have template set to true
+ create(:label, template: true)
+ valid_template_label = described_class.create!(title: 'test', template: true, type: nil)
+
+ expect(described_class.templates).to eq([valid_template_label])
+ end
+ end
+ end
end
diff --git a/spec/models/lfs_download_object_spec.rb b/spec/models/lfs_download_object_spec.rb
index effd8b08124..8b53effe98f 100644
--- a/spec/models/lfs_download_object_spec.rb
+++ b/spec/models/lfs_download_object_spec.rb
@@ -50,7 +50,7 @@ describe LfsDownloadObject do
before do
allow(ApplicationSetting)
.to receive(:current)
- .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: setting))
+ .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_web_hooks_and_services: setting))
end
context 'are allowed' do
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 782a84f922b..2cb4f222ea4 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -172,6 +172,19 @@ describe Member do
it { expect(described_class.non_request).to include @accepted_request_member }
end
+ describe '.search_invite_email' do
+ it 'returns only members the matching e-mail' do
+ create(:group_member, :invited)
+
+ invited = described_class.search_invite_email(@invited_member.invite_email)
+
+ expect(invited.count).to eq(1)
+ expect(invited.first).to eq(@invited_member)
+
+ expect(described_class.search_invite_email('bad-email@example.com').count).to eq(0)
+ end
+ end
+
describe '.developers' do
subject { described_class.developers.to_a }
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index f227abd3dae..ad7dfac87af 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -3,19 +3,29 @@
require 'spec_helper'
describe GroupMember do
- describe '.count_users_by_group_id' do
- it 'counts users by group ID' do
- user_1 = create(:user)
- user_2 = create(:user)
- group_1 = create(:group)
- group_2 = create(:group)
-
- group_1.add_owner(user_1)
- group_1.add_owner(user_2)
- group_2.add_owner(user_1)
-
- expect(described_class.count_users_by_group_id).to eq(group_1.id => 2,
- group_2.id => 1)
+ context 'scopes' do
+ describe '.count_users_by_group_id' do
+ it 'counts users by group ID' do
+ user_1 = create(:user)
+ user_2 = create(:user)
+ group_1 = create(:group)
+ group_2 = create(:group)
+
+ group_1.add_owner(user_1)
+ group_1.add_owner(user_2)
+ group_2.add_owner(user_1)
+
+ expect(described_class.count_users_by_group_id).to eq(group_1.id => 2,
+ group_2.id => 1)
+ end
+ end
+
+ describe '.of_ldap_type' do
+ it 'returns ldap type users' do
+ group_member = create(:group_member, :ldap)
+
+ expect(described_class.of_ldap_type).to eq([group_member])
+ end
end
end
@@ -69,7 +79,7 @@ describe GroupMember do
end
end
- context 'access levels', :nested_groups do
+ context 'access levels' do
context 'with parent group' do
it_behaves_like 'inherited access level as a member of entity' do
let(:entity) { create(:group, parent: parent_entity) }
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 497764b6825..79c39b81196 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -130,7 +130,7 @@ describe ProjectMember do
end
end
- context 'with parent group and a subgroup', :nested_groups do
+ context 'with parent group and a subgroup' do
it_behaves_like 'inherited access level as a member of entity' do
let(:subgroup) { create(:group, parent: parent_entity) }
let(:entity) { create(:project, group: subgroup) }
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index a53add67066..e7dd7287a75 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -484,4 +484,12 @@ describe MergeRequestDiff do
end
end
end
+
+ describe '#lines_count' do
+ subject { diff_with_commits }
+
+ it 'returns sum of all changed lines count in diff files' do
+ expect(subject.lines_count).to eq 109
+ end
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index a2547755510..d344a6d0f0d 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -7,6 +7,8 @@ describe MergeRequest do
include ProjectForksHelper
include ReactiveCachingHelpers
+ using RSpec::Parameterized::TableSyntax
+
subject { create(:merge_request) }
describe 'associations' do
@@ -1986,6 +1988,7 @@ describe MergeRequest do
params = {}
merge_jid = 'hash-123'
+ expect(merge_request).to receive(:expire_etag_cache)
expect(MergeWorker).to receive(:perform_async).with(merge_request.id, user_id, params) do
merge_jid
end
@@ -1996,6 +1999,48 @@ describe MergeRequest do
end
end
+ describe '#rebase_async' do
+ let(:merge_request) { create(:merge_request) }
+ let(:user_id) { double(:user_id) }
+ let(:rebase_jid) { 'rebase-jid' }
+
+ subject(:execute) { merge_request.rebase_async(user_id) }
+
+ it 'atomically enqueues a RebaseWorker job and updates rebase_jid' do
+ expect(RebaseWorker)
+ .to receive(:perform_async)
+ .with(merge_request.id, user_id)
+ .and_return(rebase_jid)
+
+ expect(merge_request).to receive(:expire_etag_cache)
+ expect(merge_request).to receive(:lock!).and_call_original
+
+ execute
+
+ expect(merge_request.rebase_jid).to eq(rebase_jid)
+ end
+
+ it 'refuses to enqueue a job if a rebase is in progress' do
+ merge_request.update_column(:rebase_jid, rebase_jid)
+
+ expect(RebaseWorker).not_to receive(:perform_async)
+ expect(Gitlab::SidekiqStatus)
+ .to receive(:running?)
+ .with(rebase_jid)
+ .and_return(true)
+
+ expect { execute }.to raise_error(ActiveRecord::StaleObjectError)
+ end
+
+ it 'refuses to enqueue a job if the MR is not open' do
+ merge_request.update_column(:state, 'foo')
+
+ expect(RebaseWorker).not_to receive(:perform_async)
+
+ expect { execute }.to raise_error(ActiveRecord::StaleObjectError)
+ end
+ end
+
describe '#mergeable?' do
let(:project) { create(:project) }
@@ -2411,6 +2456,13 @@ describe MergeRequest do
describe "#diff_refs" do
context "with diffs" do
subject { create(:merge_request, :with_diffs) }
+ let(:expected_diff_refs) do
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: subject.merge_request_diff.base_commit_sha,
+ start_sha: subject.merge_request_diff.start_commit_sha,
+ head_sha: subject.merge_request_diff.head_commit_sha
+ )
+ end
it "does not touch the repository" do
subject # Instantiate the object
@@ -2421,14 +2473,18 @@ describe MergeRequest do
end
it "returns expected diff_refs" do
- expected_diff_refs = Gitlab::Diff::DiffRefs.new(
- base_sha: subject.merge_request_diff.base_commit_sha,
- start_sha: subject.merge_request_diff.start_commit_sha,
- head_sha: subject.merge_request_diff.head_commit_sha
- )
-
expect(subject.diff_refs).to eq(expected_diff_refs)
end
+
+ context 'when importing' do
+ before do
+ subject.importing = true
+ end
+
+ it "returns MR diff_refs" do
+ expect(subject.diff_refs).to eq(expected_diff_refs)
+ end
+ end
end
end
@@ -2946,39 +3002,24 @@ describe MergeRequest do
end
describe '#rebase_in_progress?' do
- shared_examples 'checking whether a rebase is in progress' do
- let(:repo_path) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- subject.source_project.repository.path
- end
- end
- let(:rebase_path) { File.join(repo_path, "gitlab-worktree", "rebase-#{subject.id}") }
-
- before do
- system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} worktree add --detach #{rebase_path} master))
- end
+ where(:rebase_jid, :jid_valid, :result) do
+ 'foo' | true | true
+ 'foo' | false | false
+ '' | true | false
+ nil | true | false
+ end
- it 'returns true when there is a current rebase directory' do
- expect(subject.rebase_in_progress?).to be_truthy
- end
+ with_them do
+ let(:merge_request) { create(:merge_request) }
- it 'returns false when there is no rebase directory' do
- FileUtils.rm_rf(rebase_path)
+ subject { merge_request.rebase_in_progress? }
- expect(subject.rebase_in_progress?).to be_falsey
- end
+ it do
+ allow(Gitlab::SidekiqStatus).to receive(:running?).with(rebase_jid) { jid_valid }
- it 'returns false when the rebase directory has expired' do
- time = 20.minutes.ago.to_time
- File.utime(time, time, rebase_path)
+ merge_request.rebase_jid = rebase_jid
- expect(subject.rebase_in_progress?).to be_falsey
- end
-
- it 'returns false when the source project has been removed' do
- allow(subject).to receive(:source_project).and_return(nil)
-
- expect(subject.rebase_in_progress?).to be_falsey
+ is_expected.to eq(result)
end
end
end
@@ -3153,4 +3194,34 @@ describe MergeRequest do
it { is_expected.to be_truthy }
end
end
+
+ describe '#cleanup_refs' do
+ subject { merge_request.cleanup_refs(only: only) }
+
+ let(:merge_request) { build(:merge_request) }
+
+ context 'when removing all refs' do
+ let(:only) { :all }
+
+ it 'deletes all refs from the target project' do
+ expect(merge_request.target_project.repository)
+ .to receive(:delete_refs)
+ .with(merge_request.ref_path, merge_request.merge_ref_path, merge_request.train_ref_path)
+
+ subject
+ end
+ end
+
+ context 'when removing only train ref' do
+ let(:only) { :train }
+
+ it 'deletes train ref from the target project' do
+ expect(merge_request.target_project.repository)
+ .to receive(:delete_refs)
+ .with(merge_request.train_ref_path)
+
+ subject
+ end
+ end
+ end
end
diff --git a/spec/models/namespace/aggregation_schedule_spec.rb b/spec/models/namespace/aggregation_schedule_spec.rb
index 5ba7547ff4d..38bf8089411 100644
--- a/spec/models/namespace/aggregation_schedule_spec.rb
+++ b/spec/models/namespace/aggregation_schedule_spec.rb
@@ -2,6 +2,72 @@
require 'spec_helper'
-RSpec.describe Namespace::AggregationSchedule, type: :model do
+RSpec.describe Namespace::AggregationSchedule, :clean_gitlab_redis_shared_state, type: :model do
+ include ExclusiveLeaseHelpers
+
it { is_expected.to belong_to :namespace }
+
+ describe '#schedule_root_storage_statistics' do
+ let(:namespace) { create(:namespace) }
+ let(:aggregation_schedule) { namespace.build_aggregation_schedule }
+ let(:lease_key) { "namespace:namespaces_root_statistics:#{namespace.id}" }
+
+ context "when we can't obtain the lease" do
+ it 'does not schedule the workers' do
+ stub_exclusive_lease_taken(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT)
+
+ expect(Namespaces::RootStatisticsWorker)
+ .not_to receive(:perform_async)
+
+ expect(Namespaces::RootStatisticsWorker)
+ .not_to receive(:perform_in)
+
+ aggregation_schedule.save!
+ end
+ end
+
+ context 'when we can obtain the lease' do
+ it 'schedules a root storage statistics after create' do
+ stub_exclusive_lease(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT)
+
+ expect(Namespaces::RootStatisticsWorker)
+ .to receive(:perform_async).once
+
+ expect(Namespaces::RootStatisticsWorker)
+ .to receive(:perform_in).once
+ .with(described_class::DEFAULT_LEASE_TIMEOUT, aggregation_schedule.namespace_id)
+
+ aggregation_schedule.save!
+ end
+
+ it 'does not release the lease' do
+ stub_exclusive_lease(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT)
+
+ aggregation_schedule.save!
+
+ exclusive_lease = aggregation_schedule.exclusive_lease
+ expect(exclusive_lease.exists?).to be_truthy
+ end
+
+ it 'only executes the workers once' do
+ # Avoid automatic deletion of Namespace::AggregationSchedule
+ # for testing purposes.
+ expect(Namespaces::RootStatisticsWorker)
+ .to receive(:perform_async).once
+ .and_return(nil)
+
+ expect(Namespaces::RootStatisticsWorker)
+ .to receive(:perform_in).once
+ .with(described_class::DEFAULT_LEASE_TIMEOUT, aggregation_schedule.namespace_id)
+ .and_return(nil)
+
+ # Scheduling workers for the first time
+ aggregation_schedule.schedule_root_storage_statistics
+
+ # Executing again, this time workers should not be scheduled
+ # due to the lease not been released.
+ aggregation_schedule.schedule_root_storage_statistics
+ end
+ end
+ end
end
diff --git a/spec/models/namespace/root_storage_statistics_spec.rb b/spec/models/namespace/root_storage_statistics_spec.rb
index f6fb5af5aae..9e12831a704 100644
--- a/spec/models/namespace/root_storage_statistics_spec.rb
+++ b/spec/models/namespace/root_storage_statistics_spec.rb
@@ -7,4 +7,82 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
it { is_expected.to have_one(:route).through(:namespace) }
it { is_expected.to delegate_method(:all_projects).to(:namespace) }
+
+ context 'scopes' do
+ describe '.for_namespace_ids' do
+ it 'returns only requested namespaces' do
+ stats = create_list(:namespace_root_storage_statistics, 3)
+ namespace_ids = stats[0..1].map { |s| s.namespace_id }
+
+ requested_stats = described_class.for_namespace_ids(namespace_ids).pluck(:namespace_id)
+
+ expect(requested_stats).to eq(namespace_ids)
+ end
+ end
+ end
+
+ describe '#recalculate!' do
+ let(:namespace) { create(:group) }
+ let(:root_storage_statistics) { create(:namespace_root_storage_statistics, namespace: namespace) }
+
+ let(:project1) { create(:project, namespace: namespace) }
+ let(:project2) { create(:project, namespace: namespace) }
+
+ let!(:stat1) { create(:project_statistics, project: project1, with_data: true, size_multiplier: 100) }
+ let!(:stat2) { create(:project_statistics, project: project2, with_data: true, size_multiplier: 200) }
+
+ shared_examples 'data refresh' do
+ it 'aggregates project statistics' do
+ root_storage_statistics.recalculate!
+
+ root_storage_statistics.reload
+
+ total_repository_size = stat1.repository_size + stat2.repository_size
+ total_wiki_size = stat1.wiki_size + stat2.wiki_size
+ total_lfs_objects_size = stat1.lfs_objects_size + stat2.lfs_objects_size
+ total_build_artifacts_size = stat1.build_artifacts_size + stat2.build_artifacts_size
+ total_packages_size = stat1.packages_size + stat2.packages_size
+ total_storage_size = stat1.storage_size + stat2.storage_size
+
+ expect(root_storage_statistics.repository_size).to eq(total_repository_size)
+ expect(root_storage_statistics.wiki_size).to eq(total_wiki_size)
+ expect(root_storage_statistics.lfs_objects_size).to eq(total_lfs_objects_size)
+ expect(root_storage_statistics.build_artifacts_size).to eq(total_build_artifacts_size)
+ expect(root_storage_statistics.packages_size).to eq(total_packages_size)
+ expect(root_storage_statistics.storage_size).to eq(total_storage_size)
+ end
+
+ it 'works when there are no projects' do
+ Project.delete_all
+
+ root_storage_statistics.recalculate!
+
+ root_storage_statistics.reload
+ expect(root_storage_statistics.repository_size).to eq(0)
+ expect(root_storage_statistics.wiki_size).to eq(0)
+ expect(root_storage_statistics.lfs_objects_size).to eq(0)
+ expect(root_storage_statistics.build_artifacts_size).to eq(0)
+ expect(root_storage_statistics.packages_size).to eq(0)
+ expect(root_storage_statistics.storage_size).to eq(0)
+ end
+ end
+
+ it_behaves_like 'data refresh'
+
+ context 'with subgroups' do
+ let(:subgroup1) { create(:group, parent: namespace)}
+ let(:subgroup2) { create(:group, parent: subgroup1)}
+
+ let(:project1) { create(:project, namespace: subgroup1) }
+ let(:project2) { create(:project, namespace: subgroup2) }
+
+ it_behaves_like 'data refresh'
+ end
+
+ context 'with a personal namespace' do
+ let(:namespace) { create(:user).namespace }
+
+ it_behaves_like 'data refresh'
+ end
+ end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 30e49cf204f..972f26ac745 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -191,7 +191,7 @@ describe Namespace do
end
end
- describe '#ancestors_upto', :nested_groups do
+ describe '#ancestors_upto' do
let(:parent) { create(:group) }
let(:child) { create(:group, parent: parent) }
let(:child2) { create(:group, parent: child) }
@@ -271,7 +271,7 @@ describe Namespace do
end
end
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let(:parent) { create(:group, name: 'parent', path: 'parent') }
let(:new_parent) { create(:group, name: 'new_parent', path: 'new_parent') }
let(:child) { create(:group, name: 'child', path: 'child', parent: parent) }
@@ -475,7 +475,7 @@ describe Namespace do
end
end
- describe '#self_and_hierarchy', :nested_groups do
+ describe '#self_and_hierarchy' do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }
let!(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -490,7 +490,7 @@ describe Namespace do
end
end
- describe '#ancestors', :nested_groups do
+ describe '#ancestors' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -504,7 +504,7 @@ describe Namespace do
end
end
- describe '#self_and_ancestors', :nested_groups do
+ describe '#self_and_ancestors' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -518,7 +518,7 @@ describe Namespace do
end
end
- describe '#descendants', :nested_groups do
+ describe '#descendants' do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }
let!(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -534,7 +534,7 @@ describe Namespace do
end
end
- describe '#self_and_descendants', :nested_groups do
+ describe '#self_and_descendants' do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }
let!(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -550,7 +550,7 @@ describe Namespace do
end
end
- describe '#users_with_descendants', :nested_groups do
+ describe '#users_with_descendants' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
@@ -597,7 +597,7 @@ describe Namespace do
it { expect(group.all_pipelines.to_a).to match_array([pipeline1, pipeline2]) }
end
- describe '#share_with_group_lock with subgroups', :nested_groups do
+ describe '#share_with_group_lock with subgroups' do
context 'when creating a subgroup' do
let(:subgroup) { create(:group, parent: root_group )}
@@ -738,7 +738,7 @@ describe Namespace do
end
describe '#root_ancestor' do
- it 'returns the top most ancestor', :nested_groups do
+ it 'returns the top most ancestor' do
root_group = create(:group)
nested_group = create(:group, parent: root_group)
deep_nested_group = create(:group, parent: nested_group)
@@ -837,4 +837,80 @@ describe Namespace do
it { is_expected.to be_falsy }
end
end
+
+ describe '#aggregation_scheduled?' do
+ let(:namespace) { create(:namespace) }
+
+ subject { namespace.aggregation_scheduled? }
+
+ context 'with an aggregation scheduled association' do
+ let(:namespace) { create(:namespace, :with_aggregation_schedule) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'without an aggregation scheduled association' do
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#emails_disabled?' do
+ context 'when not a subgroup' do
+ it 'returns false' do
+ group = create(:group, emails_disabled: false)
+
+ expect(group.emails_disabled?).to be_falsey
+ end
+
+ it 'returns true' do
+ group = create(:group, emails_disabled: true)
+
+ expect(group.emails_disabled?).to be_truthy
+ end
+ end
+
+ context 'when a subgroup' do
+ let(:grandparent) { create(:group) }
+ let(:parent) { create(:group, parent: grandparent) }
+ let(:group) { create(:group, parent: parent) }
+
+ it 'returns false' do
+ expect(group.emails_disabled?).to be_falsey
+ end
+
+ context 'when ancestor emails are disabled' do
+ it 'returns true' do
+ grandparent.update_attribute(:emails_disabled, true)
+
+ expect(group.emails_disabled?).to be_truthy
+ end
+ end
+ end
+
+ context 'when :emails_disabled feature flag is off' do
+ before do
+ stub_feature_flags(emails_disabled: false)
+ end
+
+ context 'when not a subgroup' do
+ it 'returns false' do
+ group = create(:group, emails_disabled: true)
+
+ expect(group.emails_disabled?).to be_falsey
+ end
+ end
+
+ context 'when a subgroup and ancestor emails are disabled' do
+ let(:grandparent) { create(:group) }
+ let(:parent) { create(:group, parent: grandparent) }
+ let(:group) { create(:group, parent: parent) }
+
+ it 'returns false' do
+ grandparent.update_attribute(:emails_disabled, true)
+
+ expect(group.emails_disabled?).to be_falsey
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 7a1ab20186a..bfd0e5f0558 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -177,6 +177,7 @@ describe Note do
pipeline: :note,
cache_key: [note1, "note"],
project: note1.project,
+ rendered: note1.note_html,
author: note1.author
}
}]).and_call_original
@@ -189,6 +190,7 @@ describe Note do
pipeline: :note,
cache_key: [note2, "note"],
project: note2.project,
+ rendered: note2.note_html,
author: note2.author
}
}]).and_call_original
@@ -911,6 +913,22 @@ describe Note do
end
end
+ describe '#special_role=' do
+ let(:role) { Note::SpecialRole::FIRST_TIME_CONTRIBUTOR }
+
+ it 'assigns role' do
+ subject.special_role = role
+
+ expect(subject.special_role).to eq(role)
+ end
+
+ it 'does not assign unknown role' do
+ expect { subject.special_role = :bogus }.to raise_error(/Role is undefined/)
+
+ expect(subject.special_role).to be_nil
+ end
+ end
+
describe '#parent' do
it 'returns project for project notes' do
project = create(:project)
diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb
index 20278d81f6d..2ba53818e54 100644
--- a/spec/models/notification_recipient_spec.rb
+++ b/spec/models/notification_recipient_spec.rb
@@ -9,6 +9,38 @@ describe NotificationRecipient do
subject(:recipient) { described_class.new(user, :watch, target: target, project: project) }
+ describe '#notifiable?' do
+ let(:recipient) { described_class.new(user, :mention, target: target, project: project) }
+
+ context 'when emails are disabled' do
+ it 'returns false if group disabled' do
+ expect(project.namespace).to receive(:emails_disabled?).and_return(true)
+ expect(recipient).to receive(:emails_disabled?).and_call_original
+ expect(recipient.notifiable?).to eq false
+ end
+
+ it 'returns false if project disabled' do
+ expect(project).to receive(:emails_disabled?).and_return(true)
+ expect(recipient).to receive(:emails_disabled?).and_call_original
+ expect(recipient.notifiable?).to eq false
+ end
+ end
+
+ context 'when emails are enabled' do
+ it 'returns true if group enabled' do
+ expect(project.namespace).to receive(:emails_disabled?).and_return(false)
+ expect(recipient).to receive(:emails_disabled?).and_call_original
+ expect(recipient.notifiable?).to eq true
+ end
+
+ it 'returns true if project enabled' do
+ expect(project).to receive(:emails_disabled?).and_return(false)
+ expect(recipient).to receive(:emails_disabled?).and_call_original
+ expect(recipient.notifiable?).to eq true
+ end
+ end
+ end
+
describe '#has_access?' do
before do
allow(user).to receive(:can?).and_call_original
@@ -49,7 +81,7 @@ describe NotificationRecipient do
end
context '#notification_setting' do
- context 'for child groups', :nested_groups do
+ context 'for child groups' do
let!(:moved_group) { create(:group) }
let(:group) { create(:group) }
let(:sub_group_1) { create(:group, parent: group) }
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index 661957cf08b..519c519fbcf 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -53,24 +53,33 @@ describe PagesDomain do
end
let(:pages_domain) do
- build(:pages_domain, certificate: certificate, key: key).tap do |pd|
+ build(:pages_domain, certificate: certificate, key: key,
+ auto_ssl_enabled: auto_ssl_enabled).tap do |pd|
allow(pd).to receive(:project).and_return(project)
pd.valid?
end
end
- where(:pages_https_only, :certificate, :key, :errors_on) do
+ where(:pages_https_only, :certificate, :key, :auto_ssl_enabled, :errors_on) do
attributes = attributes_for(:pages_domain)
cert, key = attributes.fetch_values(:certificate, :key)
- true | nil | nil | %i(certificate key)
- true | cert | nil | %i(key)
- true | nil | key | %i(certificate key)
- true | cert | key | []
- false | nil | nil | []
- false | cert | nil | %i(key)
- false | nil | key | %i(key)
- false | cert | key | []
+ true | nil | nil | false | %i(certificate key)
+ true | nil | nil | true | []
+ true | cert | nil | false | %i(key)
+ true | cert | nil | true | %i(key)
+ true | nil | key | false | %i(certificate key)
+ true | nil | key | true | %i(key)
+ true | cert | key | false | []
+ true | cert | key | true | []
+ false | nil | nil | false | []
+ false | nil | nil | true | []
+ false | cert | nil | false | %i(key)
+ false | cert | nil | true | %i(key)
+ false | nil | key | false | %i(key)
+ false | nil | key | true | %i(key)
+ false | cert | key | false | []
+ false | cert | key | true | []
end
with_them do
@@ -118,6 +127,30 @@ describe PagesDomain do
it { is_expected.not_to be_valid }
end
+
+ context 'when certificate is expired' do
+ let(:domain) do
+ build(:pages_domain, :with_trusted_expired_chain)
+ end
+
+ context 'when certificate is being changed' do
+ it "adds error to certificate" do
+ domain.valid?
+
+ expect(domain.errors.keys).to contain_exactly(:key, :certificate)
+ end
+ end
+
+ context 'when certificate is already saved' do
+ it "doesn't add error to certificate" do
+ domain.save(validate: false)
+
+ domain.valid?
+
+ expect(domain.errors.keys).to contain_exactly(:key)
+ end
+ end
+ end
end
describe 'validations' do
diff --git a/spec/models/postgresql/replication_slot_spec.rb b/spec/models/postgresql/replication_slot_spec.rb
index 95ae204a8a8..d435fccc09a 100644
--- a/spec/models/postgresql/replication_slot_spec.rb
+++ b/spec/models/postgresql/replication_slot_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Postgresql::ReplicationSlot, :postgresql do
+describe Postgresql::ReplicationSlot do
describe '.in_use?' do
it 'returns true when replication slots are present' do
expect(described_class).to receive(:exists?).and_return(true)
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index 7bdd2367a68..da9e56ef897 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -15,7 +15,7 @@ describe ProjectAutoDevops do
it { is_expected.to respond_to(:updated_at) }
describe '#predefined_variables' do
- let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: domain) }
+ let(:auto_devops) { build_stubbed(:project_auto_devops, project: project) }
context 'when deploy_strategy is manual' do
let(:auto_devops) { build_stubbed(:project_auto_devops, :manual_deployment, project: project) }
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index 50c9d5968ac..dc7a8433064 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -8,11 +8,7 @@ describe ProjectFeature do
describe '.quoted_access_level_column' do
it 'returns the table name and quoted column name for a feature' do
- expected = if Gitlab::Database.postgresql?
- '"project_features"."issues_access_level"'
- else
- '`project_features`.`issues_access_level`'
- end
+ expected = '"project_features"."issues_access_level"'
expect(described_class.quoted_access_level_column(:issues)).to eq(expected)
end
@@ -150,4 +146,32 @@ describe ProjectFeature do
end
end
end
+
+ describe 'default pages access level' do
+ subject { project.project_feature.pages_access_level }
+
+ before do
+ # project factory overrides all values in project_feature after creation
+ project.project_feature.destroy!
+ project.build_project_feature.save!
+ end
+
+ context 'when new project is private' do
+ let(:project) { create(:project, :private) }
+
+ it { is_expected.to eq(ProjectFeature::PRIVATE) }
+ end
+
+ context 'when new project is internal' do
+ let(:project) { create(:project, :internal) }
+
+ it { is_expected.to eq(ProjectFeature::PRIVATE) }
+ end
+
+ context 'when new project is public' do
+ let(:project) { create(:project, :public) }
+
+ it { is_expected.to eq(ProjectFeature::ENABLED) }
+ end
+ end
end
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index dad5506900b..cd997224122 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -25,7 +25,7 @@ describe ProjectGroupLink do
expect(project_group_link).not_to be_valid
end
- it "doesn't allow a project to be shared with an ancestor of the group it is in", :nested_groups do
+ it "doesn't allow a project to be shared with an ancestor of the group it is in" do
project_group_link.group = parent_group
expect(project_group_link).not_to be_valid
diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb
index 6818db48fee..74c85a13c88 100644
--- a/spec/models/project_services/bugzilla_service_spec.rb
+++ b/spec/models/project_services/bugzilla_service_spec.rb
@@ -32,4 +32,51 @@ describe BugzillaService do
it { is_expected.not_to validate_presence_of(:new_issue_url) }
end
end
+
+ context 'overriding properties' do
+ let(:default_title) { 'JIRA' }
+ let(:default_description) { 'JiraService|Jira issue tracker' }
+ let(:url) { 'http://bugzilla.example.com' }
+ let(:access_params) do
+ { project_url: url, issues_url: url, new_issue_url: url }
+ end
+
+ # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
+ context 'when data are stored in properties' do
+ let(:properties) { access_params.merge(title: title, description: description) }
+ let(:service) do
+ create(:bugzilla_service, :without_properties_callback, properties: properties)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when data are stored in separated fields' do
+ let(:service) do
+ create(:bugzilla_service, title: title, description: description, properties: access_params)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when data are stored in both properties and separated fields' do
+ let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
+ let(:service) do
+ create(:bugzilla_service, :without_properties_callback, title: title, description: description, properties: properties)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when no title & description are set' do
+ let(:service) do
+ create(:bugzilla_service, properties: access_params)
+ end
+
+ it 'returns default values' do
+ expect(service.title).to eq('Bugzilla')
+ expect(service.description).to eq('Bugzilla issue tracker')
+ end
+ end
+ end
end
diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb
index 8f9fa310ad4..cf7c7bf7e61 100644
--- a/spec/models/project_services/chat_message/pipeline_message_spec.rb
+++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb
@@ -1,12 +1,9 @@
# frozen_string_literal: true
-
require 'spec_helper'
describe ChatMessage::PipelineMessage do
subject { described_class.new(args) }
- let(:user) { { name: "The Hacker", username: 'hacker' } }
- let(:duration) { 7210 }
let(:args) do
{
object_attributes: {
@@ -14,122 +11,437 @@ describe ChatMessage::PipelineMessage do
sha: '97de212e80737a608d939f648d959671fb0a0142',
tag: false,
ref: 'develop',
- status: status,
- duration: duration
+ status: 'success',
+ detailed_status: nil,
+ duration: 7210,
+ finished_at: "2019-05-27 11:56:36 -0300"
},
project: {
- path_with_namespace: 'project_name',
- web_url: 'http://example.gitlab.com'
+ id: 234,
+ name: "project_name",
+ path_with_namespace: 'group/project_name',
+ web_url: 'http://example.gitlab.com',
+ avatar_url: 'http://example.com/project_avatar'
+ },
+ user: {
+ id: 345,
+ name: "The Hacker",
+ username: "hacker",
+ email: "hacker@example.gitlab.com",
+ avatar_url: "http://example.com/avatar"
+ },
+ commit: {
+ id: "abcdef"
},
- user: user
+ builds: nil,
+ markdown: false
}
end
- let(:combined_name) { "The Hacker (hacker)" }
- context 'without markdown' do
- context 'pipeline succeeded' do
- let(:status) { 'success' }
- let(:color) { 'good' }
- let(:message) { build_message('passed', combined_name) }
+ let(:has_yaml_errors) { false }
+
+ before do
+ test_commit = double("A test commit", committer: args[:user], title: "A test commit message")
+ test_project = double("A test project", commit_by: test_commit, name: args[:project][:name], web_url: args[:project][:web_url])
+ allow(test_project).to receive(:avatar_url).with(no_args).and_return("/avatar")
+ allow(test_project).to receive(:avatar_url).with(only_path: false).and_return(args[:project][:avatar_url])
+ allow(Project).to receive(:find) { test_project }
+
+ test_pipeline = double("A test pipeline", has_yaml_errors?: has_yaml_errors,
+ yaml_errors: "yaml error description here")
+ allow(Ci::Pipeline).to receive(:find) { test_pipeline }
+
+ allow(Gitlab::UrlBuilder).to receive(:build).with(test_commit).and_return("http://example.com/commit")
+ allow(Gitlab::UrlBuilder).to receive(:build).with(args[:user]).and_return("http://example.gitlab.com/hacker")
+ end
+
+ context 'when the fancy_pipeline_slack_notifications feature flag is disabled' do
+ before do
+ stub_feature_flags(fancy_pipeline_slack_notifications: false)
+ end
+
+ it 'returns an empty pretext' do
+ expect(subject.pretext).to be_empty
+ end
+
+ it "returns the pipeline summary in the activity's title" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) passed"
+ )
+ end
- it 'returns a message with information about succeeded build' do
- expect(subject.pretext).to be_empty
- expect(subject.fallback).to eq(message)
- expect(subject.attachments).to eq([text: message, color: color])
+ context "when the pipeline failed" do
+ before do
+ args[:object_attributes][:status] = 'failed'
+ end
+
+ it "returns the summary with a 'failed' status" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) failed"
+ )
end
end
- context 'pipeline failed' do
- let(:status) { 'failed' }
- let(:color) { 'danger' }
- let(:message) { build_message(status, combined_name) }
+ context 'when no user is provided because the pipeline was triggered by the API' do
+ before do
+ args[:user] = nil
+ end
- it 'returns a message with information about failed build' do
- expect(subject.pretext).to be_empty
- expect(subject.fallback).to eq(message)
- expect(subject.attachments).to eq([text: message, color: color])
+ it "returns the summary with 'API' as the username" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by API passed"
+ )
end
+ end
- context 'when triggered by API therefore lacking user' do
- let(:user) { nil }
- let(:message) { build_message(status, 'API') }
+ it "returns a link to the project in the activity's subtitle" do
+ expect(subject.activity[:subtitle]).to eq("in [project_name](http://example.gitlab.com)")
+ end
- it 'returns a message stating it is by API' do
- expect(subject.pretext).to be_empty
- expect(subject.fallback).to eq(message)
- expect(subject.attachments).to eq([text: message, color: color])
- end
+ it "returns the build duration in the activity's text property" do
+ expect(subject.activity[:text]).to eq("in 02:00:10")
+ end
+
+ it "returns the user's avatar image URL in the activity's image property" do
+ expect(subject.activity[:image]).to eq("http://example.com/avatar")
+ end
+
+ context 'when the user does not have an avatar' do
+ before do
+ args[:user][:avatar_url] = nil
+ end
+
+ it "returns an empty string in the activity's image property" do
+ expect(subject.activity[:image]).to be_empty
+ end
+ end
+
+ it "returns the pipeline summary as the attachment's text property" do
+ expect(subject.attachments.first[:text]).to eq(
+ "<http://example.gitlab.com|project_name>:" \
+ " Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
+ " of branch <http://example.gitlab.com/commits/develop|develop>" \
+ " by The Hacker (hacker) passed in 02:00:10"
+ )
+ end
+
+ it "returns 'good' as the attachment's color property" do
+ expect(subject.attachments.first[:color]).to eq('good')
+ end
+
+ context "when the pipeline failed" do
+ before do
+ args[:object_attributes][:status] = 'failed'
+ end
+
+ it "returns 'danger' as the attachment's color property" do
+ expect(subject.attachments.first[:color]).to eq('danger')
end
end
- def build_message(status_text = status, name = user[:name])
- "<http://example.gitlab.com|project_name>:" \
- " Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
- " of branch <http://example.gitlab.com/commits/develop|develop>" \
- " by #{name} #{status_text} in 02:00:10"
+ context 'when rendering markdown' do
+ before do
+ args[:markdown] = true
+ end
+
+ it 'returns the pipeline summary as the attachments in markdown format' do
+ expect(subject.attachments).to eq(
+ "[project_name](http://example.gitlab.com):" \
+ " Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) passed in 02:00:10"
+ )
+ end
end
end
- context 'with markdown' do
+ context 'when the fancy_pipeline_slack_notifications feature flag is enabled' do
before do
- args[:markdown] = true
- end
-
- context 'pipeline succeeded' do
- let(:status) { 'success' }
- let(:color) { 'good' }
- let(:message) { build_markdown_message('passed', combined_name) }
-
- it 'returns a message with information about succeeded build' do
- expect(subject.pretext).to be_empty
- expect(subject.attachments).to eq(message)
- expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by The Hacker (hacker) passed',
- subtitle: 'in [project_name](http://example.gitlab.com)',
- text: 'in 02:00:10',
- image: ''
- })
+ stub_feature_flags(fancy_pipeline_slack_notifications: true)
+ end
+
+ it 'returns an empty pretext' do
+ expect(subject.pretext).to be_empty
+ end
+
+ it "returns the pipeline summary in the activity's title" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) has passed"
+ )
+ end
+
+ context "when the pipeline failed" do
+ before do
+ args[:object_attributes][:status] = 'failed'
+ end
+
+ it "returns the summary with a 'failed' status" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) has failed"
+ )
+ end
+ end
+
+ context "when the pipeline passed with warnings" do
+ before do
+ args[:object_attributes][:detailed_status] = 'passed with warnings'
+ end
+
+ it "returns the summary with a 'passed with warnings' status" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) has passed with warnings"
+ )
+ end
+ end
+
+ context 'when no user is provided because the pipeline was triggered by the API' do
+ before do
+ args[:user] = nil
+ end
+
+ it "returns the summary with 'API' as the username" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by API has passed"
+ )
+ end
+ end
+
+ it "returns a link to the project in the activity's subtitle" do
+ expect(subject.activity[:subtitle]).to eq("in [project_name](http://example.gitlab.com)")
+ end
+
+ it "returns the build duration in the activity's text property" do
+ expect(subject.activity[:text]).to eq("in 02:00:10")
+ end
+
+ it "returns the user's avatar image URL in the activity's image property" do
+ expect(subject.activity[:image]).to eq("http://example.com/avatar")
+ end
+
+ context 'when the user does not have an avatar' do
+ before do
+ args[:user][:avatar_url] = nil
+ end
+
+ it "returns an empty string in the activity's image property" do
+ expect(subject.activity[:image]).to be_empty
+ end
+ end
+
+ it "returns the pipeline summary as the attachment's fallback property" do
+ expect(subject.attachments.first[:fallback]).to eq(
+ "<http://example.gitlab.com|project_name>:" \
+ " Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
+ " of branch <http://example.gitlab.com/commits/develop|develop>" \
+ " by The Hacker (hacker) has passed in 02:00:10"
+ )
+ end
+
+ it "returns 'good' as the attachment's color property" do
+ expect(subject.attachments.first[:color]).to eq('good')
+ end
+
+ context "when the pipeline failed" do
+ before do
+ args[:object_attributes][:status] = 'failed'
+ end
+
+ it "returns 'danger' as the attachment's color property" do
+ expect(subject.attachments.first[:color]).to eq('danger')
+ end
+ end
+
+ context "when the pipeline passed with warnings" do
+ before do
+ args[:object_attributes][:detailed_status] = 'passed with warnings'
+ end
+
+ it "returns 'warning' as the attachment's color property" do
+ expect(subject.attachments.first[:color]).to eq('warning')
end
end
- context 'pipeline failed' do
- let(:status) { 'failed' }
- let(:color) { 'danger' }
- let(:message) { build_markdown_message(status, combined_name) }
+ it "returns the committer's name and username as the attachment's author_name property" do
+ expect(subject.attachments.first[:author_name]).to eq('The Hacker (hacker)')
+ end
+
+ it "returns the committer's avatar URL as the attachment's author_icon property" do
+ expect(subject.attachments.first[:author_icon]).to eq('http://example.com/avatar')
+ end
+
+ it "returns the committer's GitLab profile URL as the attachment's author_link property" do
+ expect(subject.attachments.first[:author_link]).to eq('http://example.gitlab.com/hacker')
+ end
+
+ context 'when no user is provided because the pipeline was triggered by the API' do
+ before do
+ args[:user] = nil
+ end
+
+ it "returns the committer's name and username as the attachment's author_name property" do
+ expect(subject.attachments.first[:author_name]).to eq('API')
+ end
+
+ it "returns nil as the attachment's author_icon property" do
+ expect(subject.attachments.first[:author_icon]).to be_nil
+ end
+
+ it "returns nil as the attachment's author_link property" do
+ expect(subject.attachments.first[:author_link]).to be_nil
+ end
+ end
+
+ it "returns the pipeline ID, status, and duration as the attachment's title property" do
+ expect(subject.attachments.first[:title]).to eq("Pipeline #123 has passed in 02:00:10")
+ end
+
+ it "returns the pipeline URL as the attachment's title_link property" do
+ expect(subject.attachments.first[:title_link]).to eq("http://example.gitlab.com/pipelines/123")
+ end
+
+ it "returns two attachment fields" do
+ expect(subject.attachments.first[:fields].count).to eq(2)
+ end
+
+ it "returns the commit message as the attachment's second field property" do
+ expect(subject.attachments.first[:fields][0]).to eq({
+ title: "Branch",
+ value: "<http://example.gitlab.com/commits/develop|develop>",
+ short: true
+ })
+ end
+
+ it "returns the ref name and link as the attachment's second field property" do
+ expect(subject.attachments.first[:fields][1]).to eq({
+ title: "Commit",
+ value: "<http://example.com/commit|A test commit message>",
+ short: true
+ })
+ end
+
+ context "when a job in the pipeline fails" do
+ before do
+ args[:builds] = [
+ { id: 1, name: "rspec", status: "failed", stage: "test" },
+ { id: 2, name: "karma", status: "success", stage: "test" }
+ ]
+ end
+
+ it "returns four attachment fields" do
+ expect(subject.attachments.first[:fields].count).to eq(4)
+ end
- it 'returns a message with information about failed build' do
- expect(subject.pretext).to be_empty
- expect(subject.attachments).to eq(message)
- expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by The Hacker (hacker) failed',
- subtitle: 'in [project_name](http://example.gitlab.com)',
- text: 'in 02:00:10',
- image: ''
+ it "returns the stage name and link to the 'Failed jobs' tab on the pipeline's page as the attachment's third field property" do
+ expect(subject.attachments.first[:fields][2]).to eq({
+ title: "Failed stage",
+ value: "<http://example.gitlab.com/pipelines/123/failures|test>",
+ short: true
})
end
- context 'when triggered by API therefore lacking user' do
- let(:user) { nil }
- let(:message) { build_markdown_message(status, 'API') }
+ it "returns the job name and link as the attachment's fourth field property" do
+ expect(subject.attachments.first[:fields][3]).to eq({
+ title: "Failed job",
+ value: "<http://example.gitlab.com/-/jobs/1|rspec>",
+ short: true
+ })
+ end
+ end
+
+ context "when lots of jobs across multiple stages fail" do
+ before do
+ args[:builds] = (1..25).map do |i|
+ { id: i, name: "job-#{i}", status: "failed", stage: "stage-" + ((i % 3) + 1).to_s }
+ end
+ end
+
+ it "returns the stage names and links to the 'Failed jobs' tab on the pipeline's page as the attachment's third field property" do
+ expect(subject.attachments.first[:fields][2]).to eq({
+ title: "Failed stages",
+ value: "<http://example.gitlab.com/pipelines/123/failures|stage-2>, <http://example.gitlab.com/pipelines/123/failures|stage-1>, <http://example.gitlab.com/pipelines/123/failures|stage-3>",
+ short: true
+ })
+ end
- it 'returns a message stating it is by API' do
- expect(subject.pretext).to be_empty
- expect(subject.attachments).to eq(message)
- expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by API failed',
- subtitle: 'in [project_name](http://example.gitlab.com)',
- text: 'in 02:00:10',
- image: ''
- })
+ it "returns the job names and links as the attachment's fourth field property" do
+ expected_jobs = 25.downto(16).map do |i|
+ "<http://example.gitlab.com/-/jobs/#{i}|job-#{i}>"
end
+
+ expected_jobs << "and <http://example.gitlab.com/pipelines/123/failures|15 more>"
+
+ expect(subject.attachments.first[:fields][3]).to eq({
+ title: "Failed jobs",
+ value: expected_jobs.join(", "),
+ short: true
+ })
end
end
- def build_markdown_message(status_text = status, name = user[:name])
- "[project_name](http://example.gitlab.com):" \
- " Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
- " of branch [develop](http://example.gitlab.com/commits/develop)" \
- " by #{name} #{status_text} in 02:00:10"
+ context "when the CI config file contains a YAML error" do
+ let(:has_yaml_errors) { true }
+
+ it "returns three attachment fields" do
+ expect(subject.attachments.first[:fields].count).to eq(3)
+ end
+
+ it "returns the YAML error deatils as the attachment's third field property" do
+ expect(subject.attachments.first[:fields][2]).to eq({
+ title: "Invalid CI config YAML file",
+ value: "yaml error description here",
+ short: false
+ })
+ end
+ end
+
+ it "returns the stage name and link as the attachment's second field property" do
+ expect(subject.attachments.first[:fields][1]).to eq({
+ title: "Commit",
+ value: "<http://example.com/commit|A test commit message>",
+ short: true
+ })
+ end
+
+ it "returns the project's name as the attachment's footer property" do
+ expect(subject.attachments.first[:footer]).to eq("project_name")
+ end
+
+ it "returns the project's avatar URL as the attachment's footer_icon property" do
+ expect(subject.attachments.first[:footer_icon]).to eq("http://example.com/project_avatar")
+ end
+
+ it "returns the pipeline's timestamp as the attachment's ts property" do
+ expected_ts = Time.parse(args[:object_attributes][:finished_at]).to_i
+ expect(subject.attachments.first[:ts]).to eq(expected_ts)
+ end
+
+ context 'when rendering markdown' do
+ before do
+ args[:markdown] = true
+ end
+
+ it 'returns the pipeline summary as the attachments in markdown format' do
+ expect(subject.attachments).to eq(
+ "[project_name](http://example.gitlab.com):" \
+ " Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) has passed in 02:00:10"
+ )
+ end
end
end
end
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
index f0e7551693d..5259357a254 100644
--- a/spec/models/project_services/custom_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -48,4 +48,49 @@ describe CustomIssueTrackerService do
end
end
end
+
+ context 'overriding properties' do
+ let(:url) { 'http://custom.example.com' }
+ let(:access_params) do
+ { project_url: url, issues_url: url, new_issue_url: url }
+ end
+
+ # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
+ context 'when data are stored in properties' do
+ let(:properties) { access_params.merge(title: title, description: description) }
+ let(:service) do
+ create(:custom_issue_tracker_service, :without_properties_callback, properties: properties)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when data are stored in separated fields' do
+ let(:service) do
+ create(:custom_issue_tracker_service, title: title, description: description, properties: access_params)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when data are stored in both properties and separated fields' do
+ let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
+ let(:service) do
+ create(:custom_issue_tracker_service, :without_properties_callback, title: title, description: description, properties: properties)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when no title & description are set' do
+ let(:service) do
+ create(:custom_issue_tracker_service, properties: access_params)
+ end
+
+ it 'returns default values' do
+ expect(service.title).to eq('Custom Issue Tracker')
+ expect(service.description).to eq('Custom issue tracker')
+ end
+ end
+ end
end
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index 22df19d943f..a771d1bf27f 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -101,6 +101,15 @@ describe DroneCiService, :use_clean_rails_memory_store_caching do
is_expected.to eq(:error)
end
+ Gitlab::HTTP::HTTP_ERRORS.each do |http_error|
+ it "sets commit status to :error with a #{http_error.name} error" do
+ WebMock.stub_request(:get, commit_status_path)
+ .to_raise(http_error)
+
+ is_expected.to eq(:error)
+ end
+ end
+
{
"killed" => :canceled,
"failure" => :failed,
diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb
index 0a58eb367e3..ffe241aa880 100644
--- a/spec/models/project_services/emails_on_push_service_spec.rb
+++ b/spec/models/project_services/emails_on_push_service_spec.rb
@@ -20,4 +20,24 @@ describe EmailsOnPushService do
it { is_expected.not_to validate_presence_of(:recipients) }
end
end
+
+ context 'project emails' do
+ let(:push_data) { { object_kind: 'push' } }
+ let(:project) { create(:project, :repository) }
+ let(:service) { create(:emails_on_push_service, project: project) }
+
+ it 'does not send emails when disabled' do
+ expect(project).to receive(:emails_disabled?).and_return(true)
+ expect(EmailsOnPushWorker).not_to receive(:perform_async)
+
+ service.execute(push_data)
+ end
+
+ it 'does send emails when enabled' do
+ expect(project).to receive(:emails_disabled?).and_return(false)
+ expect(EmailsOnPushWorker).to receive(:perform_async)
+
+ service.execute(push_data)
+ end
+ end
end
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index 11f96c03d46..0c4fc290a13 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -51,4 +51,49 @@ describe GitlabIssueTrackerService do
end
end
end
+
+ context 'overriding properties' do
+ let(:url) { 'http://gitlab.example.com' }
+ let(:access_params) do
+ { project_url: url, issues_url: url, new_issue_url: url }
+ end
+
+ # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
+ context 'when data are stored in properties' do
+ let(:properties) { access_params.merge(title: title, description: description) }
+ let(:service) do
+ create(:gitlab_issue_tracker_service, :without_properties_callback, properties: properties)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when data are stored in separated fields' do
+ let(:service) do
+ create(:gitlab_issue_tracker_service, title: title, description: description, properties: access_params)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when data are stored in both properties and separated fields' do
+ let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
+ let(:service) do
+ create(:gitlab_issue_tracker_service, :without_properties_callback, title: title, description: description, properties: properties)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when no title & description are set' do
+ let(:service) do
+ create(:gitlab_issue_tracker_service, properties: access_params)
+ end
+
+ it 'returns default values' do
+ expect(service.title).to eq('GitLab')
+ expect(service.description).to eq('GitLab issue tracker')
+ end
+ end
+ end
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index fc08457a3c5..02060699e9a 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -115,6 +115,72 @@ describe JiraService do
end
end
+ describe '#create' do
+ let(:params) do
+ {
+ project: create(:project), title: 'custom title', description: 'custom description'
+ }
+ end
+
+ subject { described_class.create(params) }
+
+ it 'does not store title & description into properties' do
+ expect(subject.properties.keys).not_to include('title', 'description')
+ end
+
+ it 'sets title & description correctly' do
+ service = subject
+
+ expect(service.title).to eq('custom title')
+ expect(service.description).to eq('custom description')
+ end
+ end
+
+ context 'overriding properties' do
+ let(:url) { 'http://issue_tracker.example.com' }
+ let(:access_params) do
+ { url: url, username: 'username', password: 'password' }
+ end
+
+ # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
+ context 'when data are stored in properties' do
+ let(:properties) { access_params.merge(title: title, description: description) }
+ let(:service) do
+ create(:jira_service, :without_properties_callback, properties: properties)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when data are stored in separated fields' do
+ let(:service) do
+ create(:jira_service, title: title, description: description, properties: access_params)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when data are stored in both properties and separated fields' do
+ let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
+ let(:service) do
+ create(:jira_service, :without_properties_callback, title: title, description: description, properties: properties)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when no title & description are set' do
+ let(:service) do
+ create(:jira_service, properties: access_params)
+ end
+
+ it 'returns default values' do
+ expect(service.title).to eq('Jira')
+ expect(service.description).to eq('Jira issue tracker')
+ end
+ end
+ end
+
describe '#close_issue' do
let(:custom_base_url) { 'http://custom_url' }
let(:user) { create(:user) }
@@ -170,7 +236,7 @@ describe JiraService do
allow(JIRA::Resource::Remotelink).to receive(:all).and_return(nil)
expect { @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) }
- .not_to raise_error(NoMethodError)
+ .not_to raise_error
end
# Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links
@@ -450,36 +516,54 @@ describe JiraService do
end
describe 'description and title' do
- let(:project) { create(:project) }
+ let(:title) { 'Jira One' }
+ let(:description) { 'Jira One issue tracker' }
+ let(:properties) do
+ {
+ url: 'http://jira.example.com/web',
+ username: 'mic',
+ password: 'password',
+ title: title,
+ description: description
+ }
+ end
context 'when it is not set' do
- before do
- @service = project.create_jira_service(active: true)
- end
+ it 'default values are returned' do
+ service = create(:jira_service)
- after do
- @service.destroy!
+ expect(service.title).to eq('Jira')
+ expect(service.description).to eq('Jira issue tracker')
end
+ end
- it 'is initialized' do
- expect(@service.title).to eq('Jira')
- expect(@service.description).to eq('Jira issue tracker')
+ context 'when it is set in properties' do
+ it 'values from properties are returned' do
+ service = create(:jira_service, properties: properties)
+
+ expect(service.title).to eq(title)
+ expect(service.description).to eq(description)
end
end
- context 'when it is set' do
- before do
- properties = { 'title' => 'Jira One', 'description' => 'Jira One issue tracker' }
- @service = project.create_jira_service(active: true, properties: properties)
- end
+ context 'when it is in title & description fields' do
+ it 'values from title and description fields are returned' do
+ service = create(:jira_service, title: title, description: description)
- after do
- @service.destroy!
+ expect(service.title).to eq(title)
+ expect(service.description).to eq(description)
end
+ end
+
+ context 'when it is in both properites & title & description fields' do
+ it 'values from title and description fields are returned' do
+ title2 = 'Jira 2'
+ description2 = 'Jira description 2'
- it 'is correct' do
- expect(@service.title).to eq('Jira One')
- expect(@service.description).to eq('Jira One issue tracker')
+ service = create(:jira_service, title: title2, description: description2, properties: properties)
+
+ expect(service.title).to eq(title2)
+ expect(service.description).to eq(description2)
end
end
end
@@ -505,31 +589,29 @@ describe JiraService do
end
describe 'project and issue urls' do
- let(:project) { create(:project) }
-
context 'when gitlab.yml was initialized' do
- before do
+ it 'is prepopulated with the settings' do
settings = {
'jira' => {
- 'title' => 'Jira',
'url' => 'http://jira.sample/projects/project_a',
'api_url' => 'http://jira.sample/api'
}
}
allow(Gitlab.config).to receive(:issues_tracker).and_return(settings)
- @service = project.create_jira_service(active: true)
- end
- after do
- @service.destroy!
- end
+ project = create(:project)
+ service = project.create_jira_service(active: true)
- it 'is prepopulated with the settings' do
- expect(@service.properties['title']).to eq('Jira')
- expect(@service.properties['url']).to eq('http://jira.sample/projects/project_a')
- expect(@service.properties['api_url']).to eq('http://jira.sample/api')
+ expect(service.properties['url']).to eq('http://jira.sample/projects/project_a')
+ expect(service.properties['api_url']).to eq('http://jira.sample/api')
end
end
+
+ it 'removes trailing slashes from url' do
+ service = described_class.new(url: 'http://jira.test.com/path/')
+
+ expect(service.url).to eq('http://jira.test.com/path')
+ end
end
describe 'favicon urls', :request_store do
@@ -545,4 +627,20 @@ describe JiraService do
expect(props[:object][:icon][:url16x16]).to match %r{^http://localhost/uploads/-/system/appearance/favicon/\d+/dk.png$}
end
end
+
+ context 'generating external URLs' do
+ let(:service) { described_class.new(url: 'http://jira.test.com/path/') }
+
+ describe '#issues_url' do
+ it 'handles trailing slashes' do
+ expect(service.issues_url).to eq('http://jira.test.com/path/browse/:id')
+ end
+ end
+
+ describe '#new_issue_url' do
+ it 'handles trailing slashes' do
+ expect(service.new_issue_url).to eq('http://jira.test.com/path/secure/CreateIssue.jspa')
+ end
+ end
+ end
end
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
deleted file mode 100644
index 5d7d6c34e67..00000000000
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ /dev/null
@@ -1,396 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe KubernetesService, :use_clean_rails_memory_store_caching do
- include KubernetesHelpers
- include ReactiveCachingHelpers
-
- let(:project) { create(:kubernetes_project) }
- let(:service) { create(:kubernetes_service, project: project) }
-
- describe 'Associations' do
- it { is_expected.to belong_to :project }
- end
-
- describe 'Validations' do
- context 'when service is active' do
- before do
- subject.active = true
- subject.skip_deprecation_validation = true
- end
-
- it { is_expected.not_to validate_presence_of(:namespace) }
- it { is_expected.to validate_presence_of(:api_url) }
- it { is_expected.to validate_presence_of(:token) }
-
- context 'namespace format' do
- before do
- subject.project = project
- subject.api_url = "http://example.com"
- subject.token = "test"
- end
-
- {
- 'foo' => true,
- '1foo' => true,
- 'foo1' => true,
- 'foo-bar' => true,
- '-foo' => false,
- 'foo-' => false,
- 'a' * 63 => true,
- 'a' * 64 => false,
- 'a.b' => false,
- 'a*b' => false,
- 'FOO' => true
- }.each do |namespace, validity|
- it "validates #{namespace} as #{validity ? 'valid' : 'invalid'}" do
- subject.namespace = namespace
-
- expect(subject.valid?).to eq(validity)
- end
- end
- end
- end
-
- context 'when service is inactive' do
- before do
- subject.project = project
- subject.active = false
- end
-
- it { is_expected.not_to validate_presence_of(:api_url) }
- it { is_expected.not_to validate_presence_of(:token) }
- end
-
- context 'with a deprecated service' do
- let(:kubernetes_service) { create(:kubernetes_service) }
-
- before do
- kubernetes_service.update_attribute(:active, false)
- kubernetes_service.skip_deprecation_validation = false
- kubernetes_service.properties['namespace'] = "foo"
- end
-
- it 'does not update attributes' do
- expect(kubernetes_service.save).to be_falsy
- end
-
- it 'includes an error with a deprecation message' do
- kubernetes_service.valid?
- expect(kubernetes_service.errors[:base].first).to match(/Kubernetes service integration has been disabled/)
- end
- end
-
- context 'with an active and deprecated service' do
- let(:kubernetes_service) { create(:kubernetes_service) }
-
- before do
- kubernetes_service.skip_deprecation_validation = false
- kubernetes_service.active = false
- kubernetes_service.properties['namespace'] = 'foo'
- kubernetes_service.save
- end
-
- it 'deactivates the service' do
- expect(kubernetes_service.active?).to be_falsy
- end
-
- it 'does not include a deprecation message as error' do
- expect(kubernetes_service.errors.messages.count).to eq(0)
- end
-
- it 'updates attributes' do
- expect(kubernetes_service.properties['namespace']).to eq("foo")
- end
- end
- end
-
- describe '#initialize_properties' do
- context 'without a project' do
- it 'leaves the namespace unset' do
- expect(described_class.new.namespace).to be_nil
- end
- end
- end
-
- describe '#fields' do
- let(:kube_namespace) do
- subject.fields.find { |h| h[:name] == 'namespace' }
- end
-
- context 'as template' do
- before do
- subject.template = true
- end
-
- it 'sets the namespace to the default' do
- expect(kube_namespace).not_to be_nil
- expect(kube_namespace[:placeholder]).to eq(subject.class::TEMPLATE_PLACEHOLDER)
- end
- end
-
- context 'with associated project' do
- before do
- subject.project = project
- end
-
- it 'sets the namespace to the default' do
- expect(kube_namespace).not_to be_nil
- expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
- end
- end
- end
-
- describe '#kubernetes_namespace_for' do
- subject { service.kubernetes_namespace_for(project) }
-
- shared_examples 'a correctly formatted namespace' do
- it 'returns a valid Kubernetes namespace name' do
- expect(subject).to match(Gitlab::Regex.kubernetes_namespace_regex)
- expect(subject).to eq(expected_namespace)
- end
- end
-
- it_behaves_like 'a correctly formatted namespace' do
- let(:expected_namespace) { service.send(:default_namespace) }
- end
-
- context 'when the project path contains forbidden characters' do
- before do
- project.path = '-a_Strange.Path--forSure'
- end
-
- it_behaves_like 'a correctly formatted namespace' do
- let(:expected_namespace) { "a-strange-path--forsure-#{project.id}" }
- end
- end
-
- context 'when namespace is specified' do
- before do
- service.namespace = 'my-namespace'
- end
-
- it_behaves_like 'a correctly formatted namespace' do
- let(:expected_namespace) { 'my-namespace' }
- end
- end
-
- context 'when service is not assigned to project' do
- before do
- service.project = nil
- end
-
- it 'does not return namespace' do
- is_expected.to be_nil
- end
- end
- end
-
- describe '#test' do
- let(:discovery_url) { 'https://kubernetes.example.com/api/v1' }
-
- before do
- stub_kubeclient_discover(service.api_url)
- end
-
- context 'with path prefix in api_url' do
- let(:discovery_url) { 'https://kubernetes.example.com/prefix/api/v1' }
-
- it 'tests with the prefix' do
- service.api_url = 'https://kubernetes.example.com/prefix'
- stub_kubeclient_discover(service.api_url)
-
- expect(service.test[:success]).to be_truthy
- expect(WebMock).to have_requested(:get, discovery_url).once
- end
- end
-
- context 'with custom CA certificate' do
- it 'is added to the certificate store' do
- service.ca_pem = "CA PEM DATA"
-
- cert = double("certificate")
- expect(OpenSSL::X509::Certificate).to receive(:new).with(service.ca_pem).and_return(cert)
- expect_any_instance_of(OpenSSL::X509::Store).to receive(:add_cert).with(cert)
-
- expect(service.test[:success]).to be_truthy
- expect(WebMock).to have_requested(:get, discovery_url).once
- end
- end
-
- context 'success' do
- it 'reads the discovery endpoint' do
- expect(service.test[:success]).to be_truthy
- expect(WebMock).to have_requested(:get, discovery_url).once
- end
- end
-
- context 'failure' do
- it 'fails to read the discovery endpoint' do
- WebMock.stub_request(:get, service.api_url + '/api/v1').to_return(status: 404)
-
- expect(service.test[:success]).to be_falsy
- expect(WebMock).to have_requested(:get, discovery_url).once
- end
- end
- end
-
- describe '#predefined_variable' do
- let(:kubeconfig) do
- config_file = expand_fixture_path('config/kubeconfig.yml')
- config = YAML.load(File.read(config_file))
- config.dig('users', 0, 'user')['token'] = 'token'
- config.dig('contexts', 0, 'context')['namespace'] = namespace
- config.dig('clusters', 0, 'cluster')['certificate-authority-data'] =
- Base64.strict_encode64('CA PEM DATA')
-
- YAML.dump(config)
- end
-
- before do
- subject.api_url = 'https://kube.domain.com'
- subject.token = 'token'
- subject.ca_pem = 'CA PEM DATA'
- subject.project = project
- end
-
- shared_examples 'setting variables' do
- it 'sets the variables' do
- expect(subject.predefined_variables(project: project)).to include(
- { key: 'KUBE_URL', value: 'https://kube.domain.com', public: true },
- { key: 'KUBE_TOKEN', value: 'token', public: false, masked: true },
- { key: 'KUBE_NAMESPACE', value: namespace, public: true },
- { key: 'KUBECONFIG', value: kubeconfig, public: false, file: true },
- { key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true },
- { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true }
- )
- end
- end
-
- context 'namespace is provided' do
- let(:namespace) { 'my-project' }
-
- before do
- subject.namespace = namespace
- end
-
- it_behaves_like 'setting variables'
- end
-
- context 'no namespace provided' do
- let(:namespace) { subject.kubernetes_namespace_for(project) }
-
- it_behaves_like 'setting variables'
-
- it 'sets the KUBE_NAMESPACE' do
- kube_namespace = subject.predefined_variables(project: project).find { |h| h[:key] == 'KUBE_NAMESPACE' }
-
- expect(kube_namespace).not_to be_nil
- expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
- end
- end
- end
-
- describe '#terminals' do
- let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") }
-
- subject { service.terminals(environment) }
-
- context 'with invalid pods' do
- it 'returns no terminals' do
- stub_reactive_cache(service, pods: [{ "bad" => "pod" }])
-
- is_expected.to be_empty
- end
- end
-
- context 'with valid pods' do
- let(:pod) { kube_pod(environment_slug: environment.slug, namespace: service.kubernetes_namespace_for(project), project_slug: project.full_path_slug) }
- let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") }
- let(:terminals) { kube_terminals(service, pod) }
-
- before do
- stub_reactive_cache(
- service,
- pods: [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")]
- )
- end
-
- it 'returns terminals' do
- is_expected.to eq(terminals + terminals)
- end
-
- it 'uses max session time from settings' do
- stub_application_setting(terminal_max_session_time: 600)
-
- times = subject.map { |terminal| terminal[:max_session_time] }
- expect(times).to eq [600, 600, 600, 600]
- end
- end
- end
-
- describe '#calculate_reactive_cache' do
- subject { service.calculate_reactive_cache }
-
- let(:namespace) { service.kubernetes_namespace_for(project) }
-
- context 'when service is inactive' do
- before do
- service.active = false
- end
-
- it { is_expected.to be_nil }
- end
-
- context 'when kubernetes responds with valid pods' do
- before do
- stub_kubeclient_pods(namespace)
- stub_kubeclient_deployments(namespace) # Used by EE
- end
-
- it { is_expected.to include(pods: [kube_pod]) }
- end
-
- context 'when kubernetes responds with 500s' do
- before do
- stub_kubeclient_pods(namespace, status: 500)
- stub_kubeclient_deployments(namespace, status: 500) # Used by EE
- end
-
- it { expect { subject }.to raise_error(Kubeclient::HttpError) }
- end
-
- context 'when kubernetes responds with 404s' do
- before do
- stub_kubeclient_pods(namespace, status: 404)
- stub_kubeclient_deployments(namespace, status: 404) # Used by EE
- end
-
- it { is_expected.to include(pods: []) }
- end
- end
-
- describe "#deprecated?" do
- let(:kubernetes_service) { create(:kubernetes_service) }
-
- it 'returns true' do
- expect(kubernetes_service.deprecated?).to be_truthy
- end
- end
-
- describe "#deprecation_message" do
- let(:kubernetes_service) { create(:kubernetes_service) }
-
- it 'indicates the service is deprecated' do
- expect(kubernetes_service.deprecation_message).to match(/Kubernetes service integration has been disabled/)
- end
-
- context 'if the service is not active' do
- it 'returns a message' do
- kubernetes_service.update_attribute(:active, false)
- expect(kubernetes_service.deprecation_message).to match(/Fields on this page are not used by GitLab/)
- end
- end
- end
-end
diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb
index 3ffe633868f..73c20359091 100644
--- a/spec/models/project_services/microsoft_teams_service_spec.rb
+++ b/spec/models/project_services/microsoft_teams_service_spec.rb
@@ -292,7 +292,8 @@ describe MicrosoftTeamsService do
context 'when disabled' do
let(:pipeline) do
- create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch')
+ create(:ci_pipeline, :failed, project: project,
+ sha: project.commit.sha, ref: 'not-the-default-branch')
end
before do
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index e9c7c94ad70..e5ac6ca65d6 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -105,10 +105,6 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
context 'manual configuration is enabled' do
let(:manual_configuration) { true }
- it 'returns rest client from api_url' do
- expect(service.prometheus_client.url).to eq(api_url)
- end
-
it 'calls valid?' do
allow(service).to receive(:valid?).and_call_original
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
index ac570ac27e1..c1ee6546b12 100644
--- a/spec/models/project_services/redmine_service_spec.rb
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -40,4 +40,49 @@ describe RedmineService do
expect(described_class.reference_pattern.match('#123')[:issue]).to eq('123')
end
end
+
+ context 'overriding properties' do
+ let(:url) { 'http://redmine.example.com' }
+ let(:access_params) do
+ { project_url: url, issues_url: url, new_issue_url: url }
+ end
+
+ # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
+ context 'when data are stored in properties' do
+ let(:properties) { access_params.merge(title: title, description: description) }
+ let(:service) do
+ create(:redmine_service, :without_properties_callback, properties: properties)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when data are stored in separated fields' do
+ let(:service) do
+ create(:redmine_service, title: title, description: description, properties: access_params)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when data are stored in both properties and separated fields' do
+ let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
+ let(:service) do
+ create(:redmine_service, :without_properties_callback, title: title, description: description, properties: properties)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when no title & description are set' do
+ let(:service) do
+ create(:redmine_service, properties: access_params)
+ end
+
+ it 'returns default values' do
+ expect(service.title).to eq('Redmine')
+ expect(service.description).to eq('Redmine issue tracker')
+ end
+ end
+ end
end
diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb
index bf9d892f66c..c48bf487af0 100644
--- a/spec/models/project_services/youtrack_service_spec.rb
+++ b/spec/models/project_services/youtrack_service_spec.rb
@@ -37,4 +37,49 @@ describe YoutrackService do
expect(described_class.reference_pattern.match('YT-123')[:issue]).to eq('YT-123')
end
end
+
+ context 'overriding properties' do
+ let(:url) { 'http://youtrack.example.com' }
+ let(:access_params) do
+ { project_url: url, issues_url: url, new_issue_url: url }
+ end
+
+ # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
+ context 'when data are stored in properties' do
+ let(:properties) { access_params.merge(title: title, description: description) }
+ let(:service) do
+ create(:youtrack_service, :without_properties_callback, properties: properties)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when data are stored in separated fields' do
+ let(:service) do
+ create(:youtrack_service, title: title, description: description, properties: access_params)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when data are stored in both properties and separated fields' do
+ let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
+ let(:service) do
+ create(:youtrack_service, :without_properties_callback, title: title, description: description, properties: properties)
+ end
+
+ include_examples 'issue tracker fields'
+ end
+
+ context 'when no title & description are set' do
+ let(:service) do
+ create(:youtrack_service, properties: access_params)
+ end
+
+ it 'returns default values' do
+ expect(service.title).to eq('YouTrack')
+ expect(service.description).to eq('YouTrack issue tracker')
+ end
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 1bc092fa41a..bd352db2236 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -98,6 +98,7 @@ describe Project do
it { is_expected.to have_many(:lfs_file_locks) }
it { is_expected.to have_many(:project_deploy_tokens) }
it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
+ it { is_expected.to have_many(:cycle_analytics_stages) }
it 'has an inverse relationship with merge requests' do
expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project)
@@ -173,24 +174,6 @@ describe Project do
it { is_expected.to include_module(Sortable) }
end
- describe '.missing_kubernetes_namespace' do
- let!(:project) { create(:project) }
- let!(:cluster) { create(:cluster, :provided_by_user, :group) }
- let(:kubernetes_namespaces) { project.kubernetes_namespaces }
-
- subject { described_class.missing_kubernetes_namespace(kubernetes_namespaces) }
-
- it { is_expected.to contain_exactly(project) }
-
- context 'kubernetes namespace exists' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- it { is_expected.to be_empty }
- end
- end
-
describe 'validation' do
let!(:project) { create(:project) }
@@ -213,7 +196,7 @@ describe Project do
.only_integer
.is_greater_than_or_equal_to(10.minutes)
.is_less_than(1.month)
- .with_message('needs to be beetween 10 minutes and 1 month')
+ .with_message('needs to be between 10 minutes and 1 month')
end
it 'does not allow new projects beyond user limits' do
@@ -1174,7 +1157,6 @@ describe Project do
describe '#pipeline_for' do
let(:project) { create(:project, :repository) }
- let!(:pipeline) { create_pipeline(project) }
shared_examples 'giving the correct pipeline' do
it { is_expected.to eq(pipeline) }
@@ -1186,19 +1168,47 @@ describe Project do
end
end
- context 'with explicit sha' do
- subject { project.pipeline_for('master', pipeline.sha) }
+ context 'with a matching pipeline' do
+ let!(:pipeline) { create_pipeline(project) }
+
+ context 'with explicit sha' do
+ subject { project.pipeline_for('master', pipeline.sha) }
- it_behaves_like 'giving the correct pipeline'
+ it_behaves_like 'giving the correct pipeline'
+
+ context 'with supplied id' do
+ let!(:other_pipeline) { create_pipeline(project) }
+
+ subject { project.pipeline_for('master', pipeline.sha, other_pipeline.id) }
+
+ it { is_expected.to eq(other_pipeline) }
+ end
+ end
+
+ context 'with implicit sha' do
+ subject { project.pipeline_for('master') }
+
+ it_behaves_like 'giving the correct pipeline'
+ end
end
- context 'with implicit sha' do
+ context 'when there is no matching pipeline' do
subject { project.pipeline_for('master') }
- it_behaves_like 'giving the correct pipeline'
+ it { is_expected.to be_nil }
end
end
+ describe '#pipelines_for' do
+ let(:project) { create(:project, :repository) }
+ let!(:pipeline) { create_pipeline(project) }
+ let!(:other_pipeline) { create_pipeline(project) }
+
+ subject { project.pipelines_for(project.default_branch, project.commit.sha) }
+
+ it { is_expected.to contain_exactly(pipeline, other_pipeline) }
+ end
+
describe '#builds_enabled' do
let(:project) { create(:project) }
@@ -1675,26 +1685,6 @@ describe Project do
end
end
- describe '.paginate_in_descending_order_using_id' do
- let!(:project1) { create(:project) }
- let!(:project2) { create(:project) }
-
- it 'orders the relation in descending order' do
- expect(described_class.paginate_in_descending_order_using_id)
- .to eq([project2, project1])
- end
-
- it 'applies a limit to the relation' do
- expect(described_class.paginate_in_descending_order_using_id(limit: 1))
- .to eq([project2])
- end
-
- it 'limits projects by and ID when given' do
- expect(described_class.paginate_in_descending_order_using_id(before: project2.id))
- .to eq([project1])
- end
- end
-
describe '.including_namespace_and_owner' do
it 'eager loads the namespace and namespace owner' do
create(:project)
@@ -2019,62 +2009,33 @@ describe Project do
end
end
- describe '#latest_successful_build_for' do
+ describe '#latest_successful_build_for_ref' do
let(:project) { create(:project, :repository) }
let(:pipeline) { create_pipeline(project) }
- context 'with many builds' do
- it 'gives the latest builds from latest pipeline' do
- pipeline1 = create_pipeline(project)
- pipeline2 = create_pipeline(project)
- create_build(pipeline1, 'test')
- create_build(pipeline1, 'test2')
- build1_p2 = create_build(pipeline2, 'test')
- create_build(pipeline2, 'test2')
+ it_behaves_like 'latest successful build for sha or ref'
- expect(project.latest_successful_build_for(build1_p2.name))
- .to eq(build1_p2)
- end
- end
+ subject { project.latest_successful_build_for_ref(build_name) }
- context 'with succeeded pipeline' do
- let!(:build) { create_build }
+ context 'with a specified ref' do
+ let(:build) { create_build }
- context 'standalone pipeline' do
- it 'returns builds for ref for default_branch' do
- expect(project.latest_successful_build_for(build.name))
- .to eq(build)
- end
+ subject { project.latest_successful_build_for_ref(build.name, project.default_branch) }
- it 'returns empty relation if the build cannot be found' do
- expect(project.latest_successful_build_for('TAIL'))
- .to be_nil
- end
- end
-
- context 'with some pending pipeline' do
- before do
- create_build(create_pipeline(project, 'pending'))
- end
-
- it 'gives the latest build from latest pipeline' do
- expect(project.latest_successful_build_for(build.name))
- .to eq(build)
- end
- end
+ it { is_expected.to eq(build) }
end
+ end
- context 'with pending pipeline' do
- it 'returns empty relation' do
- pipeline.update(status: 'pending')
- pending_build = create_build(pipeline)
+ describe '#latest_successful_build_for_sha' do
+ let(:project) { create(:project, :repository) }
+ let(:pipeline) { create_pipeline(project) }
- expect(project.latest_successful_build_for(pending_build.name)).to be_nil
- end
- end
+ it_behaves_like 'latest successful build for sha or ref'
+
+ subject { project.latest_successful_build_for_sha(build_name, project.commit.sha) }
end
- describe '#latest_successful_build_for!' do
+ describe '#latest_successful_build_for_ref!' do
let(:project) { create(:project, :repository) }
let(:pipeline) { create_pipeline(project) }
@@ -2087,7 +2048,7 @@ describe Project do
build1_p2 = create_build(pipeline2, 'test')
create_build(pipeline2, 'test2')
- expect(project.latest_successful_build_for(build1_p2.name))
+ expect(project.latest_successful_build_for_ref!(build1_p2.name))
.to eq(build1_p2)
end
end
@@ -2097,12 +2058,12 @@ describe Project do
context 'standalone pipeline' do
it 'returns builds for ref for default_branch' do
- expect(project.latest_successful_build_for!(build.name))
+ expect(project.latest_successful_build_for_ref!(build.name))
.to eq(build)
end
it 'returns exception if the build cannot be found' do
- expect { project.latest_successful_build_for!(build.name, 'TAIL') }
+ expect { project.latest_successful_build_for_ref!(build.name, 'TAIL') }
.to raise_error(ActiveRecord::RecordNotFound)
end
end
@@ -2113,7 +2074,7 @@ describe Project do
end
it 'gives the latest build from latest pipeline' do
- expect(project.latest_successful_build_for!(build.name))
+ expect(project.latest_successful_build_for_ref!(build.name))
.to eq(build)
end
end
@@ -2124,7 +2085,7 @@ describe Project do
pipeline.update(status: 'pending')
pending_build = create_build(pipeline)
- expect { project.latest_successful_build_for!(pending_build.name) }
+ expect { project.latest_successful_build_for_ref!(pending_build.name) }
.to raise_error(ActiveRecord::RecordNotFound)
end
end
@@ -2292,7 +2253,22 @@ describe Project do
end
end
- describe '#ancestors_upto', :nested_groups do
+ describe '#mark_stuck_remote_mirrors_as_failed!' do
+ it 'fails stuck remote mirrors' do
+ project = create(:project, :repository, :remote_mirror)
+
+ project.remote_mirrors.first.update(
+ update_status: :started,
+ last_update_started_at: 2.days.ago
+ )
+
+ expect do
+ project.mark_stuck_remote_mirrors_as_failed!
+ end.to change { project.remote_mirrors.stuck.count }.from(1).to(0)
+ end
+ end
+
+ describe '#ancestors_upto' do
let(:parent) { create(:group) }
let(:child) { create(:group, parent: parent) }
let(:child2) { create(:group, parent: child) }
@@ -2331,7 +2307,7 @@ describe Project do
it { is_expected.to eq(group) }
end
- context 'in a nested group', :nested_groups do
+ context 'in a nested group' do
let(:root) { create(:group) }
let(:child) { create(:group, parent: root) }
let(:project) { create(:project, group: child) }
@@ -2340,6 +2316,57 @@ describe Project do
end
end
+ describe '#emails_disabled?' do
+ let(:project) { create(:project, emails_disabled: false) }
+
+ context 'emails disabled in group' do
+ it 'returns true' do
+ allow(project.namespace).to receive(:emails_disabled?) { true }
+
+ expect(project.emails_disabled?).to be_truthy
+ end
+ end
+
+ context 'emails enabled in group' do
+ before do
+ allow(project.namespace).to receive(:emails_disabled?) { false }
+ end
+
+ it 'returns false' do
+ expect(project.emails_disabled?).to be_falsey
+ end
+
+ it 'returns true' do
+ project.update_attribute(:emails_disabled, true)
+
+ expect(project.emails_disabled?).to be_truthy
+ end
+ end
+
+ context 'when :emails_disabled feature flag is off' do
+ before do
+ stub_feature_flags(emails_disabled: false)
+ end
+
+ context 'emails disabled in group' do
+ it 'returns false' do
+ allow(project.namespace).to receive(:emails_disabled?) { true }
+
+ expect(project.emails_disabled?).to be_falsey
+ end
+ end
+
+ context 'emails enabled in group' do
+ it 'returns false' do
+ allow(project.namespace).to receive(:emails_disabled?) { false }
+ project.update_attribute(:emails_disabled, true)
+
+ expect(project.emails_disabled?).to be_falsey
+ end
+ end
+ end
+ end
+
describe '#lfs_enabled?' do
let(:project) { create(:project) }
@@ -2479,7 +2506,7 @@ describe Project do
expect(forked_project.in_fork_network_of?(project)).to be_truthy
end
- it 'is true for a fork of a fork', :postgresql do
+ it 'is true for a fork of a fork' do
other_fork = fork_project(forked_project)
expect(other_fork.in_fork_network_of?(project)).to be_truthy
@@ -2634,45 +2661,33 @@ describe Project do
end
describe '#deployment_variables' do
- context 'when project has no deployment service' do
- let(:project) { create(:project) }
+ let(:project) { create(:project) }
+ let(:environment) { 'production' }
- it 'returns an empty array' do
- expect(project.deployment_variables).to eq []
- end
+ subject { project.deployment_variables(environment: environment) }
+
+ before do
+ expect(project).to receive(:deployment_platform).with(environment: environment)
+ .and_return(deployment_platform)
end
- context 'when project uses mock deployment service' do
- let(:project) { create(:mock_deployment_project) }
+ context 'when project has no deployment platform' do
+ let(:deployment_platform) { nil }
- it 'returns an empty array' do
- expect(project.deployment_variables).to eq []
- end
+ it { is_expected.to eq [] }
end
- context 'when project has a deployment service' do
- context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has not been executed' do
- let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:project) { cluster.project }
+ context 'when project has a deployment platform' do
+ let(:platform_variables) { %w(platform variables) }
+ let(:deployment_platform) { double }
- it 'does not return variables from this service' do
- expect(project.deployment_variables).not_to include(
- { key: 'KUBE_TOKEN', value: project.deployment_platform.token, public: false, masked: true }
- )
- end
+ before do
+ expect(deployment_platform).to receive(:predefined_variables)
+ .with(project: project, environment_name: environment)
+ .and_return(platform_variables)
end
- context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has been executed' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token) }
- let!(:cluster) { kubernetes_namespace.cluster }
- let(:project) { kubernetes_namespace.project }
-
- it 'returns token from kubernetes namespace' do
- expect(project.deployment_variables).to include(
- { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true }
- )
- end
- end
+ it { is_expected.to eq platform_variables }
end
end
@@ -2700,9 +2715,10 @@ describe Project do
describe '#ci_variables_for' do
let(:project) { create(:project) }
+ let(:environment_scope) { '*' }
let!(:ci_variable) do
- create(:ci_variable, value: 'secret', project: project)
+ create(:ci_variable, value: 'secret', project: project, environment_scope: environment_scope)
end
let!(:protected_variable) do
@@ -2747,6 +2763,96 @@ describe Project do
it_behaves_like 'ref is protected'
end
+
+ context 'when environment name is specified' do
+ let(:environment) { 'review/name' }
+
+ subject do
+ project.ci_variables_for(ref: 'ref', environment: environment)
+ end
+
+ context 'when environment scope is exactly matched' do
+ let(:environment_scope) { 'review/name' }
+
+ it { is_expected.to contain_exactly(ci_variable) }
+ end
+
+ context 'when environment scope is matched by wildcard' do
+ let(:environment_scope) { 'review/*' }
+
+ it { is_expected.to contain_exactly(ci_variable) }
+ end
+
+ context 'when environment scope does not match' do
+ let(:environment_scope) { 'review/*/special' }
+
+ it { is_expected.not_to contain_exactly(ci_variable) }
+ end
+
+ context 'when environment scope has _' do
+ let(:environment_scope) { '*_*' }
+
+ it 'does not treat it as wildcard' do
+ is_expected.not_to contain_exactly(ci_variable)
+ end
+
+ context 'when environment name contains underscore' do
+ let(:environment) { 'foo_bar/test' }
+ let(:environment_scope) { 'foo_bar/*' }
+
+ it 'matches literally for _' do
+ is_expected.to contain_exactly(ci_variable)
+ end
+ end
+ end
+
+ # The environment name and scope cannot have % at the moment,
+ # but we're considering relaxing it and we should also make sure
+ # it doesn't break in case some data sneaked in somehow as we're
+ # not checking this integrity in database level.
+ context 'when environment scope has %' do
+ it 'does not treat it as wildcard' do
+ ci_variable.update_attribute(:environment_scope, '*%*')
+
+ is_expected.not_to contain_exactly(ci_variable)
+ end
+
+ context 'when environment name contains a percent' do
+ let(:environment) { 'foo%bar/test' }
+
+ it 'matches literally for _' do
+ ci_variable.update(environment_scope: 'foo%bar/*')
+
+ is_expected.to contain_exactly(ci_variable)
+ end
+ end
+ end
+
+ context 'when variables with the same name have different environment scopes' do
+ let!(:partially_matched_variable) do
+ create(:ci_variable,
+ key: ci_variable.key,
+ value: 'partial',
+ environment_scope: 'review/*',
+ project: project)
+ end
+
+ let!(:perfectly_matched_variable) do
+ create(:ci_variable,
+ key: ci_variable.key,
+ value: 'prefect',
+ environment_scope: 'review/name',
+ project: project)
+ end
+
+ it 'puts variables matching environment scope more in the end' do
+ is_expected.to eq(
+ [ci_variable,
+ partially_matched_variable,
+ perfectly_matched_variable])
+ end
+ end
+ end
end
describe '#any_lfs_file_locks?', :request_store do
@@ -2997,6 +3103,16 @@ describe Project do
expect(project.public_path_for_source_path('file.html', sha)).to be_nil
end
end
+
+ it 'returns a public path with a leading slash unmodified' do
+ route_map = Gitlab::RouteMap.new(<<-MAP.strip_heredoc)
+ - source: 'source/file.html'
+ public: '/public/file'
+ MAP
+ allow(project).to receive(:route_map_for).with(sha).and_return(route_map)
+
+ expect(project.public_path_for_source_path('source/file.html', sha)).to eq('/public/file')
+ end
end
context 'when there is no route map' do
@@ -3117,11 +3233,8 @@ describe Project do
let(:project) { create(:project) }
it 'shows full error updating an invalid MR' do
- error_message = 'Failed to replace merge_requests because one or more of the new records could not be saved.'\
- ' Validate fork Source project is not a fork of the target project'
-
expect { project.append_or_update_attribute(:merge_requests, [create(:merge_request)]) }
- .to raise_error(ActiveRecord::RecordNotSaved, error_message)
+ .to raise_error(ActiveRecord::RecordInvalid, /Failed to set merge_requests:/)
end
it 'updates the project successfully' do
@@ -3804,7 +3917,7 @@ describe Project do
end
end
- context 'when enabled on root parent', :nested_groups do
+ context 'when enabled on root parent' do
let(:parent_group) { create(:group, parent: create(:group, :auto_devops_enabled)) }
context 'when auto devops instance enabled' do
@@ -3824,7 +3937,7 @@ describe Project do
end
end
- context 'when disabled on root parent', :nested_groups do
+ context 'when disabled on root parent' do
let(:parent_group) { create(:group, parent: create(:group, :auto_devops_disabled)) }
context 'when auto devops instance enabled' do
@@ -4036,7 +4149,7 @@ describe Project do
context 'with a ref that is not the default branch' do
it 'returns the latest successful pipeline for the given ref' do
- expect(project.ci_pipelines).to receive(:latest_successful_for).with('foo')
+ expect(project.ci_pipelines).to receive(:latest_successful_for_ref).with('foo')
project.latest_successful_pipeline_for('foo')
end
@@ -4064,7 +4177,7 @@ describe Project do
it 'memoizes and returns the latest successful pipeline for the default branch' do
pipeline = double(:pipeline)
- expect(project.ci_pipelines).to receive(:latest_successful_for)
+ expect(project.ci_pipelines).to receive(:latest_successful_for_ref)
.with(project.default_branch)
.and_return(pipeline)
.once
@@ -4251,6 +4364,39 @@ describe Project do
end
end
+ describe '#has_active_hooks?' do
+ set(:project) { create(:project) }
+
+ it { expect(project.has_active_hooks?).to be_falsey }
+
+ it 'returns true when a matching push hook exists' do
+ create(:project_hook, push_events: true, project: project)
+
+ expect(project.has_active_hooks?(:merge_request_events)).to be_falsey
+ expect(project.has_active_hooks?).to be_truthy
+ end
+
+ it 'returns true when a matching system hook exists' do
+ create(:system_hook, push_events: true)
+
+ expect(project.has_active_hooks?(:merge_request_events)).to be_falsey
+ expect(project.has_active_hooks?).to be_truthy
+ end
+ end
+
+ describe '#has_active_services?' do
+ set(:project) { create(:project) }
+
+ it { expect(project.has_active_services?).to be_falsey }
+
+ it 'returns true when a matching service exists' do
+ create(:custom_issue_tracker_service, push_events: true, merge_requests_events: false, project: project)
+
+ expect(project.has_active_services?(:merge_request_hooks)).to be_falsey
+ expect(project.has_active_services?).to be_truthy
+ end
+ end
+
describe '#badges' do
let(:project_group) { create(:group) }
let(:project) { create(:project, path: 'avatar', namespace: project_group) }
@@ -4267,18 +4413,16 @@ describe Project do
expect(project.badges.count).to eq 3
end
- if Group.supports_nested_objects?
- context 'with nested_groups' do
- let(:parent_group) { create(:group) }
+ context 'with nested_groups' do
+ let(:parent_group) { create(:group) }
- before do
- create_list(:group_badge, 2, group: project_group)
- project_group.update(parent: parent_group)
- end
+ before do
+ create_list(:group_badge, 2, group: project_group)
+ project_group.update(parent: parent_group)
+ end
- it 'returns the project and the project nested groups badges' do
- expect(project.badges.count).to eq 5
- end
+ it 'returns the project and the project nested groups badges' do
+ expect(project.badges.count).to eq 5
end
end
end
@@ -4733,35 +4877,22 @@ describe Project do
describe '#git_objects_poolable?' do
subject { project }
-
- context 'when the feature flag is turned off' do
- before do
- stub_feature_flags(object_pools: false)
- end
-
- let(:project) { create(:project, :repository, :public) }
+ context 'when not using hashed storage' do
+ let(:project) { create(:project, :legacy_storage, :public, :repository) }
it { is_expected.not_to be_git_objects_poolable }
end
- context 'when the feature flag is enabled' do
- context 'when not using hashed storage' do
- let(:project) { create(:project, :legacy_storage, :public, :repository) }
-
- it { is_expected.not_to be_git_objects_poolable }
- end
-
- context 'when the project is not public' do
- let(:project) { create(:project, :private) }
+ context 'when the project is not public' do
+ let(:project) { create(:project, :private) }
- it { is_expected.not_to be_git_objects_poolable }
- end
+ it { is_expected.not_to be_git_objects_poolable }
+ end
- context 'when objects are poolable' do
- let(:project) { create(:project, :repository, :public) }
+ context 'when objects are poolable' do
+ let(:project) { create(:project, :repository, :public) }
- it { is_expected.to be_git_objects_poolable }
- end
+ it { is_expected.to be_git_objects_poolable }
end
end
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index 1cb49d83ffa..a164ed9bbea 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -135,6 +135,38 @@ describe ProjectStatistics do
expect(statistics.wiki_size).to eq(0)
end
end
+
+ context 'when the column is namespace relatable' do
+ let(:namespace) { create(:group) }
+ let(:project) { create(:project, namespace: namespace) }
+
+ context 'when arguments are passed' do
+ it 'schedules the aggregation worker' do
+ expect(Namespaces::ScheduleAggregationWorker)
+ .to receive(:perform_async)
+
+ statistics.refresh!(only: [:lfs_objects_size])
+ end
+ end
+
+ context 'when no argument is passed' do
+ it 'schedules the aggregation worker' do
+ expect(Namespaces::ScheduleAggregationWorker)
+ .to receive(:perform_async)
+
+ statistics.refresh!
+ end
+ end
+ end
+
+ context 'when the column is not namespace relatable' do
+ it 'does not schedules an aggregation worker' do
+ expect(Namespaces::ScheduleAggregationWorker)
+ .not_to receive(:perform_async)
+
+ statistics.refresh!(only: [:commit_count])
+ end
+ end
end
describe '#update_commit_count' do
diff --git a/spec/models/prometheus_metric_spec.rb b/spec/models/prometheus_metric_spec.rb
index 3610408c138..a123ff5a2a6 100644
--- a/spec/models/prometheus_metric_spec.rb
+++ b/spec/models/prometheus_metric_spec.rb
@@ -150,4 +150,17 @@ describe PrometheusMetric do
expect(subject.to_query_metric.queries).to eq(queries)
end
end
+
+ describe '#to_metric_hash' do
+ it 'returns a hash suitable for inclusion on a metrics dashboard' do
+ expected_output = {
+ query_range: subject.query,
+ unit: subject.unit,
+ label: subject.legend,
+ metric_id: subject.id
+ }
+
+ expect(subject.to_metric_hash).to eq(expected_output)
+ end
+ end
end
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index 7c106ce6b85..e9d846e7291 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -64,4 +64,14 @@ RSpec.describe Release do
is_expected.to all(be_a(Releases::Source))
end
end
+
+ describe '#upcoming_release?' do
+ context 'during the backfill migration when released_at could be nil' do
+ it 'handles a nil released_at value and returns false' do
+ allow(release).to receive(:released_at).and_return nil
+
+ expect(release.upcoming_release?).to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index e14b19db915..7edeb56efe2 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -113,7 +113,7 @@ describe RemoteMirror, :mailer do
remote_mirror = create(:remote_mirror)
- expect(remote_mirror.remote_name).to eq("remote_mirror_secret")
+ expect(remote_mirror.remote_name).to eq('remote_mirror_secret')
end
end
@@ -153,14 +153,14 @@ describe RemoteMirror, :mailer do
end
end
- describe '#mark_as_failed' do
+ describe '#mark_as_failed!' do
let(:remote_mirror) { create(:remote_mirror) }
let(:error_message) { 'http://user:pass@test.com/root/repoC.git/' }
let(:sanitized_error_message) { 'http://*****:*****@test.com/root/repoC.git/' }
subject do
remote_mirror.update_start
- remote_mirror.mark_as_failed(error_message)
+ remote_mirror.mark_as_failed!(error_message)
end
it 'sets the update_status to failed' do
@@ -201,11 +201,20 @@ describe RemoteMirror, :mailer do
end
context 'stuck mirrors' do
- it 'includes mirrors stuck in started with no last_update_at set' do
+ it 'includes mirrors that were started over an hour ago' do
+ mirror = create_mirror(url: 'http://cantbeblank',
+ update_status: 'started',
+ last_update_started_at: 3.hours.ago,
+ last_update_at: 2.hours.ago)
+
+ expect(described_class.stuck.last).to eq(mirror)
+ end
+
+ it 'includes mirrors started over 3 hours ago for their first sync' do
mirror = create_mirror(url: 'http://cantbeblank',
update_status: 'started',
last_update_at: nil,
- updated_at: 25.hours.ago)
+ last_update_started_at: 4.hours.ago)
expect(described_class.stuck.last).to eq(mirror)
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 0bd17dbacd7..79395fcc994 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1223,36 +1223,66 @@ describe Repository do
end
describe '#branch_exists?' do
- it 'uses branch_names' do
- allow(repository).to receive(:branch_names).and_return(['foobar'])
+ let(:branch) { repository.root_ref }
- expect(repository.branch_exists?('foobar')).to eq(true)
- expect(repository.branch_exists?('master')).to eq(false)
+ subject { repository.branch_exists?(branch) }
+
+ it 'delegates to branch_names when the cache is empty' do
+ repository.expire_branches_cache
+
+ expect(repository).to receive(:branch_names).and_call_original
+ is_expected.to eq(true)
+ end
+
+ it 'uses redis set caching when the cache is filled' do
+ repository.branch_names # ensure the branch name cache is filled
+
+ expect(repository)
+ .to receive(:branch_names_include?)
+ .with(branch)
+ .and_call_original
+
+ is_expected.to eq(true)
end
end
describe '#tag_exists?' do
- it 'uses tag_names' do
- allow(repository).to receive(:tag_names).and_return(['foobar'])
+ let(:tag) { repository.tags.first.name }
+
+ subject { repository.tag_exists?(tag) }
+
+ it 'delegates to tag_names when the cache is empty' do
+ repository.expire_tags_cache
+
+ expect(repository).to receive(:tag_names).and_call_original
+ is_expected.to eq(true)
+ end
+
+ it 'uses redis set caching when the cache is filled' do
+ repository.tag_names # ensure the tag name cache is filled
+
+ expect(repository)
+ .to receive(:tag_names_include?)
+ .with(tag)
+ .and_call_original
- expect(repository.tag_exists?('foobar')).to eq(true)
- expect(repository.tag_exists?('master')).to eq(false)
+ is_expected.to eq(true)
end
end
- describe '#branch_names', :use_clean_rails_memory_store_caching do
+ describe '#branch_names', :clean_gitlab_redis_cache do
let(:fake_branch_names) { ['foobar'] }
it 'gets cached across Repository instances' do
allow(repository.raw_repository).to receive(:branch_names).once.and_return(fake_branch_names)
- expect(repository.branch_names).to eq(fake_branch_names)
+ expect(repository.branch_names).to match_array(fake_branch_names)
fresh_repository = Project.find(project.id).repository
expect(fresh_repository.object_id).not_to eq(repository.object_id)
expect(fresh_repository.raw_repository).not_to receive(:branch_names)
- expect(fresh_repository.branch_names).to eq(fake_branch_names)
+ expect(fresh_repository.branch_names).to match_array(fake_branch_names)
end
end
@@ -1420,12 +1450,13 @@ describe Repository do
source_project: project)
end
- it 'writes merge of source and target to MR merge_ref_path' do
+ it 'writes merge of source SHA and first parent ref to MR merge_ref_path' do
merge_commit_id = repository.merge_to_ref(user,
merge_request.diff_head_sha,
merge_request,
merge_request.merge_ref_path,
- 'Custom message')
+ 'Custom message',
+ merge_request.target_branch_ref)
merge_commit = repository.commit(merge_commit_id)
@@ -1743,12 +1774,23 @@ describe Repository do
end
end
- describe '#before_push_tag' do
+ describe '#expires_caches_for_tags' do
it 'flushes the cache' do
expect(repository).to receive(:expire_statistics_caches)
expect(repository).to receive(:expire_emptiness_caches)
expect(repository).to receive(:expire_tags_cache)
+ repository.expire_caches_for_tags
+ end
+ end
+
+ describe '#before_push_tag' do
+ it 'logs an event' do
+ expect(repository).not_to receive(:expire_statistics_caches)
+ expect(repository).not_to receive(:expire_emptiness_caches)
+ expect(repository).not_to receive(:expire_tags_cache)
+ expect(repository).to receive(:repository_event).with(:push_tag)
+
repository.before_push_tag
end
end
@@ -1780,6 +1822,12 @@ describe Repository do
repository.after_create_branch
end
+
+ it 'does not expire the branch caches when specified' do
+ expect(repository).not_to receive(:expire_branches_cache)
+
+ repository.after_create_branch(expire_cache: false)
+ end
end
describe '#after_remove_branch' do
@@ -1788,25 +1836,45 @@ describe Repository do
repository.after_remove_branch
end
+
+ it 'does not expire the branch caches when specified' do
+ expect(repository).not_to receive(:expire_branches_cache)
+
+ repository.after_remove_branch(expire_cache: false)
+ end
end
describe '#after_create' do
+ it 'calls expire_status_cache' do
+ expect(repository).to receive(:expire_status_cache)
+
+ repository.after_create
+ end
+
+ it 'logs an event' do
+ expect(repository).to receive(:repository_event).with(:create_repository)
+
+ repository.after_create
+ end
+ end
+
+ describe '#expire_status_cache' do
it 'flushes the exists cache' do
expect(repository).to receive(:expire_exists_cache)
- repository.after_create
+ repository.expire_status_cache
end
it 'flushes the root ref cache' do
expect(repository).to receive(:expire_root_ref_cache)
- repository.after_create
+ repository.expire_status_cache
end
it 'flushes the emptiness caches' do
expect(repository).to receive(:expire_emptiness_caches)
- repository.after_create
+ repository.expire_status_cache
end
end
@@ -2298,48 +2366,6 @@ describe Repository do
end
end
- describe '#diverging_commit_counts' do
- let(:diverged_branch) { repository.find_branch('fix') }
- let(:root_ref_sha) { repository.raw_repository.commit(repository.root_ref).id }
- let(:diverged_branch_sha) { diverged_branch.dereferenced_target.sha }
-
- it 'returns the commit counts behind and ahead of default branch' do
- result = repository.diverging_commit_counts(diverged_branch)
-
- expect(result).to eq(behind: 29, ahead: 2)
- end
-
- context 'when gitaly_count_diverging_commits_no_max is enabled' do
- before do
- stub_feature_flags(gitaly_count_diverging_commits_no_max: true)
- end
-
- it 'calls diverging_commit_count without max count' do
- expect(repository.raw_repository)
- .to receive(:diverging_commit_count)
- .with(root_ref_sha, diverged_branch_sha)
- .and_return([29, 2])
-
- repository.diverging_commit_counts(diverged_branch)
- end
- end
-
- context 'when gitaly_count_diverging_commits_no_max is disabled' do
- before do
- stub_feature_flags(gitaly_count_diverging_commits_no_max: false)
- end
-
- it 'calls diverging_commit_count with max count' do
- expect(repository.raw_repository)
- .to receive(:diverging_commit_count)
- .with(root_ref_sha, diverged_branch_sha, max_count: Repository::MAX_DIVERGING_COUNT)
- .and_return([29, 2])
-
- repository.diverging_commit_counts(diverged_branch)
- end
- end
- end
-
describe '#refresh_method_caches' do
it 'refreshes the caches of the given types' do
expect(repository).to receive(:expire_method_caches)
@@ -2467,16 +2493,15 @@ describe Repository do
# Gets the commit oid, and warms the cache
oid = project.commit.id
- expect(Gitlab::Git::Commit).not_to receive(:find).once
+ expect(Gitlab::Git::Commit).to receive(:find).once
- project.commit_by(oid: oid)
+ 2.times { project.commit_by(oid: oid) }
end
it 'caches nil values' do
expect(Gitlab::Git::Commit).to receive(:find).once
- project.commit_by(oid: '1' * 40)
- project.commit_by(oid: '1' * 40)
+ 2.times { project.commit_by(oid: '1' * 40) }
end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index d442c73c118..0797b9a9d83 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -244,7 +244,8 @@ describe Service do
let(:service) do
GitlabIssueTrackerService.create(
project: create(:project),
- title: 'random title'
+ title: 'random title',
+ project_url: 'http://gitlab.example.com'
)
end
@@ -252,8 +253,12 @@ describe Service do
expect { service }.not_to raise_error
end
+ it 'sets title correctly' do
+ expect(service.title).to eq('random title')
+ end
+
it 'creates the properties' do
- expect(service.properties).to eq({ "title" => "random title" })
+ expect(service.properties).to eq({ "project_url" => "http://gitlab.example.com" })
end
end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index b5bf294790a..9aeef7c3b4b 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -262,11 +262,7 @@ describe Todo do
todo2 = create(:todo, group: child_group)
todos = described_class.for_group_and_descendants(parent_group)
- expect(todos).to include(todo1)
-
- # Nested groups only work on PostgreSQL, so on MySQL todo2 won't be
- # present.
- expect(todos).to include(todo2) if Gitlab::Database.postgresql?
+ expect(todos).to contain_exactly(todo1, todo2)
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index b098fe3c9f4..8338d2b5b39 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -103,6 +103,14 @@ describe User do
it { is_expected.to validate_length_of(:name).is_at_most(128) }
end
+ describe 'first name' do
+ it { is_expected.to validate_length_of(:first_name).is_at_most(255) }
+ end
+
+ describe 'last name' do
+ it { is_expected.to validate_length_of(:last_name).is_at_most(255) }
+ end
+
describe 'username' do
it 'validates presence' do
expect(subject).to validate_presence_of(:username)
@@ -530,6 +538,17 @@ describe User do
end
describe 'before save hook' do
+ context '#default_private_profile_to_false' do
+ let(:user) { create(:user, private_profile: true) }
+
+ it 'converts nil to false' do
+ user.private_profile = nil
+ user.save!
+
+ expect(user.private_profile).to eq false
+ end
+ end
+
context 'when saving an external user' do
let(:user) { create(:user) }
let(:external_user) { create(:user, external: true) }
@@ -667,6 +686,18 @@ describe User do
end
end
+ describe 'name getters' do
+ let(:user) { create(:user, name: 'Kane Martin William') }
+
+ it 'derives first name from full name, if not present' do
+ expect(user.first_name).to eq('Kane')
+ end
+
+ it 'derives last name from full name, if not present' do
+ expect(user.last_name).to eq('Martin William')
+ end
+ end
+
describe '#highest_role' do
let(:user) { create(:user) }
@@ -783,6 +814,24 @@ describe User do
end
end
+ describe '#accessible_deploy_keys' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let!(:private_deploy_keys_project) { create(:deploy_keys_project) }
+ let!(:public_deploy_keys_project) { create(:deploy_keys_project) }
+ let!(:accessible_deploy_keys_project) { create(:deploy_keys_project, project: project) }
+
+ before do
+ public_deploy_keys_project.deploy_key.update(public: true)
+ project.add_developer(user)
+ end
+
+ it 'user can only see deploy keys accessible to right projects' do
+ expect(user.accessible_deploy_keys).to match_array([public_deploy_keys_project.deploy_key,
+ accessible_deploy_keys_project.deploy_key])
+ end
+ end
+
describe '#deploy_keys' do
include_context 'user keys'
@@ -974,7 +1023,7 @@ describe User do
it { expect(user.namespaces).to contain_exactly(user.namespace, group) }
it { expect(user.manageable_namespaces).to contain_exactly(user.namespace, group) }
- context 'with child groups', :nested_groups do
+ context 'with child groups' do
let!(:subgroup) { create(:group, parent: group) }
describe '#manageable_namespaces' do
@@ -1127,6 +1176,7 @@ describe User do
expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
expect(user.external).to be_falsey
+ expect(user.private_profile).to eq false
end
end
@@ -2070,11 +2120,7 @@ describe User do
subject { user.membership_groups }
- if Group.supports_nested_objects?
- it { is_expected.to contain_exactly parent_group, child_group }
- else
- it { is_expected.to contain_exactly parent_group }
- end
+ it { is_expected.to contain_exactly parent_group, child_group }
end
describe '#authorizations_for_projects' do
@@ -2374,7 +2420,7 @@ describe User do
it_behaves_like :member
end
- context 'with subgroup with different owner for project runner', :nested_groups do
+ context 'with subgroup with different owner for project runner' do
let(:group) { create(:group) }
let(:another_user) { create(:user) }
let(:subgroup) { create(:group, parent: group) }
@@ -2478,22 +2524,16 @@ describe User do
group.add_owner(user)
end
- if Group.supports_nested_objects?
- it 'returns all groups' do
- is_expected.to match_array [
- group,
- nested_group_1, nested_group_1_1,
- nested_group_2, nested_group_2_1
- ]
- end
- else
- it 'returns the top-level groups' do
- is_expected.to match_array [group]
- end
+ it 'returns all groups' do
+ is_expected.to match_array [
+ group,
+ nested_group_1, nested_group_1_1,
+ nested_group_2, nested_group_2_1
+ ]
end
end
- context 'user is member of the first child (internal node), branch 1', :nested_groups do
+ context 'user is member of the first child (internal node), branch 1' do
before do
nested_group_1.add_owner(user)
end
@@ -2506,7 +2546,7 @@ describe User do
end
end
- context 'user is member of the first child (internal node), branch 2', :nested_groups do
+ context 'user is member of the first child (internal node), branch 2' do
before do
nested_group_2.add_owner(user)
end
@@ -2519,7 +2559,7 @@ describe User do
end
end
- context 'user is member of the last child (leaf node)', :nested_groups do
+ context 'user is member of the last child (leaf node)' do
before do
nested_group_1_1.add_owner(user)
end
@@ -2675,7 +2715,7 @@ describe User do
end
end
- context 'with 2FA requirement from expanded groups', :nested_groups do
+ context 'with 2FA requirement from expanded groups' do
let!(:group1) { create :group, require_two_factor_authentication: true }
let!(:group1a) { create :group, parent: group1 }
@@ -2690,7 +2730,7 @@ describe User do
end
end
- context 'with 2FA requirement on nested child group', :nested_groups do
+ context 'with 2FA requirement on nested child group' do
let!(:group1) { create :group, require_two_factor_authentication: false }
let!(:group1a) { create :group, require_two_factor_authentication: true, parent: group1 }
@@ -2931,7 +2971,7 @@ describe User do
let(:user) { create(:user, username: username) }
context 'when the user is updated' do
- context 'when the username is changed' do
+ context 'when the username or name is changed' do
let(:new_username) { 'bar' }
it 'changes the namespace (just to compare to when username is not changed)' do
@@ -2942,16 +2982,24 @@ describe User do
end.to change { user.namespace.updated_at }
end
- it 'updates the namespace name' do
+ it 'updates the namespace path when the username was changed' do
user.update!(username: new_username)
- expect(user.namespace.name).to eq(new_username)
+ expect(user.namespace.path).to eq(new_username)
end
- it 'updates the namespace path' do
- user.update!(username: new_username)
+ it 'updates the namespace name if the name was changed' do
+ user.update!(name: 'New name')
- expect(user.namespace.path).to eq(new_username)
+ expect(user.namespace.name).to eq('New name')
+ end
+
+ it 'updates nested routes for the namespace if the name was changed' do
+ project = create(:project, namespace: user.namespace)
+
+ user.update!(name: 'New name')
+
+ expect(project.route.reload.name).to include('New name')
end
context 'when there is a validation error (namespace name taken) while updating namespace' do
@@ -3354,7 +3402,7 @@ describe User do
end
describe '#requires_usage_stats_consent?' do
- let(:user) { create(:user, created_at: 8.days.ago) }
+ let(:user) { create(:user, :admin, created_at: 8.days.ago) }
before do
allow(user).to receive(:has_current_license?).and_return false
@@ -3378,7 +3426,7 @@ describe User do
end
it 'does not require consent if usage stats were set by this user' do
- allow(Gitlab::CurrentSettings).to receive(:usage_stats_set_by_user_id).and_return(user.id)
+ create(:application_setting, usage_stats_set_by_user_id: user.id)
expect(user.requires_usage_stats_consent?).to be false
end
@@ -3484,4 +3532,37 @@ describe User do
expect(described_class.reorder_by_name).to eq([user1, user2])
end
end
+
+ describe '#notification_email_for' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+
+ subject { user.notification_email_for(group) }
+
+ context 'when group is nil' do
+ let(:group) { nil }
+
+ it 'returns global notification email' do
+ is_expected.to eq(user.notification_email)
+ end
+ end
+
+ context 'when group has no notification email set' do
+ it 'returns global notification email' do
+ create(:notification_setting, user: user, source: group, notification_email: '')
+
+ is_expected.to eq(user.notification_email)
+ end
+ end
+
+ context 'when group has notification email set' do
+ it 'returns group notification email' do
+ group_notification_email = 'user+group@example.com'
+
+ create(:notification_setting, user: user, source: group, notification_email: group_notification_email)
+
+ is_expected.to eq(group_notification_email)
+ end
+ end
+ end
end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 520a06e138e..18c62c917dc 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -81,10 +81,9 @@ describe WikiPage do
grouped_entries = described_class.group_by_directory(wiki.list_pages)
actual_order =
- grouped_entries.map do |page_or_dir|
+ grouped_entries.flat_map do |page_or_dir|
get_slugs(page_or_dir)
end
- .flatten
expect(actual_order).to eq(expected_order)
end
end
diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb
index d8a63066265..e9a85890082 100644
--- a/spec/policies/ci/trigger_policy_spec.rb
+++ b/spec/policies/ci/trigger_policy_spec.rb
@@ -3,52 +3,24 @@ require 'spec_helper'
describe Ci::TriggerPolicy do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
+ let(:trigger) { create(:ci_trigger, project: project, owner: create(:user)) }
- let(:policies) do
- described_class.new(user, trigger)
- end
-
- shared_examples 'allows to admin and manage trigger' do
- it 'does include ability to admin trigger' do
- expect(policies).to be_allowed :admin_trigger
- end
-
- it 'does include ability to manage trigger' do
- expect(policies).to be_allowed :manage_trigger
- end
- end
-
- shared_examples 'allows to manage trigger' do
- it 'does not include ability to admin trigger' do
- expect(policies).not_to be_allowed :admin_trigger
- end
-
- it 'does include ability to manage trigger' do
- expect(policies).to be_allowed :manage_trigger
- end
- end
-
- shared_examples 'disallows to admin and manage trigger' do
- it 'does not include ability to admin trigger' do
- expect(policies).not_to be_allowed :admin_trigger
- end
-
- it 'does not include ability to manage trigger' do
- expect(policies).not_to be_allowed :manage_trigger
- end
- end
+ subject { described_class.new(user, trigger) }
describe '#rules' do
context 'when owner is undefined' do
- let(:owner) { nil }
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ trigger.update_attribute(:owner, nil)
+ end
context 'when user is maintainer of the project' do
before do
project.add_maintainer(user)
end
- it_behaves_like 'allows to admin and manage trigger'
+ it { is_expected.to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
context 'when user is developer of the project' do
@@ -56,35 +28,63 @@ describe Ci::TriggerPolicy do
project.add_developer(user)
end
- it_behaves_like 'disallows to admin and manage trigger'
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
- context 'when user is not member of the project' do
- it_behaves_like 'disallows to admin and manage trigger'
+ context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: true)
+ end
+
+ context 'when user is maintainer of the project' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it { is_expected.to be_allowed(:manage_trigger) }
+ it { is_expected.to be_allowed(:admin_trigger) }
+ end
+
+ context 'when user is developer of the project' do
+ before do
+ project.add_developer(user)
+ end
+
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
+ end
+
+ context 'when user is not member of the project' do
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
+ end
end
end
context 'when owner is an user' do
- let(:owner) { user }
+ before do
+ trigger.update!(owner: user)
+ end
context 'when user is maintainer of the project' do
before do
project.add_maintainer(user)
end
- it_behaves_like 'allows to admin and manage trigger'
+ it { is_expected.to be_allowed(:manage_trigger) }
+ it { is_expected.to be_allowed(:admin_trigger) }
end
end
context 'when owner is another user' do
- let(:owner) { create(:user) }
-
context 'when user is maintainer of the project' do
before do
project.add_maintainer(user)
end
- it_behaves_like 'allows to manage trigger'
+ it { is_expected.to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
context 'when user is developer of the project' do
@@ -92,11 +92,13 @@ describe Ci::TriggerPolicy do
project.add_developer(user)
end
- it_behaves_like 'disallows to admin and manage trigger'
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
context 'when user is not member of the project' do
- it_behaves_like 'disallows to admin and manage trigger'
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
end
end
diff --git a/spec/policies/clusters/instance_policy_spec.rb b/spec/policies/clusters/instance_policy_spec.rb
index 7b61819e079..2373fef8aa6 100644
--- a/spec/policies/clusters/instance_policy_spec.rb
+++ b/spec/policies/clusters/instance_policy_spec.rb
@@ -9,6 +9,8 @@ describe Clusters::InstancePolicy do
describe 'rules' do
context 'when user' do
it { expect(policy).to be_disallowed :read_cluster }
+ it { expect(policy).to be_disallowed :add_cluster }
+ it { expect(policy).to be_disallowed :create_cluster }
it { expect(policy).to be_disallowed :update_cluster }
it { expect(policy).to be_disallowed :admin_cluster }
end
@@ -17,6 +19,8 @@ describe Clusters::InstancePolicy do
let(:user) { create(:admin) }
it { expect(policy).to be_allowed :read_cluster }
+ it { expect(policy).to be_allowed :add_cluster }
+ it { expect(policy).to be_allowed :create_cluster }
it { expect(policy).to be_allowed :update_cluster }
it { expect(policy).to be_allowed :admin_cluster }
end
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
index 12be3927e18..df6cc526eb0 100644
--- a/spec/policies/global_policy_spec.rb
+++ b/spec/policies/global_policy_spec.rb
@@ -226,4 +226,32 @@ describe GlobalPolicy do
it { is_expected.not_to be_allowed(:read_instance_statistics) }
end
end
+
+ describe 'slash commands' do
+ context 'regular user' do
+ it { is_expected.to be_allowed(:use_slash_commands) }
+ end
+
+ context 'when internal' do
+ let(:current_user) { User.ghost }
+
+ it { is_expected.not_to be_allowed(:use_slash_commands) }
+ end
+
+ context 'when blocked' do
+ before do
+ current_user.block
+ end
+
+ it { is_expected.not_to be_allowed(:use_slash_commands) }
+ end
+
+ context 'when access locked' do
+ before do
+ current_user.lock_access!
+ end
+
+ it { is_expected.not_to be_allowed(:use_slash_commands) }
+ end
+ end
end
diff --git a/spec/policies/group_member_policy_spec.rb b/spec/policies/group_member_policy_spec.rb
index 7bd7184cffe..a4f3301a064 100644
--- a/spec/policies/group_member_policy_spec.rb
+++ b/spec/policies/group_member_policy_spec.rb
@@ -58,7 +58,7 @@ describe GroupMemberPolicy do
end
end
- context 'with the group parent', :postgresql do
+ context 'with the group parent' do
let(:current_user) { create :user }
let(:subgroup) { create(:group, :private, parent: group)}
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 59f3a961d50..be55d94daec 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -51,7 +51,7 @@ describe GroupPolicy do
it { expect_allowed(:read_label, :read_list) }
- context 'in subgroups', :nested_groups do
+ context 'in subgroups' do
let(:subgroup) { create(:group, :private, parent: group) }
let(:project) { create(:project, namespace: subgroup) }
@@ -98,12 +98,34 @@ describe GroupPolicy do
context 'maintainer' do
let(:current_user) { maintainer }
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*reporter_permissions)
- expect_allowed(*developer_permissions)
- expect_allowed(*maintainer_permissions)
- expect_disallowed(*owner_permissions)
+ context 'with subgroup_creation level set to maintainer' do
+ let(:group) do
+ create(:group, :private, subgroup_creation_level: ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS)
+ end
+
+ it 'allows every maintainer permission plus creating subgroups' do
+ create_subgroup_permission = [:create_subgroup]
+ updated_maintainer_permissions =
+ maintainer_permissions + create_subgroup_permission
+ updated_owner_permissions =
+ owner_permissions - create_subgroup_permission
+
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*updated_maintainer_permissions)
+ expect_disallowed(*updated_owner_permissions)
+ end
+ end
+
+ context 'with subgroup_creation_level set to owner' do
+ it 'allows every maintainer permission' do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
end
end
@@ -111,8 +133,6 @@ describe GroupPolicy do
let(:current_user) { owner }
it do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
-
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
@@ -125,8 +145,6 @@ describe GroupPolicy do
let(:current_user) { admin }
it do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
-
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
@@ -135,38 +153,10 @@ describe GroupPolicy do
end
end
- describe 'when nested group support feature is disabled' do
- before do
- allow(Group).to receive(:supports_nested_objects?).and_return(false)
- end
-
- context 'admin' do
- let(:current_user) { admin }
-
- it 'allows every owner permission except creating subgroups' do
- create_subgroup_permission = [:create_subgroup]
- updated_owner_permissions = owner_permissions - create_subgroup_permission
-
- expect_disallowed(*create_subgroup_permission)
- expect_allowed(*updated_owner_permissions)
- end
- end
-
- context 'owner' do
- let(:current_user) { owner }
-
- it 'allows every owner permission except creating subgroups' do
- create_subgroup_permission = [:create_subgroup]
- updated_owner_permissions = owner_permissions - create_subgroup_permission
-
- expect_disallowed(*create_subgroup_permission)
- expect_allowed(*updated_owner_permissions)
- end
+ describe 'private nested group use the highest access level from the group and inherited permissions' do
+ let(:nested_group) do
+ create(:group, :private, :owner_subgroup_creation_only, parent: group)
end
- end
-
- describe 'private nested group use the highest access level from the group and inherited permissions', :nested_groups do
- let(:nested_group) { create(:group, :private, parent: group) }
before do
nested_group.add_guest(guest)
@@ -246,8 +236,6 @@ describe GroupPolicy do
let(:current_user) { owner }
it do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
-
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
@@ -461,6 +449,72 @@ describe GroupPolicy do
end
end
+ context "create_subgroup" do
+ context 'when group has subgroup creation level set to owner' do
+ let(:group) do
+ create(
+ :group,
+ subgroup_creation_level: ::Gitlab::Access::OWNER_SUBGROUP_ACCESS)
+ end
+
+ context 'reporter' do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_disallowed(:create_subgroup) }
+ end
+
+ context 'developer' do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_disallowed(:create_subgroup) }
+ end
+
+ context 'maintainer' do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_disallowed(:create_subgroup) }
+ end
+
+ context 'owner' do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_allowed(:create_subgroup) }
+ end
+ end
+
+ context 'when group has subgroup creation level set to maintainer' do
+ let(:group) do
+ create(
+ :group,
+ subgroup_creation_level: ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS)
+ end
+
+ context 'reporter' do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_disallowed(:create_subgroup) }
+ end
+
+ context 'developer' do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_disallowed(:create_subgroup) }
+ end
+
+ context 'maintainer' do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_allowed(:create_subgroup) }
+ end
+
+ context 'owner' do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_allowed(:create_subgroup) }
+ end
+ end
+ end
+
it_behaves_like 'clusterable policies' do
let(:clusterable) { create(:group) }
let(:cluster) do
diff --git a/spec/policies/namespace/root_storage_statistics_policy_spec.rb b/spec/policies/namespace/root_storage_statistics_policy_spec.rb
new file mode 100644
index 00000000000..8d53050fffb
--- /dev/null
+++ b/spec/policies/namespace/root_storage_statistics_policy_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Namespace::RootStorageStatisticsPolicy do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '#rules' do
+ let(:statistics) { create(:namespace_root_storage_statistics, namespace: namespace) }
+ let(:user) { create(:user) }
+
+ subject { Ability.allowed?(user, :read_statistics, statistics) }
+
+ shared_examples 'deny anonymous users' do
+ context 'when the users is anonymous' do
+ let(:user) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'when the namespace is a personal namespace' do
+ let(:owner) { create(:user) }
+ let(:namespace) { owner.namespace }
+
+ include_examples 'deny anonymous users'
+
+ context 'when the user is not the owner' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when the user is the owner' do
+ let(:user) { owner }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when the namespace is a group' do
+ let(:user) { create(:user) }
+ let(:external) { create(:user, :external) }
+
+ shared_examples 'allows only owners' do |group_type|
+ let(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel.level_value(group_type.to_s)) }
+ let(:namespace) { group }
+
+ include_examples 'deny anonymous users'
+
+ where(:user_type, :outcome) do
+ [
+ [:non_member, false],
+ [:guest, false],
+ [:reporter, false],
+ [:developer, false],
+ [:maintainer, false],
+ [:owner, true]
+ ]
+ end
+
+ with_them do
+ before do
+ group.add_user(user, user_type) unless user_type == :non_member
+ end
+
+ it { is_expected.to eq(outcome) }
+
+ context 'when the user is external' do
+ let(:user) { external }
+
+ it { is_expected.to eq(outcome) }
+ end
+ end
+ end
+
+ include_examples 'allows only owners', :public
+ include_examples 'allows only owners', :private
+ include_examples 'allows only owners', :internal
+ end
+ end
+end
diff --git a/spec/policies/namespace_policy_spec.rb b/spec/policies/namespace_policy_spec.rb
index 99fa8b1fe44..216aaae70ee 100644
--- a/spec/policies/namespace_policy_spec.rb
+++ b/spec/policies/namespace_policy_spec.rb
@@ -6,7 +6,7 @@ describe NamespacePolicy do
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, owner: owner) }
- let(:owner_permissions) { [:create_projects, :admin_namespace, :read_namespace] }
+ let(:owner_permissions) { [:create_projects, :admin_namespace, :read_namespace, :read_statistics] }
subject { described_class.new(current_user, namespace) }
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index fd82150c12a..71ba73d5661 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -94,6 +94,19 @@ describe ProjectPolicy do
permissions.each { |p| is_expected.not_to be_allowed(p) }
end
+ context 'with no project feature' do
+ subject { described_class.new(owner, project) }
+
+ before do
+ project.project_feature.destroy
+ project.reload
+ end
+
+ it 'returns false' do
+ is_expected.to be_disallowed(:read_build)
+ end
+ end
+
it 'does not include the read_issue permission when the issue author is not a member of the private project' do
project = create(:project, :private)
issue = create(:issue, project: project, author: create(:user))
@@ -126,6 +139,126 @@ describe ProjectPolicy do
end
end
end
+
+ describe 'read_wiki' do
+ subject { described_class.new(user, project) }
+
+ member_roles = %i[guest developer]
+ stranger_roles = %i[anonymous non_member]
+
+ user_roles = stranger_roles + member_roles
+
+ # When a user is anonymous, their `current_user == nil`
+ let(:user) { create(:user) unless user_role == :anonymous }
+
+ before do
+ project.visibility = project_visibility
+ project.project_feature.update_attribute(:wiki_access_level, wiki_access_level)
+ project.add_user(user, user_role) if member_roles.include?(user_role)
+ end
+
+ title = ->(project_visibility, wiki_access_level, user_role) do
+ [
+ "project is #{Gitlab::VisibilityLevel.level_name project_visibility}",
+ "wiki is #{ProjectFeature.str_from_access_level wiki_access_level}",
+ "user is #{user_role}"
+ ].join(', ')
+ end
+
+ describe 'Situations where :read_wiki is always false' do
+ where(case_names: title,
+ project_visibility: Gitlab::VisibilityLevel.options.values,
+ wiki_access_level: [ProjectFeature::DISABLED],
+ user_role: user_roles)
+
+ with_them do
+ it { is_expected.to be_disallowed(:read_wiki) }
+ end
+ end
+
+ describe 'Situations where :read_wiki is always true' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::PUBLIC],
+ wiki_access_level: [ProjectFeature::ENABLED],
+ user_role: user_roles)
+
+ with_them do
+ it { is_expected.to be_allowed(:read_wiki) }
+ end
+ end
+
+ describe 'Situations where :read_wiki requires project membership' do
+ context 'the wiki is private, and the user is a member' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::PUBLIC,
+ Gitlab::VisibilityLevel::INTERNAL],
+ wiki_access_level: [ProjectFeature::PRIVATE],
+ user_role: member_roles)
+
+ with_them do
+ it { is_expected.to be_allowed(:read_wiki) }
+ end
+ end
+
+ context 'the wiki is private, and the user is not member' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::PUBLIC,
+ Gitlab::VisibilityLevel::INTERNAL],
+ wiki_access_level: [ProjectFeature::PRIVATE],
+ user_role: stranger_roles)
+
+ with_them do
+ it { is_expected.to be_disallowed(:read_wiki) }
+ end
+ end
+
+ context 'the wiki is enabled, and the user is a member' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::PRIVATE],
+ wiki_access_level: [ProjectFeature::ENABLED],
+ user_role: member_roles)
+
+ with_them do
+ it { is_expected.to be_allowed(:read_wiki) }
+ end
+ end
+
+ context 'the wiki is enabled, and the user is not a member' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::PRIVATE],
+ wiki_access_level: [ProjectFeature::ENABLED],
+ user_role: stranger_roles)
+
+ with_them do
+ it { is_expected.to be_disallowed(:read_wiki) }
+ end
+ end
+ end
+
+ describe 'Situations where :read_wiki prohibits anonymous access' do
+ context 'the user is not anonymous' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::INTERNAL],
+ wiki_access_level: [ProjectFeature::ENABLED, ProjectFeature::PUBLIC],
+ user_role: user_roles.reject { |u| u == :anonymous })
+
+ with_them do
+ it { is_expected.to be_allowed(:read_wiki) }
+ end
+ end
+
+ context 'the user is not anonymous' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::INTERNAL],
+ wiki_access_level: [ProjectFeature::ENABLED, ProjectFeature::PUBLIC],
+ user_role: %i[anonymous])
+
+ with_them do
+ it { is_expected.to be_disallowed(:read_wiki) }
+ end
+ end
+ end
+ end
end
context 'issues feature' do
diff --git a/spec/presenters/blobs/unfold_presenter_spec.rb b/spec/presenters/blobs/unfold_presenter_spec.rb
index 7ece5f623ce..83004809536 100644
--- a/spec/presenters/blobs/unfold_presenter_spec.rb
+++ b/spec/presenters/blobs/unfold_presenter_spec.rb
@@ -10,16 +10,31 @@ describe Blobs::UnfoldPresenter do
let(:subject) { described_class.new(blob, params) }
describe '#initialize' do
+ let(:result) { subject }
+
+ context 'with empty params' do
+ let(:params) { {} }
+
+ it 'sets default attributes' do
+ expect(result.full?).to eq(false)
+ expect(result.since).to eq(1)
+ expect(result.to).to eq(1)
+ expect(result.bottom).to eq(false)
+ expect(result.unfold).to eq(true)
+ expect(result.offset).to eq(0)
+ expect(result.indent).to eq(0)
+ end
+ end
+
context 'when full is false' do
let(:params) { { full: false, since: 2, to: 3, bottom: false, offset: 1, indent: 1 } }
it 'sets attributes' do
- result = subject
-
expect(result.full?).to eq(false)
expect(result.since).to eq(2)
expect(result.to).to eq(3)
expect(result.bottom).to eq(false)
+ expect(result.unfold).to eq(true)
expect(result.offset).to eq(1)
expect(result.indent).to eq(1)
end
@@ -29,12 +44,25 @@ describe Blobs::UnfoldPresenter do
let(:params) { { full: true, since: 2, to: 3, bottom: false, offset: 1, indent: 1 } }
it 'sets other attributes' do
- result = subject
-
expect(result.full?).to eq(true)
expect(result.since).to eq(1)
expect(result.to).to eq(blob.lines.size)
expect(result.bottom).to eq(false)
+ expect(result.unfold).to eq(false)
+ expect(result.offset).to eq(0)
+ expect(result.indent).to eq(0)
+ end
+ end
+
+ context 'when to is -1' do
+ let(:params) { { full: false, since: 2, to: -1, bottom: true, offset: 1, indent: 1 } }
+
+ it 'sets other attributes' do
+ expect(result.full?).to eq(false)
+ expect(result.since).to eq(2)
+ expect(result.to).to eq(blob.lines.size)
+ expect(result.bottom).to eq(false)
+ expect(result.unfold).to eq(false)
expect(result.offset).to eq(0)
expect(result.indent).to eq(0)
end
@@ -54,8 +82,10 @@ describe Blobs::UnfoldPresenter do
expect(lines.size).to eq(total_lines)
lines.each.with_index do |line, index|
- expect(line.text).to include("LC#{index + 1}")
- expect(line.text).to eq(line.rich_text)
+ line_number = index + 1
+
+ expect(line.text).to eq(line_number.to_s)
+ expect(line.rich_text).to include("LC#{line_number}")
expect(line.type).to be_nil
end
end
@@ -81,8 +111,9 @@ describe Blobs::UnfoldPresenter do
end
end
- context 'when since is greater than 1' do
- let(:params) { { since: 5, to: 10, offset: 10 } }
+ context 'when "since" is greater than 1' do
+ let(:default_params) { { since: 5, to: 10, offset: 10 } }
+ let(:params) { default_params }
it 'adds top match line' do
line = subject.diff_lines.first
@@ -91,6 +122,38 @@ describe Blobs::UnfoldPresenter do
expect(line.old_pos).to eq(5)
expect(line.new_pos).to eq(5)
end
+
+ context '"to" is higher than blob size' do
+ let(:params) { default_params.merge(to: total_lines + 10, bottom: true) }
+
+ it 'does not add bottom match line' do
+ line = subject.diff_lines.last
+
+ expect(line.type).to be_nil
+ end
+ end
+
+ context '"to" is equal to blob size' do
+ let(:params) { default_params.merge(to: total_lines, bottom: true) }
+
+ it 'does not add bottom match line' do
+ line = subject.diff_lines.last
+
+ expect(line.type).to be_nil
+ end
+ end
+
+ context '"to" is less than blob size' do
+ let(:params) { default_params.merge(to: total_lines - 3, bottom: true) }
+
+ it 'adds bottom match line' do
+ line = subject.diff_lines.last
+
+ expect(line.type).to eq('match')
+ expect(line.old_pos).to eq(total_lines - 3 - params[:offset])
+ expect(line.new_pos).to eq(total_lines - 3)
+ end
+ end
end
context 'when "to" is less than blob size' do
@@ -114,6 +177,22 @@ describe Blobs::UnfoldPresenter do
expect(line.type).to be_nil
end
end
+
+ context 'when "to" is "-1"' do
+ let(:params) { { since: 10, to: -1, offset: 10, bottom: true } }
+
+ it 'does not add bottom match line' do
+ line = subject.diff_lines.last
+
+ expect(line.type).to be_nil
+ end
+
+ it 'last line is the latest blob line' do
+ line = subject.diff_lines.last
+
+ expect(line.text).to eq(total_lines.to_s)
+ end
+ end
end
describe '#lines' do
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index 7054a70e2ed..6b988e2645b 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -43,7 +43,7 @@ describe Clusters::ClusterPresenter do
end
shared_examples 'ancestor clusters' do
- context 'ancestor clusters', :nested_groups do
+ context 'ancestor clusters' do
let(:root_group) { create(:group, name: 'Root Group') }
let(:parent) { create(:group, name: 'parent', parent: root_group) }
let(:child) { create(:group, name: 'child', parent: parent) }
diff --git a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
index 001545bb5df..b4bf39f3cdb 100644
--- a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
+++ b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
@@ -29,10 +29,6 @@ describe Projects::Settings::DeployKeysPresenter do
it 'returns the enabled_keys size' do
expect(presenter.enabled_keys_size).to eq(1)
end
-
- it 'returns true if there is any enabled_keys' do
- expect(presenter.any_keys_enabled?).to eq(true)
- end
end
describe '#available_keys/#available_project_keys' do
@@ -54,9 +50,5 @@ describe Projects::Settings::DeployKeysPresenter do
it 'returns the available_project_keys size' do
expect(presenter.available_project_keys_size).to eq(1)
end
-
- it 'shows if there is an available key' do
- expect(presenter.key_available?(deploy_key)).to eq(false)
- end
end
end
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 6c67d84b59b..342fcfa1041 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -155,6 +155,14 @@ describe API::AwardEmoji do
expect(json_response['user']['username']).to eq(user.username)
end
+ it 'marks Todos on the Issue as done' do
+ todo = create(:todo, target: issue, project: project, user: user)
+
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), params: { name: '8ball' }
+
+ expect(todo.reload).to be_done
+ end
+
it "returns a 400 bad request error if the name is not given" do
post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user)
@@ -209,6 +217,14 @@ describe API::AwardEmoji do
expect(json_response['user']['username']).to eq(user.username)
end
+ it 'marks Todos on the Noteable as done' do
+ todo = create(:todo, target: note2.noteable, project: project, user: user)
+
+ post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), params: { name: 'rocket' }
+
+ expect(todo.reload).to be_done
+ end
+
it "normalizes +1 as thumbsup award" do
post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), params: { name: '+1' }
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index de79e8c4c5c..0b9c0c2ebe9 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -63,7 +63,7 @@ describe API::Boards do
end
end
- describe "POST /groups/:id/boards/lists", :nested_groups do
+ describe "POST /groups/:id/boards/lists" do
set(:group) { create(:group) }
set(:board_parent) { create(:group, parent: group ) }
let(:url) { "/groups/#{board_parent.id}/boards/#{board.id}/lists" }
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index b5e45f99109..1be8883bd3c 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -8,10 +8,6 @@ describe API::CommitStatuses do
let(:developer) { create_user(:developer) }
let(:sha) { commit.id }
- let(:commit_status) do
- create(:commit_status, status: :pending, pipeline: pipeline)
- end
-
describe "GET /projects/:id/repository/commits/:sha/statuses" do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
@@ -239,6 +235,26 @@ describe API::CommitStatuses do
expect(CommitStatus.count).to eq 1
end
end
+
+ context 'when a pipeline id is specified' do
+ let!(:first_pipeline) { project.ci_pipelines.create(source: :push, sha: commit.id, ref: 'master', status: 'created') }
+ let!(:other_pipeline) { project.ci_pipelines.create(source: :push, sha: commit.id, ref: 'master', status: 'created') }
+
+ subject do
+ post api(post_url, developer), params: {
+ pipeline_id: other_pipeline.id,
+ state: 'success',
+ ref: 'master'
+ }
+ end
+
+ it 'update the correct pipeline' do
+ subject
+
+ expect(first_pipeline.reload.status).to eq('created')
+ expect(other_pipeline.reload.status).to eq('success')
+ end
+ end
end
context 'when retrying a commit status' do
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 3df5d9412f8..5e6ff40e8cf 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -126,6 +126,12 @@ describe API::Commits do
end
end
+ context "with empty ref_name parameter" do
+ let(:route) { "/projects/#{project_id}/repository/commits?ref_name=" }
+
+ it_behaves_like 'project commits'
+ end
+
context "path optional parameter" do
it "returns project commits matching provided path parameter" do
path = 'files/ruby/popen.rb'
@@ -281,7 +287,7 @@ describe API::Commits do
end
it 'does not increment the usage counters using access token authentication' do
- expect(::Gitlab::WebIdeCommitsCounter).not_to receive(:increment)
+ expect(::Gitlab::UsageDataCounters::WebIdeCounter).not_to receive(:increment_commits_count)
post api(url, user), params: valid_c_params
end
@@ -320,67 +326,132 @@ describe API::Commits do
end
end
- context 'when the API user is a guest' do
+ context 'when committing to a new branch' do
def last_commit_id(project, branch_name)
project.repository.find_branch(branch_name)&.dereferenced_target&.id
end
- let(:public_project) { create(:project, :public, :repository) }
- let!(:url) { "/projects/#{public_project.id}/repository/commits" }
- let(:guest) { create(:user).tap { |u| public_project.add_guest(u) } }
+ before do
+ valid_c_params[:start_branch] = 'master'
+ valid_c_params[:branch] = 'patch'
+ end
- it 'returns a 403' do
- post api(url, guest), params: valid_c_params
+ context 'when the API user is a guest' do
+ let(:public_project) { create(:project, :public, :repository) }
+ let(:url) { "/projects/#{public_project.id}/repository/commits" }
+ let(:guest) { create(:user).tap { |u| public_project.add_guest(u) } }
- expect(response).to have_gitlab_http_status(403)
- end
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
- context 'when start_project is provided' do
- context 'when posting to a forked project the user owns' do
- let!(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: true) }
- let!(:url) { "/projects/#{forked_project.id}/repository/commits" }
+ expect(response).to have_gitlab_http_status(403)
+ end
- before do
- valid_c_params[:start_branch] = "master"
- valid_c_params[:branch] = "patch"
- end
+ context 'when start_project is provided' do
+ context 'when posting to a forked project the user owns' do
+ let(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: true) }
+ let(:url) { "/projects/#{forked_project.id}/repository/commits" }
+
+ context 'identified by Integer (id)' do
+ before do
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect_request_with_status(201) { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+ end
+ end
- context 'identified by Integer (id)' do
- before do
- valid_c_params[:start_project] = public_project.id
+ context 'identified by String (full_path)' do
+ before do
+ valid_c_params[:start_project] = public_project.full_path
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect_request_with_status(201) { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+ end
+ end
+
+ context 'when branch already exists' do
+ before do
+ valid_c_params.delete(:start_branch)
+ valid_c_params[:branch] = 'master'
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'returns a 400' do
+ post api(url, guest), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq("A branch called 'master' already exists. Switch to that branch in order to make changes")
+ end
+
+ context 'when force is set to true' do
+ before do
+ valid_c_params[:force] = true
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect_request_with_status(201) { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:branch]) }
+ end
+ end
end
- it 'adds a new commit to forked_project and returns a 201' do
- expect { post api(url, guest), params: valid_c_params }
- .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
- .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+ context 'when start_sha is also provided' do
+ let(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: false) }
+ let(:start_sha) { public_project.repository.commit.parent.sha }
- expect(response).to have_gitlab_http_status(201)
+ before do
+ # initialize an empty repository to force fetching from the original project
+ forked_project.repository.create_if_not_exists
+
+ valid_c_params[:start_project] = public_project.id
+ valid_c_params[:start_sha] = start_sha
+ valid_c_params.delete(:start_branch)
+ end
+
+ it 'fetches the start_sha from the original project to use as parent commit and returns a 201' do
+ expect_request_with_status(201) { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(forked_project, 'master') }
+
+ last_commit = forked_project.repository.find_branch(valid_c_params[:branch]).dereferenced_target
+ expect(last_commit.parent_id).to eq(start_sha)
+ end
end
end
- context 'identified by String (full_path)' do
+ context 'when the target project is not part of the fork network of start_project' do
+ let(:unrelated_project) { create(:project, :public, :repository, creator: guest) }
+ let(:url) { "/projects/#{unrelated_project.id}/repository/commits" }
+
before do
- valid_c_params[:start_project] = public_project.full_path
+ valid_c_params[:start_branch] = 'master'
+ valid_c_params[:branch] = 'patch'
+ valid_c_params[:start_project] = public_project.id
end
- it 'adds a new commit to forked_project and returns a 201' do
- expect { post api(url, guest), params: valid_c_params }
- .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
- .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
- expect(response).to have_gitlab_http_status(201)
+ expect(response).to have_gitlab_http_status(403)
end
end
end
- context 'when the target project is not part of the fork network of start_project' do
- let(:unrelated_project) { create(:project, :public, :repository, creator: guest) }
- let!(:url) { "/projects/#{unrelated_project.id}/repository/commits" }
+ context 'when posting to a forked project the user does not have write access' do
+ let(:forked_project) { fork_project(public_project, user, namespace: user.namespace, repository: true) }
+ let(:url) { "/projects/#{forked_project.id}/repository/commits" }
before do
- valid_c_params[:start_branch] = "master"
- valid_c_params[:branch] = "patch"
+ valid_c_params[:start_branch] = 'master'
+ valid_c_params[:branch] = 'patch'
valid_c_params[:start_project] = public_project.id
end
@@ -392,20 +463,68 @@ describe API::Commits do
end
end
- context 'when posting to a forked project the user does not have write access' do
- let!(:forked_project) { fork_project(public_project, user, namespace: user.namespace, repository: true) }
- let!(:url) { "/projects/#{forked_project.id}/repository/commits" }
+ context 'when start_sha is provided' do
+ let(:start_sha) { project.repository.commit.parent.sha }
before do
- valid_c_params[:start_branch] = "master"
- valid_c_params[:branch] = "patch"
- valid_c_params[:start_project] = public_project.id
+ valid_c_params[:start_sha] = start_sha
+ valid_c_params.delete(:start_branch)
end
- it 'returns a 403' do
- post api(url, guest), params: valid_c_params
+ it 'returns a 400 if start_branch is also provided' do
+ valid_c_params[:start_branch] = 'master'
+ post api(url, user), params: valid_c_params
- expect(response).to have_gitlab_http_status(403)
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('start_branch, start_sha are mutually exclusive')
+ end
+
+ it 'returns a 400 if branch already exists' do
+ valid_c_params[:branch] = 'master'
+ post api(url, user), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq("A branch called 'master' already exists. Switch to that branch in order to make changes")
+ end
+
+ it 'returns a 400 if start_sha does not exist' do
+ valid_c_params[:start_sha] = '1' * 40
+ post api(url, user), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq("Cannot find start_sha '#{valid_c_params[:start_sha]}'")
+ end
+
+ it 'returns a 400 if start_sha is not a full SHA' do
+ valid_c_params[:start_sha] = start_sha.slice(0, 7)
+ post api(url, user), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq("Invalid start_sha '#{valid_c_params[:start_sha]}'")
+ end
+
+ it 'uses the start_sha as parent commit and returns a 201' do
+ expect_request_with_status(201) { post api(url, user), params: valid_c_params }
+ .to change { last_commit_id(project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(project, 'master') }
+
+ last_commit = project.repository.find_branch(valid_c_params[:branch]).dereferenced_target
+ expect(last_commit.parent_id).to eq(start_sha)
+ end
+
+ context 'when force is set to true and branch already exists' do
+ before do
+ valid_c_params[:force] = true
+ valid_c_params[:branch] = 'master'
+ end
+
+ it 'uses the start_sha as parent commit and returns a 201' do
+ expect_request_with_status(201) { post api(url, user), params: valid_c_params }
+ .to change { last_commit_id(project, valid_c_params[:branch]) }
+
+ last_commit = project.repository.find_branch(valid_c_params[:branch]).dereferenced_target
+ expect(last_commit.parent_id).to eq(start_sha)
+ end
end
end
end
diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb
index ca1ffe3c524..0420201efe3 100644
--- a/spec/requests/api/discussions_spec.rb
+++ b/spec/requests/api/discussions_spec.rb
@@ -9,6 +9,13 @@ describe API::Discussions do
project.add_developer(user)
end
+ context 'when discussions have cross-reference system notes' do
+ let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/discussions" }
+ let(:notes_in_response) { json_response.first['notes'] }
+
+ it_behaves_like 'with cross-reference system notes'
+ end
+
context 'when noteable is an Issue' do
let!(:issue) { create(:issue, project: project, author: user) }
let!(:issue_note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: user) }
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 1ad536258ba..21b67357543 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -186,6 +186,14 @@ describe API::Files do
expect(headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
+ it 'returns blame file info' do
+ url = route(file_path) + '/blame'
+
+ get api(url, current_user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
it 'sets inline content disposition by default' do
url = route(file_path) + "/raw"
@@ -252,6 +260,160 @@ describe API::Files do
end
end
+ describe 'GET /projects/:id/repository/files/:file_path/blame' do
+ shared_examples_for 'repository blame files' do
+ let(:expected_blame_range_sizes) do
+ [3, 2, 1, 2, 1, 1, 1, 1, 8, 1, 3, 1, 2, 1, 4, 1, 2, 2]
+ end
+
+ let(:expected_blame_range_commit_ids) do
+ %w[
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ ]
+ end
+
+ it 'returns file attributes in headers' do
+ head api(route(file_path) + '/blame', current_user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.headers['X-Gitlab-File-Path']).to eq(CGI.unescape(file_path))
+ expect(response.headers['X-Gitlab-File-Name']).to eq('popen.rb')
+ expect(response.headers['X-Gitlab-Last-Commit-Id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
+ expect(response.headers['X-Gitlab-Content-Sha256'])
+ .to eq('c440cd09bae50c4632cc58638ad33c6aa375b6109d811e76a9cc3a613c1e8887')
+ end
+
+ it 'returns blame file attributes as json' do
+ get api(route(file_path) + '/blame', current_user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.map { |x| x['lines'].size }).to eq(expected_blame_range_sizes)
+ expect(json_response.map { |x| x['commit']['id'] }).to eq(expected_blame_range_commit_ids)
+ range = json_response[0]
+ expect(range['lines']).to eq(["require 'fileutils'", "require 'open3'", ''])
+ expect(range['commit']['id']).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
+ expect(range['commit']['parent_ids']).to eq(['cfe32cf61b73a0d5e9f13e774abde7ff789b1660'])
+ expect(range['commit']['message'])
+ .to eq("Files, encoding and much more\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n")
+
+ expect(range['commit']['authored_date']).to eq('2014-02-27T08:14:56.000Z')
+ expect(range['commit']['author_name']).to eq('Dmitriy Zaporozhets')
+ expect(range['commit']['author_email']).to eq('dmitriy.zaporozhets@gmail.com')
+
+ expect(range['commit']['committed_date']).to eq('2014-02-27T08:14:56.000Z')
+ expect(range['commit']['committer_name']).to eq('Dmitriy Zaporozhets')
+ expect(range['commit']['committer_email']).to eq('dmitriy.zaporozhets@gmail.com')
+ end
+
+ it 'returns blame file info for files with dots' do
+ url = route('.gitignore') + '/blame'
+
+ get api(url, current_user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'returns file by commit sha' do
+ # This file is deleted on HEAD
+ file_path = 'files%2Fjs%2Fcommit%2Ejs%2Ecoffee'
+ params[:ref] = '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
+
+ get api(route(file_path) + '/blame', current_user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ context 'when mandatory params are not given' do
+ it_behaves_like '400 response' do
+ let(:request) { get api(route('any%2Ffile/blame'), current_user) }
+ end
+ end
+
+ context 'when file_path does not exist' do
+ let(:params) { { ref: 'master' } }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb/blame'), current_user), params: params }
+ let(:message) { '404 File Not Found' }
+ end
+ end
+
+ context 'when commit does not exist' do
+ let(:params) { { ref: '1111111111111111111111111111111111111111' } }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route(file_path + '/blame'), current_user), params: params }
+ let(:message) { '404 Commit Not Found' }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get api(route(file_path + '/blame'), current_user), params: params }
+ end
+ end
+ end
+
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository blame files' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route(file_path)), params: params }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository blame files' do
+ let(:current_user) { user }
+ end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route(file_path) + '/blame', guest), params: params }
+ end
+ end
+
+ context 'when PATs are used' do
+ it 'returns blame file by commit sha' do
+ token = create(:personal_access_token, scopes: ['read_repository'], user: user)
+
+ # This file is deleted on HEAD
+ file_path = 'files%2Fjs%2Fcommit%2Ejs%2Ecoffee'
+ params[:ref] = '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
+
+ get api(route(file_path) + '/blame', personal_access_token: token), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+
describe "GET /projects/:id/repository/files/:file_path/raw" do
shared_examples_for 'repository raw files' do
it 'returns raw file info' do
diff --git a/spec/requests/api/graphql/multiplexed_queries_spec.rb b/spec/requests/api/graphql/multiplexed_queries_spec.rb
index 844fd979285..9ebb57f6b9c 100644
--- a/spec/requests/api/graphql/multiplexed_queries_spec.rb
+++ b/spec/requests/api/graphql/multiplexed_queries_spec.rb
@@ -6,9 +6,9 @@ describe 'Multiplexed queries' do
it 'returns responses for multiple queries' do
queries = [
- { query: 'query($text: String) { echo(text: $text) }',
+ { query: 'query($text: String!) { echo(text: $text) }',
variables: { 'text' => 'Hello' } },
- { query: 'query($text: String) { echo(text: $text) }',
+ { query: 'query($text: String!) { echo(text: $text) }',
variables: { 'text' => 'World' } }
]
@@ -23,8 +23,8 @@ describe 'Multiplexed queries' do
it 'returns error and data combinations' do
queries = [
- { query: 'query($text: String) { broken query }' },
- { query: 'query working($text: String) { echo(text: $text) }',
+ { query: 'query($text: String!) { broken query }' },
+ { query: 'query working($text: String!) { echo(text: $text) }',
variables: { 'text' => 'World' } }
]
diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
index 3982125a38a..5b910d5bfe0 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
describe 'Adding an AwardEmoji' do
include GraphqlHelpers
- let(:current_user) { create(:user) }
- let(:awardable) { create(:note) }
- let(:project) { awardable.project }
+ set(:current_user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:awardable) { create(:note, project: project) }
let(:emoji_name) { 'thumbsup' }
let(:mutation) do
variables = {
@@ -43,7 +43,7 @@ describe 'Adding an AwardEmoji' do
end
context 'when the given awardable is not an Awardable' do
- let(:awardable) { create(:label) }
+ let(:awardable) { create(:label, project: project) }
it_behaves_like 'a mutation that does not create an AwardEmoji'
@@ -52,7 +52,7 @@ describe 'Adding an AwardEmoji' do
end
context 'when the given awardable is an Awardable but still cannot be awarded an emoji' do
- let(:awardable) { create(:system_note) }
+ let(:awardable) { create(:system_note, project: project) }
it_behaves_like 'a mutation that does not create an AwardEmoji'
@@ -73,6 +73,13 @@ describe 'Adding an AwardEmoji' do
expect(mutation_response['awardEmoji']['name']).to eq(emoji_name)
end
+ describe 'marking Todos as done' do
+ let(:user) { current_user}
+ subject { post_graphql_mutation(mutation, current_user: user) }
+
+ include_examples 'creating award emojis marks Todos as done'
+ end
+
context 'when there were active record validation errors' do
before do
expect_next_instance_of(AwardEmoji) do |award|
diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
index 31145730f10..ae628d3e56c 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
describe 'Toggling an AwardEmoji' do
include GraphqlHelpers
- let(:current_user) { create(:user) }
- let(:awardable) { create(:note) }
- let(:project) { awardable.project }
+ set(:current_user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:awardable) { create(:note, project: project) }
let(:emoji_name) { 'thumbsup' }
let(:mutation) do
variables = {
@@ -40,7 +40,7 @@ describe 'Toggling an AwardEmoji' do
end
context 'when the given awardable is not an Awardable' do
- let(:awardable) { create(:label) }
+ let(:awardable) { create(:label, project: project) }
it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
@@ -49,7 +49,7 @@ describe 'Toggling an AwardEmoji' do
end
context 'when the given awardable is an Awardable but still cannot be awarded an emoji' do
- let(:awardable) { create(:system_note) }
+ let(:awardable) { create(:system_note, project: project) }
it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
@@ -81,6 +81,13 @@ describe 'Toggling an AwardEmoji' do
expect(mutation_response['toggledOn']).to eq(true)
end
+ describe 'marking Todos as done' do
+ let(:user) { current_user}
+ subject { post_graphql_mutation(mutation, current_user: user) }
+
+ include_examples 'creating award emojis marks Todos as done'
+ end
+
context 'when there were active record validation errors' do
before do
expect_next_instance_of(AwardEmoji) do |award|
diff --git a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
new file mode 100644
index 00000000000..b04fcb9aece
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Adding a DiffNote' do
+ include GraphqlHelpers
+
+ set(:current_user) { create(:user) }
+ let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
+ let(:project) { create(:project, :repository) }
+ let(:diff_refs) { noteable.diff_refs }
+ let(:mutation) do
+ variables = {
+ noteable_id: GitlabSchema.id_from_object(noteable).to_s,
+ body: 'Body text',
+ position: {
+ paths: {
+ old_path: 'files/ruby/popen.rb',
+ new_path: 'files/ruby/popen2.rb'
+ },
+ new_line: 14,
+ base_sha: diff_refs.base_sha,
+ head_sha: diff_refs.head_sha,
+ start_sha: diff_refs.start_sha
+ }
+ }
+
+ graphql_mutation(:create_diff_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:create_diff_note)
+ end
+
+ it_behaves_like 'a Note mutation when the user does not have permission'
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a Note mutation that creates a Note'
+
+ it_behaves_like 'a Note mutation when there are active record validation errors', model: DiffNote
+
+ context do
+ let(:diff_refs) { build(:merge_request).diff_refs } # Allow fake diff refs so arguments are valid
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
+ end
+
+ it 'returns the note with the correct position' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq('Body text')
+ mutation_position_response = mutation_response['note']['position']
+ expect(mutation_position_response['positionType']).to eq('text')
+ expect(mutation_position_response['filePath']).to eq('files/ruby/popen2.rb')
+ expect(mutation_position_response['oldPath']).to eq('files/ruby/popen.rb')
+ expect(mutation_position_response['newPath']).to eq('files/ruby/popen2.rb')
+ expect(mutation_position_response['newLine']).to eq(14)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
new file mode 100644
index 00000000000..3ba6c689024
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Adding an image DiffNote' do
+ include GraphqlHelpers
+
+ set(:current_user) { create(:user) }
+ let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
+ let(:project) { create(:project, :repository) }
+ let(:diff_refs) { noteable.diff_refs }
+ let(:mutation) do
+ variables = {
+ noteable_id: GitlabSchema.id_from_object(noteable).to_s,
+ body: 'Body text',
+ position: {
+ paths: {
+ old_path: 'files/images/any_image.png',
+ new_path: 'files/images/any_image2.png'
+ },
+ width: 100,
+ height: 200,
+ x: 1,
+ y: 2,
+ base_sha: diff_refs.base_sha,
+ head_sha: diff_refs.head_sha,
+ start_sha: diff_refs.start_sha
+ }
+ }
+
+ graphql_mutation(:create_image_diff_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:create_image_diff_note)
+ end
+
+ it_behaves_like 'a Note mutation when the user does not have permission'
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a Note mutation that creates a Note'
+
+ it_behaves_like 'a Note mutation when there are active record validation errors', model: DiffNote
+
+ context do
+ let(:diff_refs) { build(:merge_request).diff_refs } # Allow fake diff refs so arguments are valid
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
+ end
+
+ it 'returns the note with the correct position' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq('Body text')
+ mutation_position_response = mutation_response['note']['position']
+ expect(mutation_position_response['filePath']).to eq('files/images/any_image2.png')
+ expect(mutation_position_response['oldPath']).to eq('files/images/any_image.png')
+ expect(mutation_position_response['newPath']).to eq('files/images/any_image2.png')
+ expect(mutation_position_response['positionType']).to eq('image')
+ expect(mutation_position_response['width']).to eq(100)
+ expect(mutation_position_response['height']).to eq(200)
+ expect(mutation_position_response['x']).to eq(1)
+ expect(mutation_position_response['y']).to eq(2)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
new file mode 100644
index 00000000000..14aaa430ac9
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Adding a Note' do
+ include GraphqlHelpers
+
+ set(:current_user) { create(:user) }
+ let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
+ let(:project) { create(:project) }
+ let(:discussion) { nil }
+ let(:mutation) do
+ variables = {
+ noteable_id: GitlabSchema.id_from_object(noteable).to_s,
+ discussion_id: (GitlabSchema.id_from_object(discussion).to_s if discussion),
+ body: 'Body text'
+ }
+
+ graphql_mutation(:create_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:create_note)
+ end
+
+ it_behaves_like 'a Note mutation when the user does not have permission'
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a Note mutation that creates a Note'
+
+ it_behaves_like 'a Note mutation when there are active record validation errors'
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
+
+ it 'returns the note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq('Body text')
+ end
+
+ describe 'creating Notes in reply to a discussion' do
+ context 'when the user does not have permission to create notes on the discussion' do
+ let(:discussion) { create(:discussion_note).to_discussion }
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ["The discussion does not exist or you don't have permission to perform this action"]
+ end
+
+ context 'when the user has permission to create notes on the discussion' do
+ let(:discussion) { create(:discussion_note, project: project).to_discussion }
+
+ it 'creates a Note in a discussion' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['discussion']['id']).to eq(discussion.to_global_id.to_s)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
new file mode 100644
index 00000000000..337a6e6f6e6
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Destroying a Note' do
+ include GraphqlHelpers
+
+ let!(:note) { create(:note) }
+ let(:mutation) do
+ variables = {
+ id: GitlabSchema.id_from_object(note).to_s
+ }
+
+ graphql_mutation(:destroy_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:destroy_note)
+ end
+
+ context 'when the user does not have permission' do
+ let(:current_user) { create(:user) }
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+
+ it 'does not destroy the Note' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { Note.count }
+ end
+ end
+
+ context 'when the user has permission' do
+ let(:current_user) { note.author }
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Note'
+
+ it 'destroys the Note' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { Note.count }.by(-1)
+ end
+
+ it 'returns an empty Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('note')
+ expect(mutation_response['note']).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/update_spec.rb b/spec/requests/api/graphql/mutations/notes/update_spec.rb
new file mode 100644
index 00000000000..958f640995a
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/update_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Updating a Note' do
+ include GraphqlHelpers
+
+ let!(:note) { create(:note, note: original_body) }
+ let(:original_body) { 'Initial body text' }
+ let(:updated_body) { 'Updated body text' }
+ let(:mutation) do
+ variables = {
+ id: GitlabSchema.id_from_object(note).to_s,
+ body: updated_body
+ }
+
+ graphql_mutation(:update_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:update_note)
+ end
+
+ context 'when the user does not have permission' do
+ let(:current_user) { create(:user) }
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+
+ it 'does not update the Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(note.reload.note).to eq(original_body)
+ end
+ end
+
+ context 'when the user has permission' do
+ let(:current_user) { note.author }
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Note'
+
+ it 'updates the Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(note.reload.note).to eq(updated_body)
+ end
+
+ it 'returns the updated Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq(updated_body)
+ end
+
+ context 'when there are ActiveRecord validation errors' do
+ let(:updated_body) { '' }
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ["Note can't be blank"]
+
+ it 'does not update the Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(note.reload.note).to eq(original_body)
+ end
+
+ it 'returns the Note with its original body' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq(original_body)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index de1cd9586b6..815e9531ecf 100644
--- a/spec/requests/api/graphql/namespace/projects_spec.rb
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting projects', :nested_groups do
+describe 'getting projects' do
include GraphqlHelpers
let(:group) { create(:group) }
@@ -58,9 +58,7 @@ describe 'getting projects', :nested_groups do
it 'finds only public projects' do
post_graphql(query, current_user: nil)
- expect(graphql_data['namespace']['projects']['edges'].size).to eq(1)
- project = graphql_data['namespace']['projects']['edges'][0]['node']
- expect(project['id']).to eq(public_project.to_global_id.to_s)
+ expect(graphql_data['namespace']).to be_nil
end
end
end
diff --git a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
new file mode 100644
index 00000000000..ac76d991bd4
--- /dev/null
+++ b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'rendering namespace statistics' do
+ include GraphqlHelpers
+
+ let(:namespace) { user.namespace }
+ let!(:statistics) { create(:namespace_root_storage_statistics, namespace: namespace, packages_size: 5.megabytes) }
+ let(:user) { create(:user) }
+
+ let(:query) do
+ graphql_query_for('namespace',
+ { 'fullPath' => namespace.full_path },
+ "rootStorageStatistics { #{all_graphql_fields_for('RootStorageStatistics')} }")
+ end
+
+ shared_examples 'a working namespace with storage statistics query' do
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: user)
+ end
+ end
+
+ it 'includes the packages size if the user can read the statistics' do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data['namespace']['rootStorageStatistics']).not_to be_blank
+ expect(graphql_data['namespace']['rootStorageStatistics']['packagesSize']).to eq(5.megabytes)
+ end
+ end
+
+ it_behaves_like 'a working namespace with storage statistics query'
+
+ context 'when the namespace is a group' do
+ let(:group) { create(:group) }
+ let(:namespace) { group }
+
+ before do
+ group.add_owner(user)
+ end
+
+ it_behaves_like 'a working namespace with storage statistics query'
+
+ context 'when the namespace is public' do
+ let(:group) { create(:group, :public)}
+
+ it 'hides statistics for unauthenticated requests' do
+ post_graphql(query, current_user: nil)
+
+ expect(graphql_data['namespace']).to be_blank
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/project_statistics_spec.rb b/spec/requests/api/graphql/project/project_statistics_spec.rb
index 14a3f37b779..ddee8537454 100644
--- a/spec/requests/api/graphql/project/project_statistics_spec.rb
+++ b/spec/requests/api/graphql/project/project_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'rendering namespace statistics' do
+describe 'rendering project statistics' do
include GraphqlHelpers
let(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/repository_spec.rb b/spec/requests/api/graphql/project/repository_spec.rb
index 67af612a4a0..261433a3d6a 100644
--- a/spec/requests/api/graphql/project/repository_spec.rb
+++ b/spec/requests/api/graphql/project/repository_spec.rb
@@ -34,4 +34,28 @@ describe 'getting a repository in a project' do
expect(graphql_data['project']).to be(nil)
end
end
+
+ context 'when the repository is only accessible to members' do
+ let(:project) do
+ create(:project, :public, :repository, repository_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it 'returns a repository for the owner' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']['repository']).not_to be_nil
+ end
+
+ it 'returns nil for the repository for other users' do
+ post_graphql(query, current_user: create(:user))
+
+ expect(graphql_data['project']['repository']).to be_nil
+ end
+
+ it 'returns nil for the repository for other users' do
+ post_graphql(query, current_user: nil)
+
+ expect(graphql_data['project']['repository']).to be_nil
+ end
+ end
end
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index 656d6f8b50b..54401ec4085 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -6,16 +6,6 @@ describe 'GraphQL' do
let(:query) { graphql_query_for('echo', 'text' => 'Hello world' ) }
- context 'graphql is disabled by feature flag' do
- before do
- stub_feature_flags(graphql: false)
- end
-
- it 'does not generate a route for GraphQL' do
- expect { post_graphql(query) }.to raise_error(ActionController::RoutingError)
- end
- end
-
context 'logging' do
shared_examples 'logging a graphql query' do
let(:expected_params) do
@@ -131,4 +121,35 @@ describe 'GraphQL' do
end
end
end
+
+ describe 'testing for Gitaly calls' do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+
+ let(:query) do
+ graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id))
+ end
+
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: user)
+ end
+ end
+
+ context 'when Gitaly is called' do
+ before do
+ allow(Gitlab::GitalyClient).to receive(:get_request_count).and_return(1, 2)
+ end
+
+ it "logs a warning that the 'calls_gitaly' field declaration is missing" do
+ expect(Gitlab::Sentry).to receive(:track_exception).once
+
+ post_graphql(query, current_user: user)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb
new file mode 100644
index 00000000000..46e3dd650cc
--- /dev/null
+++ b/spec/requests/api/group_clusters_spec.rb
@@ -0,0 +1,452 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::GroupClusters do
+ include KubernetesHelpers
+
+ let(:current_user) { create(:user) }
+ let(:developer_user) { create(:user) }
+ let(:group) { create(:group, :private) }
+
+ before do
+ group.add_developer(developer_user)
+ group.add_maintainer(current_user)
+ end
+
+ describe 'GET /groups/:id/clusters' do
+ let!(:extra_cluster) { create(:cluster, :provided_by_gcp, :group) }
+
+ let!(:clusters) do
+ create_list(:cluster, 5, :provided_by_gcp, :group, :production_environment,
+ groups: [group])
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ get api("/groups/#{group.id}/clusters", developer_user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ get api("/groups/#{group.id}/clusters", current_user)
+ end
+
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'includes pagination headers' do
+ expect(response).to include_pagination_headers
+ end
+
+ it 'only include authorized clusters' do
+ cluster_ids = json_response.map { |cluster| cluster['id'] }
+
+ expect(cluster_ids).to match_array(clusters.pluck(:id))
+ expect(cluster_ids).not_to include(extra_cluster.id)
+ end
+ end
+ end
+
+ describe 'GET /groups/:id/clusters/:cluster_id' do
+ let(:cluster_id) { cluster.id }
+
+ let(:platform_kubernetes) do
+ create(:cluster_platform_kubernetes, :configured)
+ end
+
+ let(:cluster) do
+ create(:cluster, :group, :provided_by_gcp, :with_domain,
+ platform_kubernetes: platform_kubernetes,
+ user: current_user,
+ groups: [group])
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ get api("/groups/#{group.id}/clusters/#{cluster_id}", developer_user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ get api("/groups/#{group.id}/clusters/#{cluster_id}", current_user)
+ end
+
+ it 'returns specific cluster' do
+ expect(json_response['id']).to eq(cluster.id)
+ end
+
+ it 'returns cluster information' do
+ expect(json_response['provider_type']).to eq('gcp')
+ expect(json_response['platform_type']).to eq('kubernetes')
+ expect(json_response['environment_scope']).to eq('*')
+ expect(json_response['cluster_type']).to eq('group_type')
+ expect(json_response['domain']).to eq('example.com')
+ end
+
+ it 'returns group information' do
+ cluster_group = json_response['group']
+
+ expect(cluster_group['id']).to eq(group.id)
+ expect(cluster_group['name']).to eq(group.name)
+ expect(cluster_group['web_url']).to eq(group.web_url)
+ end
+
+ it 'returns kubernetes platform information' do
+ platform = json_response['platform_kubernetes']
+
+ expect(platform['api_url']).to eq('https://kubernetes.example.com')
+ expect(platform['ca_cert']).to be_present
+ end
+
+ it 'returns user information' do
+ user = json_response['user']
+
+ expect(user['id']).to eq(current_user.id)
+ expect(user['username']).to eq(current_user.username)
+ end
+
+ it 'returns GCP provider information' do
+ gcp_provider = json_response['provider_gcp']
+
+ expect(gcp_provider['cluster_id']).to eq(cluster.id)
+ expect(gcp_provider['status_name']).to eq('created')
+ expect(gcp_provider['gcp_project_id']).to eq('test-gcp-project')
+ expect(gcp_provider['zone']).to eq('us-central1-a')
+ expect(gcp_provider['machine_type']).to eq('n1-standard-2')
+ expect(gcp_provider['num_nodes']).to eq(3)
+ expect(gcp_provider['endpoint']).to eq('111.111.111.111')
+ end
+
+ context 'when cluster has no provider' do
+ let(:cluster) do
+ create(:cluster, :group, :provided_by_user,
+ groups: [group])
+ end
+
+ it 'does not include GCP provider info' do
+ expect(json_response['provider_gcp']).not_to be_present
+ end
+ end
+
+ context 'with non-existing cluster' do
+ let(:cluster_id) { 123 }
+
+ it 'returns 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
+
+ shared_context 'kubernetes calls stubbed' do
+ before do
+ stub_kubeclient_discover(api_url)
+ end
+ end
+
+ describe 'POST /groups/:id/clusters/user' do
+ include_context 'kubernetes calls stubbed'
+
+ let(:api_url) { 'https://kubernetes.example.com' }
+ let(:authorization_type) { 'rbac' }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token',
+ authorization_type: authorization_type
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-cluster',
+ domain: 'domain.example.com',
+ managed: false,
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
+ end
+
+ context 'with valid params' do
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(201)
+ end
+
+ it 'creates a new Cluster::Cluster' do
+ cluster_result = Clusters::Cluster.find(json_response["id"])
+ platform_kubernetes = cluster_result.platform
+
+ expect(cluster_result).to be_user
+ expect(cluster_result).to be_kubernetes
+ expect(cluster_result.group).to eq(group)
+ expect(cluster_result.name).to eq('test-cluster')
+ expect(cluster_result.domain).to eq('domain.example.com')
+ expect(cluster_result.managed).to be_falsy
+ expect(platform_kubernetes.rbac?).to be_truthy
+ expect(platform_kubernetes.api_url).to eq(api_url)
+ expect(platform_kubernetes.token).to eq('sample-token')
+ end
+ end
+
+ context 'when user does not indicate authorization type' do
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token'
+ }
+ end
+
+ it 'defaults to RBAC' do
+ cluster_result = Clusters::Cluster.find(json_response['id'])
+
+ expect(cluster_result.platform_kubernetes.rbac?).to be_truthy
+ end
+ end
+
+ context 'when user sets authorization type as ABAC' do
+ let(:authorization_type) { 'abac' }
+
+ it 'creates an ABAC cluster' do
+ cluster_result = Clusters::Cluster.find(json_response['id'])
+
+ expect(cluster_result.platform.abac?).to be_truthy
+ end
+ end
+
+ context 'with invalid params' do
+ let(:api_url) { 'invalid_api_url' }
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'does not create a new Clusters::Cluster' do
+ expect(group.reload.clusters).to be_empty
+ end
+
+ it 'returns validation errors' do
+ expect(json_response['message']['platform_kubernetes.api_url'].first).to be_present
+ end
+ end
+ end
+
+ context 'when user tries to add multiple clusters' do
+ before do
+ create(:cluster, :provided_by_gcp, :group,
+ groups: [group])
+
+ post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
+ end
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['base'].first).to include('Instance does not support multiple Kubernetes clusters')
+ end
+ end
+
+ context 'non-authorized user' do
+ before do
+ post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
+ end
+
+ it 'responds with 403' do
+ expect(response).to have_gitlab_http_status(403)
+
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+ end
+ end
+
+ describe 'PUT /groups/:id/clusters/:cluster_id' do
+ include_context 'kubernetes calls stubbed'
+
+ let(:api_url) { 'https://kubernetes.example.com' }
+
+ let(:update_params) do
+ {
+ domain: domain,
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ let(:domain) { 'new-domain.com' }
+ let(:platform_kubernetes_attributes) { {} }
+
+ let(:cluster) do
+ create(:cluster, :group, :provided_by_gcp,
+ groups: [group], domain: 'old-domain.com')
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ put api("/groups/#{group.id}/clusters/#{cluster.id}", developer_user), params: update_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ put api("/groups/#{group.id}/clusters/#{cluster.id}", current_user), params: update_params
+
+ cluster.reload
+ end
+
+ context 'with valid params' do
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'updates cluster attributes' do
+ expect(cluster.domain).to eq('new-domain.com')
+ end
+ end
+
+ context 'with invalid params' do
+ let(:domain) { 'invalid domain' }
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'does not update cluster attributes' do
+ expect(cluster.domain).to eq('old-domain.com')
+ end
+
+ it 'returns validation errors' do
+ expect(json_response['message']['domain'].first).to match('contains invalid characters (valid characters: [a-z0-9\\-])')
+ end
+ end
+
+ context 'with a GCP cluster' do
+ context 'when user tries to change GCP specific fields' do
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: 'https://new-api-url.com',
+ token: 'new-sample-token'
+ }
+ end
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'returns validation error' do
+ expect(json_response['message']['platform_kubernetes.base'].first).to eq('Cannot modify managed Kubernetes cluster')
+ end
+ end
+
+ context 'when user tries to change domain' do
+ let(:domain) { 'new-domain.com' }
+
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+
+ context 'with an user cluster' do
+ let(:api_url) { 'https://new-api-url.com' }
+
+ let(:cluster) do
+ create(:cluster, :group, :provided_by_user,
+ groups: [group])
+ end
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'new-sample-token'
+ }
+ end
+
+ let(:update_params) do
+ {
+ name: 'new-name',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'updates platform kubernetes attributes' do
+ platform_kubernetes = cluster.platform_kubernetes
+
+ expect(cluster.name).to eq('new-name')
+ expect(platform_kubernetes.api_url).to eq('https://new-api-url.com')
+ expect(platform_kubernetes.token).to eq('new-sample-token')
+ end
+ end
+
+ context 'with a cluster that does not belong to user' do
+ let(:cluster) { create(:cluster, :group, :provided_by_user) }
+
+ it 'responds with 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /groups/:id/clusters/:cluster_id' do
+ let(:cluster_params) { { cluster_id: cluster.id } }
+
+ let(:cluster) do
+ create(:cluster, :group, :provided_by_gcp,
+ groups: [group])
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ delete api("/groups/#{group.id}/clusters/#{cluster.id}", developer_user), params: cluster_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ delete api("/groups/#{group.id}/clusters/#{cluster.id}", current_user), params: cluster_params
+ end
+
+ it 'responds with 204' do
+ expect(response).to have_gitlab_http_status(204)
+ end
+
+ it 'deletes the cluster' do
+ expect(Clusters::Cluster.exists?(id: cluster.id)).to be_falsy
+ end
+
+ context 'with a cluster that does not belong to user' do
+ let(:cluster) { create(:cluster, :group, :provided_by_user) }
+
+ it 'responds with 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/group_container_repositories_spec.rb b/spec/requests/api/group_container_repositories_spec.rb
new file mode 100644
index 00000000000..0a41e455d01
--- /dev/null
+++ b/spec/requests/api/group_container_repositories_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::GroupContainerRepositories do
+ set(:group) { create(:group, :private) }
+ set(:project) { create(:project, :private, group: group) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+
+ let(:root_repository) { create(:container_repository, :root, project: project) }
+ let(:test_repository) { create(:container_repository, project: project) }
+
+ let(:users) do
+ {
+ anonymous: nil,
+ guest: guest,
+ reporter: reporter
+ }
+ end
+
+ let(:api_user) { reporter }
+
+ before do
+ group.add_reporter(reporter)
+ group.add_guest(guest)
+
+ stub_feature_flags(container_registry_api: true)
+ stub_container_registry_config(enabled: true)
+
+ root_repository
+ test_repository
+ end
+
+ describe 'GET /groups/:id/registry/repositories' do
+ let(:url) { "/groups/#{group.id}/registry/repositories" }
+
+ subject { get api(url, api_user) }
+
+ it_behaves_like 'rejected container repository access', :guest, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
+
+ it_behaves_like 'returns repositories for allowed users', :reporter, 'group' do
+ let(:object) { group }
+ end
+
+ context 'with invalid group id' do
+ let(:url) { '/groups/123412341234/registry/repositories' }
+
+ it 'returns not found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb
index 3769f8b78e4..0be4e2e9121 100644
--- a/spec/requests/api/group_labels_spec.rb
+++ b/spec/requests/api/group_labels_spec.rb
@@ -14,12 +14,25 @@ describe API::GroupLabels do
get api("/groups/#{group.id}/labels", user)
expect(response).to have_gitlab_http_status(200)
- expect(response).to match_response_schema('public_api/v4/group_labels')
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
+ expect(json_response).to all(match_schema('public_api/v4/labels/label'))
expect(json_response.size).to eq(2)
expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug')
end
+
+ context 'when the with_counts parameter is set' do
+ it 'includes counts in the response' do
+ get api("/groups/#{group.id}/labels", user), params: { with_counts: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response).to all(match_schema('public_api/v4/labels/label_with_counts'))
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |r| r['open_issues_count'] }).to contain_exactly(0, 0)
+ end
+ end
end
describe 'POST /groups/:id/labels' do
@@ -94,7 +107,7 @@ describe API::GroupLabels do
expect(response).to have_gitlab_http_status(400)
end
- it "does not delete parent's group labels", :nested_groups do
+ it "does not delete parent's group labels" do
subgroup = create(:group, parent: group)
subgroup_label = create(:group_label, title: 'feature', group: subgroup)
@@ -127,7 +140,7 @@ describe API::GroupLabels do
expect(json_response['description']).to eq('test')
end
- it "does not update parent's group label", :nested_groups do
+ it "does not update parent's group label" do
subgroup = create(:group, parent: group)
subgroup_label = create(:group_label, title: 'feature', group: subgroup)
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index c41408fba65..50f36141aed 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -530,7 +530,7 @@ describe API::Groups do
expect(json_response.length).to eq(2)
end
- it "returns projects including those in subgroups", :nested_groups do
+ it "returns projects including those in subgroups" do
subgroup = create(:group, parent: group1)
create(:project, group: subgroup)
create(:project, group: subgroup)
@@ -642,7 +642,7 @@ describe API::Groups do
end
end
- describe 'GET /groups/:id/subgroups', :nested_groups do
+ describe 'GET /groups/:id/subgroups' do
let!(:subgroup1) { create(:group, parent: group1) }
let!(:subgroup2) { create(:group, :private, parent: group1) }
let!(:subgroup3) { create(:group, :private, parent: group2) }
@@ -786,7 +786,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(403)
end
- context 'as owner', :nested_groups do
+ context 'as owner' do
before do
group2.add_owner(user1)
end
@@ -798,15 +798,15 @@ describe API::Groups do
end
end
- context 'as maintainer', :nested_groups do
+ context 'as maintainer' do
before do
group2.add_maintainer(user1)
end
- it 'cannot create subgroups' do
+ it 'can create subgroups' do
post api("/groups", user1), params: { parent_id: group2.id, name: 'foo', path: 'foo' }
- expect(response).to have_gitlab_http_status(403)
+ expect(response).to have_gitlab_http_status(201)
end
end
end
@@ -825,7 +825,7 @@ describe API::Groups do
expect(json_response["visibility"]).to eq(Gitlab::VisibilityLevel.string_level(Gitlab::CurrentSettings.current_application_settings.default_group_visibility))
end
- it "creates a nested group", :nested_groups do
+ it "creates a nested group" do
parent = create(:group)
parent.add_owner(user3)
group = attributes_for(:group, { parent_id: parent.id })
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index fcbff19bd61..3ab1818bebb 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -474,7 +474,7 @@ describe API::Internal do
'ssh',
{
authentication_abilities: [:read_project, :download_code, :push_code],
- namespace_path: project.namespace.name,
+ namespace_path: project.namespace.path,
project_path: project.path,
redirected_path: nil
}
@@ -753,7 +753,7 @@ describe API::Internal do
end
describe 'GET /internal/merge_request_urls' do
- let(:repo_name) { "#{project.namespace.name}/#{project.path}" }
+ let(:repo_name) { "#{project.full_path}" }
let(:changes) { URI.escape("#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch") }
before do
@@ -765,7 +765,7 @@ describe API::Internal do
expect(json_response).to match [{
"branch_name" => "new_branch",
- "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
+ "url" => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
"new_merge_request" => true
}]
end
@@ -786,7 +786,7 @@ describe API::Internal do
expect(json_response).to match [{
"branch_name" => "new_branch",
- "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
+ "url" => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
"new_merge_request" => true
}]
end
@@ -927,7 +927,7 @@ describe API::Internal do
expect(json_response['merge_request_urls']).to match [{
"branch_name" => branch_name,
- "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}",
+ "url" => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}",
"new_merge_request" => true
}]
end
@@ -970,7 +970,7 @@ describe API::Internal do
expect(json_response['merge_request_urls']).to match [{
'branch_name' => branch_name,
- 'url' => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/1",
+ 'url' => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/1",
'new_merge_request' => false
}]
end
diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb
index 8b02cf56e9f..c487471e4a1 100644
--- a/spec/requests/api/issues/get_group_issues_spec.rb
+++ b/spec/requests/api/issues/get_group_issues_spec.rb
@@ -23,7 +23,11 @@ describe API::Issues do
describe 'GET /groups/:id/issues' do
let!(:group) { create(:group) }
- let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) }
+ let!(:group_project) { create(:project, :public, :repository, creator_id: user.id, namespace: group) }
+ let!(:private_mrs_project) do
+ create(:project, :public, :repository, creator_id: user.id, namespace: group, merge_requests_access_level: ProjectFeature::PRIVATE)
+ end
+
let!(:group_closed_issue) do
create :closed_issue,
author: user,
@@ -78,7 +82,7 @@ describe API::Issues do
end
end
- context 'when group has subgroups', :nested_groups do
+ context 'when group has subgroups' do
let(:subgroup_1) { create(:group, parent: group) }
let(:subgroup_2) { create(:group, parent: subgroup_1) }
@@ -234,6 +238,30 @@ describe API::Issues do
it_behaves_like 'group issues statistics'
end
end
+
+ context "when returns issue merge_requests_count for different access levels" do
+ let!(:merge_request1) do
+ create(:merge_request,
+ :simple,
+ author: user,
+ source_project: private_mrs_project,
+ target_project: private_mrs_project,
+ description: "closes #{group_issue.to_reference(private_mrs_project)}")
+ end
+ let!(:merge_request2) do
+ create(:merge_request,
+ :simple,
+ author: user,
+ source_project: group_project,
+ target_project: group_project,
+ description: "closes #{group_issue.to_reference}")
+ end
+
+ it_behaves_like 'accessible merge requests count' do
+ let(:api_url) { base_url }
+ let(:target_issue) { group_issue }
+ end
+ end
end
end
@@ -314,6 +342,14 @@ describe API::Issues do
group_project.add_reporter(user)
end
+ it 'exposes known attributes' do
+ get api(base_url, admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.last.keys).to include(*%w(id iid project_id title description))
+ expect(json_response.last).not_to have_key('subscribed')
+ end
+
it 'returns all group issues (including opened and closed)' do
get api(base_url, admin)
diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb
index 0b0f754ab57..b7aa3f93451 100644
--- a/spec/requests/api/issues/get_project_issues_spec.rb
+++ b/spec/requests/api/issues/get_project_issues_spec.rb
@@ -4,8 +4,9 @@ require 'spec_helper'
describe API::Issues do
set(:user) { create(:user) }
- set(:project) do
- create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ set(:project) { create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace) }
+ set(:private_mrs_project) do
+ create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace, merge_requests_access_level: ProjectFeature::PRIVATE)
end
let(:user2) { create(:user) }
@@ -60,9 +61,28 @@ describe API::Issues do
let(:no_milestone_title) { 'None' }
let(:any_milestone_title) { 'Any' }
+ let!(:merge_request1) do
+ create(:merge_request,
+ :simple,
+ author: user,
+ source_project: project,
+ target_project: project,
+ description: "closes #{issue.to_reference}")
+ end
+ let!(:merge_request2) do
+ create(:merge_request,
+ :simple,
+ author: user,
+ source_project: private_mrs_project,
+ target_project: private_mrs_project,
+ description: "closes #{issue.to_reference(private_mrs_project)}")
+ end
+
before(:all) do
project.add_reporter(user)
project.add_guest(guest)
+ private_mrs_project.add_reporter(user)
+ private_mrs_project.add_guest(guest)
end
before do
@@ -257,6 +277,11 @@ describe API::Issues do
expect_paginated_array_response(issue.id)
end
+ it_behaves_like 'accessible merge requests count' do
+ let(:api_url) { "/projects/#{project.id}/issues" }
+ let(:target_issue) { issue }
+ end
+
context 'with labeled issues' do
let(:label_b) { create(:label, title: 'foo', project: project) }
let(:label_c) { create(:label, title: 'bar', project: project) }
@@ -421,6 +446,14 @@ describe API::Issues do
expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
end
+ it 'exposes known attributes' do
+ get api("#{base_url}/issues", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.last.keys).to include(*%w(id iid project_id title description))
+ expect(json_response.last).not_to have_key('subscribed')
+ end
+
context 'issues_statistics' do
context 'no state is treated as all state' do
let(:params) { {} }
@@ -550,6 +583,7 @@ describe API::Issues do
expect(json_response['assignee']).to be_a Hash
expect(json_response['author']).to be_a Hash
expect(json_response['confidential']).to be_falsy
+ expect(json_response['subscribed']).to be_truthy
end
it 'exposes the closed_at attribute' do
@@ -636,34 +670,26 @@ describe API::Issues do
expect(json_response['iid']).to eq(confidential_issue.iid)
end
end
- end
-
- describe 'GET :id/issues/:issue_iid/closed_by' do
- let(:merge_request) do
- create(:merge_request,
- :simple,
- author: user,
- source_project: project,
- target_project: project,
- description: "closes #{issue.to_reference}")
- end
- before do
- create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request)
+ it_behaves_like 'accessible merge requests count' do
+ let(:api_url) { "/projects/#{project.id}/issues/#{issue.iid}" }
+ let(:target_issue) { issue }
end
+ end
+ describe 'GET :id/issues/:issue_iid/closed_by' do
context 'when unauthenticated' do
it 'return public project issues' do
get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by")
- expect_paginated_array_response(merge_request.id)
+ expect_paginated_array_response(merge_request1.id)
end
end
it 'returns merge requests that will close issue on merge' do
get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by", user)
- expect_paginated_array_response(merge_request.id)
+ expect_paginated_array_response(merge_request1.id)
end
context 'when no merge requests will close issue' do
@@ -721,13 +747,6 @@ describe API::Issues do
end
it 'returns merge requests that mentioned a issue' do
- create(:merge_request,
- :simple,
- author: user,
- source_project: project,
- target_project: project,
- description: 'Some description')
-
get_related_merge_requests(project.id, issue.iid, user)
expect_paginated_array_response(related_mr.id)
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
index f32ffd1c77b..f19c2dcc6fe 100644
--- a/spec/requests/api/issues/issues_spec.rb
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -4,8 +4,9 @@ require 'spec_helper'
describe API::Issues do
set(:user) { create(:user) }
- set(:project) do
- create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ set(:project) { create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace) }
+ set(:private_mrs_project) do
+ create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace, merge_requests_access_level: ProjectFeature::PRIVATE)
end
let(:user2) { create(:user) }
@@ -63,6 +64,8 @@ describe API::Issues do
before(:all) do
project.add_reporter(user)
project.add_guest(guest)
+ private_mrs_project.add_reporter(user)
+ private_mrs_project.add_guest(guest)
end
before do
@@ -213,6 +216,10 @@ describe API::Issues do
expect_paginated_array_response([issue.id, closed_issue.id])
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.last).to have_key('web_url')
+ # Calculating the value of subscribed field triggers Markdown
+ # processing. We can't do that for multiple issues / merge
+ # requests in a single API request.
+ expect(json_response.last).not_to have_key('subscribed')
end
it 'returns an array of closed issues' do
@@ -600,6 +607,22 @@ describe API::Issues do
expect_paginated_array_response([closed_issue.id, issue.id])
end
+ context 'with issues list sort options' do
+ it 'accepts only predefined order by params' do
+ API::Helpers::IssuesHelpers.sort_options.each do |sort_opt|
+ get api('/issues', user), params: { order_by: sort_opt, sort: 'asc' }
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ it 'fails to sort with non predefined options' do
+ %w(milestone title abracadabra).each do |sort_opt|
+ get api('/issues', user), params: { order_by: sort_opt, sort: 'asc' }
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+ end
+
it 'matches V4 response schema' do
get api('/issues', user)
@@ -725,6 +748,30 @@ describe API::Issues do
end
end
end
+
+ context "when returns issue merge_requests_count for different access levels" do
+ let!(:merge_request1) do
+ create(:merge_request,
+ :simple,
+ author: user,
+ source_project: private_mrs_project,
+ target_project: private_mrs_project,
+ description: "closes #{issue.to_reference(private_mrs_project)}")
+ end
+ let!(:merge_request2) do
+ create(:merge_request,
+ :simple,
+ author: user,
+ source_project: project,
+ target_project: project,
+ description: "closes #{issue.to_reference}")
+ end
+
+ it_behaves_like 'accessible merge requests count' do
+ let(:api_url) { "/issues" }
+ let(:target_issue) { issue }
+ end
+ end
end
describe 'DELETE /projects/:id/issues/:issue_iid' do
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 518181e4d93..9aef67e28a7 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -6,70 +6,255 @@ describe API::Labels do
let!(:label1) { create(:label, title: 'label1', project: project) }
let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) }
+ shared_examples 'label update API' do
+ it 'returns 200 if name is changed' do
+ request_params = {
+ new_name: 'New Label'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['name']).to eq('New Label')
+ expect(json_response['color']).to eq(label1.color)
+ end
+
+ it 'returns 200 if colors is changed' do
+ request_params = {
+ color: '#FFFFFF'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['name']).to eq(label1.name)
+ expect(json_response['color']).to eq('#FFFFFF')
+ end
+
+ it 'returns 200 if a priority is added' do
+ request_params = {
+ priority: 3
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq(label1.name)
+ expect(json_response['priority']).to eq(3)
+ end
+
+ it 'returns 400 if no new parameters given' do
+ put api("/projects/#{project.id}/labels", user), params: spec_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('new_name, color, description, priority are missing, '\
+ 'at least one parameter must be provided')
+ end
+
+ it 'returns 400 when color code is too short' do
+ request_params = {
+ color: '#FF'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['color']).to eq(['must be a valid color code'])
+ end
+
+ it 'returns 400 for too long color code' do
+ request_params = {
+ color: '#FFAAFFFF'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['color']).to eq(['must be a valid color code'])
+ end
+
+ it 'returns 400 for invalid priority' do
+ request_params = {
+ priority: 'foo'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'returns 200 if name and colors and description are changed' do
+ request_params = {
+ new_name: 'New Label',
+ color: '#FFFFFF',
+ description: 'test'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['name']).to eq('New Label')
+ expect(json_response['color']).to eq('#FFFFFF')
+ expect(json_response['description']).to eq('test')
+ end
+
+ it 'returns 400 for invalid name' do
+ request_params = {
+ new_name: ',',
+ color: '#FFFFFF'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['title']).to eq(['is invalid'])
+ end
+
+ it 'returns 200 if description is changed' do
+ request_params = {
+ description: 'test'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['id']).to eq(expected_response_label_id)
+ expect(json_response['description']).to eq('test')
+ end
+
+ it 'returns 200 if priority is changed' do
+ request_params = {
+ priority: 10
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response.status).to eq(200)
+ expect(json_response['id']).to eq(expected_response_label_id)
+ expect(json_response['priority']).to eq(10)
+ end
+
+ it 'returns 200 if a priority is removed' do
+ label = find_by_spec_params(spec_params)
+ expect(label).not_to be_nil
+
+ label.priorities.create(project: label.project, priority: 1)
+ label.save!
+
+ request_params = {
+ priority: nil
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response.status).to eq(200)
+ expect(json_response['id']).to eq(expected_response_label_id)
+ expect(json_response['priority']).to be_nil
+ end
+
+ def find_by_spec_params(params)
+ if params.key?(:label_id)
+ Label.find(params[:label_id])
+ else
+ Label.find_by(name: params[:name])
+ end
+ end
+ end
+
+ shared_examples 'label delete API' do
+ it 'returns 204 for existing label' do
+ delete api("/projects/#{project.id}/labels", user), params: spec_params
+
+ expect(response).to have_gitlab_http_status(204)
+ end
+ end
+
before do
project.add_maintainer(user)
end
describe 'GET /projects/:id/labels' do
- it 'returns all available labels to the project' do
- group = create(:group)
- group_label = create(:group_label, title: 'feature', group: group)
- project.update(group: group)
- create(:labeled_issue, project: project, labels: [group_label], author: user)
- create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed)
- create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
+ let(:group) { create(:group) }
+ let!(:group_label) { create(:group_label, title: 'feature', group: group) }
- expected_keys = %w(
- id name color text_color description
- open_issues_count closed_issues_count open_merge_requests_count
- subscribed priority is_project_label
- )
+ before do
+ project.update!(group: group)
+ end
+ it 'returns all available labels to the project' do
get api("/projects/#{project.id}/labels", user)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+ expect(json_response).to all(match_schema('public_api/v4/labels/project_label'))
expect(json_response.size).to eq(3)
- expect(json_response.first.keys).to match_array expected_keys
expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name])
+ end
- label1_response = json_response.find { |l| l['name'] == label1.title }
- group_label_response = json_response.find { |l| l['name'] == group_label.title }
- priority_label_response = json_response.find { |l| l['name'] == priority_label.title }
-
- expect(label1_response['open_issues_count']).to eq(0)
- expect(label1_response['closed_issues_count']).to eq(1)
- expect(label1_response['open_merge_requests_count']).to eq(0)
- expect(label1_response['name']).to eq(label1.name)
- expect(label1_response['color']).to be_present
- expect(label1_response['text_color']).to be_present
- expect(label1_response['description']).to be_nil
- expect(label1_response['priority']).to be_nil
- expect(label1_response['subscribed']).to be_falsey
- expect(label1_response['is_project_label']).to be_truthy
-
- expect(group_label_response['open_issues_count']).to eq(1)
- expect(group_label_response['closed_issues_count']).to eq(0)
- expect(group_label_response['open_merge_requests_count']).to eq(0)
- expect(group_label_response['name']).to eq(group_label.name)
- expect(group_label_response['color']).to be_present
- expect(group_label_response['text_color']).to be_present
- expect(group_label_response['description']).to be_nil
- expect(group_label_response['priority']).to be_nil
- expect(group_label_response['subscribed']).to be_falsey
- expect(group_label_response['is_project_label']).to be_falsey
-
- expect(priority_label_response['open_issues_count']).to eq(0)
- expect(priority_label_response['closed_issues_count']).to eq(0)
- expect(priority_label_response['open_merge_requests_count']).to eq(1)
- expect(priority_label_response['name']).to eq(priority_label.name)
- expect(priority_label_response['color']).to be_present
- expect(priority_label_response['text_color']).to be_present
- expect(priority_label_response['description']).to be_nil
- expect(priority_label_response['priority']).to eq(3)
- expect(priority_label_response['subscribed']).to be_falsey
- expect(priority_label_response['is_project_label']).to be_truthy
+ context 'when the with_counts parameter is set' do
+ before do
+ create(:labeled_issue, project: project, labels: [group_label], author: user)
+ create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed)
+ create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
+ end
+
+ it 'includes counts in the response' do
+ get api("/projects/#{project.id}/labels", user), params: { with_counts: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to all(match_schema('public_api/v4/labels/project_label_with_counts'))
+ expect(json_response.size).to eq(3)
+ expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name])
+
+ label1_response = json_response.find { |l| l['name'] == label1.title }
+ group_label_response = json_response.find { |l| l['name'] == group_label.title }
+ priority_label_response = json_response.find { |l| l['name'] == priority_label.title }
+
+ expect(label1_response).to include('open_issues_count' => 0,
+ 'closed_issues_count' => 1,
+ 'open_merge_requests_count' => 0,
+ 'name' => label1.name,
+ 'description' => nil,
+ 'color' => a_string_matching(/^#\h{6}$/),
+ 'text_color' => a_string_matching(/^#\h{6}$/),
+ 'priority' => nil,
+ 'subscribed' => false,
+ 'is_project_label' => true)
+
+ expect(group_label_response).to include('open_issues_count' => 1,
+ 'closed_issues_count' => 0,
+ 'open_merge_requests_count' => 0,
+ 'name' => group_label.name,
+ 'description' => nil,
+ 'color' => a_string_matching(/^#\h{6}$/),
+ 'text_color' => a_string_matching(/^#\h{6}$/),
+ 'priority' => nil,
+ 'subscribed' => false,
+ 'is_project_label' => false)
+
+ expect(priority_label_response).to include('open_issues_count' => 0,
+ 'closed_issues_count' => 0,
+ 'open_merge_requests_count' => 1,
+ 'name' => priority_label.name,
+ 'description' => nil,
+ 'color' => a_string_matching(/^#\h{6}$/),
+ 'text_color' => a_string_matching(/^#\h{6}$/),
+ 'priority' => 3,
+ 'subscribed' => false,
+ 'is_project_label' => true)
+ end
end
end
@@ -197,20 +382,34 @@ describe API::Labels do
end
describe 'DELETE /projects/:id/labels' do
- it 'returns 204 for existing label' do
- delete api("/projects/#{project.id}/labels", user), params: { name: 'label1' }
+ it_behaves_like 'label delete API' do
+ let(:spec_params) { { name: 'label1' } }
+ end
- expect(response).to have_gitlab_http_status(204)
+ it_behaves_like 'label delete API' do
+ let(:spec_params) { { label_id: label1.id } }
end
it 'returns 404 for non existing label' do
delete api("/projects/#{project.id}/labels", user), params: { name: 'label2' }
+
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Label Not Found')
end
it 'returns 400 for wrong parameters' do
delete api("/projects/#{project.id}/labels", user)
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'fails if label_id and name are given in params' do
+ delete api("/projects/#{project.id}/labels", user),
+ params: {
+ label_id: label1.id,
+ name: priority_label.name
+ }
+
expect(response).to have_gitlab_http_status(400)
end
@@ -221,152 +420,105 @@ describe API::Labels do
end
describe 'PUT /projects/:id/labels' do
- it 'returns 200 if name and colors and description are changed' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- new_name: 'New Label',
- color: '#FFFFFF',
- description: 'test'
- }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['name']).to eq('New Label')
- expect(json_response['color']).to eq('#FFFFFF')
- expect(json_response['description']).to eq('test')
+ context 'when using name' do
+ it_behaves_like 'label update API' do
+ let(:spec_params) { { name: 'label1' } }
+ let(:expected_response_label_id) { label1.id }
+ end
end
- it 'returns 200 if name is changed' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- new_name: 'New Label'
- }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['name']).to eq('New Label')
- expect(json_response['color']).to eq(label1.color)
+ context 'when using label_id' do
+ it_behaves_like 'label update API' do
+ let(:spec_params) { { label_id: label1.id } }
+ let(:expected_response_label_id) { label1.id }
+ end
end
- it 'returns 200 if colors is changed' do
+ it 'returns 404 if label does not exist' do
put api("/projects/#{project.id}/labels", user),
params: {
- name: 'label1',
- color: '#FFFFFF'
+ name: 'label2',
+ new_name: 'label3'
}
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['name']).to eq(label1.name)
- expect(json_response['color']).to eq('#FFFFFF')
+
+ expect(response).to have_gitlab_http_status(404)
end
- it 'returns 200 if description is changed' do
+ it 'returns 404 if label by id does not exist' do
put api("/projects/#{project.id}/labels", user),
params: {
- name: 'bug',
- description: 'test'
+ label_id: 0,
+ new_name: 'label3'
}
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['name']).to eq(priority_label.name)
- expect(json_response['description']).to eq('test')
- expect(json_response['priority']).to eq(3)
- end
-
- it 'returns 200 if priority is changed' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'bug',
- priority: 10
- }
-
- expect(response.status).to eq(200)
- expect(json_response['name']).to eq(priority_label.name)
- expect(json_response['priority']).to eq(10)
+ expect(response).to have_gitlab_http_status(404)
end
- it 'returns 200 if a priority is added' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- priority: 3
- }
+ it 'returns 400 if no label name and id is given' do
+ put api("/projects/#{project.id}/labels", user), params: { new_name: 'label2' }
- expect(response.status).to eq(200)
- expect(json_response['name']).to eq(label1.name)
- expect(json_response['priority']).to eq(3)
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('label_id, name are missing, exactly one parameter must be provided')
end
- it 'returns 200 if the priority is removed' do
+ it 'fails if label_id and name are given in params' do
put api("/projects/#{project.id}/labels", user),
params: {
+ label_id: label1.id,
name: priority_label.name,
- priority: nil
+ new_name: 'New Label'
}
- expect(response.status).to eq(200)
- expect(json_response['name']).to eq(priority_label.name)
- expect(json_response['priority']).to be_nil
+ expect(response).to have_gitlab_http_status(400)
end
+ end
- it 'returns 404 if label does not exist' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label2',
- new_name: 'label3'
- }
- expect(response).to have_gitlab_http_status(404)
- end
+ describe 'PUT /projects/:id/labels/promote' do
+ let(:group) { create(:group) }
- it 'returns 400 if no label name given' do
- put api("/projects/#{project.id}/labels", user), params: { new_name: 'label2' }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['error']).to eq('name is missing')
+ before do
+ group.add_owner(user)
+ project.update!(group: group)
end
- it 'returns 400 if no new parameters given' do
- put api("/projects/#{project.id}/labels", user), params: { name: 'label1' }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['error']).to eq('new_name, color, description, priority are missing, '\
- 'at least one parameter must be provided')
+ it 'returns 200 if label is promoted' do
+ put api("/projects/#{project.id}/labels/promote", user), params: { name: label1.name }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['name']).to eq(label1.name)
+ expect(json_response['color']).to eq(label1.color)
end
- it 'returns 400 for invalid name' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- new_name: ',',
- color: '#FFFFFF'
- }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']['title']).to eq(['is invalid'])
+ it 'returns 200 if group label already exists' do
+ create(:group_label, title: label1.name, group: group)
+
+ expect { put api("/projects/#{project.id}/labels/promote", user), params: { name: label1.name } }
+ .to change(project.labels, :count).by(-1)
+ .and change(group.labels, :count).by(0)
+
+ expect(response).to have_gitlab_http_status(200)
end
- it 'returns 400 when color code is too short' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- color: '#FF'
- }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']['color']).to eq(['must be a valid color code'])
+ it 'returns 403 if guest promotes label' do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ put api("/projects/#{project.id}/labels/promote", guest), params: { name: label1.name }
+
+ expect(response).to have_gitlab_http_status(403)
end
- it 'returns 400 for too long color code' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- color: '#FFAAFFFF'
- }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']['color']).to eq(['must be a valid color code'])
+ it 'returns 404 if label does not exist' do
+ put api("/projects/#{project.id}/labels/promote", user), params: { name: 'unknown' }
+
+ expect(response).to have_gitlab_http_status(404)
end
- it 'returns 400 for invalid priority' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- priority: 'foo'
- }
+ it 'returns 400 if no label name given' do
+ put api("/projects/#{project.id}/labels/promote", user)
expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('name is missing')
end
end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 55f38079b1f..26f6e705528 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -99,7 +99,7 @@ describe API::Members do
end
end
- describe 'GET /:source_type/:id/members/all', :nested_groups do
+ describe 'GET /:source_type/:id/members/all' do
let(:nested_user) { create(:user) }
let(:project_user) { create(:user) }
let(:linked_group_user) { create(:user) }
@@ -238,7 +238,7 @@ describe API::Members do
end
context 'access levels' do
- it 'does not create the member if group level is higher', :nested_groups do
+ it 'does not create the member if group level is higher' do
parent = create(:group)
group.update(parent: parent)
@@ -252,7 +252,7 @@ describe API::Members do
expect(json_response['message']['access_level']).to eq(["should be greater than or equal to Developer inherited membership from group #{parent.name}"])
end
- it 'creates the member if group level is lower', :nested_groups do
+ it 'creates the member if group level is lower' do
parent = create(:group)
group.update(parent: parent)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 76d093e0774..15d6db42760 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -723,7 +723,7 @@ describe API::MergeRequests do
it_behaves_like 'merge requests list'
- context 'when have subgroups', :nested_groups do
+ context 'when have subgroups' do
let!(:group) { create(:group, :public) }
let!(:subgroup) { create(:group, parent: group) }
let!(:project) { create(:project, :public, :repository, creator: user, namespace: subgroup, only_allow_merge_if_pipeline_succeeds: false) }
@@ -834,6 +834,31 @@ describe API::MergeRequests do
end
end
+ context 'head_pipeline' do
+ before do
+ merge_request.update(head_pipeline: create(:ci_pipeline))
+ merge_request.project.project_feature.update(builds_access_level: 10)
+ end
+
+ context 'when user can read the pipeline' do
+ it 'exposes pipeline information' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
+
+ expect(json_response).to include('head_pipeline')
+ end
+ end
+
+ context 'when user can not read the pipeline' do
+ let(:guest) { create(:user) }
+
+ it 'does not expose pipeline information' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", guest)
+
+ expect(json_response).not_to include('head_pipeline')
+ end
+ end
+ end
+
it 'returns the commits behind the target branch when include_diverged_commits_count is present' do
allow_any_instance_of(merge_request.class).to receive(:diverged_commits_count).and_return(1)
@@ -1546,7 +1571,7 @@ describe API::MergeRequests do
end
end
- describe "GET /projects/:id/merge_requests/:merge_request_iid/merge_ref" do
+ describe "GET /projects/:id/merge_requests/:merge_request_iid/merge_ref", :clean_gitlab_redis_shared_state do
before do
merge_request.mark_as_unchecked!
end
@@ -2008,6 +2033,9 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(202)
expect(RebaseWorker.jobs.size).to eq(1)
+
+ expect(merge_request.reload).to be_rebase_in_progress
+ expect(json_response['rebase_in_progress']).to be(true)
end
it 'returns 403 if the user cannot push to the branch' do
@@ -2018,6 +2046,16 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(403)
end
+
+ it 'returns 409 if a rebase is already in progress' do
+ Sidekiq::Testing.fake! do
+ merge_request.rebase_async(user.id)
+
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/rebase", user)
+ end
+
+ expect(response).to have_gitlab_http_status(409)
+ end
end
describe 'Time tracking' do
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 424f0a82e43..6c1e30791d2 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -9,6 +9,13 @@ describe API::Notes do
project.add_reporter(user)
end
+ context 'when there are cross-reference system notes' do
+ let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes" }
+ let(:notes_in_response) { json_response }
+
+ it_behaves_like 'with cross-reference system notes'
+ end
+
context "when noteable is an Issue" do
let!(:issue) { create(:issue, project: project, author: user) }
let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index 35b3dd219f7..174b3214d13 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -17,6 +17,8 @@ describe API::Pipelines do
end
describe 'GET /projects/:id/pipelines ' do
+ it_behaves_like 'pipelines visibility table'
+
context 'authorized user' do
it 'returns project pipelines' do
get api("/projects/#{project.id}/pipelines", user)
@@ -401,6 +403,15 @@ describe API::Pipelines do
end
describe 'GET /projects/:id/pipelines/:pipeline_id' do
+ it_behaves_like 'pipelines visibility table' do
+ let(:pipelines_api_path) do
+ "/projects/#{project.id}/pipelines/#{pipeline.id}"
+ end
+
+ let(:api_response) { response_status == 200 ? response : json_response }
+ let(:response_200) { match_response_schema('public_api/v4/pipeline/detail') }
+ end
+
context 'authorized user' do
it 'exposes known attributes' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index fc0381159dd..a7b919de2ef 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -6,11 +6,12 @@ describe API::ProjectClusters do
include KubernetesHelpers
let(:current_user) { create(:user) }
- let(:non_member) { create(:user) }
- let(:project) { create(:project, :repository) }
+ let(:developer_user) { create(:user) }
+ let(:project) { create(:project) }
before do
project.add_maintainer(current_user)
+ project.add_developer(developer_user)
end
describe 'GET /projects/:id/clusters' do
@@ -22,10 +23,10 @@ describe API::ProjectClusters do
end
context 'non-authorized user' do
- it 'responds with 404' do
- get api("/projects/#{project.id}/clusters", non_member)
+ it 'responds with 403' do
+ get api("/projects/#{project.id}/clusters", developer_user)
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(403)
end
end
@@ -67,10 +68,10 @@ describe API::ProjectClusters do
end
context 'non-authorized user' do
- it 'responds with 404' do
- get api("/projects/#{project.id}/clusters/#{cluster_id}", non_member)
+ it 'responds with 403' do
+ get api("/projects/#{project.id}/clusters/#{cluster_id}", developer_user)
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(403)
end
end
@@ -147,31 +148,7 @@ describe API::ProjectClusters do
end
end
- shared_context 'kubernetes calls stubbed' do
- before do
- stub_kubeclient_discover(api_url)
- stub_kubeclient_get_namespace(api_url, namespace: namespace)
- stub_kubeclient_get_service_account(api_url, "#{namespace}-service-account", namespace: namespace)
- stub_kubeclient_put_service_account(api_url, "#{namespace}-service-account", namespace: namespace)
-
- stub_kubeclient_get_secret(
- api_url,
- {
- metadata_name: "#{namespace}-token",
- token: Base64.encode64('sample-token'),
- namespace: namespace
- }
- )
-
- stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace)
- stub_kubeclient_get_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace)
- stub_kubeclient_put_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace)
- end
- end
-
describe 'POST /projects/:id/clusters/user' do
- include_context 'kubernetes calls stubbed'
-
let(:api_url) { 'https://kubernetes.example.com' }
let(:namespace) { project.path }
let(:authorization_type) { 'rbac' }
@@ -195,10 +172,10 @@ describe API::ProjectClusters do
end
context 'non-authorized user' do
- it 'responds with 404' do
- post api("/projects/#{project.id}/clusters/user", non_member), params: cluster_params
+ it 'responds with 403' do
+ post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(403)
end
end
@@ -280,19 +257,27 @@ describe API::ProjectClusters do
post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
end
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(400)
+
+ expect(json_response['message']['base'].first).to eq('Instance does not support multiple Kubernetes clusters')
+ end
+ end
+
+ context 'non-authorized user' do
+ before do
+ post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
+ end
+
it 'responds with 403' do
expect(response).to have_gitlab_http_status(403)
- end
- it 'returns an appropriate message' do
- expect(json_response['message']).to include('Instance does not support multiple Kubernetes clusters')
+ expect(json_response['message']).to eq('403 Forbidden')
end
end
end
describe 'PUT /projects/:id/clusters/:cluster_id' do
- include_context 'kubernetes calls stubbed'
-
let(:api_url) { 'https://kubernetes.example.com' }
let(:namespace) { 'new-namespace' }
let(:platform_kubernetes_attributes) { { namespace: namespace } }
@@ -316,10 +301,10 @@ describe API::ProjectClusters do
end
context 'non-authorized user' do
- it 'responds with 404' do
- put api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: update_params
+ it 'responds with 403' do
+ put api("/projects/#{project.id}/clusters/#{cluster.id}", developer_user), params: update_params
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(403)
end
end
@@ -351,7 +336,6 @@ describe API::ProjectClusters do
it 'does not update cluster attributes' do
expect(cluster.domain).not_to eq('new_domain.com')
expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace')
- expect(cluster.kubernetes_namespace_for(project)).not_to eq('invalid_namespace')
end
it 'returns validation errors' do
@@ -442,10 +426,10 @@ describe API::ProjectClusters do
end
context 'non-authorized user' do
- it 'responds with 404' do
- delete api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: cluster_params
+ it 'responds with 403' do
+ delete api("/projects/#{project.id}/clusters/#{cluster.id}", developer_user), params: cluster_params
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(403)
end
end
diff --git a/spec/requests/api/container_registry_spec.rb b/spec/requests/api/project_container_repositories_spec.rb
index b64f3ea1081..f1dc4e6f0b2 100644
--- a/spec/requests/api/container_registry_spec.rb
+++ b/spec/requests/api/project_container_repositories_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe API::ContainerRegistry do
+describe API::ProjectContainerRepositories do
include ExclusiveLeaseHelpers
set(:project) { create(:project, :private) }
@@ -12,6 +12,16 @@ describe API::ContainerRegistry do
let(:root_repository) { create(:container_repository, :root, project: project) }
let(:test_repository) { create(:container_repository, project: project) }
+ let(:users) do
+ {
+ anonymous: nil,
+ developer: developer,
+ guest: guest,
+ maintainer: maintainer,
+ reporter: reporter
+ }
+ end
+
let(:api_user) { maintainer }
before do
@@ -27,57 +37,24 @@ describe API::ContainerRegistry do
test_repository
end
- shared_examples 'being disallowed' do |param|
- context "for #{param}" do
- let(:api_user) { public_send(param) }
-
- it 'returns access denied' do
- subject
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context "for anonymous" do
- let(:api_user) { nil }
-
- it 'returns not found' do
- subject
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
describe 'GET /projects/:id/registry/repositories' do
- subject { get api("/projects/#{project.id}/registry/repositories", api_user) }
-
- it_behaves_like 'being disallowed', :guest
-
- context 'for reporter' do
- let(:api_user) { reporter }
-
- it 'returns a list of repositories' do
- subject
+ let(:url) { "/projects/#{project.id}/registry/repositories" }
- expect(json_response.length).to eq(2)
- expect(json_response.map { |repository| repository['id'] }).to contain_exactly(
- root_repository.id, test_repository.id)
- end
+ subject { get api(url, api_user) }
- it 'returns a matching schema' do
- subject
+ it_behaves_like 'rejected container repository access', :guest, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('registry/repositories')
- end
+ it_behaves_like 'returns repositories for allowed users', :reporter, 'project' do
+ let(:object) { project }
end
end
describe 'DELETE /projects/:id/registry/repositories/:repository_id' do
subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}", api_user) }
- it_behaves_like 'being disallowed', :developer
+ it_behaves_like 'rejected container repository access', :developer, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
context 'for maintainer' do
let(:api_user) { maintainer }
@@ -96,7 +73,8 @@ describe API::ContainerRegistry do
describe 'GET /projects/:id/registry/repositories/:repository_id/tags' do
subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user) }
- it_behaves_like 'being disallowed', :guest
+ it_behaves_like 'rejected container repository access', :guest, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
context 'for reporter' do
let(:api_user) { reporter }
@@ -124,10 +102,13 @@ describe API::ContainerRegistry do
describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags' do
subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user), params: params }
- it_behaves_like 'being disallowed', :developer do
+ context 'disallowed' do
let(:params) do
{ name_regex: 'v10.*' }
end
+
+ it_behaves_like 'rejected container repository access', :developer, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
end
context 'for maintainer' do
@@ -191,7 +172,8 @@ describe API::ContainerRegistry do
describe 'GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do
subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) }
- it_behaves_like 'being disallowed', :guest
+ it_behaves_like 'rejected container repository access', :guest, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
context 'for reporter' do
let(:api_user) { reporter }
@@ -222,7 +204,8 @@ describe API::ContainerRegistry do
describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do
subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) }
- it_behaves_like 'being disallowed', :reporter
+ it_behaves_like 'rejected container repository access', :reporter, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
context 'for developer' do
let(:api_user) { developer }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 5f7d2fa6d9c..5465fe0c366 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -46,8 +46,6 @@ shared_examples 'languages and percentages JSON response' do
end
describe API::Projects do
- include ExternalAuthorizationServiceHelpers
-
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
@@ -840,6 +838,28 @@ describe API::Projects do
end
end
+ describe 'GET /users/:user_id/starred_projects/' do
+ before do
+ user3.update(starred_projects: [project, project2, project3])
+ end
+
+ it 'returns error when user not found' do
+ get api('/users/9999/starred_projects/')
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns projects filtered by user' do
+ get api("/users/#{user3.id}/starred_projects/", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, project2.id, project3.id)
+ end
+ end
+
describe 'POST /projects/user/:id' do
it 'creates new project without path but with name and return 201' do
expect { post api("/projects/user/#{user.id}", admin), params: { name: 'Foo Project' } }.to change { Project.count }.by(1)
@@ -1102,6 +1122,12 @@ describe API::Projects do
expect(json_response['wiki_enabled']).to be_present
expect(json_response['jobs_enabled']).to be_present
expect(json_response['snippets_enabled']).to be_present
+ expect(json_response['snippets_access_level']).to be_present
+ expect(json_response['repository_access_level']).to be_present
+ expect(json_response['issues_access_level']).to be_present
+ expect(json_response['merge_requests_access_level']).to be_present
+ expect(json_response['wiki_access_level']).to be_present
+ expect(json_response['builds_access_level']).to be_present
expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
expect(json_response['container_registry_enabled']).to be_present
expect(json_response['created_at']).to be_present
@@ -1353,7 +1379,7 @@ describe API::Projects do
end
end
- context 'nested group project', :nested_groups do
+ context 'nested group project' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:project2) { create(:project, group: nested_group) }
@@ -1419,39 +1445,6 @@ describe API::Projects do
end
end
end
-
- context 'with external authorization' do
- let(:project) do
- create(:project,
- namespace: user.namespace,
- external_authorization_classification_label: 'the-label')
- end
-
- context 'when the user has access to the project' do
- before do
- external_service_allow_access(user, project)
- end
-
- it 'includes the label in the response' do
- get api("/projects/#{project.id}", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['external_authorization_classification_label']).to eq('the-label')
- end
- end
-
- context 'when the external service denies access' do
- before do
- external_service_deny_access(user, project)
- end
-
- it 'returns a 404' do
- get api("/projects/#{project.id}", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
end
describe 'GET /projects/:id/users' do
@@ -1501,6 +1494,17 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(404)
end
+
+ it 'filters out users listed in skip_users' do
+ other_user = create(:user)
+ project.team.add_developer(other_user)
+
+ get api("/projects/#{project.id}/users?skip_users=#{user.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(1)
+ expect(json_response[0]['id']).to eq(other_user.id)
+ end
end
end
@@ -1913,6 +1917,34 @@ describe API::Projects do
end
end
+ it 'updates builds_access_level' do
+ project_param = { builds_access_level: 'private' }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['builds_access_level']).to eq('private')
+ end
+
+ it 'updates build_git_strategy' do
+ project_param = { build_git_strategy: 'clone' }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['build_git_strategy']).to eq('clone')
+ end
+
+ it 'rejects to update build_git_strategy when build_git_strategy is invalid' do
+ project_param = { build_git_strategy: 'invalid' }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
it 'updates merge_method' do
project_param = { merge_method: 'ff' }
@@ -1946,6 +1978,26 @@ describe API::Projects do
'-/system/project/avatar/'\
"#{project3.id}/banana_sample.gif")
end
+
+ it 'updates auto_devops_deploy_strategy' do
+ project_param = { auto_devops_deploy_strategy: 'timed_incremental' }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['auto_devops_deploy_strategy']).to eq('timed_incremental')
+ end
+
+ it 'updates auto_devops_enabled' do
+ project_param = { auto_devops_enabled: false }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['auto_devops_enabled']).to eq(false)
+ end
end
context 'when authenticated as project maintainer' do
@@ -2007,20 +2059,6 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(403)
end
end
-
- context 'when updating external classification' do
- before do
- enable_external_authorization_service_check
- end
-
- it 'updates the classification label' do
- put(api("/projects/#{project.id}", user), params: { external_authorization_classification_label: 'new label' })
-
- expect(response).to have_gitlab_http_status(200)
-
- expect(project.reload.external_authorization_classification_label).to eq('new label')
- end
- end
end
describe 'POST /projects/:id/archive' do
@@ -2143,6 +2181,85 @@ describe API::Projects do
end
end
+ describe 'GET /projects/:id/starrers' do
+ shared_examples_for 'project starrers response' do
+ it 'returns an array of starrers' do
+ get api("/projects/#{public_project.id}/starrers", current_user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response[0]['starred_since']).to be_present
+ expect(json_response[0]['user']).to be_present
+ end
+
+ it 'returns the proper security headers' do
+ get api('/projects/1/starrers', current_user)
+
+ expect(response).to include_security_headers
+ end
+ end
+
+ let(:public_project) { create(:project, :public) }
+ let(:private_user) { create(:user, private_profile: true) }
+
+ before do
+ user.update(starred_projects: [public_project])
+ private_user.update(starred_projects: [public_project])
+ end
+
+ it 'returns not_found(404) for not existing project' do
+ get api("/projects/9999999999/starrers", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'public project without user' do
+ it_behaves_like 'project starrers response' do
+ let(:current_user) { nil }
+ end
+
+ it 'returns only starrers with a public profile' do
+ get api("/projects/#{public_project.id}/starrers", nil)
+
+ user_ids = json_response.map { |s| s['user']['id'] }
+ expect(user_ids).to include(user.id)
+ expect(user_ids).not_to include(private_user.id)
+ end
+ end
+
+ context 'public project with user with private profile' do
+ it_behaves_like 'project starrers response' do
+ let(:current_user) { private_user }
+ end
+
+ it 'returns current user with a private profile' do
+ get api("/projects/#{public_project.id}/starrers", private_user)
+
+ user_ids = json_response.map { |s| s['user']['id'] }
+ expect(user_ids).to include(user.id, private_user.id)
+ end
+ end
+
+ context 'private project' do
+ context 'with unauthorized user' do
+ it 'returns not_found for existing but unauthorized project' do
+ get api("/projects/#{project3.id}/starrers", user3)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'without user' do
+ it 'returns not_found for existing but unauthorized project' do
+ get api("/projects/#{project3.id}/starrers", nil)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
describe 'GET /projects/:id/languages' do
context 'with an authorized user' do
it_behaves_like 'languages and percentages JSON response' do
@@ -2428,7 +2545,7 @@ describe API::Projects do
let(:housekeeping) { Projects::HousekeepingService.new(project) }
before do
- allow(Projects::HousekeepingService).to receive(:new).with(project).and_return(housekeeping)
+ allow(Projects::HousekeepingService).to receive(:new).with(project, :gc).and_return(housekeeping)
end
context 'when authenticated as owner' do
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index 8603fa2a73d..206e898381d 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -24,7 +24,7 @@ describe API::Releases do
project: project,
tag: 'v0.1',
author: maintainer,
- created_at: 2.days.ago)
+ released_at: 2.days.ago)
end
let!(:release_2) do
@@ -32,7 +32,7 @@ describe API::Releases do
project: project,
tag: 'v0.2',
author: maintainer,
- created_at: 1.day.ago)
+ released_at: 1.day.ago)
end
it 'returns 200 HTTP status' do
@@ -41,7 +41,7 @@ describe API::Releases do
expect(response).to have_gitlab_http_status(:ok)
end
- it 'returns releases ordered by created_at' do
+ it 'returns releases ordered by released_at' do
get api("/projects/#{project.id}/releases", maintainer)
expect(json_response.count).to eq(2)
@@ -56,6 +56,26 @@ describe API::Releases do
end
end
+ it 'returns an upcoming_release status for a future release' do
+ tomorrow = Time.now.utc + 1.day
+ create(:release, project: project, tag: 'v0.1', author: maintainer, released_at: tomorrow)
+
+ get api("/projects/#{project.id}/releases", maintainer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.first['upcoming_release']).to eq(true)
+ end
+
+ it 'returns an upcoming_release status for a past release' do
+ yesterday = Time.now.utc - 1.day
+ create(:release, project: project, tag: 'v0.1', author: maintainer, released_at: yesterday)
+
+ get api("/projects/#{project.id}/releases", maintainer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.first['upcoming_release']).to eq(false)
+ end
+
context 'when tag does not exist in git repository' do
let!(:release) { create(:release, project: project, tag: 'v1.1.5') }
@@ -316,6 +336,51 @@ describe API::Releases do
expect(project.releases.last.description).to eq('Super nice release')
end
+ it 'sets the released_at to the current time if the released_at parameter is not provided' do
+ now = Time.zone.parse('2015-08-25 06:00:00Z')
+ Timecop.freeze(now) do
+ post api("/projects/#{project.id}/releases", maintainer), params: params
+
+ expect(project.releases.last.released_at).to eq(now)
+ end
+ end
+
+ it 'sets the released_at to the value in the parameters if specified' do
+ params = {
+ name: 'New release',
+ tag_name: 'v0.1',
+ description: 'Super nice release',
+ released_at: '2019-03-20T10:00:00Z'
+ }
+ post api("/projects/#{project.id}/releases", maintainer), params: params
+
+ expect(project.releases.last.released_at).to eq('2019-03-20T10:00:00Z')
+ end
+
+ it 'assumes the utc timezone for released_at if the timezone is not provided' do
+ params = {
+ name: 'New release',
+ tag_name: 'v0.1',
+ description: 'Super nice release',
+ released_at: '2019-03-25 10:00:00'
+ }
+ post api("/projects/#{project.id}/releases", maintainer), params: params
+
+ expect(project.releases.last.released_at).to eq('2019-03-25T10:00:00Z')
+ end
+
+ it 'allows specifying a released_at with a local time zone' do
+ params = {
+ name: 'New release',
+ tag_name: 'v0.1',
+ description: 'Super nice release',
+ released_at: '2019-03-25T10:00:00+09:00'
+ }
+ post api("/projects/#{project.id}/releases", maintainer), params: params
+
+ expect(project.releases.last.released_at).to eq('2019-03-25T01:00:00Z')
+ end
+
context 'when description is empty' do
let(:params) do
{
@@ -540,6 +605,7 @@ describe API::Releases do
project: project,
tag: 'v0.1',
name: 'New release',
+ released_at: '2018-03-01T22:00:00Z',
description: 'Super nice release')
end
@@ -560,6 +626,7 @@ describe API::Releases do
expect(project.releases.last.tag).to eq('v0.1')
expect(project.releases.last.name).to eq('New release')
+ expect(project.releases.last.released_at).to eq('2018-03-01T22:00:00Z')
end
it 'matches response schema' do
@@ -568,6 +635,14 @@ describe API::Releases do
expect(response).to match_response_schema('public_api/v4/release')
end
+ it 'updates released_at' do
+ params = { released_at: '2015-10-10T05:00:00Z' }
+
+ put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params
+
+ expect(project.releases.last.released_at).to eq('2015-10-10T05:00:00Z')
+ end
+
context 'when user tries to update sha' do
let(:params) { { sha: 'xxx' } }
diff --git a/spec/requests/api/resource_label_events_spec.rb b/spec/requests/api/resource_label_events_spec.rb
index 37b46eaeb86..25bea627b0c 100644
--- a/spec/requests/api/resource_label_events_spec.rb
+++ b/spec/requests/api/resource_label_events_spec.rb
@@ -4,55 +4,12 @@ require 'spec_helper'
describe API::ResourceLabelEvents do
set(:user) { create(:user) }
- set(:project) { create(:project, :public, :repository, namespace: user.namespace) }
- set(:private_user) { create(:user) }
+ set(:project) { create(:project, :public, namespace: user.namespace) }
before do
project.add_developer(user)
end
- shared_examples 'resource_label_events API' do |parent_type, eventable_type, id_name|
- describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events" do
- it "returns an array of resource label events" do
- get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.first['id']).to eq(event.id)
- end
-
- it "returns a 404 error when eventable id not found" do
- get api("/#{parent_type}/#{parent.id}/#{eventable_type}/12345/resource_label_events", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- it "returns 404 when not authorized" do
- parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
-
- get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", private_user)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events/:event_id" do
- it "returns a resource label event by id" do
- get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/#{event.id}", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['id']).to eq(event.id)
- end
-
- it "returns a 404 error if resource label event not found" do
- get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/12345", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
-
context 'when eventable is an Issue' do
let(:issue) { create(:issue, project: project, author: user) }
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index 5548e3fd01a..f5ce3a3570e 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -584,6 +584,34 @@ describe API::Runners do
end
end
+ context 'when valid order_by is provided' do
+ context 'when sort order is not specified' do
+ it 'return jobs in descending order' do
+ get api("/runners/#{project_runner.id}/jobs?order_by=id", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(2)
+ expect(json_response.first).to include('id' => job_5.id)
+ end
+ end
+
+ context 'when sort order is specified as asc' do
+ it 'return jobs sorted in ascending order' do
+ get api("/runners/#{project_runner.id}/jobs?order_by=id&sort=asc", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(2)
+ expect(json_response.first).to include('id' => job_4.id)
+ end
+ end
+ end
+
context 'when invalid status is provided' do
it 'return 400' do
get api("/runners/#{project_runner.id}/jobs?status=non-existing", admin)
@@ -591,6 +619,22 @@ describe API::Runners do
expect(response).to have_gitlab_http_status(400)
end
end
+
+ context 'when invalid order_by is provided' do
+ it 'return 400' do
+ get api("/runners/#{project_runner.id}/jobs?order_by=non-existing", admin)
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+
+ context 'when invalid sort is provided' do
+ it 'return 400' do
+ get api("/runners/#{project_runner.id}/jobs?sort=non-existing", admin)
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
end
context "when runner doesn't exist" do
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index 3e0b478abb3..8abdcaa2e0e 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -89,7 +89,7 @@ describe API::Search do
it 'returns empty array' do
get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
- milestones = JSON.parse(response.body)
+ milestones = json_response
expect(milestones).to be_empty
end
@@ -356,7 +356,7 @@ describe API::Search do
it 'returns empty array' do
get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
- milestones = JSON.parse(response.body)
+ milestones = json_response
expect(milestones).to be_empty
end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 3f79e332b90..76a70ab6e9e 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -10,10 +10,7 @@ describe API::Services do
end
Service.available_services_names.each do |service|
- # TODO: Remove below `if: (service != "kubernetes")` in the next release
- # KubernetesService was deprecated and it can't be updated. Right now it's
- # only readable. It should be completely removed in the next iteration.
- describe "PUT /projects/:id/services/#{service.dasherize}", if: (service != "kubernetes") do
+ describe "PUT /projects/:id/services/#{service.dasherize}" do
include_context service
it "updates #{service} settings" do
@@ -62,10 +59,7 @@ describe API::Services do
end
end
- # TODO: Remove below `if: (service != "kubernetes")` in the next release
- # KubernetesService was deprecated and it can't be updated. Right now it's
- # only readable. It should be completely removed in the next iteration.
- describe "DELETE /projects/:id/services/#{service.dasherize}", if: (service != "kubernetes") do
+ describe "DELETE /projects/:id/services/#{service.dasherize}" do
include_context service
before do
@@ -85,9 +79,7 @@ describe API::Services do
include_context service
# inject some properties into the service
- before do
- initialize_service(service)
- end
+ let!(:initialized_service) { initialize_service(service) }
it 'returns authentication error when unauthenticated' do
get api("/projects/#{project.id}/services/#{dashed_service}")
@@ -108,6 +100,15 @@ describe API::Services do
expect(json_response['properties'].keys).to match_array(service_instance.api_field_names)
end
+ it "returns empty hash if properties are empty" do
+ # deprecated services are not valid for update
+ initialized_service.update_attribute(:properties, {})
+ get api("/projects/#{project.id}/services/#{dashed_service}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['properties'].keys).to be_empty
+ end
+
it "returns error when authenticated but not a project owner" do
project.add_developer(user2)
get api("/projects/#{project.id}/services/#{dashed_service}", user2)
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 8a60980fe80..590107d5161 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -25,6 +25,9 @@ describe API::Settings, 'Settings' do
expect(json_response['ed25519_key_restriction']).to eq(0)
expect(json_response['performance_bar_allowed_group_id']).to be_nil
expect(json_response['instance_statistics_visibility_private']).to be(false)
+ expect(json_response['allow_local_requests_from_hooks_and_services']).to be(false)
+ expect(json_response['allow_local_requests_from_web_hooks_and_services']).to be(false)
+ expect(json_response['allow_local_requests_from_system_hooks']).to be(true)
expect(json_response).not_to have_key('performance_bar_allowed_group_path')
expect(json_response).not_to have_key('performance_bar_enabled')
end
@@ -67,7 +70,9 @@ describe API::Settings, 'Settings' do
instance_statistics_visibility_private: true,
diff_max_patch_bytes: 150_000,
default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE,
- local_markdown_version: 3
+ local_markdown_version: 3,
+ allow_local_requests_from_web_hooks_and_services: true,
+ allow_local_requests_from_system_hooks: false
}
expect(response).to have_gitlab_http_status(200)
@@ -95,6 +100,8 @@ describe API::Settings, 'Settings' do
expect(json_response['diff_max_patch_bytes']).to eq(150_000)
expect(json_response['default_branch_protection']).to eq(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
expect(json_response['local_markdown_version']).to eq(3)
+ expect(json_response['allow_local_requests_from_web_hooks_and_services']).to eq(true)
+ expect(json_response['allow_local_requests_from_system_hooks']).to eq(false)
end
end
@@ -117,6 +124,14 @@ describe API::Settings, 'Settings' do
expect(json_response['performance_bar_allowed_group_id']).to be_nil
end
+ it 'supports legacy allow_local_requests_from_hooks_and_services' do
+ put api("/application/settings", admin),
+ params: { allow_local_requests_from_hooks_and_services: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['allow_local_requests_from_hooks_and_services']).to eq(true)
+ end
+
context 'external policy classification settings' do
let(:settings) do
{
@@ -129,6 +144,7 @@ describe API::Settings, 'Settings' do
external_auth_client_key_pass: "5iveL!fe"
}
end
+
let(:attribute_names) { settings.keys.map(&:to_s) }
it 'includes the attributes in the API' do
@@ -150,6 +166,56 @@ describe API::Settings, 'Settings' do
end
end
+ context "snowplow tracking settings" do
+ let(:settings) do
+ {
+ snowplow_collector_hostname: "snowplow.example.com",
+ snowplow_cookie_domain: ".example.com",
+ snowplow_enabled: true,
+ snowplow_site_id: "site_id"
+ }
+ end
+
+ let(:attribute_names) { settings.keys.map(&:to_s) }
+
+ it "includes the attributes in the API" do
+ get api("/application/settings", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ attribute_names.each do |attribute|
+ expect(json_response.keys).to include(attribute)
+ end
+ end
+
+ it "allows updating the settings" do
+ put api("/application/settings", admin), params: settings
+
+ expect(response).to have_gitlab_http_status(200)
+ settings.each do |attribute, value|
+ expect(ApplicationSetting.current.public_send(attribute)).to eq(value)
+ end
+ end
+
+ context "missing snowplow_collector_hostname value when snowplow_enabled is true" do
+ it "returns a blank parameter error message" do
+ put api("/application/settings", admin), params: { snowplow_enabled: true }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response["error"]).to eq("snowplow_collector_hostname is missing")
+ end
+
+ it "handles validation errors" do
+ put api("/application/settings", admin), params: settings.merge({
+ snowplow_collector_hostname: nil
+ })
+
+ expect(response).to have_gitlab_http_status(400)
+ message = json_response["message"]
+ expect(message["snowplow_collector_hostname"]).to include("can't be blank")
+ end
+ end
+ end
+
context "missing plantuml_url value when plantuml_enabled is true" do
it "returns a blank parameter error message" do
put api("/application/settings", admin), params: { plantuml_enabled: true }
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index f0f01e97f1d..8ea3d16a41f 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -270,34 +270,6 @@ describe API::Triggers do
end
end
- describe 'POST /projects/:id/triggers/:trigger_id/take_ownership' do
- context 'authenticated user with valid permissions' do
- it 'updates owner' do
- post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to include('owner')
- expect(trigger.reload.owner).to eq(user)
- end
- end
-
- context 'authenticated user with invalid permissions' do
- it 'does not update owner' do
- post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user2)
-
- expect(response).to have_gitlab_http_status(403)
- end
- end
-
- context 'unauthenticated user' do
- it 'does not update owner' do
- post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership")
-
- expect(response).to have_gitlab_http_status(401)
- end
- end
- end
-
describe 'DELETE /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do
it 'deletes trigger' do
diff --git a/spec/requests/api/user_counts_spec.rb b/spec/requests/api/user_counts_spec.rb
new file mode 100644
index 00000000000..c833bd047e2
--- /dev/null
+++ b/spec/requests/api/user_counts_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::UserCounts do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ let!(:merge_request) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, title: "Test") }
+
+ describe 'GET /user_counts' do
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get api('/user_counts')
+
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns open counts for current user' do
+ get api('/user_counts', user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a Hash
+ expect(json_response['merge_requests']).to eq(1)
+ end
+
+ it 'updates the mr count when a new mr is assigned' do
+ create(:merge_request, source_project: project, author: user, assignees: [user])
+
+ get api('/user_counts', user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a Hash
+ expect(json_response['merge_requests']).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 46925daf40a..af2bee4563a 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -448,6 +448,7 @@ describe API::Users do
it "returns 201 Created on success" do
post api("/users", admin), params: attributes_for(:user, projects_limit: 3)
+ expect(response).to match_response_schema('public_api/v4/user/admin')
expect(response).to have_gitlab_http_status(201)
end
@@ -643,6 +644,13 @@ describe API::Users do
describe "PUT /users/:id" do
let!(:admin_user) { create(:admin) }
+ it "returns 200 OK on success" do
+ put api("/users/#{user.id}", admin), params: { bio: 'new test bio' }
+
+ expect(response).to match_response_schema('public_api/v4/user/admin')
+ expect(response).to have_gitlab_http_status(200)
+ end
+
it "updates user with new bio" do
put api("/users/#{user.id}", admin), params: { bio: 'new test bio' }
@@ -745,6 +753,14 @@ describe API::Users do
expect(user.reload.private_profile).to eq(true)
end
+ it "updates private profile when nil is given to false" do
+ admin.update(private_profile: true)
+
+ put api("/users/#{user.id}", admin), params: { private_profile: nil }
+
+ expect(user.reload.private_profile).to eq(false)
+ end
+
it "does not update admin status" do
put api("/users/#{admin_user.id}", admin), params: { can_create_group: false }
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index 55b1419a004..69f105b71a8 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -106,6 +106,30 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(400)
end
+
+ it 'creates variable with a specific environment scope' do
+ expect do
+ post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', environment_scope: 'review/*' }
+ end.to change { project.variables.reload.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['key']).to eq('TEST_VARIABLE_2')
+ expect(json_response['value']).to eq('VALUE_2')
+ expect(json_response['environment_scope']).to eq('review/*')
+ end
+
+ it 'allows duplicated variable key given different environment scopes' do
+ variable = create(:ci_variable, project: project)
+
+ expect do
+ post api("/projects/#{project.id}/variables", user), params: { key: variable.key, value: 'VALUE_2', environment_scope: 'review/*' }
+ end.to change { project.variables.reload.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['key']).to eq(variable.key)
+ expect(json_response['value']).to eq('VALUE_2')
+ expect(json_response['environment_scope']).to eq('review/*')
+ end
end
context 'authorized user with invalid permissions' do
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index bba473f1c20..8b2c698fee1 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -108,6 +108,14 @@ describe JwtController do
end
end
end
+
+ it 'does not cause session based checks to be activated' do
+ expect(Gitlab::Session).not_to receive(:with_session)
+
+ get '/jwt/auth', params: parameters, headers: headers
+
+ expect(response).to have_gitlab_http_status(200)
+ end
end
context 'using invalid login' do
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 1781759c54b..dc25e4d808e 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -1439,8 +1439,4 @@ describe 'Git LFS API and storage' do
post(url, params: params, headers: headers)
end
-
- def json_response
- @json_response ||= JSON.parse(response.body)
- end
end
diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb
index 5b7b3d2fdd6..11436e5cd0c 100644
--- a/spec/requests/lfs_locks_api_spec.rb
+++ b/spec/requests/lfs_locks_api_spec.rb
@@ -163,8 +163,4 @@ describe 'Git LFS File Locking API' do
def do_get(url, params = nil, headers = nil)
get(url, params: (params || {}), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
end
-
- def json_response
- @json_response ||= JSON.parse(response.body)
- end
end
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index 86e41cbdf00..025568d8bea 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -104,7 +104,7 @@ describe 'OpenID Connect requests' do
expect(json_response).to match(id_token_claims.merge(user_info_claims))
expected_groups = [group1.full_path, group3.full_path]
- expected_groups << group4.full_path if Group.supports_nested_objects?
+ expected_groups << group4.full_path
expect(json_response['groups']).to match_array(expected_groups)
end
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index 89adbc77a7f..478f09a7881 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -102,6 +102,27 @@ describe 'Rack Attack global throttles' do
expect_rejection { get(*get_args) }
end
+
+ it 'logs RackAttack info into structured logs' do
+ requests_per_period.times do
+ get(*get_args)
+ expect(response).to have_http_status 200
+ end
+
+ arguments = {
+ message: 'Rack_Attack',
+ env: :throttle,
+ remote_ip: '127.0.0.1',
+ request_method: 'GET',
+ path: get_args.first,
+ user_id: user.id,
+ username: user.username
+ }
+
+ expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
+
+ expect_rejection { get(*get_args) }
+ end
end
context 'when the throttle is disabled' do
@@ -189,7 +210,15 @@ describe 'Rack Attack global throttles' do
expect(response).to have_http_status 200
end
- expect(Gitlab::AuthLogger).to receive(:error).once
+ arguments = {
+ message: 'Rack_Attack',
+ env: :throttle,
+ remote_ip: '127.0.0.1',
+ request_method: 'GET',
+ path: '/users/sign_in'
+ }
+
+ expect(Gitlab::AuthLogger).to receive(:error).with(arguments)
get url_that_does_not_require_authentication
end
@@ -345,7 +374,17 @@ describe 'Rack Attack global throttles' do
expect(response).to have_http_status 200
end
- expect(Gitlab::AuthLogger).to receive(:error).once
+ arguments = {
+ message: 'Rack_Attack',
+ env: :throttle,
+ remote_ip: '127.0.0.1',
+ request_method: 'GET',
+ path: '/dashboard/snippets',
+ user_id: user.id,
+ username: user.username
+ }
+
+ expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
get url_that_requires_authentication
end
diff --git a/spec/requests/request_profiler_spec.rb b/spec/requests/request_profiler_spec.rb
index 75b22b1879b..851affbcf88 100644
--- a/spec/requests/request_profiler_spec.rb
+++ b/spec/requests/request_profiler_spec.rb
@@ -3,13 +3,18 @@ require 'spec_helper'
describe 'Request Profiler' do
let(:user) { create(:user) }
- shared_examples 'profiling a request' do
+ shared_examples 'profiling a request' do |profile_type, extension|
before do
allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
allow(RubyProf::Profile).to receive(:profile) do |&blk|
blk.call
RubyProf::Profile.new
end
+ allow(MemoryProfiler).to receive(:report) do |&blk|
+ blk.call
+ MemoryProfiler.start
+ MemoryProfiler.stop
+ end
end
it 'creates a profile of the request' do
@@ -18,10 +23,11 @@ describe 'Request Profiler' do
path = "/#{project.full_path}"
Timecop.freeze(time) do
- get path, params: {}, headers: { 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token }
+ get path, params: {}, headers: { 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token, 'X-Profile-Mode' => profile_type }
end
- profile_path = "#{Gitlab.config.shared.path}/tmp/requests_profiles/#{path.tr('/', '|')}_#{time.to_i}.html"
+ profile_type = 'execution' if profile_type.nil?
+ profile_path = "#{Gitlab.config.shared.path}/tmp/requests_profiles/#{path.tr('/', '|')}_#{time.to_i}_#{profile_type}.#{extension}"
expect(File.exist?(profile_path)).to be true
end
@@ -35,10 +41,14 @@ describe 'Request Profiler' do
login_as(user)
end
- include_examples 'profiling a request'
+ include_examples 'profiling a request', 'execution', 'html'
+ include_examples 'profiling a request', nil, 'html'
+ include_examples 'profiling a request', 'memory', 'txt'
end
context "when user is not logged-in" do
- include_examples 'profiling a request'
+ include_examples 'profiling a request', 'execution', 'html'
+ include_examples 'profiling a request', nil, 'html'
+ include_examples 'profiling a request', 'memory', 'txt'
end
end
diff --git a/spec/routing/api_routing_spec.rb b/spec/routing/api_routing_spec.rb
deleted file mode 100644
index 3c48ead4ff2..00000000000
--- a/spec/routing/api_routing_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'spec_helper'
-
-describe 'api', 'routing' do
- context 'when graphql is disabled' do
- before do
- stub_feature_flags(graphql: false)
- end
-
- it 'does not route to the GraphqlController' do
- expect(post('/api/graphql')).not_to route_to('graphql#execute')
- end
- end
-
- context 'when graphql is enabled' do
- before do
- stub_feature_flags(graphql: true)
- end
-
- it 'routes to the GraphqlController' do
- expect(post('/api/graphql')).to route_to('graphql#execute')
- end
- end
-end
diff --git a/spec/routing/environments_spec.rb b/spec/routing/environments_spec.rb
index aacbe300966..28b3e79c1ff 100644
--- a/spec/routing/environments_spec.rb
+++ b/spec/routing/environments_spec.rb
@@ -9,7 +9,7 @@ describe 'environments routing' do
end
let(:environments_route) do
- "#{project.namespace.name}/#{project.name}/environments/"
+ "#{project.full_path}/environments/"
end
describe 'routing environment folders' do
@@ -36,13 +36,12 @@ describe 'environments routing' do
end
def get_folder(folder)
- get("#{project.namespace.name}/#{project.name}/" \
- "environments/folders/#{folder}")
+ get("#{project.full_path}/environments/folders/#{folder}")
end
def folder_action(**opts)
- options = { namespace_id: project.namespace.name,
- project_id: project.name }
+ options = { namespace_id: project.namespace.path,
+ project_id: project.path }
['projects/environments#folder', options.merge(opts)]
end
diff --git a/spec/routing/git_http_routing_spec.rb b/spec/routing/git_http_routing_spec.rb
new file mode 100644
index 00000000000..af14e5f81cb
--- /dev/null
+++ b/spec/routing/git_http_routing_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'git_http routing' do
+ include RSpec::Rails::RequestExampleGroup
+
+ describe 'wiki.git routing', 'routing' do
+ let(:wiki_path) { '/gitlab/gitlabhq/wikis' }
+
+ it 'redirects namespace/project.wiki.git to the project wiki' do
+ expect(get('/gitlab/gitlabhq.wiki.git')).to redirect_to(wiki_path)
+ end
+
+ it 'preserves query parameters' do
+ expect(get('/gitlab/gitlabhq.wiki.git?foo=bar&baz=qux')).to redirect_to("#{wiki_path}?foo=bar&baz=qux")
+ end
+
+ it 'only redirects when the format is .git' do
+ expect(get('/gitlab/gitlabhq.wiki')).not_to redirect_to(wiki_path)
+ expect(get('/gitlab/gitlabhq.wiki.json')).not_to redirect_to(wiki_path)
+ end
+ end
+end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 83775b1040e..8a3de2a52fc 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -693,4 +693,30 @@ describe 'project routing' do
it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/settings/repository", "/gitlab/gitlabhq/-/settings/repository"
end
+
+ describe Projects::TemplatesController, 'routing' do
+ describe '#show' do
+ def show_with_template_type(template_type)
+ "/gitlab/gitlabhq/templates/#{template_type}/template_name"
+ end
+
+ it 'routes when :template_type is `merge_request`' do
+ expect(get(show_with_template_type('merge_request'))).to route_to('projects/templates#show', namespace_id: 'gitlab', project_id: 'gitlabhq', template_type: 'merge_request', key: 'template_name', format: 'json')
+ end
+
+ it 'routes when :template_type is `issue`' do
+ expect(get(show_with_template_type('issue'))).to route_to('projects/templates#show', namespace_id: 'gitlab', project_id: 'gitlabhq', template_type: 'issue', key: 'template_name', format: 'json')
+ end
+
+ it 'routes to application#route_not_found when :template_type is unknown' do
+ expect(get(show_with_template_type('invalid'))).to route_to('application#route_not_found', unmatched_route: 'gitlab/gitlabhq/templates/invalid/template_name')
+ end
+ end
+ end
+
+ describe Projects::DeployTokensController, 'routing' do
+ it 'routes to deploy_tokens#revoke' do
+ expect(put("/gitlab/gitlabhq/-/deploy_tokens/1/revoke")).to route_to("projects/deploy_tokens#revoke", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+ end
+ end
end
diff --git a/spec/routing/uploads_routing_spec.rb b/spec/routing/uploads_routing_spec.rb
index 6a041ffdd6c..42e84774088 100644
--- a/spec/routing/uploads_routing_spec.rb
+++ b/spec/routing/uploads_routing_spec.rb
@@ -12,10 +12,19 @@ describe 'Uploads', 'routing' do
)
end
+ it 'allows creating uploads for users' do
+ expect(post('/uploads/user?id=1')).to route_to(
+ controller: 'uploads',
+ action: 'create',
+ model: 'user',
+ id: '1'
+ )
+ end
+
it 'does not allow creating uploads for other models' do
- UploadsController::MODEL_CLASSES.keys.compact.each do |model|
- next if model == 'personal_snippet'
+ unroutable_models = UploadsController::MODEL_CLASSES.keys.compact - %w(personal_snippet user)
+ unroutable_models.each do |model|
expect(post("/uploads/#{model}?id=1")).not_to be_routable
end
end
diff --git a/spec/rubocop/cop/gitlab/rails_logger_spec.rb b/spec/rubocop/cop/gitlab/rails_logger_spec.rb
new file mode 100644
index 00000000000..f0158ddcc5c
--- /dev/null
+++ b/spec/rubocop/cop/gitlab/rails_logger_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/gitlab/rails_logger'
+
+describe RuboCop::Cop::Gitlab::RailsLogger do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'flags the use of Rails.logger.error with a constant receiver' do
+ inspect_source("Rails.logger.error('some error')")
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'flags the use of Rails.logger.info with a constant receiver' do
+ inspect_source("Rails.logger.info('some info')")
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'flags the use of Rails.logger.warn with a constant receiver' do
+ inspect_source("Rails.logger.warn('some warning')")
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not flag the use of Rails.logger with a constant that is not Rails' do
+ inspect_source("AppLogger.error('some error')")
+
+ expect(cop.offenses.size).to eq(0)
+ end
+
+ it 'does not flag the use of logger with a send receiver' do
+ inspect_source("file_logger.info('important info')")
+
+ expect(cop.offenses.size).to eq(0)
+ end
+end
diff --git a/spec/rubocop/cop/gitlab/union_spec.rb b/spec/rubocop/cop/gitlab/union_spec.rb
index 5b06f30b25f..f0544fdb66e 100644
--- a/spec/rubocop/cop/gitlab/union_spec.rb
+++ b/spec/rubocop/cop/gitlab/union_spec.rb
@@ -16,10 +16,4 @@ describe RuboCop::Cop::Gitlab::Union do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `FromUnion` concern, instead of using `Gitlab::SQL::Union` directly
SOURCE
end
-
- it 'does not flag the use of Gitlab::SQL::Union in a spec' do
- allow(cop).to receive(:in_spec?).and_return(true)
-
- expect_no_offenses('Gitlab::SQL::Union.new([foo])')
- end
end
diff --git a/spec/rubocop/cop/graphql/authorize_types_spec.rb b/spec/rubocop/cop/graphql/authorize_types_spec.rb
new file mode 100644
index 00000000000..eae3e176d64
--- /dev/null
+++ b/spec/rubocop/cop/graphql/authorize_types_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/graphql/authorize_types'
+
+describe RuboCop::Cop::Graphql::AuthorizeTypes do
+ include RuboCop::RSpec::ExpectOffense
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'when in a type folder' do
+ before do
+ allow(cop).to receive(:in_type?).and_return(true)
+ end
+
+ it 'adds an offense when there is no authorize call' do
+ inspect_source(<<~TYPE)
+ module Types
+ class AType < BaseObject
+ field :a_thing
+ field :another_thing
+ end
+ end
+ TYPE
+
+ expect(cop.offenses.size).to eq 1
+ end
+
+ it 'does not add an offense for classes that have an authorize call' do
+ expect_no_offenses(<<~TYPE.strip)
+ module Types
+ class AType < BaseObject
+ graphql_name 'ATypeName'
+
+ authorize :an_ability, :second_ability
+
+ field :a_thing
+ end
+ end
+ TYPE
+ end
+
+ it 'does not add an offense for classes that only have an authorize call' do
+ expect_no_offenses(<<~TYPE.strip)
+ module Types
+ class AType < SuperClassWithFields
+ authorize :an_ability
+ end
+ end
+ TYPE
+ end
+
+ it 'does not add an offense for base types' do
+ expect_no_offenses(<<~TYPE)
+ module Types
+ class AType < BaseEnum
+ field :a_thing
+ end
+ end
+ TYPE
+ end
+ end
+end
diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
index 0ff777388e5..27df42c0aee 100644
--- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
+++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
@@ -10,91 +10,91 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
subject(:cop) { described_class.new }
- it 'flags the use of `prepend EE` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend EE::Foo
- ^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee 'EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'does not flag the use of `prepend EEFoo` in the middle of a file' do
+ it 'does not flag the use of `prepend_if_ee EEFoo` in the middle of a file' do
expect_no_offenses(<<~SOURCE)
class Foo
- prepend EEFoo
+ prepend_if_ee 'EEFoo'
end
SOURCE
end
- it 'flags the use of `prepend EE::Foo::Bar` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee EE::Foo::Bar` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend EE::Foo::Bar
- ^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee 'EE::Foo::Bar'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `prepend(EE::Foo::Bar)` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee(EE::Foo::Bar)` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend(EE::Foo::Bar)
- ^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee('EE::Foo::Bar')
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `prepend EE::Foo::Bar::Baz` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee EE::Foo::Bar::Baz` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend EE::Foo::Bar::Baz
- ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee 'EE::Foo::Bar::Baz'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `prepend ::EE` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee ::EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend ::EE::Foo
- ^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee '::EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `include EE` in the middle of a file' do
+ it 'flags the use of `include_if_ee EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- include EE::Foo
- ^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ include_if_ee 'EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `include ::EE` in the middle of a file' do
+ it 'flags the use of `include_if_ee ::EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- include ::EE::Foo
- ^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ include_if_ee '::EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `extend EE` in the middle of a file' do
+ it 'flags the use of `extend_if_ee EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- extend EE::Foo
- ^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ extend_if_ee 'EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `extend ::EE` in the middle of a file' do
+ it 'flags the use of `extend_if_ee ::EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- extend ::EE::Foo
- ^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ extend_if_ee '::EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
@@ -102,7 +102,7 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
it 'does not flag prepending of regular modules' do
expect_no_offenses(<<~SOURCE)
class Foo
- prepend Foo
+ prepend_if_ee 'Foo'
end
SOURCE
end
@@ -110,7 +110,7 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
it 'does not flag including of regular modules' do
expect_no_offenses(<<~SOURCE)
class Foo
- include Foo
+ include_if_ee 'Foo'
end
SOURCE
end
@@ -118,51 +118,111 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
it 'does not flag extending using regular modules' do
expect_no_offenses(<<~SOURCE)
class Foo
- extend Foo
+ extend_if_ee 'Foo'
end
SOURCE
end
- it 'does not flag the use of `prepend EE` on the last line' do
+ it 'does not flag the use of `prepend_if_ee EE` on the last line' do
expect_no_offenses(<<~SOURCE)
class Foo
end
- Foo.prepend(EE::Foo)
+ Foo.prepend_if_ee('EE::Foo')
SOURCE
end
- it 'does not flag the use of `include EE` on the last line' do
+ it 'does not flag the use of `include_if_ee EE` on the last line' do
expect_no_offenses(<<~SOURCE)
class Foo
end
- Foo.include(EE::Foo)
+ Foo.include_if_ee('EE::Foo')
SOURCE
end
- it 'does not flag the use of `extend EE` on the last line' do
+ it 'does not flag the use of `extend_if_ee EE` on the last line' do
expect_no_offenses(<<~SOURCE)
class Foo
end
- Foo.extend(EE::Foo)
+ Foo.extend_if_ee('EE::Foo')
SOURCE
end
it 'autocorrects offenses by just disabling the Cop' do
source = <<~SOURCE
class Foo
- prepend EE::Foo
- include Bar
+ prepend_if_ee 'EE::Foo'
+ include_if_ee 'Bar'
end
SOURCE
expect(autocorrect_source(source)).to eq(<<~SOURCE)
class Foo
- prepend EE::Foo # rubocop: disable Cop/InjectEnterpriseEditionModule
- include Bar
+ prepend_if_ee 'EE::Foo' # rubocop: disable Cop/InjectEnterpriseEditionModule
+ include_if_ee 'Bar'
+ end
+ SOURCE
+ end
+
+ it 'disallows the use of prepend to inject an EE module' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.prepend(EE::Foo)
+ ^^^^^^^^^^^^^^^^^^^^ EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`
+ SOURCE
+ end
+
+ it 'disallows the use of extend to inject an EE module' do
+ expect_offense(<<~SOURCE)
+ class Foo
end
+
+ Foo.extend(EE::Foo)
+ ^^^^^^^^^^^^^^^^^^^ EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`
+ SOURCE
+ end
+
+ it 'disallows the use of include to inject an EE module' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.include(EE::Foo)
+ ^^^^^^^^^^^^^^^^^^^^ EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`
+ SOURCE
+ end
+
+ it 'disallows the use of prepend_if_ee without a String' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.prepend_if_ee(EE::Foo)
+ ^^^^^^^ EE modules to inject must be specified as a String
+ SOURCE
+ end
+
+ it 'disallows the use of include_if_ee without a String' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.include_if_ee(EE::Foo)
+ ^^^^^^^ EE modules to inject must be specified as a String
+ SOURCE
+ end
+
+ it 'disallows the use of extend_if_ee without a String' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.extend_if_ee(EE::Foo)
+ ^^^^^^^ EE modules to inject must be specified as a String
SOURCE
end
end
diff --git a/spec/rubocop/cop/migration/add_limit_to_string_columns_spec.rb b/spec/rubocop/cop/migration/add_limit_to_string_columns_spec.rb
new file mode 100644
index 00000000000..97a3ae8f2bc
--- /dev/null
+++ b/spec/rubocop/cop/migration/add_limit_to_string_columns_spec.rb
@@ -0,0 +1,268 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/add_limit_to_string_columns'
+
+describe RuboCop::Cop::Migration::AddLimitToStringColumns do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+
+ inspect_source(migration)
+ end
+
+ context 'when creating a table' do
+ context 'with string columns and limit' do
+ let(:migration) do
+ %q(
+ class CreateUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :users do |t|
+ t.string :username, null: false, limit: 255
+ t.timestamps_with_timezone null: true
+ end
+ end
+ end
+ )
+ end
+
+ it 'register no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+
+ context 'with limit in a different position' do
+ let(:migration) do
+ %q(
+ class CreateUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :users do |t|
+ t.string :username, limit: 255, null: false
+ t.timestamps_with_timezone null: true
+ end
+ end
+ end
+ )
+ end
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'with string columns and no limit' do
+ let(:migration) do
+ %q(
+ class CreateUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :users do |t|
+ t.string :username, null: false
+ t.timestamps_with_timezone null: true
+ end
+ end
+ end
+ )
+ end
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('String columns should have a limit constraint. 255 is suggested')
+ end
+ end
+
+ context 'with no string columns' do
+ let(:migration) do
+ %q(
+ class CreateMilestoneReleases < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :milestone_releases do |t|
+ t.integer :milestone_id
+ t.integer :release_id
+ end
+ end
+ end
+ )
+ end
+
+ it 'register no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'when adding columns' do
+ context 'with string columns with limit' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :users, :email, :string, limit: 255
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+
+ context 'with limit in a different position' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :users, :email, :string, limit: 255, default: 'example@email.com'
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'with string columns with no limit' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :users, :email, :string
+ end
+ end
+ )
+ end
+
+ it 'registers offense' do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('String columns should have a limit constraint. 255 is suggested')
+ end
+ end
+
+ context 'with no string columns' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :users, :active, :boolean, default: false
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'with add_column_with_default' do
+ context 'with a limit' do
+ let(:migration) do
+ %q(
+ class AddRuleTypeToApprovalMergeRequestRules < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column_with_default(:approval_merge_request_rules, :rule_type, :string, limit: 2, default: 1)
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+
+ context 'without a limit' do
+ let(:migration) do
+ %q(
+ class AddRuleTypeToApprovalMergeRequestRules < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column_with_default(:approval_merge_request_rules, :rule_type, :string, default: 1)
+ end
+ end
+ )
+ end
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+ end
+
+ context 'with methods' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column_if_table_not_exists :users, :first_name, :string, limit: 255
+ search_namespace(user_name)
+ end
+
+ def add_column_if_not_exists(table, name, *args)
+ add_column(table, name, *args) unless column_exists?(table, name)
+ end
+
+ def search_namespace(username)
+ Uniquify.new.string(username) do |str|
+ query = "SELECT id FROM namespaces WHERE parent_id IS NULL AND path='#{str}' LIMIT 1"
+ connection.exec_query(query)
+ end
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'outside of migrations' do
+ let(:active_record_model) do
+ %q(
+ class User < ApplicationRecord
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ inspect_source(active_record_model)
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/qa/element_with_pattern_spec.rb b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
index ef20d9a1f26..fee390caa9f 100644
--- a/spec/rubocop/cop/qa/element_with_pattern_spec.rb
+++ b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
@@ -23,9 +23,9 @@ describe RuboCop::Cop::QA::ElementWithPattern do
expect_offense(<<-RUBY)
view 'app/views/shared/groups/_search_form.html.haml' do
element :groups_filter, 'search_field_tag :filter'
- ^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter` instead.
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `data-qa-selector=groups_filter` instead.
element :groups_filter_placeholder, /Search by name/
- ^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter-placeholder` instead.
+ ^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `data-qa-selector=groups_filter_placeholder` instead.
end
RUBY
end
diff --git a/spec/rubocop/cop/rspec/env_assignment_spec.rb b/spec/rubocop/cop/rspec/env_assignment_spec.rb
index 659633f6467..621afbad3ba 100644
--- a/spec/rubocop/cop/rspec/env_assignment_spec.rb
+++ b/spec/rubocop/cop/rspec/env_assignment_spec.rb
@@ -33,27 +33,13 @@ describe RuboCop::Cop::RSpec::EnvAssignment do
end
end
- context 'in a spec file' do
- before do
- allow(cop).to receive(:in_spec?).and_return(true)
- end
-
- context 'with a key using single quotes' do
- it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY
- it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY, %(stub_env('FOO', 'bar'))
- end
-
- context 'with a key using double quotes' do
- it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY
- it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY, %(stub_env("FOO", 'bar'))
- end
+ context 'with a key using single quotes' do
+ it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY
+ it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY, %(stub_env('FOO', 'bar'))
end
- context 'outside of a spec file' do
- it "does not register an offense for `#{OFFENSE_CALL_SINGLE_QUOTES_KEY}` in a non-spec file" do
- inspect_source(OFFENSE_CALL_SINGLE_QUOTES_KEY)
-
- expect(cop.offenses.size).to eq(0)
- end
+ context 'with a key using double quotes' do
+ it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY
+ it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY, %(stub_env("FOO", 'bar'))
end
end
diff --git a/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb b/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
index 2763f2bda21..94324bc615d 100644
--- a/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
+++ b/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
@@ -8,8 +8,6 @@ require_relative '../../../../rubocop/cop/rspec/factories_in_migration_specs'
describe RuboCop::Cop::RSpec::FactoriesInMigrationSpecs do
include CopHelper
- let(:source_file) { 'spec/migrations/foo_spec.rb' }
-
subject(:cop) { described_class.new }
shared_examples 'an offensive factory call' do |namespace|
@@ -27,22 +25,6 @@ describe RuboCop::Cop::RSpec::FactoriesInMigrationSpecs do
end
end
- context 'in a migration spec file' do
- before do
- allow(cop).to receive(:in_migration_spec?).and_return(true)
- end
-
- it_behaves_like 'an offensive factory call', ''
- it_behaves_like 'an offensive factory call', 'FactoryBot.'
- end
-
- context 'outside of a migration spec file' do
- it "does not register an offense" do
- expect_no_offenses(<<-RUBY)
- describe 'foo' do
- let(:user) { create(:user) }
- end
- RUBY
- end
- end
+ it_behaves_like 'an offensive factory call', ''
+ it_behaves_like 'an offensive factory call', 'FactoryBot.'
end
diff --git a/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb b/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb
new file mode 100644
index 00000000000..258144d4000
--- /dev/null
+++ b/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/rspec/top_level_describe_path'
+
+describe RuboCop::Cop::RSpec::TopLevelDescribePath do
+ include RuboCop::RSpec::ExpectOffense
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'when the file ends in _spec.rb' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~SOURCE.strip_indent, 'spec/foo_spec.rb')
+ describe 'Foo' do
+ end
+ SOURCE
+ end
+ end
+
+ context 'when the file is a frontend fixture' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~SOURCE.strip_indent, 'spec/frontend/fixtures/foo.rb')
+ describe 'Foo' do
+ end
+ SOURCE
+ end
+ end
+
+ context 'when the describe is in a shared example' do
+ context 'with shared_examples' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~SOURCE.strip_indent, 'spec/foo.rb')
+ shared_examples 'Foo' do
+ describe '#bar' do
+ end
+ end
+ SOURCE
+ end
+ end
+
+ context 'with shared_examples_for' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~SOURCE.strip_indent, 'spec/foo.rb')
+ shared_examples_for 'Foo' do
+ describe '#bar' do
+ end
+ end
+ SOURCE
+ end
+ end
+ end
+
+ context 'when the describe is at the top level' do
+ it 'marks the describe as offending' do
+ expect_offense(<<~SOURCE.strip_indent, 'spec/foo.rb')
+ describe 'Foo' do
+ ^^^^^^^^^^^^^^ #{described_class::MESSAGE}
+ end
+ SOURCE
+ end
+ end
+end
diff --git a/spec/serializers/analytics_issue_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb
index 89588b4df2b..c5b03bdd8c1 100644
--- a/spec/serializers/analytics_issue_entity_spec.rb
+++ b/spec/serializers/analytics_issue_entity_spec.rb
@@ -9,12 +9,14 @@ describe AnalyticsIssueEntity do
iid: "1",
id: "1",
created_at: "2016-11-12 15:04:02.948604",
- author: user
+ author: user,
+ project_path: project.path,
+ namespace_path: project.namespace.route.path
}
end
- let(:project) { create(:project) }
- let(:request) { EntityRequest.new(project: project, entity: :merge_request) }
+ let(:project) { create(:project, name: 'my project') }
+ let(:request) { EntityRequest.new(entity: :merge_request) }
let(:entity) do
described_class.new(entity_hash, request: request, project: project)
diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb
index 5befc28f4fa..9cb2ce13d12 100644
--- a/spec/serializers/analytics_issue_serializer_spec.rb
+++ b/spec/serializers/analytics_issue_serializer_spec.rb
@@ -3,12 +3,12 @@ require 'spec_helper'
describe AnalyticsIssueSerializer do
subject do
described_class
- .new(project: project, entity: :merge_request)
+ .new(entity: :merge_request)
.represent(resource)
end
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, name: 'my project') }
let(:resource) do
{
total_time: "172802.724419",
@@ -16,7 +16,9 @@ describe AnalyticsIssueSerializer do
iid: "1",
id: "1",
created_at: "2016-11-12 15:04:02.948604",
- author: user
+ author: user,
+ project_path: project.path,
+ namespace_path: project.namespace.route.path
}
end
diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb
index 62067cc0ef2..a864051b2a3 100644
--- a/spec/serializers/analytics_merge_request_serializer_spec.rb
+++ b/spec/serializers/analytics_merge_request_serializer_spec.rb
@@ -3,12 +3,12 @@ require 'spec_helper'
describe AnalyticsMergeRequestSerializer do
subject do
described_class
- .new(project: project, entity: :merge_request)
+ .new(entity: :merge_request)
.represent(resource)
end
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, name: 'my project') }
let(:resource) do
{
total_time: "172802.724419",
@@ -17,7 +17,9 @@ describe AnalyticsMergeRequestSerializer do
id: "1",
state: 'open',
created_at: "2016-11-12 15:04:02.948604",
- author: user
+ author: user,
+ project_path: project.path,
+ namespace_path: project.namespace.route.path
}
end
diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb
index 5b05c2f2ef3..86a796a2d94 100644
--- a/spec/serializers/analytics_stage_serializer_spec.rb
+++ b/spec/serializers/analytics_stage_serializer_spec.rb
@@ -6,11 +6,11 @@ describe AnalyticsStageSerializer do
end
let(:resource) do
- Gitlab::CycleAnalytics::CodeStage.new(project: double, options: {})
+ Gitlab::CycleAnalytics::CodeStage.new(options: { project: double })
end
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(1.12)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(1.12)
allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
end
@@ -24,7 +24,7 @@ describe AnalyticsStageSerializer do
context 'when median is equal 0' do
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(0)
end
it 'sets the value to nil' do
@@ -34,7 +34,7 @@ describe AnalyticsStageSerializer do
context 'when median is below 1' do
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0.12)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(0.12)
end
it 'sets the value to equal to median' do
@@ -44,7 +44,7 @@ describe AnalyticsStageSerializer do
context 'when median is above 1' do
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(60.12)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(60.12)
end
it 'sets the value to equal to median' do
diff --git a/spec/serializers/cluster_application_entity_spec.rb b/spec/serializers/cluster_application_entity_spec.rb
index f38a18fcf59..76ecca06522 100644
--- a/spec/serializers/cluster_application_entity_spec.rb
+++ b/spec/serializers/cluster_application_entity_spec.rb
@@ -22,7 +22,7 @@ describe ClusterApplicationEntity do
end
it 'has can_uninstall' do
- expect(subject[:can_uninstall]).to be_falsey
+ expect(subject[:can_uninstall]).to be_truthy
end
context 'non-helm application' do
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 76ad2aee5c5..1b19eac9a97 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -32,6 +32,10 @@ describe DeploymentEntity do
expect(subject).to include(:created_at)
end
+ it 'exposes deployed_at' do
+ expect(subject).to include(:deployed_at)
+ end
+
context 'when the pipeline has another manual action' do
let(:other_build) { create(:ci_build, :manual, name: 'another deploy', pipeline: pipeline) }
let!(:other_deployment) { create(:deployment, deployable: other_build) }
diff --git a/spec/serializers/diff_file_base_entity_spec.rb b/spec/serializers/diff_file_base_entity_spec.rb
new file mode 100644
index 00000000000..68c5c665ed6
--- /dev/null
+++ b/spec/serializers/diff_file_base_entity_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DiffFileBaseEntity do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ context 'diff for a changed submodule' do
+ let(:commit_sha_with_changed_submodule) do
+ "cfe32cf61b73a0d5e9f13e774abde7ff789b1660"
+ end
+ let(:commit) { project.commit(commit_sha_with_changed_submodule) }
+ let(:diff_file) { commit.diffs.diff_files.to_a.last }
+ let(:options) { { request: {}, submodule_links: Gitlab::SubmoduleLinks.new(repository) } }
+ let(:entity) { described_class.new(diff_file, options).as_json }
+
+ it do
+ expect(entity[:submodule]).to eq(true)
+ expect(entity[:submodule_link]).to eq("https://github.com/randx/six")
+ expect(entity[:submodule_tree_url]).to eq(
+ "https://github.com/randx/six/tree/409f37c4f05865e4fb208c771485f211a22c4c2d"
+ )
+ end
+ end
+end
diff --git a/spec/serializers/environment_status_entity_spec.rb b/spec/serializers/environment_status_entity_spec.rb
index 8a6a38fe5f8..cb4749f019f 100644
--- a/spec/serializers/environment_status_entity_spec.rb
+++ b/spec/serializers/environment_status_entity_spec.rb
@@ -9,7 +9,7 @@ describe EnvironmentStatusEntity do
let(:project) { deployment.project }
let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) }
- let(:environment_status) { EnvironmentStatus.new(environment, merge_request, merge_request.diff_head_sha) }
+ let(:environment_status) { EnvironmentStatus.new(project, environment, merge_request, merge_request.diff_head_sha) }
let(:entity) { described_class.new(environment_status, request: request) }
subject { entity.as_json }
@@ -55,8 +55,14 @@ describe EnvironmentStatusEntity do
before do
project.add_maintainer(user)
allow(deployment).to receive(:prometheus_adapter).and_return(prometheus_adapter)
- allow(prometheus_adapter).to receive(:query).with(:deployment, deployment).and_return(simple_metrics)
allow(entity).to receive(:deployment).and_return(deployment)
+
+ expect_next_instance_of(DeploymentMetrics) do |deployment_metrics|
+ allow(deployment_metrics).to receive(:prometheus_adapter).and_return(prometheus_adapter)
+
+ allow(prometheus_adapter).to receive(:query)
+ .with(:deployment, deployment).and_return(simple_metrics)
+ end
end
context 'when deployment succeeded' do
@@ -64,7 +70,7 @@ describe EnvironmentStatusEntity do
it 'returns metrics url' do
expect(subject[:metrics_url])
- .to eq("/#{project.namespace.name}/#{project.name}/environments/#{environment.id}/deployments/#{deployment.iid}/metrics")
+ .to eq("/#{project.full_path}/environments/#{environment.id}/deployments/#{deployment.iid}/metrics")
end
end
diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb
index b58d95ccb43..00e2f931549 100644
--- a/spec/serializers/group_child_entity_spec.rb
+++ b/spec/serializers/group_child_entity_spec.rb
@@ -62,7 +62,7 @@ describe GroupChildEntity do
it_behaves_like 'group child json'
end
- describe 'for a group', :nested_groups do
+ describe 'for a group' do
let(:description) { 'Awesomeness' }
let(:object) do
create(:group, :nested, :with_avatar,
diff --git a/spec/serializers/group_child_serializer_spec.rb b/spec/serializers/group_child_serializer_spec.rb
index 5541ada3750..c9e8535585b 100644
--- a/spec/serializers/group_child_serializer_spec.rb
+++ b/spec/serializers/group_child_serializer_spec.rb
@@ -16,7 +16,7 @@ describe GroupChildSerializer do
end
end
- context 'with a hierarchy', :nested_groups do
+ context 'with a hierarchy' do
let(:parent) { create(:group) }
subject(:serializer) do
@@ -75,7 +75,7 @@ describe GroupChildSerializer do
expect(serializer.represent(build_list(:project, 2))).to be_kind_of(Array)
end
- context 'with a hierarchy', :nested_groups do
+ context 'with a hierarchy' do
let(:parent) { create(:group) }
subject(:serializer) do
diff --git a/spec/serializers/issue_entity_spec.rb b/spec/serializers/issue_entity_spec.rb
index caa3e41402b..0e05b3c84f4 100644
--- a/spec/serializers/issue_entity_spec.rb
+++ b/spec/serializers/issue_entity_spec.rb
@@ -17,4 +17,37 @@ describe IssueEntity do
it 'has time estimation attributes' do
expect(subject).to include(:time_estimate, :total_time_spent, :human_time_estimate, :human_total_time_spent)
end
+
+ context 'when issue got moved' do
+ let(:public_project) { create(:project, :public) }
+ let(:member) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:issue) { create(:issue, project: public_project) }
+
+ before do
+ project.add_developer(member)
+ public_project.add_developer(member)
+ Issues::MoveService.new(public_project, member).execute(issue, project)
+ end
+
+ context 'when user cannot read target project' do
+ it 'does not return moved_to_id' do
+ request = double('request', current_user: non_member)
+
+ response = described_class.new(issue, request: request).as_json
+
+ expect(response[:moved_to_id]).to be_nil
+ end
+ end
+
+ context 'when user can read target project' do
+ it 'returns moved moved_to_id' do
+ request = double('request', current_user: member)
+
+ response = described_class.new(issue, request: request).as_json
+
+ expect(response[:moved_to_id]).to eq(issue.moved_to_id)
+ end
+ end
+ end
end
diff --git a/spec/serializers/merge_request_sidebar_basic_entity_spec.rb b/spec/serializers/merge_request_sidebar_basic_entity_spec.rb
new file mode 100644
index 00000000000..b364b1a3306
--- /dev/null
+++ b/spec/serializers/merge_request_sidebar_basic_entity_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MergeRequestSidebarBasicEntity do
+ let(:project) { create :project, :repository }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:user) { create(:user) }
+
+ let(:request) { double('request', current_user: user, project: project) }
+
+ let(:entity) { described_class.new(merge_request, request: request).as_json }
+
+ describe '#current_user' do
+ it 'contains attributes related to the current user' do
+ expect(entity[:current_user].keys).to contain_exactly(
+ :id, :name, :username, :state, :avatar_url, :web_url, :todo,
+ :can_edit, :can_move, :can_admin_label, :can_merge
+ )
+ end
+ end
+end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 54e6abc2d3a..7f9827329b3 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -126,7 +126,7 @@ describe PipelineSerializer do
expect(subject.all? { |entry| entry[:merge_request].present? }).to be_truthy
end
- it 'preloads related merge requests', :postgresql do
+ it 'preloads related merge requests' do
recorded = ActiveRecord::QueryRecorder.new { subject }
expect(recorded.log)
diff --git a/spec/serializers/test_case_entity_spec.rb b/spec/serializers/test_case_entity_spec.rb
index 986c9feb07b..cc5f086ca4e 100644
--- a/spec/serializers/test_case_entity_spec.rb
+++ b/spec/serializers/test_case_entity_spec.rb
@@ -24,7 +24,7 @@ describe TestCaseEntity do
it 'contains correct test case details' do
expect(subject[:status]).to eq('failed')
- expect(subject[:name]).to eq('Test#sum when a is 2 and b is 2 returns summary')
+ expect(subject[:name]).to eq('Test#sum when a is 1 and b is 3 returns summary')
expect(subject[:classname]).to eq('spec.test_spec')
expect(subject[:execution_time]).to eq(2.22)
end
diff --git a/spec/serializers/test_reports_comparer_entity_spec.rb b/spec/serializers/test_reports_comparer_entity_spec.rb
index 59c058fe368..4a951bbbde4 100644
--- a/spec/serializers/test_reports_comparer_entity_spec.rb
+++ b/spec/serializers/test_reports_comparer_entity_spec.rb
@@ -53,13 +53,7 @@ describe TestReportsComparerEntity do
base_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
base_reports.get_suite('junit').add_test_case(create_test_case_java_failed)
head_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
- head_reports.get_suite('junit').add_test_case(create_test_case_java_resolved)
- end
-
- let(:create_test_case_java_resolved) do
- create_test_case_java_failed.tap do |test_case|
- test_case.instance_variable_set("@status", Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS)
- end
+ head_reports.get_suite('junit').add_test_case(create_test_case_java_success)
end
it 'contains correct compared test reports details' do
diff --git a/spec/serializers/test_reports_comparer_serializer_spec.rb b/spec/serializers/test_reports_comparer_serializer_spec.rb
index 9ea86c0dd83..62dc6f486c5 100644
--- a/spec/serializers/test_reports_comparer_serializer_spec.rb
+++ b/spec/serializers/test_reports_comparer_serializer_spec.rb
@@ -44,13 +44,7 @@ describe TestReportsComparerSerializer do
base_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
base_reports.get_suite('junit').add_test_case(create_test_case_java_failed)
head_reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
- head_reports.get_suite('junit').add_test_case(create_test_case_java_resolved)
- end
-
- let(:create_test_case_java_resolved) do
- create_test_case_java_failed.tap do |test_case|
- test_case.instance_variable_set("@status", Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS)
- end
+ head_reports.get_suite('junit').add_test_case(create_test_case_java_success)
end
it 'matches the schema' do
diff --git a/spec/serializers/test_suite_comparer_entity_spec.rb b/spec/serializers/test_suite_comparer_entity_spec.rb
index f61331f53a0..4b2cca2c68c 100644
--- a/spec/serializers/test_suite_comparer_entity_spec.rb
+++ b/spec/serializers/test_suite_comparer_entity_spec.rb
@@ -11,16 +11,10 @@ describe TestSuiteComparerEntity do
let(:test_case_success) { create_test_case_rspec_success }
let(:test_case_failed) { create_test_case_rspec_failed }
- let(:test_case_resolved) do
- create_test_case_rspec_failed.tap do |test_case|
- test_case.instance_variable_set("@status", Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS)
- end
- end
-
describe '#as_json' do
subject { entity.as_json }
- context 'when head sutie has a newly failed test case which does not exist in base' do
+ context 'when head suite has a newly failed test case which does not exist in base' do
before do
base_suite.add_test_case(test_case_success)
head_suite.add_test_case(test_case_failed)
@@ -41,7 +35,7 @@ describe TestSuiteComparerEntity do
end
end
- context 'when head sutie still has a failed test case which failed in base' do
+ context 'when head suite still has a failed test case which failed in base' do
before do
base_suite.add_test_case(test_case_failed)
head_suite.add_test_case(test_case_failed)
@@ -62,10 +56,10 @@ describe TestSuiteComparerEntity do
end
end
- context 'when head sutie has a success test case which failed in base' do
+ context 'when head suite has a success test case which failed in base' do
before do
base_suite.add_test_case(test_case_failed)
- head_suite.add_test_case(test_case_resolved)
+ head_suite.add_test_case(test_case_success)
end
it 'contains correct compared test suite details' do
@@ -74,13 +68,83 @@ describe TestSuiteComparerEntity do
expect(subject[:summary]).to include(total: 1, resolved: 1, failed: 0)
expect(subject[:new_failures]).to be_empty
subject[:resolved_failures].first.tap do |resolved_failure|
- expect(resolved_failure[:status]).to eq(test_case_resolved.status)
- expect(resolved_failure[:name]).to eq(test_case_resolved.name)
- expect(resolved_failure[:execution_time]).to eq(test_case_resolved.execution_time)
- expect(resolved_failure[:system_output]).to eq(test_case_resolved.system_output)
+ expect(resolved_failure[:status]).to eq(test_case_success.status)
+ expect(resolved_failure[:name]).to eq(test_case_success.name)
+ expect(resolved_failure[:execution_time]).to eq(test_case_success.execution_time)
+ expect(resolved_failure[:system_output]).to eq(test_case_success.system_output)
end
expect(subject[:existing_failures]).to be_empty
end
end
+
+ context 'limits amount of tests returned' do
+ before do
+ stub_const('TestSuiteComparerEntity::DEFAULT_MAX_TESTS', 2)
+ stub_const('TestSuiteComparerEntity::DEFAULT_MIN_TESTS', 1)
+ end
+
+ context 'prefers new over existing and resolved' do
+ before do
+ 3.times { add_new_failure }
+ 3.times { add_existing_failure }
+ 3.times { add_resolved_failure }
+ end
+
+ it 'returns 2 new failures, and 1 of resolved and existing' do
+ expect(subject[:summary]).to include(total: 9, resolved: 3, failed: 6)
+ expect(subject[:new_failures].count).to eq(2)
+ expect(subject[:existing_failures].count).to eq(1)
+ expect(subject[:resolved_failures].count).to eq(1)
+ end
+ end
+
+ context 'prefers existing over resolved' do
+ before do
+ 3.times { add_existing_failure }
+ 3.times { add_resolved_failure }
+ end
+
+ it 'returns 2 existing failures, and 1 resolved' do
+ expect(subject[:summary]).to include(total: 6, resolved: 3, failed: 3)
+ expect(subject[:new_failures].count).to eq(0)
+ expect(subject[:existing_failures].count).to eq(2)
+ expect(subject[:resolved_failures].count).to eq(1)
+ end
+ end
+
+ context 'limits amount of resolved' do
+ before do
+ 3.times { add_resolved_failure }
+ end
+
+ it 'returns 2 resolved failures' do
+ expect(subject[:summary]).to include(total: 3, resolved: 3, failed: 0)
+ expect(subject[:new_failures].count).to eq(0)
+ expect(subject[:existing_failures].count).to eq(0)
+ expect(subject[:resolved_failures].count).to eq(2)
+ end
+ end
+
+ private
+
+ def add_new_failure
+ failed_case = create_test_case_rspec_failed(SecureRandom.hex)
+ head_suite.add_test_case(failed_case)
+ end
+
+ def add_existing_failure
+ failed_case = create_test_case_rspec_failed(SecureRandom.hex)
+ base_suite.add_test_case(failed_case)
+ head_suite.add_test_case(failed_case)
+ end
+
+ def add_resolved_failure
+ case_name = SecureRandom.hex
+ failed_case = create_test_case_rspec_failed(case_name)
+ success_case = create_test_case_rspec_success(case_name)
+ base_suite.add_test_case(failed_case)
+ head_suite.add_test_case(success_case)
+ end
+ end
end
end
diff --git a/spec/serializers/user_serializer_spec.rb b/spec/serializers/user_serializer_spec.rb
new file mode 100644
index 00000000000..2e4a8c644fe
--- /dev/null
+++ b/spec/serializers/user_serializer_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe UserSerializer do
+ let(:user1) { create(:user) }
+ let(:user2) { create(:user) }
+
+ context 'serializer with merge request context' do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:serializer) { described_class.new(merge_request_iid: merge_request.iid) }
+
+ before do
+ allow(project).to(
+ receive_message_chain(:merge_requests, :find_by_iid!)
+ .with(merge_request.iid).and_return(merge_request)
+ )
+
+ project.add_maintainer(user1)
+ end
+
+ it 'returns a user with can_merge option' do
+ serialized_user1, serialized_user2 = serializer.represent([user1, user2], project: project).as_json
+
+ expect(serialized_user1).to include("id" => user1.id, "can_merge" => true)
+ expect(serialized_user2).to include("id" => user2.id, "can_merge" => false)
+ end
+ end
+end
diff --git a/spec/serializers/variable_entity_spec.rb b/spec/serializers/variable_entity_spec.rb
index effc0022633..10664ff66ec 100644
--- a/spec/serializers/variable_entity_spec.rb
+++ b/spec/serializers/variable_entity_spec.rb
@@ -8,7 +8,7 @@ describe VariableEntity do
subject { entity.as_json }
it 'contains required fields' do
- expect(subject).to include(:id, :key, :value, :protected)
+ expect(subject).to include(:id, :key, :value, :protected, :environment_scope)
end
end
end
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index a641828faa5..ab06c1a1209 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -62,6 +62,54 @@ describe ApplicationSettings::UpdateService do
end
end
+ describe 'updating outbound_local_requests_whitelist' do
+ context 'when params is blank' do
+ let(:params) { {} }
+
+ it 'does not add to whitelist' do
+ expect { subject.execute }.not_to change {
+ application_settings.outbound_local_requests_whitelist
+ }
+ end
+ end
+
+ context 'when param add_to_outbound_local_requests_whitelist contains values' do
+ before do
+ application_settings.outbound_local_requests_whitelist = ['127.0.0.1']
+ end
+
+ let(:params) { { add_to_outbound_local_requests_whitelist: ['example.com', ''] } }
+
+ it 'adds to whitelist' do
+ expect { subject.execute }.to change {
+ application_settings.outbound_local_requests_whitelist
+ }
+
+ expect(application_settings.outbound_local_requests_whitelist).to contain_exactly(
+ '127.0.0.1', 'example.com'
+ )
+ end
+ end
+
+ context 'when param outbound_local_requests_whitelist_raw is passed' do
+ before do
+ application_settings.outbound_local_requests_whitelist = ['127.0.0.1']
+ end
+
+ let(:params) { { outbound_local_requests_whitelist_raw: 'example.com;gitlab.com' } }
+
+ it 'overwrites the existing whitelist' do
+ expect { subject.execute }.to change {
+ application_settings.outbound_local_requests_whitelist
+ }
+
+ expect(application_settings.outbound_local_requests_whitelist).to contain_exactly(
+ 'example.com', 'gitlab.com'
+ )
+ end
+ end
+ end
+
describe 'performance bar settings' do
using RSpec::Parameterized::TableSyntax
@@ -153,6 +201,24 @@ describe ApplicationSettings::UpdateService do
enable_external_authorization_service_check
end
+ it 'does not validate labels if external authorization gets disabled' do
+ expect_any_instance_of(described_class).not_to receive(:validate_classification_label)
+
+ described_class.new(application_settings, admin, { external_authorization_service_enabled: false }).execute
+ end
+
+ it 'does validate labels if external authorization gets enabled ' do
+ expect_any_instance_of(described_class).to receive(:validate_classification_label)
+
+ described_class.new(application_settings, admin, { external_authorization_service_enabled: true }).execute
+ end
+
+ it 'does validate labels if external authorization is left unchanged' do
+ expect_any_instance_of(described_class).to receive(:validate_classification_label)
+
+ described_class.new(application_settings, admin, { external_authorization_service_default_label: 'new-label' }).execute
+ end
+
it 'does not save the settings with an error if the service denies access' do
expect(::Gitlab::ExternalAuthorization)
.to receive(:access_allowed?).with(admin, 'new-label') { false }
@@ -180,4 +246,20 @@ describe ApplicationSettings::UpdateService do
described_class.new(application_settings, admin, { home_page_url: 'http://foo.bar' }).execute
end
end
+
+ context 'when raw_blob_request_limit is passsed' do
+ let(:params) do
+ {
+ raw_blob_request_limit: 600
+ }
+ end
+
+ it 'updates raw_blob_request_limit value' do
+ subject.execute
+
+ application_settings.reload
+
+ expect(application_settings.raw_blob_request_limit).to eq(600)
+ end
+ end
end
diff --git a/spec/services/audit_event_service_spec.rb b/spec/services/audit_event_service_spec.rb
index 32fd98e6ef9..e42bff607b2 100644
--- a/spec/services/audit_event_service_spec.rb
+++ b/spec/services/audit_event_service_spec.rb
@@ -10,11 +10,8 @@ describe AuditEventService do
let(:logger) { instance_double(Gitlab::AuditJsonLogger) }
describe '#security_event' do
- before do
- expect(service).to receive(:file_logger).and_return(logger)
- end
-
it 'creates an event and logs to a file' do
+ expect(service).to receive(:file_logger).and_return(logger)
expect(logger).to receive(:info).with(author_id: user.id,
entity_id: project.id,
entity_type: "Project",
@@ -22,5 +19,32 @@ describe AuditEventService do
expect { service.security_event }.to change(SecurityEvent, :count).by(1)
end
+
+ it 'formats from and to fields' do
+ service = described_class.new(
+ user, project,
+ {
+ from: true,
+ to: false,
+ action: :create,
+ target_id: 1
+ })
+ expect(service).to receive(:file_logger).and_return(logger)
+ expect(logger).to receive(:info).with(author_id: user.id,
+ entity_type: 'Project',
+ entity_id: project.id,
+ from: 'true',
+ to: 'false',
+ action: :create,
+ target_id: 1)
+
+ expect { service.security_event }.to change(SecurityEvent, :count).by(1)
+
+ details = SecurityEvent.last.details
+ expect(details[:from]).to be true
+ expect(details[:to]).to be false
+ expect(details[:action]).to eq(:create)
+ expect(details[:target_id]).to eq(1)
+ end
end
end
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 4f4776bbb27..3ca389ba25b 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -145,6 +145,19 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'not a container repository factory'
end
+ describe '#pull_access_token' do
+ let(:project) { create(:project) }
+ let(:token) { described_class.pull_access_token(project.full_path) }
+
+ subject { { token: token } }
+
+ it_behaves_like 'an accessible' do
+ let(:actions) { ['pull'] }
+ end
+
+ it_behaves_like 'not a container repository factory'
+ end
+
context 'user authorization' do
let(:current_user) { create(:user) }
diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb
index cd08e0b6f32..a409f21a7e4 100644
--- a/spec/services/auto_merge/base_service_spec.rb
+++ b/spec/services/auto_merge/base_service_spec.rb
@@ -59,6 +59,11 @@ describe AutoMerge::BaseService do
context 'when strategy is merge when pipeline succeeds' do
let(:service) { AutoMerge::MergeWhenPipelineSucceedsService.new(project, user) }
+ before do
+ pipeline = build(:ci_pipeline)
+ allow(merge_request).to receive(:actual_head_pipeline) { pipeline }
+ end
+
it 'sets the auto merge strategy' do
subject
@@ -116,11 +121,7 @@ describe AutoMerge::BaseService do
end
end
- describe '#cancel' do
- subject { service.cancel(merge_request) }
-
- let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
-
+ shared_examples_for 'Canceled or Dropped' do
it 'removes properies from the merge request' do
subject
@@ -168,6 +169,20 @@ describe AutoMerge::BaseService do
it 'does not yield block' do
expect { |b| service.execute(merge_request, &b) }.not_to yield_control
end
+ end
+ end
+
+ describe '#cancel' do
+ subject { service.cancel(merge_request) }
+
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it_behaves_like 'Canceled or Dropped'
+
+ context 'when failed to save' do
+ before do
+ allow(merge_request).to receive(:save) { false }
+ end
it 'returns error status' do
expect(subject[:status]).to eq(:error)
@@ -175,4 +190,24 @@ describe AutoMerge::BaseService do
end
end
end
+
+ describe '#abort' do
+ subject { service.abort(merge_request, reason) }
+
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+ let(:reason) { 'an error'}
+
+ it_behaves_like 'Canceled or Dropped'
+
+ context 'when failed to save' do
+ before do
+ allow(merge_request).to receive(:save) { false }
+ end
+
+ it 'returns error status' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq("Can't abort the automatic merge")
+ end
+ end
+ end
end
diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
index a20bf8e17e4..931b52470c4 100644
--- a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
@@ -64,8 +64,11 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
end
it 'creates a system note' do
+ pipeline = build(:ci_pipeline)
+ allow(merge_request).to receive(:actual_head_pipeline) { pipeline }
+
note = merge_request.notes.last
- expect(note.note).to match %r{enabled an automatic merge when the pipeline for (\w+/\w+@)?\h{8}}
+ expect(note.note).to match "enabled an automatic merge when the pipeline for #{pipeline.sha}"
end
end
@@ -174,6 +177,17 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
end
end
+ describe "#abort" do
+ before do
+ service.abort(mr_merge_if_green_enabled, 'an error')
+ end
+
+ it 'posts a system note' do
+ note = mr_merge_if_green_enabled.notes.last
+ expect(note.note).to include 'aborted the automatic merge'
+ end
+ end
+
describe 'pipeline integration' do
context 'when there are multiple stages in the pipeline' do
let(:ref) { mr_merge_if_green_enabled.source_branch }
diff --git a/spec/services/auto_merge_service_spec.rb b/spec/services/auto_merge_service_spec.rb
index 93a22e60498..50dfc49a59c 100644
--- a/spec/services/auto_merge_service_spec.rb
+++ b/spec/services/auto_merge_service_spec.rb
@@ -161,4 +161,29 @@ describe AutoMergeService do
end
end
end
+
+ describe '#abort' do
+ subject { service.abort(merge_request, error) }
+
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+ let(:error) { 'an error' }
+
+ it 'delegates to a relevant service instance' do
+ expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service|
+ expect(service).to receive(:abort).with(merge_request, error)
+ end
+
+ subject
+ end
+
+ context 'when auto merge is not enabled' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'returns error' do
+ expect(subject[:message]).to eq("Can't abort the automatic merge")
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:http_status]).to eq(406)
+ end
+ end
+ end
end
diff --git a/spec/services/award_emojis/add_service_spec.rb b/spec/services/award_emojis/add_service_spec.rb
new file mode 100644
index 00000000000..037db39ba80
--- /dev/null
+++ b/spec/services/award_emojis/add_service_spec.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojis::AddService do
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:awardable) { create(:note, project: project) }
+ let(:name) { 'thumbsup' }
+ subject(:service) { described_class.new(awardable, name, user) }
+
+ describe '#execute' do
+ context 'when user is not authorized' do
+ it 'does not add an emoji' do
+ expect { service.execute }.not_to change { AwardEmoji.count }
+ end
+
+ it 'returns an error state' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(:forbidden)
+ end
+ end
+
+ context 'when user is authorized' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'creates an award emoji' do
+ expect { service.execute }.to change { AwardEmoji.count }.by(1)
+ end
+
+ it 'returns the award emoji' do
+ result = service.execute
+
+ expect(result[:award]).to be_kind_of(AwardEmoji)
+ end
+
+ it 'return a success status' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:success)
+ end
+
+ it 'sets the correct properties on the award emoji' do
+ award = service.execute[:award]
+
+ expect(award.name).to eq(name)
+ expect(award.user).to eq(user)
+ end
+
+ describe 'marking Todos as done' do
+ subject { service.execute }
+
+ include_examples 'creating award emojis marks Todos as done'
+ end
+
+ context 'when the awardable cannot have emoji awarded to it' do
+ before do
+ expect(awardable).to receive(:emoji_awardable?).and_return(false)
+ end
+
+ it 'does not add an emoji' do
+ expect { service.execute }.not_to change { AwardEmoji.count }
+ end
+
+ it 'returns an error status' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(:unprocessable_entity)
+ end
+ end
+
+ context 'when the awardable is invalid' do
+ before do
+ expect_next_instance_of(AwardEmoji) do |award|
+ expect(award).to receive(:valid?).and_return(false)
+ expect(award).to receive_message_chain(:errors, :full_messages).and_return(['Error 1', 'Error 2'])
+ end
+ end
+
+ it 'does not add an emoji' do
+ expect { service.execute }.not_to change { AwardEmoji.count }
+ end
+
+ it 'returns an error status' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:error)
+ end
+
+ it 'returns an error message' do
+ result = service.execute
+
+ expect(result[:message]).to eq('Error 1 and Error 2')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/finders/awarded_emoji_finder_spec.rb b/spec/services/award_emojis/collect_user_emoji_service_spec.rb
index d4479df7418..a0dea31b403 100644
--- a/spec/finders/awarded_emoji_finder_spec.rb
+++ b/spec/services/award_emojis/collect_user_emoji_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardedEmojiFinder do
+describe AwardEmojis::CollectUserEmojiService do
describe '#execute' do
it 'returns an Array containing the awarded emoji names' do
user = create(:user)
diff --git a/spec/services/award_emojis/destroy_service_spec.rb b/spec/services/award_emojis/destroy_service_spec.rb
new file mode 100644
index 00000000000..c4a7d5ec20e
--- /dev/null
+++ b/spec/services/award_emojis/destroy_service_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojis::DestroyService do
+ set(:user) { create(:user) }
+ set(:awardable) { create(:note) }
+ set(:project) { awardable.project }
+ let(:name) { 'thumbsup' }
+ let!(:award_from_other_user) do
+ create(:award_emoji, name: name, awardable: awardable, user: create(:user))
+ end
+ subject(:service) { described_class.new(awardable, name, user) }
+
+ describe '#execute' do
+ shared_examples_for 'a service that does not authorize the user' do |error:|
+ it 'does not remove the emoji' do
+ expect { service.execute }.not_to change { AwardEmoji.count }
+ end
+
+ it 'returns an error state' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(:forbidden)
+ end
+
+ it 'returns a nil award' do
+ result = service.execute
+
+ expect(result).to have_key(:award)
+ expect(result[:award]).to be_nil
+ end
+
+ it 'returns the error' do
+ result = service.execute
+
+ expect(result[:message]).to eq(error)
+ expect(result[:errors]).to eq([error])
+ end
+ end
+
+ context 'when user is not authorized' do
+ it_behaves_like 'a service that does not authorize the user',
+ error: 'User cannot destroy emoji on the awardable'
+ end
+
+ context 'when the user is authorized' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when user has not awarded an emoji to the awardable' do
+ let!(:award_from_user) { create(:award_emoji, name: name, user: user) }
+
+ it_behaves_like 'a service that does not authorize the user',
+ error: 'User has not awarded emoji of type thumbsup on the awardable'
+ end
+
+ context 'when user has awarded an emoji to the awardable' do
+ let!(:award_from_user) { create(:award_emoji, name: name, awardable: awardable, user: user) }
+
+ it 'removes the emoji' do
+ expect { service.execute }.to change { AwardEmoji.count }.by(-1)
+ end
+
+ it 'returns a success status' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:success)
+ end
+
+ it 'returns no errors' do
+ result = service.execute
+
+ expect(result).not_to have_key(:error)
+ expect(result).not_to have_key(:errors)
+ end
+
+ it 'returns the destroyed award' do
+ result = service.execute
+
+ expect(result[:award]).to eq(award_from_user)
+ expect(result[:award]).to be_destroyed
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/award_emojis/toggle_service_spec.rb b/spec/services/award_emojis/toggle_service_spec.rb
new file mode 100644
index 00000000000..972a1d5fc06
--- /dev/null
+++ b/spec/services/award_emojis/toggle_service_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojis::ToggleService do
+ set(:user) { create(:user) }
+ set(:project) { create(:project, :public) }
+ set(:awardable) { create(:note, project: project) }
+ let(:name) { 'thumbsup' }
+ subject(:service) { described_class.new(awardable, name, user) }
+
+ describe '#execute' do
+ context 'when user has awarded an emoji' do
+ let!(:award_from_other_user) { create(:award_emoji, name: name, awardable: awardable, user: create(:user)) }
+ let!(:award) { create(:award_emoji, name: name, awardable: awardable, user: user) }
+
+ it 'calls AwardEmojis::DestroyService' do
+ expect(AwardEmojis::AddService).not_to receive(:new)
+
+ expect_next_instance_of(AwardEmojis::DestroyService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ service.execute
+ end
+
+ it 'destroys an AwardEmoji' do
+ expect { service.execute }.to change { AwardEmoji.count }.by(-1)
+ end
+
+ it 'returns the result of DestroyService#execute' do
+ mock_result = double(foo: true)
+
+ expect_next_instance_of(AwardEmojis::DestroyService) do |service|
+ expect(service).to receive(:execute).and_return(mock_result)
+ end
+
+ result = service.execute
+
+ expect(result).to eq(mock_result)
+ end
+ end
+
+ context 'when user has not awarded an emoji' do
+ it 'calls AwardEmojis::AddService' do
+ expect_next_instance_of(AwardEmojis::AddService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ expect(AwardEmojis::DestroyService).not_to receive(:new)
+
+ service.execute
+ end
+
+ it 'creates an AwardEmoji' do
+ expect { service.execute }.to change { AwardEmoji.count }.by(1)
+ end
+
+ it 'returns the result of AddService#execute' do
+ mock_result = double(foo: true)
+
+ expect_next_instance_of(AwardEmojis::AddService) do |service|
+ expect(service).to receive(:execute).and_return(mock_result)
+ end
+
+ result = service.execute
+
+ expect(result).to eq(mock_result)
+ end
+ end
+ end
+end
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index 40878e24cb4..931b67b2950 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -112,7 +112,7 @@ describe Boards::Issues::ListService do
it_behaves_like 'issues list service'
end
- context 'and group is an ancestor', :nested_groups do
+ context 'and group is an ancestor' do
let(:parent) { create(:group) }
let(:group) { create(:group, parent: parent) }
let!(:backlog) { create(:backlog_list, board: board) }
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
index 16e2a2fba6b..cf84ec8fd4c 100644
--- a/spec/services/boards/issues/move_service_spec.rb
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -52,5 +52,91 @@ describe Boards::Issues::MoveService do
it_behaves_like 'issues move service', true
end
+
+ describe '#execute_multiple' do
+ set(:group) { create(:group) }
+ set(:user) { create(:user) }
+ set(:project) { create(:project, namespace: group) }
+ set(:board1) { create(:board, group: group) }
+ set(:development) { create(:group_label, group: group, name: 'Development') }
+ set(:testing) { create(:group_label, group: group, name: 'Testing') }
+ set(:list1) { create(:list, board: board1, label: development, position: 0) }
+ set(:list2) { create(:list, board: board1, label: testing, position: 1) }
+ let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns the expected result if list of issues is empty' do
+ expect(described_class.new(group, user, params).execute_multiple([])).to eq({ count: 0, success: false, issues: [] })
+ end
+
+ context 'moving multiple issues' do
+ let(:issue1) { create(:labeled_issue, project: project, labels: [development]) }
+ let(:issue2) { create(:labeled_issue, project: project, labels: [development]) }
+
+ it 'moves multiple issues from one list to another' do
+ expect(described_class.new(group, user, params).execute_multiple([issue1, issue2])).to be_truthy
+
+ expect(issue1.labels).to eq([testing])
+ expect(issue2.labels).to eq([testing])
+ end
+ end
+
+ context 'moving a single issue' do
+ let(:issue1) { create(:labeled_issue, project: project, labels: [development]) }
+
+ it 'moves one issue' do
+ expect(described_class.new(group, user, params).execute_multiple([issue1])).to be_truthy
+
+ expect(issue1.labels).to eq([testing])
+ end
+ end
+
+ context 'moving issues visually after an existing issue' do
+ let(:existing_issue) { create(:labeled_issue, project: project, labels: [testing], relative_position: 10) }
+ let(:issue1) { create(:labeled_issue, project: project, labels: [development]) }
+ let(:issue2) { create(:labeled_issue, project: project, labels: [development]) }
+
+ let(:move_params) do
+ params.dup.tap do |hash|
+ hash[:move_before_id] = existing_issue.id
+ end
+ end
+
+ it 'moves one issue' do
+ expect(described_class.new(group, user, move_params).execute_multiple([issue1, issue2])).to be_truthy
+
+ expect(issue1.labels).to eq([testing])
+ expect(issue2.labels).to eq([testing])
+
+ expect(issue1.relative_position > existing_issue.relative_position).to eq(true)
+ expect(issue2.relative_position > issue1.relative_position).to eq(true)
+ end
+ end
+
+ context 'moving issues visually before an existing issue' do
+ let(:existing_issue) { create(:labeled_issue, project: project, labels: [testing], relative_position: 10) }
+ let(:issue1) { create(:labeled_issue, project: project, labels: [development]) }
+ let(:issue2) { create(:labeled_issue, project: project, labels: [development]) }
+
+ let(:move_params) do
+ params.dup.tap do |hash|
+ hash[:move_after_id] = existing_issue.id
+ end
+ end
+
+ it 'moves one issue' do
+ expect(described_class.new(group, user, move_params).execute_multiple([issue1, issue2])).to be_truthy
+
+ expect(issue1.labels).to eq([testing])
+ expect(issue2.labels).to eq([testing])
+
+ expect(issue2.relative_position < existing_issue.relative_position).to eq(true)
+ expect(issue1.relative_position < issue2.relative_position).to eq(true)
+ end
+ end
+ end
end
end
diff --git a/spec/services/branches/diverging_commit_counts_service_spec.rb b/spec/services/branches/diverging_commit_counts_service_spec.rb
new file mode 100644
index 00000000000..370da773ab2
--- /dev/null
+++ b/spec/services/branches/diverging_commit_counts_service_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Branches::DivergingCommitCountsService do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ describe '#call' do
+ let(:diverged_branch) { repository.find_branch('fix') }
+ let(:root_ref_sha) { repository.raw_repository.commit(repository.root_ref).id }
+ let(:diverged_branch_sha) { diverged_branch.dereferenced_target.sha }
+
+ let(:service) { described_class.new(repository) }
+
+ it 'returns the commit counts behind and ahead of default branch' do
+ result = service.call(diverged_branch)
+
+ expect(result).to eq(behind: 29, ahead: 2)
+ end
+
+ it 'calls diverging_commit_count without max count' do
+ expect(repository.raw_repository)
+ .to receive(:diverging_commit_count)
+ .with(root_ref_sha, diverged_branch_sha)
+ .and_return([29, 2])
+
+ service.call(diverged_branch)
+ end
+ end
+end
diff --git a/spec/services/ci/archive_trace_service_spec.rb b/spec/services/ci/archive_trace_service_spec.rb
index 44a77c29086..454db3d5a48 100644
--- a/spec/services/ci/archive_trace_service_spec.rb
+++ b/spec/services/ci/archive_trace_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Ci::ArchiveTraceService, '#execute' do
- subject { described_class.new.execute(job) }
+ subject { described_class.new.execute(job, worker_name: ArchiveTraceWorker.name) }
context 'when job is finished' do
let(:job) { create(:ci_build, :success, :trace_live) }
@@ -25,6 +25,34 @@ describe Ci::ArchiveTraceService, '#execute' do
expect { subject }.not_to change { Ci::JobArtifact.trace.count }
end
end
+
+ context 'when job does not have trace' do
+ let(:job) { create(:ci_build, :success) }
+
+ it 'leaves a warning message in sidekiq log' do
+ expect(Sidekiq.logger).to receive(:warn).with(
+ class: ArchiveTraceWorker.name,
+ message: 'The job does not have live trace but going to be archived.',
+ job_id: job.id)
+
+ subject
+ end
+ end
+
+ context 'when job failed to archive trace but did not raise an exception' do
+ before do
+ allow_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!) {}
+ end
+
+ it 'leaves a warning message in sidekiq log' do
+ expect(Sidekiq.logger).to receive(:warn).with(
+ class: ArchiveTraceWorker.name,
+ message: 'The job does not have archived trace after archiving.',
+ job_id: job.id)
+
+ subject
+ end
+ end
end
context 'when job is running' do
@@ -37,10 +65,10 @@ describe Ci::ArchiveTraceService, '#execute' do
issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/51502',
extra: { job_id: job.id } ).once
- expect(Rails.logger)
- .to receive(:error)
- .with("Failed to archive trace. id: #{job.id} message: Job is not finished yet")
- .and_call_original
+ expect(Sidekiq.logger).to receive(:warn).with(
+ class: ArchiveTraceWorker.name,
+ message: "Failed to archive trace. message: Job is not finished yet.",
+ job_id: job.id).and_call_original
expect(Gitlab::Metrics)
.to receive(:counter)
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index d9b61dfe503..deb68899309 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -1099,6 +1099,63 @@ describe Ci::CreatePipelineService do
end
end
end
+
+ context 'when needs is used' do
+ let(:pipeline) { execute_service }
+
+ let(:config) do
+ {
+ build_a: {
+ stage: "build",
+ script: "ls",
+ only: %w[master]
+ },
+ test_a: {
+ stage: "test",
+ script: "ls",
+ only: %w[master feature],
+ needs: %w[build_a]
+ },
+ deploy: {
+ stage: "deploy",
+ script: "ls",
+ only: %w[tags]
+ }
+ }
+ end
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ end
+
+ context 'when pipeline on master is created' do
+ let(:ref_name) { 'refs/heads/master' }
+
+ it 'creates a pipeline with build_a and test_a' do
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.map(&:name)).to contain_exactly("build_a", "test_a")
+ end
+ end
+
+ context 'when pipeline on feature is created' do
+ let(:ref_name) { 'refs/heads/feature' }
+
+ it 'does not create a pipeline as test_a depends on build_a' do
+ expect(pipeline).not_to be_persisted
+ expect(pipeline.builds).to be_empty
+ expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
+ end
+ end
+
+ context 'when pipeline on v1.0.0 is created' do
+ let(:ref_name) { 'refs/tags/v1.0.0' }
+
+ it 'does create a pipeline only with deploy' do
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.map(&:name)).to contain_exactly("deploy")
+ end
+ end
+ end
end
describe '#execute!' do
diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb
index 1e68b7956ea..cf39f3da4fe 100644
--- a/spec/services/ci/play_build_service_spec.rb
+++ b/spec/services/ci/play_build_service_spec.rb
@@ -60,6 +60,19 @@ describe Ci::PlayBuildService, '#execute' do
expect(build.reload.user).to eq user
end
+
+ context 'when variables are supplied' do
+ let(:job_variables) do
+ [{ key: 'first', secret_value: 'first' },
+ { key: 'second', secret_value: 'second' }]
+ end
+
+ it 'assigns the variables to the build' do
+ service.execute(build, job_variables)
+
+ expect(build.reload.job_variables.map(&:key)).to contain_exactly('first', 'second')
+ end
+ end
end
context 'when build is not a playable manual action' do
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index cadb519ccee..1b28d2d4d02 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -700,6 +700,138 @@ describe Ci::ProcessPipelineService, '#execute' do
end
end
+ context 'when pipeline with needs is created' do
+ let!(:linux_build) { create_build('linux:build', stage: 'build', stage_idx: 0) }
+ let!(:mac_build) { create_build('mac:build', stage: 'build', stage_idx: 0) }
+ let!(:linux_rspec) { create_build('linux:rspec', stage: 'test', stage_idx: 1) }
+ let!(:linux_rubocop) { create_build('linux:rubocop', stage: 'test', stage_idx: 1) }
+ let!(:mac_rspec) { create_build('mac:rspec', stage: 'test', stage_idx: 1) }
+ let!(:mac_rubocop) { create_build('mac:rubocop', stage: 'test', stage_idx: 1) }
+ let!(:deploy) { create_build('deploy', stage: 'deploy', stage_idx: 2) }
+
+ let!(:linux_rspec_on_build) { create(:ci_build_need, build: linux_rspec, name: 'linux:build') }
+ let!(:linux_rubocop_on_build) { create(:ci_build_need, build: linux_rubocop, name: 'linux:build') }
+
+ let!(:mac_rspec_on_build) { create(:ci_build_need, build: mac_rspec, name: 'mac:build') }
+ let!(:mac_rubocop_on_build) { create(:ci_build_need, build: mac_rubocop, name: 'mac:build') }
+
+ it 'when linux:* finishes first it runs it out of order' do
+ expect(process_pipeline).to be_truthy
+
+ expect(stages).to eq(%w(pending created created))
+ expect(builds.pending).to contain_exactly(linux_build, mac_build)
+
+ # we follow the single path of linux
+ linux_build.reset.success!
+
+ expect(stages).to eq(%w(running pending created))
+ expect(builds.success).to contain_exactly(linux_build)
+ expect(builds.pending).to contain_exactly(mac_build, linux_rspec, linux_rubocop)
+
+ linux_rspec.reset.success!
+
+ expect(stages).to eq(%w(running running created))
+ expect(builds.success).to contain_exactly(linux_build, linux_rspec)
+ expect(builds.pending).to contain_exactly(mac_build, linux_rubocop)
+
+ linux_rubocop.reset.success!
+
+ expect(stages).to eq(%w(running running created))
+ expect(builds.success).to contain_exactly(linux_build, linux_rspec, linux_rubocop)
+ expect(builds.pending).to contain_exactly(mac_build)
+
+ mac_build.reset.success!
+ mac_rspec.reset.success!
+ mac_rubocop.reset.success!
+
+ expect(stages).to eq(%w(success success pending))
+ expect(builds.success).to contain_exactly(
+ linux_build, linux_rspec, linux_rubocop, mac_build, mac_rspec, mac_rubocop)
+ expect(builds.pending).to contain_exactly(deploy)
+ end
+
+ context 'when feature ci_dag_support is disabled' do
+ before do
+ stub_feature_flags(ci_dag_support: false)
+ end
+
+ it 'when linux:build finishes first it follows stages' do
+ expect(process_pipeline).to be_truthy
+
+ expect(stages).to eq(%w(pending created created))
+ expect(builds.pending).to contain_exactly(linux_build, mac_build)
+
+ # we follow the single path of linux
+ linux_build.reset.success!
+
+ expect(stages).to eq(%w(running created created))
+ expect(builds.success).to contain_exactly(linux_build)
+ expect(builds.pending).to contain_exactly(mac_build)
+
+ mac_build.reset.success!
+
+ expect(stages).to eq(%w(success pending created))
+ expect(builds.success).to contain_exactly(linux_build, mac_build)
+ expect(builds.pending).to contain_exactly(
+ linux_rspec, linux_rubocop, mac_rspec, mac_rubocop)
+
+ linux_rspec.reset.success!
+ linux_rubocop.reset.success!
+ mac_rspec.reset.success!
+ mac_rubocop.reset.success!
+
+ expect(stages).to eq(%w(success success pending))
+ expect(builds.success).to contain_exactly(
+ linux_build, linux_rspec, linux_rubocop, mac_build, mac_rspec, mac_rubocop)
+ expect(builds.pending).to contain_exactly(deploy)
+ end
+ end
+
+ context 'when one of the jobs is run on a failure' do
+ let!(:linux_notify) { create_build('linux:notify', stage: 'deploy', stage_idx: 2, when: 'on_failure') }
+
+ let!(:linux_notify_on_build) { create(:ci_build_need, build: linux_notify, name: 'linux:build') }
+
+ context 'when another job in build phase fails first' do
+ context 'when ci_dag_support is enabled' do
+ it 'does skip linux:notify' do
+ expect(process_pipeline).to be_truthy
+
+ mac_build.reset.drop!
+ linux_build.reset.success!
+
+ expect(linux_notify.reset).to be_skipped
+ end
+ end
+
+ context 'when ci_dag_support is disabled' do
+ before do
+ stub_feature_flags(ci_dag_support: false)
+ end
+
+ it 'does run linux:notify' do
+ expect(process_pipeline).to be_truthy
+
+ mac_build.reset.drop!
+ linux_build.reset.success!
+
+ expect(linux_notify.reset).to be_pending
+ end
+ end
+ end
+
+ context 'when linux:build job fails first' do
+ it 'does run linux:notify' do
+ expect(process_pipeline).to be_truthy
+
+ linux_build.reset.drop!
+
+ expect(linux_notify.reset).to be_pending
+ end
+ end
+ end
+ end
+
def process_pipeline
described_class.new(pipeline.project, user).execute(pipeline)
end
@@ -712,6 +844,10 @@ describe Ci::ProcessPipelineService, '#execute' do
all_builds.where.not(status: [:created, :skipped])
end
+ def stages
+ pipeline.reset.stages.map(&:status)
+ end
+
def builds_names
builds.pluck(:name)
end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index 11b06ef5019..fe7c6fe4700 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -30,7 +30,8 @@ describe Ci::RetryBuildService do
job_artifacts_sast job_artifacts_dependency_scanning
job_artifacts_container_scanning job_artifacts_dast
job_artifacts_license_management job_artifacts_performance
- job_artifacts_codequality job_artifacts_metrics scheduled_at].freeze
+ job_artifacts_codequality job_artifacts_metrics scheduled_at
+ job_variables].freeze
IGNORE_ACCESSORS =
%i[type lock_version target_url base_tags trace_sections
@@ -65,6 +66,9 @@ describe Ci::RetryBuildService do
file_type: file_type, job: build, expire_at: build.artifacts_expire_at)
end
+ create(:ci_job_variable, job: build)
+ create(:ci_build_need, build: build)
+
build.reload
end
diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb
index 4b869385128..522dd1ba1c2 100644
--- a/spec/services/ci/update_build_queue_service_spec.rb
+++ b/spec/services/ci/update_build_queue_service_spec.rb
@@ -7,84 +7,108 @@ describe Ci::UpdateBuildQueueService do
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:pipeline) { create(:ci_pipeline, project: project) }
- context 'when updating specific runners' do
- let(:runner) { create(:ci_runner, :project, projects: [project]) }
-
- context 'when there is a runner that can pick build' do
- it 'ticks runner queue value' do
- expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
- end
+ shared_examples 'refreshes runner' do
+ it 'ticks runner queue value' do
+ expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
end
+ end
- context 'when there is no runner that can pick build' do
- let(:another_project) { create(:project) }
- let(:runner) { create(:ci_runner, :project, projects: [another_project]) }
-
- it 'does not tick runner queue value' do
- expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
- end
+ shared_examples 'does not refresh runner' do
+ it 'ticks runner queue value' do
+ expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
end
end
- context 'when updating shared runners' do
- let(:runner) { create(:ci_runner, :instance) }
-
- context 'when there is no runner that can pick build' do
- it 'ticks runner queue value' do
- expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
+ shared_examples 'matching build' do
+ context 'when there is a online runner that can pick build' do
+ before do
+ runner.update!(contacted_at: 30.minutes.ago)
end
+
+ it_behaves_like 'refreshes runner'
end
+ end
+ shared_examples 'mismatching tags' do
context 'when there is no runner that can pick build due to tag mismatch' do
before do
build.tag_list = [:docker]
end
- it 'does not tick runner queue value' do
- expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
- end
+ it_behaves_like 'does not refresh runner'
end
+ end
- context 'when there is no runner that can pick build due to being disabled on project' do
+ shared_examples 'recent runner queue' do
+ context 'when there is runner with expired cache' do
before do
- build.project.shared_runners_enabled = false
+ runner.update!(contacted_at: Ci::Runner.recent_queue_deadline)
end
- it 'does not tick runner queue value' do
- expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
+ context 'when ci_update_queues_for_online_runners is enabled' do
+ before do
+ stub_feature_flags(ci_update_queues_for_online_runners: true)
+ end
+
+ it_behaves_like 'does not refresh runner'
+ end
+
+ context 'when ci_update_queues_for_online_runners is disabled' do
+ before do
+ stub_feature_flags(ci_update_queues_for_online_runners: false)
+ end
+
+ it_behaves_like 'refreshes runner'
end
end
end
- context 'when updating group runners' do
- let(:group) { create(:group) }
- let(:project) { create(:project, group: group) }
- let(:runner) { create(:ci_runner, :group, groups: [group]) }
+ context 'when updating specific runners' do
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
- context 'when there is a runner that can pick build' do
- it 'ticks runner queue value' do
- expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
- end
+ it_behaves_like 'matching build'
+ it_behaves_like 'mismatching tags'
+ it_behaves_like 'recent runner queue'
+
+ context 'when the runner is assigned to another project' do
+ let(:another_project) { create(:project) }
+ let(:runner) { create(:ci_runner, :project, projects: [another_project]) }
+
+ it_behaves_like 'does not refresh runner'
end
+ end
- context 'when there is no runner that can pick build due to tag mismatch' do
+ context 'when updating shared runners' do
+ let(:runner) { create(:ci_runner, :instance) }
+
+ it_behaves_like 'matching build'
+ it_behaves_like 'mismatching tags'
+ it_behaves_like 'recent runner queue'
+
+ context 'when there is no runner that can pick build due to being disabled on project' do
before do
- build.tag_list = [:docker]
+ build.project.shared_runners_enabled = false
end
- it 'does not tick runner queue value' do
- expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
- end
+ it_behaves_like 'does not refresh runner'
end
+ end
+
+ context 'when updating group runners' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+
+ it_behaves_like 'matching build'
+ it_behaves_like 'mismatching tags'
+ it_behaves_like 'recent runner queue'
context 'when there is no runner that can pick build due to being disabled on project' do
before do
build.project.group_runners_enabled = false
end
- it 'does not tick runner queue value' do
- expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
- end
+ it_behaves_like 'does not refresh runner'
end
end
end
diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
index 9ab83d913f5..a948b442441 100644
--- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
@@ -41,7 +41,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
end
end
- context 'when application is installing' do
+ context 'when application is uninstalling' do
RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase }
context 'when installation POD succeeded' do
@@ -56,6 +56,12 @@ describe Clusters::Applications::CheckUninstallProgressService do
service.execute
end
+ it 'runs application post_uninstall' do
+ expect(application).to receive(:post_uninstall).and_call_original
+
+ service.execute
+ end
+
it 'destroys the application' do
expect(worker_class).not_to receive(:perform_in)
diff --git a/spec/services/clusters/build_kubernetes_namespace_service_spec.rb b/spec/services/clusters/build_kubernetes_namespace_service_spec.rb
new file mode 100644
index 00000000000..36c05469542
--- /dev/null
+++ b/spec/services/clusters/build_kubernetes_namespace_service_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::BuildKubernetesNamespaceService do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:environment) { create(:environment) }
+ let(:project) { environment.project }
+
+ let(:namespace_generator) { double(from_environment_slug: namespace) }
+ let(:namespace) { 'namespace' }
+
+ subject { described_class.new(cluster, environment: environment).execute }
+
+ before do
+ allow(Gitlab::Kubernetes::DefaultNamespace).to receive(:new).and_return(namespace_generator)
+ end
+
+ shared_examples 'shared attributes' do
+ it 'initializes a new namespace and sets default values' do
+ expect(subject).to be_new_record
+ expect(subject.cluster).to eq cluster
+ expect(subject.project).to eq project
+ expect(subject.namespace).to eq namespace
+ expect(subject.service_account_name).to eq "#{namespace}-service-account"
+ end
+ end
+
+ include_examples 'shared attributes'
+
+ it 'sets cluster_project and environment' do
+ expect(subject.cluster_project).to eq cluster.cluster_project
+ expect(subject.environment).to eq environment
+ end
+
+ context 'namespace per environment is disabled' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp, :namespace_per_environment_disabled) }
+
+ include_examples 'shared attributes'
+
+ it 'does not set environment' do
+ expect(subject.cluster_project).to eq cluster.cluster_project
+ expect(subject.environment).to be_nil
+ end
+ end
+
+ context 'group cluster' do
+ let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
+
+ include_examples 'shared attributes'
+
+ it 'does not set cluster_project' do
+ expect(subject.cluster_project).to be_nil
+ expect(subject.environment).to eq environment
+ end
+ end
+end
diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
index be052a07da7..e44cc3f5a78 100644
--- a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
+++ b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
@@ -9,8 +9,9 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
let(:platform) { cluster.platform }
let(:api_url) { 'https://kubernetes.example.com' }
let(:project) { cluster.project }
+ let(:environment) { create(:environment, project: project) }
let(:cluster_project) { cluster.cluster_project }
- let(:namespace) { "#{project.path}-#{project.id}" }
+ let(:namespace) { "#{project.name}-#{project.id}-#{environment.slug}" }
subject do
described_class.new(
@@ -34,6 +35,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
stub_kubeclient_create_service_account(api_url, namespace: namespace)
stub_kubeclient_create_secret(api_url, namespace: namespace)
stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace)
+ stub_kubeclient_put_role(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
+ stub_kubeclient_put_role_binding(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
stub_kubeclient_get_secret(
api_url,
@@ -77,7 +80,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
let(:kubernetes_namespace) do
build(:cluster_kubernetes_namespace,
cluster: cluster,
- project: project)
+ project: project,
+ environment: environment)
end
it_behaves_like 'successful creation of kubernetes namespace'
@@ -90,20 +94,22 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
build(:cluster_kubernetes_namespace,
cluster: cluster,
project: cluster_project.project,
- cluster_project: cluster_project)
+ cluster_project: cluster_project,
+ environment: environment)
end
it_behaves_like 'successful creation of kubernetes namespace'
end
context 'when there is a Kubernetes Namespace associated' do
- let(:namespace) { 'new-namespace' }
+ let(:namespace) { "new-namespace-#{environment.slug}" }
let(:kubernetes_namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
project: cluster_project.project,
- cluster_project: cluster_project)
+ cluster_project: cluster_project,
+ environment: environment)
end
before do
diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb
index 382b9043566..8b874989758 100644
--- a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb
+++ b/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb
@@ -143,6 +143,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
stub_kubeclient_get_role_binding_error(api_url, role_binding_name, namespace: namespace)
stub_kubeclient_create_role_binding(api_url, namespace: namespace)
+ stub_kubeclient_put_role(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
+ stub_kubeclient_put_role_binding(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
end
it_behaves_like 'creates service account and token'
@@ -169,6 +171,24 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
)
)
end
+
+ it 'creates a role and role binding granting knative serving permissions to the service account' do
+ subject
+
+ expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME}").with(
+ body: hash_including(
+ metadata: {
+ name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
+ namespace: namespace
+ },
+ rules: [{
+ apiGroups: %w(serving.knative.dev),
+ resources: %w(configurations configurationgenerations routes revisions revisionuids autoscalers services),
+ verbs: %w(get list create update delete patch watch)
+ }]
+ )
+ )
+ end
end
end
end
diff --git a/spec/services/clusters/refresh_service_spec.rb b/spec/services/clusters/refresh_service_spec.rb
deleted file mode 100644
index 5bc8a709941..00000000000
--- a/spec/services/clusters/refresh_service_spec.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Clusters::RefreshService do
- shared_examples 'creates a kubernetes namespace' do
- let(:token) { 'aaaaaa' }
- let(:service_account_creator) { double(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService, execute: true) }
- let(:secrets_fetcher) { double(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService, execute: token) }
-
- it 'creates a kubernetes namespace' do
- expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator)
- expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher)
-
- expect { subject }.to change(project.kubernetes_namespaces, :count)
-
- kubernetes_namespace = cluster.kubernetes_namespaces.first
- expect(kubernetes_namespace).to be_present
- expect(kubernetes_namespace.project).to eq(project)
- end
- end
-
- shared_examples 'does not create a kubernetes namespace' do
- it 'does not create a new kubernetes namespace' do
- expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).not_to receive(:namespace_creator)
- expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).not_to receive(:new)
-
- expect { subject }.not_to change(Clusters::KubernetesNamespace, :count)
- end
- end
-
- describe '.create_or_update_namespaces_for_cluster' do
- let(:cluster) { create(:cluster, :provided_by_user, :project) }
- let(:project) { cluster.project }
-
- subject { described_class.create_or_update_namespaces_for_cluster(cluster) }
-
- context 'cluster is project level' do
- include_examples 'creates a kubernetes namespace'
-
- context 'when project already has kubernetes namespace' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- include_examples 'does not create a kubernetes namespace'
- end
- end
-
- context 'cluster is group level' do
- let(:cluster) { create(:cluster, :provided_by_user, :group) }
- let(:group) { cluster.group }
- let(:project) { create(:project, group: group) }
-
- include_examples 'creates a kubernetes namespace'
-
- context 'when project already has kubernetes namespace' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- include_examples 'does not create a kubernetes namespace'
- end
- end
- end
-
- describe '.create_or_update_namespaces_for_project' do
- let(:project) { create(:project) }
-
- subject { described_class.create_or_update_namespaces_for_project(project) }
-
- it 'creates no kubernetes namespaces' do
- expect { subject }.not_to change(project.kubernetes_namespaces, :count)
- end
-
- context 'project has a project cluster' do
- let!(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :project_type, projects: [project]) }
-
- include_examples 'creates a kubernetes namespace'
-
- context 'when project already has kubernetes namespace' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- include_examples 'does not create a kubernetes namespace'
- end
- end
-
- context 'project belongs to a group cluster' do
- let!(:cluster) { create(:cluster, :provided_by_gcp, :group) }
-
- let(:group) { cluster.group }
- let(:project) { create(:project, group: group) }
-
- include_examples 'does not create a kubernetes namespace'
-
- context 'when project already has kubernetes namespace' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- include_examples 'does not create a kubernetes namespace'
- end
- end
-
- context 'cluster is not managed' do
- let!(:cluster) { create(:cluster, :project, :not_managed, projects: [project]) }
-
- include_examples 'does not create a kubernetes namespace'
- end
- end
-end
diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb
index f6b6989b955..9b83f65a17e 100644
--- a/spec/services/create_snippet_service_spec.rb
+++ b/spec/services/create_snippet_service_spec.rb
@@ -36,6 +36,22 @@ describe CreateSnippetService do
end
end
+ describe 'usage counter' do
+ let(:counter) { Gitlab::UsageDataCounters::SnippetCounter }
+
+ it 'increments count' do
+ expect do
+ create_snippet(nil, @admin, @opts)
+ end.to change { counter.read(:create) }.by 1
+ end
+
+ it 'does not increment count if create fails' do
+ expect do
+ create_snippet(nil, @admin, {})
+ end.not_to change { counter.read(:create) }
+ end
+ end
+
def create_snippet(project, user, opts)
CreateSnippetService.new(project, user, opts).execute
end
diff --git a/spec/services/deploy_tokens/create_service_spec.rb b/spec/services/deploy_tokens/create_service_spec.rb
index 886ffd4593d..fbb66fe4cb7 100644
--- a/spec/services/deploy_tokens/create_service_spec.rb
+++ b/spec/services/deploy_tokens/create_service_spec.rb
@@ -32,6 +32,22 @@ describe DeployTokens::CreateService do
end
end
+ context 'when username is empty string' do
+ let(:deploy_token_params) { attributes_for(:deploy_token, username: '') }
+
+ it 'converts it to nil' do
+ expect(subject.read_attribute(:username)).to be_nil
+ end
+ end
+
+ context 'when username is provided' do
+ let(:deploy_token_params) { attributes_for(:deploy_token, username: 'deployer') }
+
+ it 'keeps the provided username' do
+ expect(subject.read_attribute(:username)).to eq('deployer')
+ end
+ end
+
context 'when the deploy token is invalid' do
let(:deploy_token_params) { attributes_for(:deploy_token, read_repository: false, read_registry: false) }
diff --git a/spec/services/git/base_hooks_service_spec.rb b/spec/services/git/base_hooks_service_spec.rb
index 4a2ec769116..874df9a68cd 100644
--- a/spec/services/git/base_hooks_service_spec.rb
+++ b/spec/services/git/base_hooks_service_spec.rb
@@ -14,6 +14,78 @@ describe Git::BaseHooksService do
let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
let(:ref) { 'refs/tags/v1.1.0' }
+ describe '#execute_project_hooks' do
+ class TestService < described_class
+ def hook_name
+ :push_hooks
+ end
+
+ def commits
+ []
+ end
+ end
+
+ let(:project) { create(:project, :repository) }
+
+ subject { TestService.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) }
+
+ context '#execute_hooks' do
+ before do
+ expect(project).to receive(:has_active_hooks?).and_return(active)
+ end
+
+ context 'active hooks' do
+ let(:active) { true }
+
+ it 'executes the hooks' do
+ expect(subject).to receive(:push_data).at_least(:once).and_call_original
+ expect(project).to receive(:execute_hooks)
+
+ subject.execute
+ end
+ end
+
+ context 'inactive hooks' do
+ let(:active) { false }
+
+ it 'does not execute the hooks' do
+ expect(subject).not_to receive(:push_data)
+ expect(project).not_to receive(:execute_hooks)
+
+ subject.execute
+ end
+ end
+ end
+
+ context '#execute_services' do
+ before do
+ expect(project).to receive(:has_active_services?).and_return(active)
+ end
+
+ context 'active services' do
+ let(:active) { true }
+
+ it 'executes the services' do
+ expect(subject).to receive(:push_data).at_least(:once).and_call_original
+ expect(project).to receive(:execute_services)
+
+ subject.execute
+ end
+ end
+
+ context 'inactive services' do
+ let(:active) { false }
+
+ it 'does not execute the services' do
+ expect(subject).not_to receive(:push_data)
+ expect(project).not_to receive(:execute_services)
+
+ subject.execute
+ end
+ end
+ end
+ end
+
describe 'with remote mirrors' do
class TestService < described_class
def commits
diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb
index 23be400059e..2bf7dc32436 100644
--- a/spec/services/git/branch_hooks_service_spec.rb
+++ b/spec/services/git/branch_hooks_service_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Git::BranchHooksService do
include RepoHelpers
+ include ProjectForksHelper
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
@@ -25,7 +26,7 @@ describe Git::BranchHooksService do
end
describe "Git Push Data" do
- subject(:push_data) { service.execute }
+ subject(:push_data) { service.send(:push_data) }
it 'has expected push data attributes' do
is_expected.to match a_hash_including(
@@ -109,6 +110,7 @@ describe Git::BranchHooksService do
expect(event.push_event_payload).to be_an_instance_of(PushEventPayload)
expect(event.push_event_payload.commit_from).to eq(oldrev)
expect(event.push_event_payload.commit_to).to eq(newrev)
+ expect(event.push_event_payload.commit_title).to eq('Change some files')
expect(event.push_event_payload.ref).to eq('master')
expect(event.push_event_payload.commit_count).to eq(1)
end
@@ -124,6 +126,7 @@ describe Git::BranchHooksService do
expect(event.push_event_payload).to be_an_instance_of(PushEventPayload)
expect(event.push_event_payload.commit_from).to be_nil
expect(event.push_event_payload.commit_to).to eq(newrev)
+ expect(event.push_event_payload.commit_title).to eq('Initial commit')
expect(event.push_event_payload.ref).to eq('master')
expect(event.push_event_payload.commit_count).to be > 1
end
@@ -156,9 +159,13 @@ describe Git::BranchHooksService do
let(:blank_sha) { Gitlab::Git::BLANK_SHA }
def clears_cache(extended: [])
- expect(ProjectCacheWorker)
- .to receive(:perform_async)
- .with(project.id, extended, %i[commit_count repository_size])
+ expect(service).to receive(:invalidated_file_types).and_return(extended)
+
+ if extended.present?
+ expect(ProjectCacheWorker)
+ .to receive(:perform_async)
+ .with(project.id, extended, [], false)
+ end
service.execute
end
@@ -266,10 +273,10 @@ describe Git::BranchHooksService do
end
describe 'Processing commit messages' do
- # Create 4 commits, 2 of which have references. Limiting to 2 commits, we
- # expect to see one commit message processor enqueued.
- let(:commit_ids) do
- Array.new(4) do |i|
+ # Create 6 commits, 3 of which have references. Limiting to 4 commits, we
+ # expect to see two commit message processors enqueued.
+ let!(:commit_ids) do
+ Array.new(6) do |i|
message = "Issue #{'#' if i.even?}#{i}"
project.repository.update_file(
user, 'README.md', '', message: message, branch_name: branch
@@ -277,18 +284,18 @@ describe Git::BranchHooksService do
end
end
- let(:oldrev) { commit_ids.first }
+ let(:oldrev) { project.commit(commit_ids.first).parent_id }
let(:newrev) { commit_ids.last }
before do
- stub_const("::Git::BaseHooksService::PROCESS_COMMIT_LIMIT", 2)
+ stub_const("::Git::BaseHooksService::PROCESS_COMMIT_LIMIT", 4)
end
context 'creating the default branch' do
let(:oldrev) { Gitlab::Git::BLANK_SHA }
it 'processes a limited number of commit messages' do
- expect(ProcessCommitWorker).to receive(:perform_async).once
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
service.execute
end
@@ -296,7 +303,7 @@ describe Git::BranchHooksService do
context 'updating the default branch' do
it 'processes a limited number of commit messages' do
- expect(ProcessCommitWorker).to receive(:perform_async).once
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
service.execute
end
@@ -317,7 +324,7 @@ describe Git::BranchHooksService do
let(:oldrev) { Gitlab::Git::BLANK_SHA }
it 'processes a limited number of commit messages' do
- expect(ProcessCommitWorker).to receive(:perform_async).once
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
service.execute
end
@@ -327,7 +334,7 @@ describe Git::BranchHooksService do
let(:branch) { 'fix' }
it 'processes a limited number of commit messages' do
- expect(ProcessCommitWorker).to receive(:perform_async).once
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
service.execute
end
@@ -343,6 +350,55 @@ describe Git::BranchHooksService do
service.execute
end
end
+
+ context 'when the project is forked' do
+ let(:upstream_project) { project }
+ let(:forked_project) { fork_project(upstream_project, user, repository: true) }
+
+ let!(:forked_service) do
+ described_class.new(forked_project, user, oldrev: oldrev, newrev: newrev, ref: ref)
+ end
+
+ context 'when commits already exists in the upstream project' do
+ it 'does not process commit messages' do
+ expect(ProcessCommitWorker).not_to receive(:perform_async)
+
+ forked_service.execute
+ end
+ end
+
+ context 'when a commit does not exist in the upstream repo' do
+ # On top of the existing 6 commits, 3 of which have references,
+ # create 2 more, 1 of which has a reference. Limiting to 4 commits, we
+ # expect to see one commit message processor enqueued.
+ let!(:forked_commit_ids) do
+ Array.new(2) do |i|
+ message = "Issue #{'#' if i.even?}#{i}"
+ forked_project.repository.update_file(
+ user, 'README.md', '', message: message, branch_name: branch
+ )
+ end
+ end
+
+ let(:newrev) { forked_commit_ids.last }
+
+ it 'processes the commit message' do
+ expect(ProcessCommitWorker).to receive(:perform_async).once
+
+ forked_service.execute
+ end
+ end
+
+ context 'when the upstream project no longer exists' do
+ it 'processes the commit messages' do
+ upstream_project.destroy!
+
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
+
+ forked_service.execute
+ end
+ end
+ end
end
describe 'New branch detection' do
diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb
index 6e39fa6b3c0..d9e607cd251 100644
--- a/spec/services/git/branch_push_service_spec.rb
+++ b/spec/services/git/branch_push_service_spec.rb
@@ -76,9 +76,28 @@ describe Git::BranchPushService, services: true do
stub_ci_pipeline_to_return_yaml_file
end
+ it 'creates a pipeline with the right parameters' do
+ expect(Ci::CreatePipelineService)
+ .to receive(:new)
+ .with(project,
+ user,
+ {
+ before: oldrev,
+ after: newrev,
+ ref: ref,
+ checkout_sha: SeedRepo::Commit::ID,
+ push_options: {}
+ }).and_call_original
+
+ subject
+ end
+
it "creates a new pipeline" do
expect { subject }.to change { Ci::Pipeline.count }
- expect(Ci::Pipeline.last).to be_push
+
+ pipeline = Ci::Pipeline.last
+ expect(pipeline).to be_push
+ expect(Gitlab::Git::BRANCH_REF_PREFIX + pipeline.ref).to eq(ref)
end
end
@@ -123,6 +142,10 @@ describe Git::BranchPushService, services: true do
describe "Webhooks" do
context "execute webhooks" do
+ before do
+ create(:project_hook, push_events: true, project: project)
+ end
+
it "when pushing a branch for the first time" do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb
index f5938a5c708..e362577d289 100644
--- a/spec/services/git/tag_hooks_service_spec.rb
+++ b/spec/services/git/tag_hooks_service_spec.rb
@@ -26,7 +26,8 @@ describe Git::TagHooksService, :service do
describe 'System hooks' do
it 'Executes system hooks' do
- push_data = service.execute
+ push_data = service.send(:push_data)
+ expect(project).to receive(:has_active_hooks?).and_return(true)
expect_next_instance_of(SystemHooksService) do |system_hooks_service|
expect(system_hooks_service)
@@ -40,6 +41,7 @@ describe Git::TagHooksService, :service do
describe "Webhooks" do
it "executes hooks on the project" do
+ expect(project).to receive(:has_active_hooks?).and_return(true)
expect(project).to receive(:execute_hooks)
service.execute
@@ -61,7 +63,7 @@ describe Git::TagHooksService, :service do
describe 'Push data' do
shared_examples_for 'tag push data expectations' do
- subject(:push_data) { service.execute }
+ subject(:push_data) { service.send(:push_data) }
it 'has expected push data attributes' do
is_expected.to match a_hash_including(
object_kind: 'tag_push',
diff --git a/spec/services/git/tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb
index 418952b52da..7e008637182 100644
--- a/spec/services/git/tag_push_service_spec.rb
+++ b/spec/services/git/tag_push_service_spec.rb
@@ -26,8 +26,8 @@ describe Git::TagPushService do
subject
end
- it 'flushes the tags cache' do
- expect(project.repository).to receive(:expire_tags_cache)
+ it 'does not flush the tags cache' do
+ expect(project.repository).not_to receive(:expire_tags_cache)
subject
end
diff --git a/spec/services/groups/auto_devops_service_spec.rb b/spec/services/groups/auto_devops_service_spec.rb
index 7f8ab517cef..7591b2f6f12 100644
--- a/spec/services/groups/auto_devops_service_spec.rb
+++ b/spec/services/groups/auto_devops_service_spec.rb
@@ -47,7 +47,7 @@ describe Groups::AutoDevopsService, '#execute' do
expect(subgroup_1.auto_devops_enabled?).to eq(false)
end
- context 'when subgroups have projects', :nested_groups do
+ context 'when subgroups have projects' do
it 'reflects changes on projects' do
subgroup_1 = create(:group, parent: group)
project_1 = create(:project, namespace: subgroup_1)
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index c5ff6cdbacd..0f9f20de586 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -44,7 +44,7 @@ describe Groups::CreateService, '#execute' do
end
end
- describe 'creating subgroup', :nested_groups do
+ describe 'creating subgroup' do
let!(:group) { create(:group) }
let!(:service) { described_class.new(user, group_params.merge(parent_id: group.id)) }
@@ -54,39 +54,31 @@ describe Groups::CreateService, '#execute' do
end
it { is_expected.to be_persisted }
+ end
- context 'when nested groups feature is disabled' do
- it 'does not save group and returns an error' do
- allow(Group).to receive(:supports_nested_objects?).and_return(false)
+ context 'as guest' do
+ it 'does not save group and returns an error' do
+ is_expected.not_to be_persisted
- is_expected.not_to be_persisted
- expect(subject.errors[:parent_id]).to include('You don’t have permission to create a subgroup in this group.')
- expect(subject.parent_id).to be_nil
- end
+ expect(subject.errors[:parent_id].first).to eq('You don’t have permission to create a subgroup in this group.')
+ expect(subject.parent_id).to be_nil
end
end
- context 'when nested groups feature is enabled' do
+ context 'as owner' do
before do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
+ group.add_owner(user)
end
- context 'as guest' do
- it 'does not save group and returns an error' do
- is_expected.not_to be_persisted
+ it { is_expected.to be_persisted }
+ end
- expect(subject.errors[:parent_id].first).to eq('You don’t have permission to create a subgroup in this group.')
- expect(subject.parent_id).to be_nil
- end
+ context 'as maintainer' do
+ before do
+ group.add_maintainer(user)
end
- context 'as owner' do
- before do
- group.add_owner(user)
- end
-
- it { is_expected.to be_persisted }
- end
+ it { is_expected.to be_persisted }
end
end
diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb
index 13acf9e055b..b30392c1b12 100644
--- a/spec/services/groups/nested_create_service_spec.rb
+++ b/spec/services/groups/nested_create_service_spec.rb
@@ -28,35 +28,7 @@ describe Groups::NestedCreateService do
end
end
- describe 'without subgroups' do
- let(:params) { { group_path: 'a-group' } }
-
- before do
- allow(Group).to receive(:supports_nested_objects?) { false }
- end
-
- it 'creates the group' do
- group = service.execute
-
- expect(group).to be_persisted
- end
-
- it 'returns the group if it already existed' do
- existing_group = create(:group, path: 'a-group')
-
- expect(service.execute).to eq(existing_group)
- end
-
- it 'raises an error when tring to create a subgroup' do
- service = described_class.new(user, group_path: 'a-group/a-sub-group')
-
- expect { service.execute }.to raise_error('Nested groups are not supported on MySQL')
- end
-
- it_behaves_like 'with a visibility level'
- end
-
- describe 'with subgroups', :nested_groups do
+ describe 'with subgroups' do
let(:params) { { group_path: 'a-group/a-sub-group' } }
describe "#execute" do
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index b5708ebba76..f3af8cf5f3b 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -2,28 +2,13 @@
require 'rails_helper'
-describe Groups::TransferService, :postgresql do
+describe Groups::TransferService do
let(:user) { create(:user) }
let(:new_parent_group) { create(:group, :public) }
let!(:group_member) { create(:group_member, :owner, group: group, user: user) }
let(:transfer_service) { described_class.new(group, user) }
shared_examples 'ensuring allowed transfer for a group' do
- context 'with other database than PostgreSQL' do
- before do
- allow(Group).to receive(:supports_nested_objects?).and_return(false)
- end
-
- it 'returns false' do
- expect(transfer_service.execute(new_parent_group)).to be_falsy
- end
-
- it 'adds an error on group' do
- transfer_service.execute(new_parent_group)
- expect(transfer_service.error).to eq('Transfer failed: Database is not supported.')
- end
- end
-
context "when there's an exception on GitLab shell directories" do
let(:new_parent_group) { create(:group, :public) }
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index d081c20f669..12e9c2b2f3a 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -86,6 +86,7 @@ describe Groups::UpdateService do
context "unauthorized visibility_level validation" do
let!(:service) { described_class.new(internal_group, user, visibility_level: 99) }
+
before do
internal_group.add_user(user, Gitlab::Access::MAINTAINER)
end
@@ -96,6 +97,20 @@ describe Groups::UpdateService do
end
end
+ context 'when updating #emails_disabled' do
+ let(:service) { described_class.new(internal_group, user, emails_disabled: true) }
+
+ it 'updates the attribute' do
+ internal_group.add_user(user, Gitlab::Access::OWNER)
+
+ expect { service.execute }.to change { internal_group.emails_disabled }.to(true)
+ end
+
+ it 'does not update when not group owner' do
+ expect { service.execute }.not_to change { internal_group.emails_disabled }
+ end
+ end
+
context 'rename group' do
let!(:service) { described_class.new(internal_group, user, path: SecureRandom.hex) }
@@ -133,7 +148,7 @@ describe Groups::UpdateService do
end
end
- context 'for a subgroup', :nested_groups do
+ context 'for a subgroup' do
let(:subgroup) { create(:group, :private, parent: private_group) }
context 'when the parent group share_with_group_lock is enabled' do
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index aebc5ba2874..e3a728f2566 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -11,173 +11,31 @@ describe Issuable::BulkUpdateService do
.reverse_merge(issuable_ids: Array(issuables).map(&:id).join(','))
type = Array(issuables).first.model_name.param_key
- Issuable::BulkUpdateService.new(project, user, bulk_update_params).execute(type)
+ Issuable::BulkUpdateService.new(user, bulk_update_params).execute(type)
end
- describe 'close issues' do
- let(:issues) { create_list(:issue, 2, project: project) }
-
- it 'succeeds and returns the correct number of issues updated' do
- result = bulk_update(issues, state_event: 'close')
+ shared_examples 'updates milestones' do
+ it 'succeeds' do
+ result = bulk_update(issuables, milestone_id: milestone.id)
expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(issues.count)
- end
-
- it 'closes all the issues passed' do
- bulk_update(issues, state_event: 'close')
-
- expect(project.issues.opened).to be_empty
- expect(project.issues.closed).not_to be_empty
+ expect(result[:count]).to eq(issuables.count)
end
- context 'when issue for a different project is created' do
- let(:private_project) { create(:project, :private) }
- let(:issue) { create(:issue, project: private_project, author: user) }
+ it 'updates the issuables milestone' do
+ bulk_update(issuables, milestone_id: milestone.id)
- context 'when user has access to the project' do
- it 'closes all issues passed' do
- private_project.add_maintainer(user)
-
- bulk_update(issues + [issue], state_event: 'close')
-
- expect(project.issues.opened).to be_empty
- expect(project.issues.closed).not_to be_empty
- expect(private_project.issues.closed).not_to be_empty
- end
- end
-
- context 'when user does not have access to project' do
- it 'only closes all issues that the user has access to' do
- bulk_update(issues + [issue], state_event: 'close')
-
- expect(project.issues.opened).to be_empty
- expect(project.issues.closed).not_to be_empty
- expect(private_project.issues.closed).to be_empty
- end
+ issuables.each do |issuable|
+ expect(issuable.reload.milestone).to eq(milestone)
end
end
end
- describe 'reopen issues' do
- let(:issues) { create_list(:closed_issue, 2, project: project) }
-
- it 'succeeds and returns the correct number of issues updated' do
- result = bulk_update(issues, state_event: 'reopen')
-
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(issues.count)
- end
-
- it 'reopens all the issues passed' do
- bulk_update(issues, state_event: 'reopen')
-
- expect(project.issues.closed).to be_empty
- expect(project.issues.opened).not_to be_empty
- end
- end
-
- describe 'updating merge request assignee' do
- let(:merge_request) { create(:merge_request, target_project: project, source_project: project, assignees: [user]) }
-
- context 'when the new assignee ID is a valid user' do
- it 'succeeds' do
- new_assignee = create(:user)
- project.add_developer(new_assignee)
-
- result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id])
-
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
- end
-
- it 'updates the assignee to the user ID passed' do
- assignee = create(:user)
- project.add_developer(assignee)
-
- expect { bulk_update(merge_request, assignee_ids: [assignee.id]) }
- .to change { merge_request.reload.assignee_ids }.from([user.id]).to([assignee.id])
- end
- end
-
- context "when the new assignee ID is #{IssuableFinder::NONE}" do
- it 'unassigns the issues' do
- expect { bulk_update(merge_request, assignee_ids: [IssuableFinder::NONE]) }
- .to change { merge_request.reload.assignee_ids }.to([])
- end
- end
-
- context 'when the new assignee ID is not present' do
- it 'does not unassign' do
- expect { bulk_update(merge_request, assignee_ids: []) }
- .not_to change { merge_request.reload.assignee_ids }
- end
- end
- end
-
- describe 'updating issue assignee' do
- let(:issue) { create(:issue, project: project, assignees: [user]) }
-
- context 'when the new assignee ID is a valid user' do
- it 'succeeds' do
- new_assignee = create(:user)
- project.add_developer(new_assignee)
-
- result = bulk_update(issue, assignee_ids: [new_assignee.id])
-
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
- end
-
- it 'updates the assignee to the user ID passed' do
- assignee = create(:user)
- project.add_developer(assignee)
- expect { bulk_update(issue, assignee_ids: [assignee.id]) }
- .to change { issue.reload.assignees.first }.from(user).to(assignee)
- end
- end
-
- context "when the new assignee ID is #{IssuableFinder::NONE}" do
- it "unassigns the issues" do
- expect { bulk_update(issue, assignee_ids: [IssuableFinder::NONE.to_s]) }
- .to change { issue.reload.assignees.count }.from(1).to(0)
- end
- end
-
- context 'when the new assignee ID is not present' do
- it 'does not unassign' do
- expect { bulk_update(issue, assignee_ids: []) }
- .not_to change { issue.reload.assignees }
- end
- end
- end
-
- describe 'updating milestones' do
- let(:issue) { create(:issue, project: project) }
- let(:milestone) { create(:milestone, project: project) }
-
- it 'succeeds' do
- result = bulk_update(issue, milestone_id: milestone.id)
-
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
- end
-
- it 'updates the issue milestone' do
- expect { bulk_update(issue, milestone_id: milestone.id) }
- .to change { issue.reload.milestone }.from(nil).to(milestone)
- end
- end
-
- describe 'updating labels' do
+ shared_examples 'updating labels' do
def create_issue_with_labels(labels)
create(:labeled_issue, project: project, labels: labels)
end
- let(:bug) { create(:label, project: project) }
- let(:regression) { create(:label, project: project) }
- let(:merge_requests) { create(:label, project: project) }
-
let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) }
let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) }
let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) }
@@ -257,7 +115,7 @@ describe Issuable::BulkUpdateService do
end
it 'removes the label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id)
end
it 'does not update issues not passed in' do
@@ -289,11 +147,11 @@ describe Issuable::BulkUpdateService do
let(:remove_labels) { [regression] }
it 'removes the label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
+ expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(regression.id)
end
it 'ignores the label IDs parameter' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id)
end
it 'does not update issues not passed in' do
@@ -312,11 +170,11 @@ describe Issuable::BulkUpdateService do
end
it 'removes the label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id)
end
it 'ignores the label IDs parameter' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
+ expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(regression.id)
end
it 'does not update issues not passed in' do
@@ -325,29 +183,226 @@ describe Issuable::BulkUpdateService do
end
end
- describe 'subscribe to issues' do
- let(:issues) { create_list(:issue, 2, project: project) }
+ context 'with issuables at a project level' do
+ describe 'close issues' do
+ let(:issues) { create_list(:issue, 2, project: project) }
+
+ it 'succeeds and returns the correct number of issues updated' do
+ result = bulk_update(issues, state_event: 'close')
+
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(issues.count)
+ end
+
+ it 'closes all the issues passed' do
+ bulk_update(issues, state_event: 'close')
+
+ expect(project.issues.opened).to be_empty
+ expect(project.issues.closed).not_to be_empty
+ end
+
+ context 'when issue for a different project is created' do
+ let(:private_project) { create(:project, :private) }
+ let(:issue) { create(:issue, project: private_project, author: user) }
+
+ context 'when user has access to the project' do
+ it 'closes all issues passed' do
+ private_project.add_maintainer(user)
- it 'subscribes the given user' do
- bulk_update(issues, subscription_event: 'subscribe')
+ bulk_update(issues + [issue], state_event: 'close')
- expect(issues).to all(be_subscribed(user, project))
+ expect(project.issues.opened).to be_empty
+ expect(project.issues.closed).not_to be_empty
+ expect(private_project.issues.closed).not_to be_empty
+ end
+ end
+
+ context 'when user does not have access to project' do
+ it 'only closes all issues that the user has access to' do
+ bulk_update(issues + [issue], state_event: 'close')
+
+ expect(project.issues.opened).to be_empty
+ expect(project.issues.closed).not_to be_empty
+ expect(private_project.issues.closed).to be_empty
+ end
+ end
+ end
+ end
+
+ describe 'reopen issues' do
+ let(:issues) { create_list(:closed_issue, 2, project: project) }
+
+ it 'succeeds and returns the correct number of issues updated' do
+ result = bulk_update(issues, state_event: 'reopen')
+
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(issues.count)
+ end
+
+ it 'reopens all the issues passed' do
+ bulk_update(issues, state_event: 'reopen')
+
+ expect(project.issues.closed).to be_empty
+ expect(project.issues.opened).not_to be_empty
+ end
+ end
+
+ describe 'updating merge request assignee' do
+ let(:merge_request) { create(:merge_request, target_project: project, source_project: project, assignees: [user]) }
+
+ context 'when the new assignee ID is a valid user' do
+ it 'succeeds' do
+ new_assignee = create(:user)
+ project.add_developer(new_assignee)
+
+ result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id])
+
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(1)
+ end
+
+ it 'updates the assignee to the user ID passed' do
+ assignee = create(:user)
+ project.add_developer(assignee)
+
+ expect { bulk_update(merge_request, assignee_ids: [assignee.id]) }
+ .to change { merge_request.reload.assignee_ids }.from([user.id]).to([assignee.id])
+ end
+ end
+
+ context "when the new assignee ID is #{IssuableFinder::NONE}" do
+ it 'unassigns the issues' do
+ expect { bulk_update(merge_request, assignee_ids: [IssuableFinder::NONE]) }
+ .to change { merge_request.reload.assignee_ids }.to([])
+ end
+ end
+
+ context 'when the new assignee ID is not present' do
+ it 'does not unassign' do
+ expect { bulk_update(merge_request, assignee_ids: []) }
+ .not_to change { merge_request.reload.assignee_ids }
+ end
+ end
+ end
+
+ describe 'updating issue assignee' do
+ let(:issue) { create(:issue, project: project, assignees: [user]) }
+
+ context 'when the new assignee ID is a valid user' do
+ it 'succeeds' do
+ new_assignee = create(:user)
+ project.add_developer(new_assignee)
+
+ result = bulk_update(issue, assignee_ids: [new_assignee.id])
+
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(1)
+ end
+
+ it 'updates the assignee to the user ID passed' do
+ assignee = create(:user)
+ project.add_developer(assignee)
+ expect { bulk_update(issue, assignee_ids: [assignee.id]) }
+ .to change { issue.reload.assignees.first }.from(user).to(assignee)
+ end
+ end
+
+ context "when the new assignee ID is #{IssuableFinder::NONE}" do
+ it "unassigns the issues" do
+ expect { bulk_update(issue, assignee_ids: [IssuableFinder::NONE.to_s]) }
+ .to change { issue.reload.assignees.count }.from(1).to(0)
+ end
+ end
+
+ context 'when the new assignee ID is not present' do
+ it 'does not unassign' do
+ expect { bulk_update(issue, assignee_ids: []) }
+ .not_to change(issue.assignees, :count)
+ end
+ end
+ end
+
+ describe 'updating milestones' do
+ let(:issuables) { [create(:issue, project: project)] }
+ let(:milestone) { create(:milestone, project: project) }
+
+ it_behaves_like 'updates milestones'
+ end
+
+ describe 'updating labels' do
+ let(:bug) { create(:label, project: project) }
+ let(:regression) { create(:label, project: project) }
+ let(:merge_requests) { create(:label, project: project) }
+
+ it_behaves_like 'updating labels'
+ end
+
+ describe 'subscribe to issues' do
+ let(:issues) { create_list(:issue, 2, project: project) }
+
+ it 'subscribes the given user' do
+ bulk_update(issues, subscription_event: 'subscribe')
+
+ expect(issues).to all(be_subscribed(user, project))
+ end
+ end
+
+ describe 'unsubscribe from issues' do
+ let(:issues) do
+ create_list(:closed_issue, 2, project: project) do |issue|
+ issue.subscriptions.create(user: user, project: project, subscribed: true)
+ end
+ end
+
+ it 'unsubscribes the given user' do
+ bulk_update(issues, subscription_event: 'unsubscribe')
+
+ issues.each do |issue|
+ expect(issue).not_to be_subscribed(user, project)
+ end
+ end
end
end
- describe 'unsubscribe from issues' do
- let(:issues) do
- create_list(:closed_issue, 2, project: project) do |issue|
- issue.subscriptions.create(user: user, project: project, subscribed: true)
+ context 'with issuables at a group level' do
+ let(:group) { create(:group) }
+
+ describe 'updating milestones' do
+ let(:milestone) { create(:milestone, group: group) }
+ let(:project) { create(:project, :repository, group: group) }
+
+ before do
+ group.add_maintainer(user)
+ end
+
+ context 'when issues' do
+ let(:issue1) { create(:issue, project: project) }
+ let(:issue2) { create(:issue, project: project) }
+ let(:issuables) { [issue1, issue2] }
+
+ it_behaves_like 'updates milestones'
+ end
+
+ context 'when merge requests' do
+ let(:merge_request1) { create(:merge_request, source_project: project, source_branch: 'branch-1') }
+ let(:merge_request2) { create(:merge_request, source_project: project, source_branch: 'branch-2') }
+ let(:issuables) { [merge_request1, merge_request2] }
+
+ it_behaves_like 'updates milestones'
end
end
- it 'unsubscribes the given user' do
- bulk_update(issues, subscription_event: 'unsubscribe')
+ describe 'updating labels' do
+ let(:project) { create(:project, :repository, group: group) }
+ let(:bug) { create(:group_label, group: group) }
+ let(:regression) { create(:group_label, group: group) }
+ let(:merge_requests) { create(:group_label, group: group) }
- issues.each do |issue|
- expect(issue).not_to be_subscribed(user, project)
+ before do
+ group.add_reporter(user)
end
+
+ it_behaves_like 'updating labels'
end
end
end
diff --git a/spec/services/issuable/clone/content_rewriter_spec.rb b/spec/services/issuable/clone/content_rewriter_spec.rb
index 230e1123280..3479c20862a 100644
--- a/spec/services/issuable/clone/content_rewriter_spec.rb
+++ b/spec/services/issuable/clone/content_rewriter_spec.rb
@@ -165,5 +165,18 @@ describe Issuable::Clone::ContentRewriter do
expect(note.note_html).not_to eq(new_note.note_html)
end
end
+
+ context "discussion notes" do
+ let(:note) { create(:note, noteable: original_issue, note: "sample note", project: project1) }
+ let!(:discussion) { create(:discussion_note_on_issue, in_reply_to: note, note: "reply to sample note") }
+
+ it 'rewrites discussion correctly' do
+ subject.execute
+
+ expect(new_issue.notes.count).to eq(original_issue.notes.count)
+ expect(new_issue.notes.where(discussion_id: discussion.discussion_id).count).to eq(0)
+ expect(original_issue.notes.where(discussion_id: discussion.discussion_id).count).to eq(1)
+ end
+ end
end
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 28fa5d12d9c..fd9a63b79cc 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -116,7 +116,7 @@ describe Issues::UpdateService, :mailer do
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
end
- context 'when moving issue between issues from different projects', :nested_groups do
+ context 'when moving issue between issues from different projects' do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
@@ -179,7 +179,7 @@ describe Issues::UpdateService, :mailer do
it 'sends email to user2 about assign of new issue and email to user3 about issue unassignment' do
deliveries = ActionMailer::Base.deliveries
email = deliveries.last
- recipients = deliveries.last(2).map(&:to).flatten
+ recipients = deliveries.last(2).flat_map(&:to)
expect(recipients).to include(user2.email, user3.email)
expect(email.subject).to include(issue.title)
end
@@ -226,6 +226,15 @@ describe Issues::UpdateService, :mailer do
end
end
+ it 'creates zoom_link_added system note when a zoom link is added to the description' do
+ update_issue(description: 'Changed description https://zoom.us/j/5873603787')
+
+ note = find_note('added a Zoom call')
+
+ expect(note).not_to be_nil
+ expect(note.note).to eq('added a Zoom call to this issue')
+ end
+
context 'when issue turns confidential' do
let(:opts) do
{
@@ -480,6 +489,22 @@ describe Issues::UpdateService, :mailer do
update_issue(description: "- [x] Task 1\n- [X] Task 2")
end
+ it 'does not check for spam on task status change' do
+ params = {
+ update_task: {
+ index: 1,
+ checked: false,
+ line_source: '- [x] Task 1',
+ line_number: 1
+ }
+ }
+ service = described_class.new(project, user, params)
+
+ expect(service).not_to receive(:spam_check)
+
+ service.execute(issue)
+ end
+
it 'creates system note about task status change' do
note1 = find_note('marked the task **Task 1** as completed')
note2 = find_note('marked the task **Task 2** as completed')
@@ -687,7 +712,7 @@ describe Issues::UpdateService, :mailer do
end
end
- context 'when moving an issue ', :nested_groups do
+ context 'when moving an issue ' do
it 'raises an error for invalid move ids within a project' do
opts = { move_between_ids: [9000, 9999] }
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index 52f9a305d8f..7dce7f035d4 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -267,15 +267,15 @@ describe Members::DestroyService do
expect(group.members.map(&:user)).not_to include(member_user)
end
- it 'removes the subgroup membership', :postgresql do
+ it 'removes the subgroup membership' do
expect(subgroup.members.map(&:user)).not_to include(member_user)
end
- it 'removes the subsubgroup membership', :postgresql do
+ it 'removes the subsubgroup membership' do
expect(subsubgroup.members.map(&:user)).not_to include(member_user)
end
- it 'removes the subsubproject membership', :postgresql do
+ it 'removes the subsubproject membership' do
expect(subsubproject.members.map(&:user)).not_to include(member_user)
end
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 5c3b209086c..f18239f6d39 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-
require 'spec_helper'
describe MergeRequests::BuildService do
@@ -225,6 +224,11 @@ describe MergeRequests::BuildService do
let(:label_ids) { [label2.id] }
let(:milestone_id) { milestone2.id }
+ before do
+ # Guests are not able to assign labels or milestones to an issue
+ project.add_developer(user)
+ end
+
it 'assigns milestone_id and label_ids instead of issue labels and milestone' do
expect(merge_request.milestone).to eq(milestone2)
expect(merge_request.labels).to match_array([label2])
@@ -479,4 +483,35 @@ describe MergeRequests::BuildService do
end
end
end
+
+ context 'when assigning labels' do
+ let(:label_ids) { [create(:label, project: project).id] }
+
+ context 'for members with less than developer access' do
+ it 'is not allowed' do
+ expect(merge_request.label_ids).to be_empty
+ end
+ end
+
+ context 'for users allowed to assign labels' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'for labels in the project' do
+ it 'is allowed for developers' do
+ expect(merge_request.label_ids).to contain_exactly(*label_ids)
+ end
+ end
+
+ context 'for unrelated labels' do
+ let(:project_label) { create(:label, project: project) }
+ let(:label_ids) { [create(:label).id, project_label.id] }
+
+ it 'only assigns related labels' do
+ expect(merge_request.label_ids).to contain_exactly(project_label.id)
+ end
+ end
+ end
+ end
end
diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb
index a0ac7dba89d..0e0da6a13ab 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe MergeRequests::CreateFromIssueService do
+ include ProjectForksHelper
+
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:label_ids) { create_pair(:label, project: project).map(&:id) }
@@ -10,139 +12,174 @@ describe MergeRequests::CreateFromIssueService do
let(:issue) { create(:issue, project: project, milestone_id: milestone_id) }
let(:custom_source_branch) { 'custom-source-branch' }
- subject(:service) { described_class.new(project, user, issue_iid: issue.iid) }
- subject(:service_with_custom_source_branch) { described_class.new(project, user, issue_iid: issue.iid, branch_name: custom_source_branch) }
+ subject(:service) { described_class.new(project, user, service_params) }
+ subject(:service_with_custom_source_branch) { described_class.new(project, user, branch_name: custom_source_branch, **service_params) }
before do
project.add_developer(user)
end
describe '#execute' do
- it 'returns an error with invalid issue iid' do
- result = described_class.new(project, user, issue_iid: -1).execute
+ shared_examples_for 'a service that creates a merge request from an issue' do
+ it 'returns an error when user can not create merge request on target project' do
+ result = described_class.new(project, create(:user), service_params).execute
- expect(result[:status]).to eq(:error)
- expect(result[:message]).to eq('Invalid issue iid')
- end
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Not allowed to create merge request')
+ end
- it 'delegates issue search to IssuesFinder' do
- expect_any_instance_of(IssuesFinder).to receive(:find_by).once.and_call_original
+ it 'returns an error with invalid issue iid' do
+ result = described_class.new(project, user, issue_iid: -1).execute
- described_class.new(project, user, issue_iid: -1).execute
- end
-
- it "inherits labels" do
- issue.assign_attributes(label_ids: label_ids)
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Invalid issue iid')
+ end
- result = service.execute
+ it 'creates a branch based on issue title' do
+ service.execute
- expect(result[:merge_request].label_ids).to eq(label_ids)
- end
+ expect(target_project.repository.branch_exists?(issue.to_branch_name)).to be_truthy
+ end
- it "inherits milestones" do
- result = service.execute
+ it 'creates a branch using passed name' do
+ service_with_custom_source_branch.execute
- expect(result[:merge_request].milestone_id).to eq(milestone_id)
- end
+ expect(target_project.repository.branch_exists?(custom_source_branch)).to be_truthy
+ end
- it 'delegates the branch creation to CreateBranchService' do
- expect_any_instance_of(CreateBranchService).to receive(:execute).once.and_call_original
+ it 'creates the new_merge_request system note' do
+ expect(SystemNoteService).to receive(:new_merge_request).with(issue, project, user, instance_of(MergeRequest))
- service.execute
- end
+ service.execute
+ end
- it 'creates a branch based on issue title' do
- service.execute
+ it 'creates the new_issue_branch system note when the branch could be created but the merge_request cannot be created' do
+ expect_any_instance_of(MergeRequest).to receive(:valid?).at_least(:once).and_return(false)
- expect(project.repository.branch_exists?(issue.to_branch_name)).to be_truthy
- end
+ expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, issue.to_branch_name, branch_project: target_project)
- it 'creates a branch using passed name' do
- service_with_custom_source_branch.execute
+ service.execute
+ end
- expect(project.repository.branch_exists?(custom_source_branch)).to be_truthy
- end
+ it 'creates a merge request' do
+ expect { service.execute }.to change(target_project.merge_requests, :count).by(1)
+ end
- it 'creates the new_merge_request system note' do
- expect(SystemNoteService).to receive(:new_merge_request).with(issue, project, user, instance_of(MergeRequest))
+ it 'sets the merge request author to current user' do
+ result = service.execute
- service.execute
- end
+ expect(result[:merge_request].author).to eq(user)
+ end
- it 'creates the new_issue_branch system note when the branch could be created but the merge_request cannot be created' do
- project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED)
+ it 'sets the merge request source branch to the new issue branch' do
+ result = service.execute
- expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, issue.to_branch_name)
+ expect(result[:merge_request].source_branch).to eq(issue.to_branch_name)
+ end
- service.execute
- end
+ it 'sets the merge request source branch to the passed branch name' do
+ result = service_with_custom_source_branch.execute
- it 'creates a merge request' do
- expect { service.execute }.to change(project.merge_requests, :count).by(1)
- end
+ expect(result[:merge_request].source_branch).to eq(custom_source_branch)
+ end
- it 'sets the merge request title to: "WIP: Resolves "$issue-title"' do
- result = service.execute
+ it 'sets the merge request target branch to the project default branch' do
+ result = service.execute
- expect(result[:merge_request].title).to eq("WIP: Resolve \"#{issue.title}\"")
- end
+ expect(result[:merge_request].target_branch).to eq(target_project.default_branch)
+ end
- it 'sets the merge request author to current user' do
- result = service.execute
+ it 'executes quick actions if the build service sets them in the description' do
+ allow(service).to receive(:merge_request).and_wrap_original do |m, *args|
+ m.call(*args).tap do |merge_request|
+ merge_request.description = "/assign #{user.to_reference}"
+ end
+ end
- expect(result[:merge_request].author).to eq(user)
- end
+ result = service.execute
- it 'sets the merge request source branch to the new issue branch' do
- result = service.execute
+ expect(result[:merge_request].assignees).to eq([user])
+ end
- expect(result[:merge_request].source_branch).to eq(issue.to_branch_name)
- end
+ context 'when ref branch is set' do
+ subject { described_class.new(project, user, ref: 'feature', **service_params).execute }
- it 'sets the merge request source branch to the passed branch name' do
- result = service_with_custom_source_branch.execute
+ it 'sets the merge request source branch to the new issue branch' do
+ expect(subject[:merge_request].source_branch).to eq(issue.to_branch_name)
+ end
- expect(result[:merge_request].source_branch).to eq(custom_source_branch)
- end
+ it 'sets the merge request target branch to the ref branch' do
+ expect(subject[:merge_request].target_branch).to eq('feature')
+ end
- it 'sets the merge request target branch to the project default branch' do
- result = service.execute
+ context 'when ref branch does not exist' do
+ subject { described_class.new(project, user, ref: 'no-such-branch', **service_params).execute }
- expect(result[:merge_request].target_branch).to eq(project.default_branch)
- end
+ it 'creates a merge request' do
+ expect { subject }.to change(target_project.merge_requests, :count).by(1)
+ end
- it 'executes quick actions if the build service sets them in the description' do
- allow(service).to receive(:merge_request).and_wrap_original do |m, *args|
- m.call(*args).tap do |merge_request|
- merge_request.description = "/assign #{user.to_reference}"
+ it 'sets the merge request target branch to the project default branch' do
+ expect(subject[:merge_request].target_branch).to eq(target_project.default_branch)
+ end
end
end
+ end
- result = service.execute
+ context 'no target_project_id specified' do
+ let(:service_params) { { issue_iid: issue.iid } }
+ let(:target_project) { project }
- expect(result[:merge_request].assignees).to eq([user])
- end
+ it_behaves_like 'a service that creates a merge request from an issue'
- context 'when ref branch is set' do
- subject { described_class.new(project, user, issue_iid: issue.iid, ref: 'feature').execute }
+ it "inherits labels" do
+ issue.assign_attributes(label_ids: label_ids)
- it 'sets the merge request source branch to the new issue branch' do
- expect(subject[:merge_request].source_branch).to eq(issue.to_branch_name)
+ result = service.execute
+
+ expect(result[:merge_request].label_ids).to eq(label_ids)
end
- it 'sets the merge request target branch to the ref branch' do
- expect(subject[:merge_request].target_branch).to eq('feature')
+ it "inherits milestones" do
+ result = service.execute
+
+ expect(result[:merge_request].milestone_id).to eq(milestone_id)
end
- context 'when ref branch does not exist' do
- subject { described_class.new(project, user, issue_iid: issue.iid, ref: 'no-such-branch').execute }
+ it 'sets the merge request title to: "WIP: Resolves "$issue-title"' do
+ result = service.execute
- it 'creates a merge request' do
- expect { subject }.to change(project.merge_requests, :count).by(1)
+ expect(result[:merge_request].title).to eq("WIP: Resolve \"#{issue.title}\"")
+ end
+ end
+
+ context 'target_project_id is specified' do
+ let(:service_params) { { issue_iid: issue.iid, target_project_id: target_project.id } }
+
+ context 'target project is not a fork of the project' do
+ let(:target_project) { create(:project, :repository) }
+
+ it 'returns an error about not finding the project' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Project not found')
end
- it 'sets the merge request target branch to the project default branch' do
- expect(subject[:merge_request].target_branch).to eq(project.default_branch)
+ it 'does not create merge request' do
+ expect { service.execute }.to change(target_project.merge_requests, :count).by(0)
+ end
+ end
+
+ context 'target project is a fork of project project' do
+ let(:target_project) { fork_project(project, user, repository: true) }
+
+ it_behaves_like 'a service that creates a merge request from an issue'
+
+ it 'sets the merge request title to: "WIP: $issue-branch-name' do
+ result = service.execute
+
+ expect(result[:merge_request].title).to eq("WIP: #{issue.to_branch_name.titleize.humanize}")
end
end
end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index ed48f4b1e44..699f2a98088 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::CreateService do
+describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
include ProjectForksHelper
let(:project) { create(:project, :repository) }
@@ -285,6 +285,12 @@ describe MergeRequests::CreateService do
end
end
end
+
+ it 'increments the usage data counter of create event' do
+ counter = Gitlab::UsageDataCounters::MergeRequestCounter
+
+ expect { service.execute }.to change { counter.read(:create) }.by(1)
+ end
end
it_behaves_like 'new issuable record that supports quick actions' do
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index 0933c6d4336..9e7a5260ca4 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -8,8 +8,8 @@ describe MergeRequests::GetUrlsService do
let(:project) { create(:project, :public, :repository) }
let(:service) { described_class.new(project) }
let(:source_branch) { "merge-test" }
- let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
- let(:show_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/#{merge_request.iid}" }
+ let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
+ let(:show_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/#{merge_request.iid}" }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
@@ -119,7 +119,7 @@ describe MergeRequests::GetUrlsService do
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" }
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/markdown" }
let(:changes) { "#{new_branch_changes}\n#{existing_branch_changes}" }
- let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch" }
+ let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch" }
it 'returns 2 urls for both creating new and showing merge request' do
result = service.execute(changes)
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index aa759ac9edc..22578436c18 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -214,6 +214,19 @@ describe MergeRequests::MergeService do
allow(Rails.logger).to receive(:error)
end
+ context 'when source is missing' do
+ it 'logs and saves error' do
+ allow(merge_request).to receive(:diff_head_sha) { nil }
+
+ error_message = 'No source for merge'
+
+ service.execute(merge_request)
+
+ expect(merge_request.merge_error).to eq(error_message)
+ expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
+ end
+ end
+
it 'logs and saves error if there is an exception' do
error_message = 'error message'
diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb
index 61f99f82a76..758679edc45 100644
--- a/spec/services/merge_requests/merge_to_ref_service_spec.rb
+++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb
@@ -22,7 +22,6 @@ describe MergeRequests::MergeToRefService do
shared_examples_for 'successfully merges to ref with merge method' do
it 'writes commit to merge ref' do
repository = project.repository
- target_ref = merge_request.merge_ref_path
expect(repository.ref_exists?(target_ref)).to be(false)
@@ -33,7 +32,7 @@ describe MergeRequests::MergeToRefService do
expect(result[:status]).to eq(:success)
expect(result[:commit_id]).to be_present
expect(result[:source_id]).to eq(merge_request.source_branch_sha)
- expect(result[:target_id]).to eq(merge_request.target_branch_sha)
+ expect(result[:target_id]).to eq(repository.commit(first_parent_ref).sha)
expect(repository.ref_exists?(target_ref)).to be(true)
expect(ref_head.id).to eq(result[:commit_id])
end
@@ -74,17 +73,22 @@ describe MergeRequests::MergeToRefService do
describe '#execute' do
let(:service) do
- described_class.new(project, user, commit_message: 'Awesome message',
- should_remove_source_branch: true)
+ described_class.new(project, user, **params)
end
+ let(:params) { { commit_message: 'Awesome message', should_remove_source_branch: true } }
+
def process_merge_to_ref
perform_enqueued_jobs do
service.execute(merge_request)
end
end
- it_behaves_like 'successfully merges to ref with merge method'
+ it_behaves_like 'successfully merges to ref with merge method' do
+ let(:first_parent_ref) { 'refs/heads/master' }
+ let(:target_ref) { merge_request.merge_ref_path }
+ end
+
it_behaves_like 'successfully evaluates pre-condition checks'
context 'commit history comparison with regular MergeService' do
@@ -129,14 +133,22 @@ describe MergeRequests::MergeToRefService do
context 'when semi-linear merge method' do
let(:merge_method) { :rebase_merge }
- it_behaves_like 'successfully merges to ref with merge method'
+ it_behaves_like 'successfully merges to ref with merge method' do
+ let(:first_parent_ref) { 'refs/heads/master' }
+ let(:target_ref) { merge_request.merge_ref_path }
+ end
+
it_behaves_like 'successfully evaluates pre-condition checks'
end
context 'when fast-forward merge method' do
let(:merge_method) { :ff }
- it_behaves_like 'successfully merges to ref with merge method'
+ it_behaves_like 'successfully merges to ref with merge method' do
+ let(:first_parent_ref) { 'refs/heads/master' }
+ let(:target_ref) { merge_request.merge_ref_path }
+ end
+
it_behaves_like 'successfully evaluates pre-condition checks'
end
@@ -178,5 +190,56 @@ describe MergeRequests::MergeToRefService do
it { expect(todo).not_to be_done }
end
+
+ context 'when source is missing' do
+ it 'returns error' do
+ allow(merge_request).to receive(:diff_head_sha) { nil }
+
+ error_message = 'No source for merge'
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq(error_message)
+ end
+ end
+
+ context 'when target ref is passed as a parameter' do
+ let(:params) { { commit_message: 'merge train', target_ref: target_ref } }
+
+ it_behaves_like 'successfully merges to ref with merge method' do
+ let(:first_parent_ref) { 'refs/heads/master' }
+ let(:target_ref) { 'refs/merge-requests/1/train' }
+ end
+ end
+
+ describe 'cascading merge refs' do
+ set(:project) { create(:project, :repository) }
+ let(:params) { { commit_message: 'Cascading merge', first_parent_ref: first_parent_ref, target_ref: target_ref } }
+
+ context 'when first merge happens' do
+ let(:merge_request) do
+ create(:merge_request, source_project: project, source_branch: 'feature',
+ target_project: project, target_branch: 'master')
+ end
+
+ it_behaves_like 'successfully merges to ref with merge method' do
+ let(:first_parent_ref) { 'refs/heads/master' }
+ let(:target_ref) { 'refs/merge-requests/1/train' }
+ end
+
+ context 'when second merge happens' do
+ let(:merge_request) do
+ create(:merge_request, source_project: project, source_branch: 'improve/awesome',
+ target_project: project, target_branch: 'master')
+ end
+
+ it_behaves_like 'successfully merges to ref with merge method' do
+ let(:first_parent_ref) { 'refs/merge-requests/1/train' }
+ let(:target_ref) { 'refs/merge-requests/2/train' }
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb
index 6efece64092..a864da0a6fb 100644
--- a/spec/services/merge_requests/mergeability_check_service_spec.rb
+++ b/spec/services/merge_requests/mergeability_check_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MergeabilityCheckService do
+describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shared_state do
shared_examples_for 'unmergeable merge request' do
it 'updates or keeps merge status as cannot_be_merged' do
subject
@@ -11,7 +11,11 @@ describe MergeRequests::MergeabilityCheckService do
end
it 'does not change the merge ref HEAD' do
- expect { subject }.not_to change(merge_request, :merge_ref_head)
+ merge_ref_head = merge_request.merge_ref_head
+
+ subject
+
+ expect(merge_request.reload.merge_ref_head).to eq merge_ref_head
end
it 'returns ServiceResponse.error' do
@@ -56,24 +60,69 @@ describe MergeRequests::MergeabilityCheckService do
subject { described_class.new(merge_request).execute }
+ def execute_within_threads(amount:, retry_lease: true)
+ threads = []
+
+ amount.times do
+ # Let's use a different object for each thread to get closer
+ # to a real world scenario.
+ mr = MergeRequest.find(merge_request.id)
+
+ threads << Thread.new do
+ described_class.new(mr).execute(retry_lease: retry_lease)
+ end
+ end
+
+ threads.each(&:join)
+ threads
+ end
+
before do
project.add_developer(merge_request.author)
end
it_behaves_like 'mergeable merge request'
- context 'when multiple calls to the service' do
- it 'returns success' do
- subject
- result = subject
+ context 'when lock is disabled' do
+ before do
+ stub_feature_flags(merge_ref_auto_sync_lock: false)
+ end
- expect(result).to be_a(ServiceResponse)
- expect(result.success?).to be(true)
+ it_behaves_like 'mergeable merge request'
+ end
+
+ context 'when concurrent calls' do
+ it 'waits first lock and returns "cached" result in subsequent calls' do
+ threads = execute_within_threads(amount: 3)
+ results = threads.map { |t| t.value.status }
+
+ expect(results).to contain_exactly(:success, :success, :success)
end
- it 'second call does not change the merge-ref' do
- expect { subject }.to change(merge_request, :merge_ref_head).from(nil)
- expect { subject }.not_to change(merge_request, :merge_ref_head)
+ it 'writes the merge-ref once' do
+ service = instance_double(MergeRequests::MergeToRefService)
+
+ expect(MergeRequests::MergeToRefService).to receive(:new).once { service }
+ expect(service).to receive(:execute).once.and_return(success: true)
+
+ execute_within_threads(amount: 3)
+ end
+
+ it 'resets one merge request upon execution' do
+ expect_any_instance_of(MergeRequest).to receive(:reset).once
+
+ execute_within_threads(amount: 2)
+ end
+
+ context 'when retry_lease flag is false' do
+ it 'the first call succeeds, subsequent concurrent calls get a lock error response' do
+ threads = execute_within_threads(amount: 3, retry_lease: false)
+ results = threads.map { |t| [t.value.status, t.value.message] }
+
+ expect(results).to contain_exactly([:error, 'Failed to obtain a lock'],
+ [:error, 'Failed to obtain a lock'],
+ [:success, nil])
+ end
end
end
@@ -98,8 +147,7 @@ describe MergeRequests::MergeabilityCheckService do
context 'when broken' do
before do
- allow(merge_request).to receive(:broken?) { true }
- allow(project.repository).to receive(:can_be_merged?) { false }
+ expect(merge_request).to receive(:broken?) { true }
end
it_behaves_like 'unmergeable merge request'
@@ -113,10 +161,13 @@ describe MergeRequests::MergeabilityCheckService do
end
end
- context 'when it has conflicts' do
- before do
- allow(merge_request).to receive(:broken?) { false }
- allow(project.repository).to receive(:can_be_merged?) { false }
+ context 'when it cannot be merged on git' do
+ let(:merge_request) do
+ create(:merge_request,
+ merge_status: :unchecked,
+ source_branch: 'conflict-resolvable',
+ source_project: project,
+ target_branch: 'conflict-start')
end
it_behaves_like 'unmergeable merge request'
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
index 54b9c6dae38..a27fea0c90f 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -11,6 +11,8 @@ describe MergeRequests::PushOptionsHandlerService do
let(:service) { described_class.new(project, user, changes, push_options) }
let(:source_branch) { 'fix' }
let(:target_branch) { 'feature' }
+ let(:title) { 'my title' }
+ let(:description) { 'my description' }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
@@ -73,6 +75,26 @@ describe MergeRequests::PushOptionsHandlerService do
end
end
+ shared_examples_for 'a service that can set the title of a merge request' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'sets the title' do
+ service.execute
+
+ expect(last_mr.title).to eq(title)
+ end
+ end
+
+ shared_examples_for 'a service that can set the description of a merge request' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'sets the description' do
+ service.execute
+
+ expect(last_mr.description).to eq(description)
+ end
+ end
+
shared_examples_for 'a service that can set the merge request to merge when pipeline succeeds' do
subject(:last_mr) { MergeRequest.last }
@@ -90,6 +112,16 @@ describe MergeRequests::PushOptionsHandlerService do
end
end
+ shared_examples_for 'a service that can remove the source branch when it is merged' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'returns true to force_remove_source_branch?' do
+ service.execute
+
+ expect(last_mr.force_remove_source_branch?).to eq(true)
+ end
+ end
+
shared_examples_for 'a service that does not create a merge request' do
it do
expect { service.execute }.not_to change { MergeRequest.count }
@@ -208,6 +240,72 @@ describe MergeRequests::PushOptionsHandlerService do
end
end
+ describe '`remove_source_branch` push option' do
+ let(:push_options) { { remove_source_branch: true } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, remove_source_branch: true } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can remove the source branch when it is merged'
+ end
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, remove_source_branch: true } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can remove the source branch when it is merged'
+ end
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can remove the source branch when it is merged'
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
describe '`target` push option' do
let(:push_options) { { target: target_branch } }
@@ -274,6 +372,138 @@ describe MergeRequests::PushOptionsHandlerService do
end
end
+ describe '`title` push option' do
+ let(:push_options) { { title: title } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, title: title } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the title of a merge request'
+ end
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, title: title } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the title of a merge request'
+ end
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can set the title of a merge request'
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
+ describe '`description` push option' do
+ let(:push_options) { { description: description } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, description: description } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the description of a merge request'
+ end
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, description: description } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the description of a merge request'
+ end
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can set the description of a merge request'
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
describe 'multiple pushed branches' do
let(:push_options) { { create: true } }
let(:changes) do
diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb
index 7e2f03d1097..7b8c94c86fe 100644
--- a/spec/services/merge_requests/rebase_service_spec.rb
+++ b/spec/services/merge_requests/rebase_service_spec.rb
@@ -6,10 +6,12 @@ describe MergeRequests::RebaseService do
include ProjectForksHelper
let(:user) { create(:user) }
+ let(:rebase_jid) { 'fake-rebase-jid' }
let(:merge_request) do
- create(:merge_request,
+ create :merge_request,
source_branch: 'feature_conflict',
- target_branch: 'master')
+ target_branch: 'master',
+ rebase_jid: rebase_jid
end
let(:project) { merge_request.project }
let(:repository) { project.repository.raw }
@@ -23,11 +25,11 @@ describe MergeRequests::RebaseService do
describe '#execute' do
context 'when another rebase is already in progress' do
before do
- allow(merge_request).to receive(:rebase_in_progress?).and_return(true)
+ allow(repository).to receive(:rebase_in_progress?).with(merge_request.id).and_return(true)
end
it 'saves the error message' do
- subject.execute(merge_request)
+ service.execute(merge_request)
expect(merge_request.reload.merge_error).to eq 'Rebase task canceled: Another rebase is already in progress'
end
@@ -36,6 +38,13 @@ describe MergeRequests::RebaseService do
expect(service.execute(merge_request)).to match(status: :error,
message: described_class::REBASE_ERROR)
end
+
+ it 'clears rebase_jid' do
+ expect { service.execute(merge_request) }
+ .to change { merge_request.rebase_jid }
+ .from(rebase_jid)
+ .to(nil)
+ end
end
shared_examples 'sequence of failure and success' do
@@ -43,14 +52,19 @@ describe MergeRequests::RebaseService do
allow(repository).to receive(:gitaly_operation_client).and_raise('Something went wrong')
service.execute(merge_request)
+ merge_request.reload
- expect(merge_request.reload.merge_error).to eq described_class::REBASE_ERROR
+ expect(merge_request.reload.merge_error).to eq(described_class::REBASE_ERROR)
+ expect(merge_request.rebase_jid).to eq(nil)
allow(repository).to receive(:gitaly_operation_client).and_call_original
+ merge_request.update!(rebase_jid: rebase_jid)
service.execute(merge_request)
+ merge_request.reload
- expect(merge_request.reload.merge_error).to eq nil
+ expect(merge_request.merge_error).to eq(nil)
+ expect(merge_request.rebase_jid).to eq(nil)
end
end
@@ -72,7 +86,7 @@ describe MergeRequests::RebaseService do
it 'saves a generic error message' do
subject.execute(merge_request)
- expect(merge_request.reload.merge_error).to eq described_class::REBASE_ERROR
+ expect(merge_request.reload.merge_error).to eq(described_class::REBASE_ERROR)
end
it 'returns an error' do
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index f566d235787..9688e02d6ac 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -91,7 +91,8 @@ describe MergeRequests::UpdateService, :mailer do
labels: [],
mentioned_users: [user2],
assignees: [user3],
- total_time_spent: 0
+ total_time_spent: 0,
+ description: "FYI #{user2.to_reference}"
}
)
end
@@ -99,7 +100,7 @@ describe MergeRequests::UpdateService, :mailer do
it 'sends email to user2 about assign of new merge request and email to user3 about merge request unassignment' do
deliveries = ActionMailer::Base.deliveries
email = deliveries.last
- recipients = deliveries.last(2).map(&:to).flatten
+ recipients = deliveries.last(2).flat_map(&:to)
expect(recipients).to include(user2.email, user3.email)
expect(email.subject).to include(merge_request.title)
end
@@ -598,7 +599,7 @@ describe MergeRequests::UpdateService, :mailer do
feature_visibility_attr = :"#{merge_request.model_name.plural}_access_level"
project.project_feature.update_attribute(feature_visibility_attr, ProjectFeature::PRIVATE)
- expect { update_merge_request(assignee_ids: [assignee]) }.not_to change { merge_request.reload.assignees }
+ expect { update_merge_request(assignee_ids: [assignee]) }.not_to change(merge_request.assignees, :count)
end
end
end
diff --git a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
new file mode 100644
index 00000000000..53b7497ae21
--- /dev/null
+++ b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
@@ -0,0 +1,145 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Metrics::Dashboard::CustomMetricEmbedService do
+ include MetricsDashboardHelpers
+
+ set(:project) { build(:project) }
+ set(:user) { create(:user) }
+ set(:environment) { create(:environment, project: project) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ let(:dashboard_path) { system_dashboard_path }
+ let(:group) { business_metric_title }
+ let(:title) { 'title' }
+ let(:y_label) { 'y_label' }
+
+ describe '.valid_params?' do
+ let(:valid_params) do
+ {
+ embedded: true,
+ dashboard_path: dashboard_path,
+ group: group,
+ title: title,
+ y_label: y_label
+ }
+ end
+
+ subject { described_class.valid_params?(params) }
+
+ let(:params) { valid_params }
+
+ it { is_expected.to be_truthy }
+
+ context 'not embedded' do
+ let(:params) { valid_params.except(:embedded) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'non-system dashboard' do
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'undefined dashboard' do
+ let(:params) { valid_params.except(:dashboard_path) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'non-custom metric group' do
+ let(:group) { 'Different Group' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'missing group' do
+ let(:group) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'missing title' do
+ let(:title) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'undefined y-axis label' do
+ let(:params) { valid_params.except(:y_label) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#get_dashboard' do
+ let(:service_params) do
+ [
+ project,
+ user,
+ {
+ embedded: true,
+ environment: environment,
+ dashboard_path: dashboard_path,
+ group: group,
+ title: title,
+ y_label: y_label
+ }
+ ]
+ end
+
+ let(:service_call) { described_class.new(*service_params).get_dashboard }
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ it_behaves_like 'raises error for users with insufficient permissions'
+
+ context 'the custom metric exists' do
+ let!(:metric) { create(:prometheus_metric, project: project) }
+
+ it_behaves_like 'valid embedded dashboard service response'
+
+ it 'does not cache the unprocessed dashboard' do
+ expect(Gitlab::Metrics::Dashboard::Cache).not_to receive(:fetch)
+
+ described_class.new(*service_params).get_dashboard
+ end
+
+ context 'multiple metrics meet criteria' do
+ let!(:metric_2) { create(:prometheus_metric, project: project, query: 'avg(metric_2)') }
+
+ it_behaves_like 'valid embedded dashboard service response'
+
+ it 'includes both metrics' do
+ result = service_call
+ included_queries = all_queries(result[:dashboard])
+
+ expect(included_queries).to include('avg(metric_2)', 'avg(metric)')
+ end
+ end
+ end
+
+ context 'when the metric exists in another project' do
+ let!(:metric) { create(:prometheus_metric, project: create(:project)) }
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ end
+ end
+
+ private
+
+ def all_queries(dashboard)
+ dashboard[:panel_groups].flat_map do |group|
+ group[:panels].flat_map do |panel|
+ panel[:metrics].map do |metric|
+ metric[:query_range]
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb b/spec/services/metrics/dashboard/default_embed_service_spec.rb
index 79a78df44ae..803b9a93be7 100644
--- a/spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/default_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::DynamicDashboardService, :use_clean_rails_memory_store_caching do
+describe Metrics::Dashboard::DefaultEmbedService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
set(:project) { build(:project) }
@@ -14,14 +14,16 @@ describe Gitlab::Metrics::Dashboard::DynamicDashboardService, :use_clean_rails_m
end
describe '#get_dashboard' do
- let(:service_params) { [project, user, { environment: environment, dashboard_path: nil }] }
+ let(:service_params) { [project, user, { environment: environment }] }
let(:service_call) { described_class.new(*service_params).get_dashboard }
it_behaves_like 'valid embedded dashboard service response'
it_behaves_like 'raises error for users with insufficient permissions'
it 'caches the unprocessed dashboard for subsequent calls' do
- expect(YAML).to receive(:safe_load).once.and_call_original
+ system_service = Metrics::Dashboard::SystemDashboardService
+
+ expect(system_service).to receive(:new).once.and_call_original
described_class.new(*service_params).get_dashboard
described_class.new(*service_params).get_dashboard
diff --git a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
new file mode 100644
index 00000000000..a0f7315f750
--- /dev/null
+++ b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_store_caching do
+ include MetricsDashboardHelpers
+
+ set(:project) { build(:project) }
+ set(:user) { create(:user) }
+ set(:environment) { create(:environment, project: project) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+ let(:group) { 'Group A' }
+ let(:title) { 'Super Chart A1' }
+ let(:y_label) { 'y_label' }
+
+ describe '.valid_params?' do
+ let(:valid_params) do
+ {
+ embedded: true,
+ dashboard_path: dashboard_path,
+ group: group,
+ title: title,
+ y_label: y_label
+ }
+ end
+
+ subject { described_class.valid_params?(params) }
+
+ let(:params) { valid_params }
+
+ it { is_expected.to be_truthy }
+
+ context 'not embedded' do
+ let(:params) { valid_params.except(:embedded) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'undefined dashboard' do
+ let(:params) { valid_params.except(:dashboard_path) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'missing dashboard' do
+ let(:dashboard) { '' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'missing group' do
+ let(:group) { '' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'missing title' do
+ let(:title) { '' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'undefined y-axis label' do
+ let(:params) { valid_params.except(:y_label) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#get_dashboard' do
+ let(:service_params) do
+ [
+ project,
+ user,
+ {
+ environment: environment,
+ dashboard_path: dashboard_path,
+ group: group,
+ title: title,
+ y_label: y_label
+ }
+ ]
+ end
+
+ let(:service_call) { described_class.new(*service_params).get_dashboard }
+
+ context 'when the dashboard does not exist' do
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ end
+
+ context 'when the dashboard is exists' do
+ let(:project) { project_with_dashboard(dashboard_path) }
+
+ it_behaves_like 'valid embedded dashboard service response'
+ it_behaves_like 'raises error for users with insufficient permissions'
+
+ it 'caches the unprocessed dashboard for subsequent calls' do
+ expect(YAML).to receive(:safe_load).once.and_call_original
+
+ described_class.new(*service_params).get_dashboard
+ described_class.new(*service_params).get_dashboard
+ end
+
+ context 'when the specified group is not present on the dashboard' do
+ let(:group) { 'Group Not Found' }
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ end
+
+ context 'when the specified title is not present on the dashboard' do
+ let(:title) { 'Title Not Found' }
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ end
+
+ context 'when the specified y-axis label is not present on the dashboard' do
+ let(:y_label) { 'Y-Axis Not Found' }
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ end
+ end
+
+ shared_examples 'uses system dashboard' do
+ it 'uses the default dashboard' do
+ expect(Gitlab::Metrics::Dashboard::Finder)
+ .to receive(:find_raw)
+ .with(project, dashboard_path: system_dashboard_path)
+ .once
+
+ service_call
+ end
+ end
+
+ context 'when the dashboard is nil' do
+ let(:dashboard_path) { nil }
+
+ it_behaves_like 'uses system dashboard'
+ end
+
+ context 'when the dashboard is not present' do
+ let(:dashboard_path) { '' }
+
+ it_behaves_like 'uses system dashboard'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb b/spec/services/metrics/dashboard/project_dashboard_service_spec.rb
index 468e8ec9ef2..1357914be2a 100644
--- a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/project_dashboard_service_spec.rb
@@ -2,7 +2,7 @@
require 'rails_helper'
-describe Gitlab::Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_memory_store_caching do
+describe Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
set(:user) { create(:user) }
diff --git a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
index 13f22dd01c5..8be3e7f6064 100644
--- a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do
+describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
set(:user) { create(:user) }
diff --git a/spec/services/namespaces/statistics_refresher_service_spec.rb b/spec/services/namespaces/statistics_refresher_service_spec.rb
new file mode 100644
index 00000000000..f4d9c96f7f4
--- /dev/null
+++ b/spec/services/namespaces/statistics_refresher_service_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Namespaces::StatisticsRefresherService, '#execute' do
+ let(:group) { create(:group) }
+ let(:projects) { create_list(:project, 5, namespace: group) }
+ let(:service) { described_class.new }
+
+ context 'without a root storage statistics relation' do
+ it 'creates one' do
+ expect do
+ service.execute(group)
+ end.to change(Namespace::RootStorageStatistics, :count).by(1)
+
+ expect(group.reload.root_storage_statistics).to be_present
+ end
+
+ it 'recalculate the namespace statistics' do
+ expect_any_instance_of(Namespace::RootStorageStatistics).to receive(:recalculate!).once
+
+ service.execute(group)
+ end
+ end
+
+ context 'with a root storage statistics relation' do
+ before do
+ Namespace::AggregationSchedule.safe_find_or_create_by!(namespace_id: group.id)
+ end
+
+ it 'does not create one' do
+ expect do
+ service.execute(group)
+ end.not_to change(Namespace::RootStorageStatistics, :count)
+ end
+
+ it 'recalculate the namespace statistics' do
+ expect(Namespace::RootStorageStatistics)
+ .to receive(:safe_find_or_create_by!).with({ namespace_id: group.id })
+ .and_return(group.root_storage_statistics)
+
+ service.execute(group)
+ end
+ end
+
+ context 'when something goes wrong' do
+ before do
+ allow_any_instance_of(Namespace::RootStorageStatistics)
+ .to receive(:recalculate!).and_raise(ActiveRecord::ActiveRecordError)
+ end
+
+ it 'raises RefreshError' do
+ expect do
+ service.execute(group)
+ end.to raise_error(Namespaces::StatisticsRefresherService::RefresherError)
+ end
+ end
+end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 46abd8f356a..cd4ea9c401d 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -365,5 +365,43 @@ describe Notes::CreateService do
.and change { existing_note.updated_at }
end
end
+
+ describe "usage counter" do
+ let(:counter) { Gitlab::UsageDataCounters::NoteCounter }
+
+ context 'snippet note' do
+ let(:snippet) { create(:project_snippet, project: project) }
+ let(:opts) { { note: 'reply', noteable_type: 'Snippet', noteable_id: snippet.id, project: project } }
+
+ it 'increments usage counter' do
+ expect do
+ note = described_class.new(project, user, opts).execute
+
+ expect(note).to be_valid
+ end.to change { counter.read(:create, opts[:noteable_type]) }.by 1
+ end
+
+ it 'does not increment usage counter when creation fails' do
+ expect do
+ note = described_class.new(project, user, { note: '' }).execute
+
+ expect(note).to be_invalid
+ end.not_to change { counter.read(:create, opts[:noteable_type]) }
+ end
+ end
+
+ context 'issue note' do
+ let(:issue) { create(:issue, project: project) }
+ let(:opts) { { note: 'reply', noteable_type: 'Issue', noteable_id: issue.id, project: project } }
+
+ it 'does not increment usage counter' do
+ expect do
+ note = described_class.new(project, user, opts).execute
+
+ expect(note).to be_valid
+ end.not_to change { counter.read(:create, opts[:noteable_type]) }
+ end
+ end
+ end
end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index f25e2fe5e2b..d925aa2b6c3 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -111,7 +111,7 @@ describe NotificationService, :mailer do
should_email(participant)
end
- context 'for subgroups', :nested_groups do
+ context 'for subgroups' do
before do
build_group(project)
end
@@ -215,13 +215,14 @@ describe NotificationService, :mailer do
let(:project) { create(:project, :private) }
let(:issue) { create(:issue, project: project, assignees: [assignee]) }
let(:mentioned_issue) { create(:issue, assignees: issue.assignees) }
- let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced, @unsubscribed_mentioned and @outsider also') }
+ let(:author) { create(:user) }
+ let(:note) { create(:note_on_issue, author: author, noteable: issue, project_id: issue.project_id, note: '@mention referenced, @unsubscribed_mentioned and @outsider also') }
before do
- build_team(note.project)
+ build_team(project)
project.add_maintainer(issue.author)
project.add_maintainer(assignee)
- project.add_maintainer(note.author)
+ project.add_maintainer(author)
@u_custom_off = create_user_with_notification(:custom, 'custom_off')
project.add_guest(@u_custom_off)
@@ -239,43 +240,50 @@ describe NotificationService, :mailer do
end
describe '#new_note' do
- it do
- add_users_with_subscription(note.project, issue)
- reset_delivered_emails!
+ context do
+ before do
+ add_users(project)
+ add_user_subscriptions(issue)
+ reset_delivered_emails!
+ end
- expect(SentNotification).to receive(:record).with(issue, any_args).exactly(10).times
+ it do
+ expect(SentNotification).to receive(:record).with(issue, any_args).exactly(10).times
- notification.new_note(note)
+ notification.new_note(note)
- should_email(@u_watcher)
- should_email(note.noteable.author)
- should_email(note.noteable.assignees.first)
- should_email(@u_custom_global)
- should_email(@u_mentioned)
- should_email(@subscriber)
- should_email(@watcher_and_subscriber)
- should_email(@subscribed_participant)
- should_email(@u_custom_off)
- should_email(@unsubscribed_mentioned)
- should_not_email(@u_guest_custom)
- should_not_email(@u_guest_watcher)
- should_not_email(note.author)
- should_not_email(@u_participating)
- should_not_email(@u_disabled)
- should_not_email(@unsubscriber)
- should_not_email(@u_outsider_mentioned)
- should_not_email(@u_lazy_participant)
- end
+ should_email(@u_watcher)
+ should_email(note.noteable.author)
+ should_email(note.noteable.assignees.first)
+ should_email(@u_custom_global)
+ should_email(@u_mentioned)
+ should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
+ should_email(@subscribed_participant)
+ should_email(@u_custom_off)
+ should_email(@unsubscribed_mentioned)
+ should_not_email(@u_guest_custom)
+ should_not_email(@u_guest_watcher)
+ should_not_email(note.author)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_outsider_mentioned)
+ should_not_email(@u_lazy_participant)
+ end
- it "emails the note author if they've opted into notifications about their activity" do
- add_users_with_subscription(note.project, issue)
- reset_delivered_emails!
+ it "emails the note author if they've opted into notifications about their activity" do
+ note.author.notified_of_own_activity = true
- note.author.notified_of_own_activity = true
+ notification.new_note(note)
- notification.new_note(note)
+ should_email(note.author)
+ end
- should_email(note.author)
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { note }
+ let(:notification_trigger) { notification.new_note(note) }
+ end
end
it 'filters out "mentioned in" notes' do
@@ -334,7 +342,12 @@ describe NotificationService, :mailer do
it_behaves_like 'new note notifications'
- context 'which is a subgroup', :nested_groups do
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { note }
+ let(:notification_trigger) { notification.new_note(note) }
+ end
+
+ context 'which is a subgroup' do
let!(:parent) { create(:group) }
let!(:group) { create(:group, parent: parent) }
@@ -385,7 +398,7 @@ describe NotificationService, :mailer do
should_email(admin)
end
- context 'on project that belongs to subgroup', :nested_groups do
+ context 'on project that belongs to subgroup' do
let(:group_reporter) { create(:user) }
let(:group_guest) { create(:user) }
let(:parent_group) { create(:group) }
@@ -415,13 +428,15 @@ describe NotificationService, :mailer do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project, assignees: [assignee]) }
let(:mentioned_issue) { create(:issue, assignees: issue.assignees) }
- let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@all mentioned') }
+ let(:author) { create(:user) }
+ let(:note) { create(:note_on_issue, author: author, noteable: issue, project_id: issue.project_id, note: '@all mentioned') }
before do
- build_team(note.project)
- build_group(note.project)
- note.project.add_maintainer(note.author)
- add_users_with_subscription(note.project, issue)
+ build_team(project)
+ build_group(project)
+ add_users(project)
+ add_user_subscriptions(issue)
+ project.add_maintainer(author)
reset_delivered_emails!
end
@@ -453,7 +468,7 @@ describe NotificationService, :mailer do
should_not_email_nested_group_user(@pg_disabled)
end
- it 'notifies parent group members with mention level', :nested_groups do
+ it 'notifies parent group members with mention level' do
note = create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: "@#{@pg_mention.username}")
notification.new_note(note)
@@ -467,23 +482,29 @@ describe NotificationService, :mailer do
expect(Notify).not_to receive(:note_issue_email)
notification.new_note(mentioned_note)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { note }
+ let(:notification_trigger) { notification.new_note(note) }
+ end
end
end
context 'project snippet note' do
let!(:project) { create(:project, :public) }
let(:snippet) { create(:project_snippet, project: project, author: create(:user)) }
- let(:note) { create(:note_on_project_snippet, noteable: snippet, project_id: project.id, note: '@all mentioned') }
+ let(:author) { create(:user) }
+ let(:note) { create(:note_on_project_snippet, author: author, noteable: snippet, project_id: project.id, note: '@all mentioned') }
before do
build_team(project)
build_group(project)
+ project.add_maintainer(author)
# make sure these users can read the project snippet!
project.add_guest(@u_guest_watcher)
project.add_guest(@u_guest_custom)
add_member_for_parent_group(@pg_watcher, project)
- note.project.add_maintainer(note.author)
reset_delivered_emails!
end
@@ -613,6 +634,11 @@ describe NotificationService, :mailer do
notification.new_note(note)
should_not_email(@u_committer)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { note }
+ let(:notification_trigger) { notification.new_note(note) }
+ end
end
end
@@ -639,6 +665,11 @@ describe NotificationService, :mailer do
.to contain_exactly(*merge_request.assignees.pluck(:id), merge_request.author.id, @u_watcher.id)
expect(SentNotification.last.in_reply_to_discussion_id).to eq(note.discussion_id)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { note }
+ let(:notification_trigger) { notification.new_note(note) }
+ end
end
end
end
@@ -708,10 +739,11 @@ describe NotificationService, :mailer do
let(:issue) { create :issue, project: project, assignees: [assignee], description: 'cc @participant @unsubscribed_mentioned' }
before do
- build_team(issue.project)
- build_group(issue.project)
+ build_team(project)
+ build_group(project)
- add_users_with_subscription(issue.project, issue)
+ add_users(project)
+ add_user_subscriptions(issue)
reset_delivered_emails!
update_custom_notification(:new_issue, @u_guest_custom, resource: project)
update_custom_notification(:new_issue, @u_custom_global)
@@ -812,6 +844,11 @@ describe NotificationService, :mailer do
should_email(user_4)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.new_issue(issue, @u_disabled) }
+ end
+
context 'confidential issues' do
let(:author) { create(:user) }
let(:assignee) { create(:user) }
@@ -854,6 +891,11 @@ describe NotificationService, :mailer do
let(:mentionable) { issue }
include_examples 'notifications for new mentions'
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global, @u_mentioned) }
+ end
end
describe '#reassigned_issue' do
@@ -962,6 +1004,11 @@ describe NotificationService, :mailer do
let(:issuable) { issue }
let(:notification_trigger) { notification.reassigned_issue(issue, @u_disabled, [assignee]) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.reassigned_issue(issue, @u_disabled, [assignee]) }
+ end
end
describe '#relabeled_issue' do
@@ -1021,6 +1068,11 @@ describe NotificationService, :mailer do
should_email(subscriber_to_both)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.relabeled_issue(issue, [group_label_2, label_2], @u_disabled) }
+ end
+
context 'confidential issues' do
let(:author) { create(:user) }
let(:assignee) { create(:user) }
@@ -1058,12 +1110,19 @@ describe NotificationService, :mailer do
end
describe '#removed_milestone_issue' do
- it_behaves_like 'altered milestone notification on issue' do
+ context do
let(:milestone) { create(:milestone, project: project, issues: [issue]) }
let!(:subscriber_to_new_milestone) { create(:user) { |u| issue.toggle_subscription(u, project) } }
- before do
- notification.removed_milestone_issue(issue, issue.author)
+ it_behaves_like 'altered milestone notification on issue' do
+ before do
+ notification.removed_milestone_issue(issue, issue.author)
+ end
+ end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.removed_milestone_issue(issue, issue.author) }
end
end
@@ -1103,12 +1162,19 @@ describe NotificationService, :mailer do
end
describe '#changed_milestone_issue' do
- it_behaves_like 'altered milestone notification on issue' do
+ context do
let(:new_milestone) { create(:milestone, project: project, issues: [issue]) }
let!(:subscriber_to_new_milestone) { create(:user) { |u| issue.toggle_subscription(u, project) } }
- before do
- notification.changed_milestone_issue(issue, new_milestone, issue.author)
+ it_behaves_like 'altered milestone notification on issue' do
+ before do
+ notification.changed_milestone_issue(issue, new_milestone, issue.author)
+ end
+ end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.changed_milestone_issue(issue, new_milestone, issue.author) }
end
end
@@ -1176,6 +1242,11 @@ describe NotificationService, :mailer do
let(:issuable) { issue }
let(:notification_trigger) { notification.close_issue(issue, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.close_issue(issue, @u_disabled) }
+ end
end
describe '#reopen_issue' do
@@ -1207,6 +1278,11 @@ describe NotificationService, :mailer do
let(:issuable) { issue }
let(:notification_trigger) { notification.reopen_issue(issue, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.reopen_issue(issue, @u_disabled) }
+ end
end
describe '#issue_moved' do
@@ -1233,6 +1309,11 @@ describe NotificationService, :mailer do
let(:issuable) { issue }
let(:notification_trigger) { notification.issue_moved(issue, new_issue, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.issue_moved(issue, new_issue, @u_disabled) }
+ end
end
describe '#issue_due' do
@@ -1273,6 +1354,11 @@ describe NotificationService, :mailer do
let(:issuable) { issue }
let(:notification_trigger) { notification.issue_due(issue) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.issue_due(issue) }
+ end
end
end
@@ -1281,13 +1367,16 @@ describe NotificationService, :mailer do
let(:project) { create(:project, :public, :repository, namespace: group) }
let(:another_project) { create(:project, :public, namespace: group) }
let(:assignee) { create(:user) }
- let(:merge_request) { create :merge_request, source_project: project, assignees: [assignee], description: 'cc @participant' }
+ let(:assignees) { Array.wrap(assignee) }
+ let(:author) { create(:user) }
+ let(:merge_request) { create :merge_request, author: author, source_project: project, assignees: assignees, description: 'cc @participant' }
before do
- project.add_maintainer(merge_request.author)
- merge_request.assignees.each { |assignee| project.add_maintainer(assignee) }
- build_team(merge_request.target_project)
- add_users_with_subscription(merge_request.target_project, merge_request)
+ project.add_maintainer(author)
+ assignees.each { |assignee| project.add_maintainer(assignee) }
+ build_team(project)
+ add_users(project)
+ add_user_subscriptions(merge_request)
update_custom_notification(:new_merge_request, @u_guest_custom, resource: project)
update_custom_notification(:new_merge_request, @u_custom_global)
reset_delivered_emails!
@@ -1364,6 +1453,11 @@ describe NotificationService, :mailer do
should_email(user_4)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) }
+ end
+
context 'participating' do
it_should_behave_like 'participating by assignee notification' do
let(:participant) { create(:user, username: 'user-participant')}
@@ -1396,6 +1490,11 @@ describe NotificationService, :mailer do
let(:mentionable) { merge_request }
include_examples 'notifications for new mentions'
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global, @u_mentioned) }
+ end
end
describe '#reassigned_merge_request' do
@@ -1439,6 +1538,11 @@ describe NotificationService, :mailer do
let(:issuable) { merge_request }
let(:notification_trigger) { notification.reassigned_merge_request(merge_request, current_user, [assignee]) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.reassigned_merge_request(merge_request, current_user, [assignee]) }
+ end
end
describe '#push_to_merge_request' do
@@ -1469,6 +1573,11 @@ describe NotificationService, :mailer do
let(:issuable) { merge_request }
let(:notification_trigger) { notification.push_to_merge_request(merge_request, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.push_to_merge_request(merge_request, @u_disabled) }
+ end
end
describe '#relabel_merge_request' do
@@ -1502,28 +1611,43 @@ describe NotificationService, :mailer do
should_not_email(@u_participating)
should_not_email(@u_lazy_participant)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.relabeled_merge_request(merge_request, [group_label_2, label_2], @u_disabled) }
+ end
end
describe '#removed_milestone_merge_request' do
- it_behaves_like 'altered milestone notification on merge request' do
- let(:milestone) { create(:milestone, project: project, merge_requests: [merge_request]) }
- let!(:subscriber_to_new_milestone) { create(:user) { |u| merge_request.toggle_subscription(u, project) } }
+ let(:milestone) { create(:milestone, project: project, merge_requests: [merge_request]) }
+ let!(:subscriber_to_new_milestone) { create(:user) { |u| merge_request.toggle_subscription(u, project) } }
+ it_behaves_like 'altered milestone notification on merge request' do
before do
notification.removed_milestone_merge_request(merge_request, merge_request.author)
end
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.removed_milestone_merge_request(merge_request, merge_request.author) }
+ end
end
describe '#changed_milestone_merge_request' do
- it_behaves_like 'altered milestone notification on merge request' do
- let(:new_milestone) { create(:milestone, project: project, merge_requests: [merge_request]) }
- let!(:subscriber_to_new_milestone) { create(:user) { |u| merge_request.toggle_subscription(u, project) } }
+ let(:new_milestone) { create(:milestone, project: project, merge_requests: [merge_request]) }
+ let!(:subscriber_to_new_milestone) { create(:user) { |u| merge_request.toggle_subscription(u, project) } }
+ it_behaves_like 'altered milestone notification on merge request' do
before do
notification.changed_milestone_merge_request(merge_request, new_milestone, merge_request.author)
end
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.changed_milestone_merge_request(merge_request, new_milestone, merge_request.author) }
+ end
end
describe '#merge_request_unmergeable' do
@@ -1534,6 +1658,11 @@ describe NotificationService, :mailer do
expect(email_recipients.size).to eq(1)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.merge_request_unmergeable(merge_request) }
+ end
+
describe 'when merge_when_pipeline_succeeds is true' do
before do
merge_request.update(
@@ -1580,6 +1709,11 @@ describe NotificationService, :mailer do
let(:issuable) { merge_request }
let(:notification_trigger) { notification.close_mr(merge_request, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.close_mr(merge_request, @u_disabled) }
+ end
end
describe '#merged_merge_request' do
@@ -1632,6 +1766,11 @@ describe NotificationService, :mailer do
let(:issuable) { merge_request }
let(:notification_trigger) { notification.merge_mr(merge_request, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.merge_mr(merge_request, @u_disabled) }
+ end
end
describe '#reopen_merge_request' do
@@ -1662,6 +1801,11 @@ describe NotificationService, :mailer do
let(:issuable) { merge_request }
let(:notification_trigger) { notification.reopen_mr(merge_request, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.reopen_mr(merge_request, @u_disabled) }
+ end
end
describe "#resolve_all_discussions" do
@@ -1685,6 +1829,11 @@ describe NotificationService, :mailer do
let(:issuable) { merge_request }
let(:notification_trigger) { notification.resolve_all_discussions(merge_request, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.resolve_all_discussions(merge_request, @u_disabled) }
+ end
end
end
@@ -1709,6 +1858,11 @@ describe NotificationService, :mailer do
should_not_email(@u_disabled)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.project_was_moved(project, "gitlab/gitlab") }
+ end
+
context 'users not having access to the new location' do
it 'does not send email' do
old_user = create(:user)
@@ -1752,6 +1906,11 @@ describe NotificationService, :mailer do
should_only_email(@u_participating)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.project_exported(project, @u_participating) }
+ end
end
describe '#project_not_exported' do
@@ -1760,6 +1919,11 @@ describe NotificationService, :mailer do
should_only_email(@u_participating)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.project_not_exported(project, @u_participating, ['error']) }
+ end
end
end
end
@@ -1790,6 +1954,11 @@ describe NotificationService, :mailer do
should_email(maintainer)
should_not_email(developer)
end
+
+ it_behaves_like 'group emails are disabled' do
+ let(:notification_target) { group }
+ let(:notification_trigger) { group.request_access(added_user) }
+ end
end
describe '#decline_group_invite' do
@@ -1829,12 +1998,17 @@ describe NotificationService, :mailer do
should_not_email_anyone
end
end
+
+ it_behaves_like 'group emails are disabled' do
+ let(:notification_target) { group }
+ let(:notification_trigger) { group.add_guest(added_user) }
+ end
end
end
describe 'ProjectMember' do
let(:project) { create(:project) }
- set(:added_user) { create(:user) }
+ let(:added_user) { create(:user) }
describe '#new_access_request' do
context 'for a project in a user namespace' do
@@ -1849,6 +2023,11 @@ describe NotificationService, :mailer do
should_only_email(project.owner)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { project.request_access(added_user) }
+ end
end
context 'for a project in a group' do
@@ -1868,7 +2047,7 @@ describe NotificationService, :mailer do
end
end
- describe '#decline_group_invite' do
+ describe '#decline_project_invite' do
let(:member) { create(:user) }
before do
@@ -1890,6 +2069,11 @@ describe NotificationService, :mailer do
should_only_email(added_user)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { create_member! }
+ end
+
context 'when notifications are disabled' do
before do
create_global_setting_for(added_user, :disabled)
@@ -2053,27 +2237,69 @@ describe NotificationService, :mailer do
end
context 'when the creator has custom notifications enabled' do
- before do
- pipeline = create_pipeline(u_custom_notification_enabled, :success)
- notification.pipeline_finished(pipeline)
- end
+ let(:pipeline) { create_pipeline(u_custom_notification_enabled, :success) }
it 'emails only the creator' do
+ notification.pipeline_finished(pipeline)
+
should_only_email(u_custom_notification_enabled, kind: :bcc)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { pipeline }
+ let(:notification_trigger) { notification.pipeline_finished(pipeline) }
+ end
+
+ context 'when the creator has group notification email set' do
+ let(:group_notification_email) { 'user+group@example.com' }
+
+ before do
+ group = create(:group)
+
+ project.update(group: group)
+ create(:notification_setting, user: u_custom_notification_enabled, source: group, notification_email: group_notification_email)
+ end
+
+ it 'sends to group notification email' do
+ notification.pipeline_finished(pipeline)
+
+ expect(email_recipients(kind: :bcc).first).to eq(group_notification_email)
+ end
+ end
end
end
context 'with a failed pipeline' do
context 'when the creator has no custom notification set' do
- before do
- pipeline = create_pipeline(u_member, :failed)
- notification.pipeline_finished(pipeline)
- end
+ let(:pipeline) { create_pipeline(u_member, :failed) }
it 'emails only the creator' do
+ notification.pipeline_finished(pipeline)
+
should_only_email(u_member, kind: :bcc)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { pipeline }
+ let(:notification_trigger) { notification.pipeline_finished(pipeline) }
+ end
+
+ context 'when the creator has group notification email set' do
+ let(:group_notification_email) { 'user+group@example.com' }
+
+ before do
+ group = create(:group)
+
+ project.update(group: group)
+ create(:notification_setting, user: u_member, source: group, notification_email: group_notification_email)
+ end
+
+ it 'sends to group notification email' do
+ notification.pipeline_finished(pipeline)
+
+ expect(email_recipients(kind: :bcc).first).to eq(group_notification_email)
+ end
+ end
end
context 'when the creator has watch set' do
@@ -2173,6 +2399,11 @@ describe NotificationService, :mailer do
should_only_email(u_maintainer1, u_maintainer2, u_owner)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { domain }
+ let(:notification_trigger) { notify! }
+ end
+
it 'emails nobody if the project is missing' do
domain.project = nil
@@ -2182,30 +2413,6 @@ describe NotificationService, :mailer do
end
end
end
-
- describe '#pages_domain_verification_failed' do
- it 'emails current watching maintainers' do
- notification.pages_domain_verification_failed(domain)
-
- should_only_email(u_maintainer1, u_maintainer2, u_owner)
- end
- end
-
- describe '#pages_domain_enabled' do
- it 'emails current watching maintainers' do
- notification.pages_domain_enabled(domain)
-
- should_only_email(u_maintainer1, u_maintainer2, u_owner)
- end
- end
-
- describe '#pages_domain_disabled' do
- it 'emails current watching maintainers' do
- notification.pages_domain_disabled(domain)
-
- should_only_email(u_maintainer1, u_maintainer2, u_owner)
- end
- end
end
context 'Auto DevOps notifications' do
@@ -2224,6 +2431,11 @@ describe NotificationService, :mailer do
should_email(owner, times: 1) # Once for the disable pipeline.
should_email(pipeline_user, times: 2) # Once for the new permission, once for the disable.
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.autodevops_disabled(pipeline, [owner.email, pipeline_user.email]) }
+ end
end
end
@@ -2237,6 +2449,11 @@ describe NotificationService, :mailer do
should_email(user)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.repository_cleanup_success(project, user) }
+ end
end
describe '#repository_cleanup_failure' do
@@ -2245,6 +2462,11 @@ describe NotificationService, :mailer do
should_email(user)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.repository_cleanup_failure(project, user, 'Some error') }
+ end
end
end
@@ -2278,6 +2500,11 @@ describe NotificationService, :mailer do
should_only_email(u_maintainer1, u_maintainer2, u_owner)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.remote_mirror_update_failed(remote_mirror) }
+ end
end
end
@@ -2368,56 +2595,44 @@ describe NotificationService, :mailer do
group
end
- # Creates a nested group only if supported
- # to avoid errors on MySQL
def create_nested_group(visibility)
- if Group.supports_nested_objects?
- parent_group = create(:group, visibility)
- child_group = create(:group, visibility, parent: parent_group)
+ parent_group = create(:group, visibility)
+ child_group = create(:group, visibility, parent: parent_group)
- # Parent group member: global=disabled, parent_group=watch, child_group=global
- @pg_watcher ||= create_user_with_notification(:watch, 'parent_group_watcher', parent_group)
- @pg_watcher.notification_settings_for(nil).disabled!
+ # Parent group member: global=disabled, parent_group=watch, child_group=global
+ @pg_watcher ||= create_user_with_notification(:watch, 'parent_group_watcher', parent_group)
+ @pg_watcher.notification_settings_for(nil).disabled!
- # Parent group member: global=global, parent_group=disabled, child_group=global
- @pg_disabled ||= create_user_with_notification(:disabled, 'parent_group_disabled', parent_group)
- @pg_disabled.notification_settings_for(nil).global!
+ # Parent group member: global=global, parent_group=disabled, child_group=global
+ @pg_disabled ||= create_user_with_notification(:disabled, 'parent_group_disabled', parent_group)
+ @pg_disabled.notification_settings_for(nil).global!
- # Parent group member: global=global, parent_group=mention, child_group=global
- @pg_mention ||= create_user_with_notification(:mention, 'parent_group_mention', parent_group)
- @pg_mention.notification_settings_for(nil).global!
+ # Parent group member: global=global, parent_group=mention, child_group=global
+ @pg_mention ||= create_user_with_notification(:mention, 'parent_group_mention', parent_group)
+ @pg_mention.notification_settings_for(nil).global!
- # Parent group member: global=global, parent_group=participating, child_group=global
- @pg_participant ||= create_user_with_notification(:participating, 'parent_group_participant', parent_group)
- @pg_mention.notification_settings_for(nil).global!
+ # Parent group member: global=global, parent_group=participating, child_group=global
+ @pg_participant ||= create_user_with_notification(:participating, 'parent_group_participant', parent_group)
+ @pg_mention.notification_settings_for(nil).global!
- child_group
- else
- create(:group, visibility)
- end
+ child_group
end
def add_member_for_parent_group(user, project)
- return unless Group.supports_nested_objects?
-
project.reload
project.group.parent.add_maintainer(user)
end
def should_email_nested_group_user(user, times: 1, recipients: email_recipients)
- return unless Group.supports_nested_objects?
-
should_email(user, times: 1, recipients: email_recipients)
end
def should_not_email_nested_group_user(user, recipients: email_recipients)
- return unless Group.supports_nested_objects?
-
should_not_email(user, recipients: email_recipients)
end
- def add_users_with_subscription(project, issuable)
+ def add_users(project)
@subscriber = create :user
@unsubscriber = create :user
@unsubscribed_mentioned = create :user, username: 'unsubscribed_mentioned'
@@ -2429,7 +2644,9 @@ describe NotificationService, :mailer do
project.add_maintainer(@unsubscriber)
project.add_maintainer(@watcher_and_subscriber)
project.add_maintainer(@unsubscribed_mentioned)
+ end
+ def add_user_subscriptions(issuable)
issuable.subscriptions.create(user: @unsubscribed_mentioned, project: project, subscribed: false)
issuable.subscriptions.create(user: @subscriber, project: project, subscribed: true)
issuable.subscriptions.create(user: @subscribed_participant, project: project, subscribed: true)
diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
index 8d43ce4f662..af79a42b611 100644
--- a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
+++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
@@ -12,6 +12,12 @@ describe PagesDomains::ObtainLetsEncryptCertificateService do
stub_lets_encrypt_settings
end
+ around do |example|
+ Sidekiq::Testing.fake! do
+ example.run
+ end
+ end
+
def expect_to_create_acme_challenge
expect(::PagesDomains::CreateAcmeOrderService).to receive(:new).with(pages_domain)
.and_wrap_original do |m, *args|
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index 2f70c8ea94d..b625653bc77 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -118,7 +118,7 @@ describe Projects::AutocompleteService do
expect(milestone_titles).to eq([group_milestone2.title, group_milestone1.title])
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let(:subgroup) { create(:group, :public, parent: group) }
let!(:subgroup_milestone) { create(:milestone, group: subgroup) }
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index b0b74407812..d4fa62fa85d 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -78,6 +78,7 @@ describe Projects::CreateService, '#execute' do
expect(project).to be_valid
expect(project.owner).to eq(group)
expect(project.namespace).to eq(group)
+ expect(project.team.owners).to include(user)
expect(user.authorized_projects).to include(project)
end
end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 3af7ee3ad50..9a6f64b825a 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -121,7 +121,22 @@ describe Projects::DestroyService do
it { expect(Dir.exist?(remove_path)).to be_truthy }
end
- context 'when flushing caches fail' do
+ context 'when flushing caches fail due to Git errors' do
+ before do
+ allow(project.repository).to receive(:before_delete).and_raise(::Gitlab::Git::CommandError)
+ allow(Gitlab::GitLogger).to receive(:warn).with(
+ class: described_class.name,
+ project_id: project.id,
+ disk_path: project.disk_path,
+ message: 'Gitlab::Git::CommandError').and_call_original
+
+ perform_enqueued_jobs { destroy_project(project, user, {}) }
+ end
+
+ it_behaves_like 'deleting the project'
+ end
+
+ context 'when flushing caches fail due to Redis' do
before do
new_user = create(:user)
project.team.add_user(new_user, Gitlab::Access::DEVELOPER)
@@ -226,6 +241,18 @@ describe Projects::DestroyService do
expect(destroy_project(project, user)).to be false
end
end
+
+ context 'when registry is disabled' do
+ before do
+ stub_container_registry_config(enabled: false)
+ end
+
+ it 'does not attempting to remove any tags' do
+ expect(Projects::ContainerRepository::DestroyService).not_to receive(:new)
+
+ destroy_project(project, user)
+ end
+ end
end
context 'when there are tags for legacy root repository' do
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index f25233ceeb1..06efc2ff825 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -20,13 +20,8 @@ describe Projects::DownloadService do
context 'for URLs that are on the whitelist' do
before do
- sham_rack_app = ShamRack.at('mycompany.fogbugz.com').stub
- sham_rack_app.register_resource('/rails_sample.jpg', File.read(Rails.root + 'spec/fixtures/rails_sample.jpg'), 'image/jpg')
- sham_rack_app.register_resource('/doc_sample.txt', File.read(Rails.root + 'spec/fixtures/doc_sample.txt'), 'text/plain')
- end
-
- after do
- ShamRack.unmount_all
+ stub_request(:get, 'http://mycompany.fogbugz.com/rails_sample.jpg').to_return(body: File.read(Rails.root + 'spec/fixtures/rails_sample.jpg'))
+ stub_request(:get, 'http://mycompany.fogbugz.com/doc_sample.txt').to_return(body: File.read(Rails.root + 'spec/fixtures/doc_sample.txt'))
end
context 'an image file' do
diff --git a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
index 80debcd3a7a..dabfd61d3f5 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
@@ -33,7 +33,7 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do
before do
allow(project).to receive(:lfs_enabled?).and_return(true)
- response = instance_double(HTTParty::Response)
+ response = instance_double(Gitlab::HTTP::Response)
allow(response).to receive(:body).and_return(objects_response.to_json)
allow(response).to receive(:success?).and_return(true)
allow(Gitlab::HTTP).to receive(:post).and_return(response)
@@ -95,7 +95,7 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do
shared_examples 'JSON parse errors' do |body|
it 'raises error' do
- response = instance_double(HTTParty::Response)
+ response = instance_double(Gitlab::HTTP::Response)
allow(response).to receive(:body).and_return(body)
allow(response).to receive(:success?).and_return(true)
allow(Gitlab::HTTP).to receive(:post).and_return(response)
diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
index 75d534c59bf..970e82e7107 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
@@ -17,7 +17,7 @@ describe Projects::LfsPointers::LfsDownloadService do
before do
ApplicationSetting.create_from_defaults
- stub_application_setting(allow_local_requests_from_hooks_and_services: local_request_setting)
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: local_request_setting)
allow(project).to receive(:lfs_enabled?).and_return(true)
end
diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb
index be2811ab1e7..4396ccab584 100644
--- a/spec/services/projects/update_remote_mirror_service_spec.rb
+++ b/spec/services/projects/update_remote_mirror_service_spec.rb
@@ -10,49 +10,91 @@ describe Projects::UpdateRemoteMirrorService do
subject(:service) { described_class.new(project, project.creator) }
- describe "#execute" do
+ describe '#execute' do
+ subject(:execute!) { service.execute(remote_mirror, 0) }
+
before do
project.repository.add_branch(project.owner, 'existing-branch', 'master')
allow(remote_mirror).to receive(:update_repository).and_return(true)
end
- it "ensures the remote exists" do
+ it 'ensures the remote exists' do
stub_fetch_remote(project, remote_name: remote_name, ssh_auth: remote_mirror)
expect(remote_mirror).to receive(:ensure_remote!)
- service.execute(remote_mirror)
+ execute!
end
- it "fetches the remote repository" do
+ it 'fetches the remote repository' do
expect(project.repository)
.to receive(:fetch_remote)
- .with(remote_mirror.remote_name, no_tags: true, ssh_auth: remote_mirror)
+ .with(remote_mirror.remote_name, no_tags: true, ssh_auth: remote_mirror)
- service.execute(remote_mirror)
+ execute!
end
- it "returns success when updated succeeds" do
+ it 'marks the mirror as started when beginning' do
+ expect(remote_mirror).to receive(:update_start!).and_call_original
+
+ execute!
+ end
+
+ it 'marks the mirror as successfully finished' do
stub_fetch_remote(project, remote_name: remote_name, ssh_auth: remote_mirror)
- result = service.execute(remote_mirror)
+ result = execute!
expect(result[:status]).to eq(:success)
+ expect(remote_mirror).to be_finished
+ end
+
+ it 'marks the mirror as failed and raises the error when an unexpected error occurs' do
+ allow(project.repository).to receive(:fetch_remote).and_raise('Badly broken')
+
+ expect { execute! }.to raise_error /Badly broken/
+
+ expect(remote_mirror).to be_failed
+ expect(remote_mirror.last_error).to include('Badly broken')
+ end
+
+ context 'when the update fails because of a `Gitlab::Git::CommandError`' do
+ before do
+ allow(project.repository).to receive(:fetch_remote).and_raise(Gitlab::Git::CommandError.new('fetch failed'))
+ end
+
+ it 'wraps `Gitlab::Git::CommandError`s in a service error' do
+ expect(execute!).to eq(status: :error, message: 'fetch failed')
+ end
+
+ it 'marks the mirror as to be retried' do
+ execute!
+
+ expect(remote_mirror).to be_to_retry
+ expect(remote_mirror.last_error).to include('fetch failed')
+ end
+
+ it "marks the mirror as failed after #{described_class::MAX_TRIES} tries" do
+ service.execute(remote_mirror, described_class::MAX_TRIES)
+
+ expect(remote_mirror).to be_failed
+ expect(remote_mirror.last_error).to include('fetch failed')
+ end
end
context 'when syncing all branches' do
- it "push all the branches the first time" do
+ it 'push all the branches the first time' do
stub_fetch_remote(project, remote_name: remote_name, ssh_auth: remote_mirror)
expect(remote_mirror).to receive(:update_repository).with({})
- service.execute(remote_mirror)
+ execute!
end
end
context 'when only syncing protected branches' do
- it "sync updated protected branches" do
+ it 'sync updated protected branches' do
stub_fetch_remote(project, remote_name: remote_name, ssh_auth: remote_mirror)
protected_branch = create_protected_branch(project)
remote_mirror.only_protected_branches = true
@@ -61,7 +103,7 @@ describe Projects::UpdateRemoteMirrorService do
.to receive(:update_repository)
.with(only_branches_matching: [protected_branch.name])
- service.execute(remote_mirror)
+ execute!
end
def create_protected_branch(project)
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 1dcfb739eb6..31bd0f0f836 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -186,7 +186,10 @@ describe Projects::UpdateService do
expect_any_instance_of(ProjectWiki).to receive(:wiki).and_raise(ProjectWiki::CouldNotCreateWikiError)
expect_any_instance_of(described_class).to receive(:log_error).with("Could not create wiki for #{project.full_name}")
- expect(Gitlab::Metrics).to receive(:counter)
+
+ counter = double(:counter)
+ expect(Gitlab::Metrics).to receive(:counter).with(:wiki_can_not_be_created_total, 'Counts the times we failed to create a wiki').and_return(counter)
+ expect(counter).to receive(:increment)
update_project(project, user, project_feature_attributes: { wiki_access_level: ProjectFeature::ENABLED })
end
@@ -347,13 +350,13 @@ describe Projects::UpdateService do
context 'when updating #pages_access_level' do
subject(:call_service) do
- update_project(project, admin, project_feature_attributes: { pages_access_level: ProjectFeature::PRIVATE })
+ update_project(project, admin, project_feature_attributes: { pages_access_level: ProjectFeature::ENABLED })
end
it 'updates the attribute' do
expect { call_service }
.to change { project.project_feature.pages_access_level }
- .to(ProjectFeature::PRIVATE)
+ .to(ProjectFeature::ENABLED)
end
it 'calls Projects::UpdatePagesConfigurationService' do
@@ -366,9 +369,28 @@ describe Projects::UpdateService do
end
end
+ context 'when updating #emails_disabled' do
+ it 'updates the attribute for the project owner' do
+ expect { update_project(project, user, emails_disabled: true) }
+ .to change { project.emails_disabled }
+ .to(true)
+ end
+
+ it 'does not update when not project owner' do
+ maintainer = create(:user)
+ project.add_user(maintainer, :maintainer)
+
+ expect { update_project(project, maintainer, emails_disabled: true) }
+ .not_to change { project.emails_disabled }
+ end
+ end
+
context 'with external authorization enabled' do
before do
enable_external_authorization_service_check
+
+ allow(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?).with(user, 'default_label', project.full_path).and_call_original
end
it 'does not save the project with an error if the service denies access' do
@@ -399,8 +421,7 @@ describe Projects::UpdateService do
end
it 'does not check the label when it does not change' do
- expect(::Gitlab::ExternalAuthorization)
- .not_to receive(:access_allowed?)
+ expect(::Gitlab::ExternalAuthorization).to receive(:access_allowed?).once
update_project(project, user, { name: 'New name' })
end
diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb
index 4bdb20de4c9..03bda94e9c6 100644
--- a/spec/services/prometheus/proxy_service_spec.rb
+++ b/spec/services/prometheus/proxy_service_spec.rb
@@ -131,7 +131,7 @@ describe Prometheus::ProxyService do
allow(environment).to receive(:prometheus_adapter)
.and_return(prometheus_adapter)
allow(prometheus_adapter).to receive(:can_query?).and_return(true)
- allow(prometheus_adapter).to receive(:prometheus_client_wrapper)
+ allow(prometheus_adapter).to receive(:prometheus_client)
.and_return(prometheus_client)
end
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 95a131e8c86..c9714964fc9 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -28,61 +28,108 @@ describe QuickActions::InterpretService do
shared_examples 'reopen command' do
it 'returns state_event: "reopen" if content contains /reopen' do
issuable.close!
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(state_event: 'reopen')
end
+
+ it 'returns the reopen message' do
+ issuable.close!
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Reopened this #{issuable.to_ability_name.humanize(capitalize: false)}.")
+ end
end
shared_examples 'close command' do
it 'returns state_event: "close" if content contains /close' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(state_event: 'close')
end
+
+ it 'returns the close message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Closed this #{issuable.to_ability_name.humanize(capitalize: false)}.")
+ end
end
shared_examples 'title command' do
it 'populates title: "A brand new title" if content contains /title A brand new title' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(title: 'A brand new title')
end
+
+ it 'returns the title message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq(%{Changed the title to "A brand new title".})
+ end
end
shared_examples 'milestone command' do
it 'fetches milestone and populates milestone_id if content contains /milestone' do
milestone # populate the milestone
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(milestone_id: milestone.id)
end
+
+ it 'returns the milestone message' do
+ milestone # populate the milestone
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Set the milestone to #{milestone.to_reference}.")
+ end
+
+ it 'returns empty milestone message when milestone is wrong' do
+ _, _, message = service.execute('/milestone %wrong-milestone', issuable)
+
+ expect(message).to be_empty
+ end
end
shared_examples 'remove_milestone command' do
it 'populates milestone_id: nil if content contains /remove_milestone' do
issuable.update!(milestone_id: milestone.id)
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(milestone_id: nil)
end
+
+ it 'returns removed milestone message' do
+ issuable.update!(milestone_id: milestone.id)
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Removed #{milestone.to_reference} milestone.")
+ end
end
shared_examples 'label command' do
it 'fetches label ids and populates add_label_ids if content contains /label' do
bug # populate the label
inprogress # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(add_label_ids: [bug.id, inprogress.id])
end
+
+ it 'returns the label message' do
+ bug # populate the label
+ inprogress # populate the label
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Added #{bug.to_reference(format: :name)} #{inprogress.to_reference(format: :name)} labels.")
+ end
end
shared_examples 'multiple label command' do
it 'fetches label ids and populates add_label_ids if content contains multiple /label' do
bug # populate the label
inprogress # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(add_label_ids: [inprogress.id, bug.id])
end
@@ -91,7 +138,7 @@ describe QuickActions::InterpretService do
shared_examples 'multiple label with same argument' do
it 'prevents duplicate label ids and populates add_label_ids if content contains multiple /label' do
inprogress # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(add_label_ids: [inprogress.id])
end
@@ -120,16 +167,23 @@ describe QuickActions::InterpretService do
shared_examples 'unlabel command' do
it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do
issuable.update!(label_ids: [inprogress.id]) # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(remove_label_ids: [inprogress.id])
end
+
+ it 'returns the unlabel message' do
+ issuable.update!(label_ids: [inprogress.id]) # populate the label
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Removed #{inprogress.to_reference(format: :name)} label.")
+ end
end
shared_examples 'multiple unlabel command' do
it 'fetches label ids and populates remove_label_ids if content contains mutiple /unlabel' do
issuable.update!(label_ids: [inprogress.id, bug.id]) # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(remove_label_ids: [inprogress.id, bug.id])
end
@@ -138,7 +192,7 @@ describe QuickActions::InterpretService do
shared_examples 'unlabel command with no argument' do
it 'populates label_ids: [] if content contains /unlabel with no arguments' do
issuable.update!(label_ids: [inprogress.id]) # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(label_ids: [])
end
@@ -148,91 +202,161 @@ describe QuickActions::InterpretService do
it 'populates label_ids: [] if content contains /relabel' do
issuable.update!(label_ids: [bug.id]) # populate the label
inprogress # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(label_ids: [inprogress.id])
end
+
+ it 'returns the relabel message' do
+ issuable.update!(label_ids: [bug.id]) # populate the label
+ inprogress # populate the label
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Replaced all labels with #{inprogress.to_reference(format: :name)} label.")
+ end
end
shared_examples 'todo command' do
it 'populates todo_event: "add" if content contains /todo' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(todo_event: 'add')
end
+
+ it 'returns the todo message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Added a To Do.')
+ end
end
shared_examples 'done command' do
it 'populates todo_event: "done" if content contains /done' do
TodoService.new.mark_todo(issuable, developer)
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(todo_event: 'done')
end
+
+ it 'returns the done message' do
+ TodoService.new.mark_todo(issuable, developer)
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Marked To Do as done.')
+ end
end
shared_examples 'subscribe command' do
it 'populates subscription_event: "subscribe" if content contains /subscribe' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(subscription_event: 'subscribe')
end
+
+ it 'returns the subscribe message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Subscribed to this #{issuable.to_ability_name.humanize(capitalize: false)}.")
+ end
end
shared_examples 'unsubscribe command' do
it 'populates subscription_event: "unsubscribe" if content contains /unsubscribe' do
issuable.subscribe(developer, project)
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(subscription_event: 'unsubscribe')
end
+
+ it 'returns the unsubscribe message' do
+ issuable.subscribe(developer, project)
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Unsubscribed from this #{issuable.to_ability_name.humanize(capitalize: false)}.")
+ end
end
shared_examples 'due command' do
+ let(:expected_date) { Date.new(2016, 8, 28) }
+
it 'populates due_date: Date.new(2016, 8, 28) if content contains /due 2016-08-28' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
+
+ expect(updates).to eq(due_date: expected_date)
+ end
- expect(updates).to eq(due_date: defined?(expected_date) ? expected_date : Date.new(2016, 8, 28))
+ it 'returns due_date message: Date.new(2016, 8, 28) if content contains /due 2016-08-28' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Set the due date to #{expected_date.to_s(:medium)}.")
end
end
shared_examples 'remove_due_date command' do
- it 'populates due_date: nil if content contains /remove_due_date' do
+ before do
issuable.update!(due_date: Date.today)
- _, updates = service.execute(content, issuable)
+ end
+
+ it 'populates due_date: nil if content contains /remove_due_date' do
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(due_date: nil)
end
+
+ it 'returns Removed the due date' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Removed the due date.')
+ end
end
shared_examples 'wip command' do
it 'returns wip_event: "wip" if content contains /wip' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(wip_event: 'wip')
end
+
+ it 'returns the wip message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Marked this #{issuable.to_ability_name.humanize(capitalize: false)} as Work In Progress.")
+ end
end
shared_examples 'unwip command' do
it 'returns wip_event: "unwip" if content contains /wip' do
issuable.update!(title: issuable.wip_title)
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(wip_event: 'unwip')
end
+
+ it 'returns the unwip message' do
+ issuable.update!(title: issuable.wip_title)
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Unmarked this #{issuable.to_ability_name.humanize(capitalize: false)} as Work In Progress.")
+ end
end
shared_examples 'estimate command' do
it 'populates time_estimate: 3600 if content contains /estimate 1h' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(time_estimate: 3600)
end
+
+ it 'returns the time_estimate formatted message' do
+ _, _, message = service.execute('/estimate 79d', issuable)
+
+ expect(message).to eq('Set time estimate to 3mo 3w 4d.')
+ end
end
shared_examples 'spend command' do
it 'populates spend_time: 3600 if content contains /spend 1h' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: {
duration: 3600,
@@ -240,11 +364,17 @@ describe QuickActions::InterpretService do
spent_at: DateTime.now.to_date
})
end
+
+ it 'returns the spend_time message including the formatted duration and verb' do
+ _, _, message = service.execute('/spend -120m', issuable)
+
+ expect(message).to eq('Subtracted 2h spent time.')
+ end
end
shared_examples 'spend command with negative time' do
it 'populates spend_time: -1800 if content contains /spend -30m' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: {
duration: -1800,
@@ -256,7 +386,7 @@ describe QuickActions::InterpretService do
shared_examples 'spend command with valid date' do
it 'populates spend time: 1800 with date in date type format' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: {
duration: 1800,
@@ -268,7 +398,7 @@ describe QuickActions::InterpretService do
shared_examples 'spend command with invalid date' do
it 'will not create any note and timelog' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq({})
end
@@ -276,7 +406,7 @@ describe QuickActions::InterpretService do
shared_examples 'spend command with future date' do
it 'will not create any note and timelog' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq({})
end
@@ -284,18 +414,30 @@ describe QuickActions::InterpretService do
shared_examples 'remove_estimate command' do
it 'populates time_estimate: 0 if content contains /remove_estimate' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(time_estimate: 0)
end
+
+ it 'returns the remove_estimate message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Removed time estimate.')
+ end
end
shared_examples 'remove_time_spent command' do
it 'populates spend_time: :reset if content contains /remove_time_spent' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: { duration: :reset, user_id: developer.id })
end
+
+ it 'returns the remove_time_spent message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Removed spent time.')
+ end
end
shared_examples 'lock command' do
@@ -303,10 +445,16 @@ describe QuickActions::InterpretService do
let(:merge_request) { create(:merge_request, source_project: project, discussion_locked: false) }
it 'returns discussion_locked: true if content contains /lock' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(discussion_locked: true)
end
+
+ it 'returns the lock discussion message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Locked the discussion.')
+ end
end
shared_examples 'unlock command' do
@@ -314,45 +462,79 @@ describe QuickActions::InterpretService do
let(:merge_request) { create(:merge_request, source_project: project, discussion_locked: true) }
it 'returns discussion_locked: true if content contains /unlock' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(discussion_locked: false)
end
+
+ it 'returns the unlock discussion message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Unlocked the discussion.')
+ end
end
- shared_examples 'empty command' do
+ shared_examples 'empty command' do |error_msg|
it 'populates {} if content contains an unsupported command' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to be_empty
end
+
+ it "returns #{error_msg || 'an empty'} message" do
+ _, _, message = service.execute(content, issuable)
+
+ if error_msg
+ expect(message).to eq(error_msg)
+ else
+ expect(message).to be_empty
+ end
+ end
end
shared_examples 'merge command' do
let(:project) { create(:project, :repository) }
it 'runs merge command if content contains /merge' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(merge: merge_request.diff_head_sha)
end
+
+ it 'returns them merge message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Scheduled to merge this merge request when the pipeline succeeds.')
+ end
end
shared_examples 'award command' do
it 'toggle award 100 emoji if content contains /award :100:' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(emoji_award: "100")
end
+
+ it 'returns the award message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Toggled :100: emoji award.')
+ end
end
shared_examples 'duplicate command' do
it 'fetches issue and populates canonical_issue_id if content contains /duplicate issue_reference' do
issue_duplicate # populate the issue
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(canonical_issue_id: issue_duplicate.id)
end
+
+ it 'returns the duplicate message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Marked this issue as a duplicate of #{issue_duplicate.to_reference(project)}.")
+ end
end
shared_examples 'copy_metadata command' do
@@ -360,7 +542,7 @@ describe QuickActions::InterpretService do
source_issuable # populate the issue
todo_label # populate this label
inreview_label # populate this label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates[:add_label_ids]).to match_array([inreview_label.id, todo_label.id])
@@ -370,19 +552,45 @@ describe QuickActions::InterpretService do
expect(updates).not_to have_key(:milestone_id)
end
end
+
+ it 'returns the copy metadata message' do
+ _, _, message = service.execute("/copy_metadata #{source_issuable.to_reference}", issuable)
+
+ expect(message).to eq("Copied labels and milestone from #{source_issuable.to_reference}.")
+ end
+ end
+
+ describe 'move issue command' do
+ it 'returns the move issue message' do
+ _, _, message = service.execute("/move #{project.full_path}", issue)
+
+ expect(message).to eq("Moved this issue to #{project.full_path}.")
+ end
+
+ it 'returns move issue failure message when the referenced issue is not found' do
+ _, _, message = service.execute('/move invalid', issue)
+
+ expect(message).to eq("Failed to move this issue because target project doesn't exist.")
+ end
end
shared_examples 'confidential command' do
it 'marks issue as confidential if content contains /confidential' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(confidential: true)
end
+
+ it 'returns the confidential message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Made this issue confidential.')
+ end
end
shared_examples 'shrug command' do
it 'appends ¯\_(ツ)_/¯ to the comment' do
- new_content, _ = service.execute(content, issuable)
+ new_content, _, _ = service.execute(content, issuable)
expect(new_content).to end_with(described_class::SHRUG)
end
@@ -390,7 +598,7 @@ describe QuickActions::InterpretService do
shared_examples 'tableflip command' do
it 'appends (╯°□°)╯︵ ┻━┻ to the comment' do
- new_content, _ = service.execute(content, issuable)
+ new_content, _, _ = service.execute(content, issuable)
expect(new_content).to end_with(described_class::TABLEFLIP)
end
@@ -398,18 +606,34 @@ describe QuickActions::InterpretService do
shared_examples 'tag command' do
it 'tags a commit' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(tag_name: tag_name, tag_message: tag_message)
end
+
+ it 'returns the tag message' do
+ _, _, message = service.execute(content, issuable)
+
+ if tag_message.present?
+ expect(message).to eq(%{Tagged this commit to #{tag_name} with "#{tag_message}".})
+ else
+ expect(message).to eq("Tagged this commit to #{tag_name}.")
+ end
+ end
end
shared_examples 'assign command' do
it 'assigns to a single user' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(assignee_ids: [developer.id])
end
+
+ it 'returns the assign message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Assigned #{developer.to_reference}.")
+ end
end
it_behaves_like 'reopen command' do
@@ -463,7 +687,7 @@ describe QuickActions::InterpretService do
let(:service) { described_class.new(project, developer, {}) }
it 'precheck passes and returns merge command' do
- _, updates = service.execute('/merge', merge_request)
+ _, updates, _ = service.execute('/merge', merge_request)
expect(updates).to eq(merge: nil)
end
@@ -559,7 +783,7 @@ describe QuickActions::InterpretService do
end
end
- it_behaves_like 'empty command' do
+ it_behaves_like 'empty command', "Failed to assign a user because no user was found." do
let(:content) { '/assign @abcd1234' }
let(:issuable) { issue }
end
@@ -574,20 +798,34 @@ describe QuickActions::InterpretService do
context 'Issue' do
it 'populates assignee_ids: [] if content contains /unassign' do
- issue.update(assignee_ids: [developer.id])
- _, updates = service.execute(content, issue)
+ issue.update!(assignee_ids: [developer.id])
+ _, updates, _ = service.execute(content, issue)
expect(updates).to eq(assignee_ids: [])
end
+
+ it 'returns the unassign message for all the assignee if content contains /unassign' do
+ issue.update(assignee_ids: [developer.id, developer2.id])
+ _, _, message = service.execute(content, issue)
+
+ expect(message).to eq("Removed assignees #{developer.to_reference} and #{developer2.to_reference}.")
+ end
end
context 'Merge Request' do
it 'populates assignee_ids: [] if content contains /unassign' do
- merge_request.update(assignee_ids: [developer.id])
- _, updates = service.execute(content, merge_request)
+ merge_request.update!(assignee_ids: [developer.id])
+ _, updates, _ = service.execute(content, merge_request)
expect(updates).to eq(assignee_ids: [])
end
+
+ it 'returns the unassign message for all the assignee if content contains /unassign' do
+ merge_request.update(assignee_ids: [developer.id, developer2.id])
+ _, _, message = service.execute(content, merge_request)
+
+ expect(message).to eq("Removed assignees #{developer.to_reference} and #{developer2.to_reference}.")
+ end
end
end
@@ -979,12 +1217,12 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
- it_behaves_like 'empty command' do
+ it_behaves_like 'empty command', 'Failed to mark this issue as a duplicate because referenced issue was not found.' do
let(:content) { "/duplicate imaginary#1234" }
let(:issuable) { issue }
end
- it_behaves_like 'empty command' do
+ it_behaves_like 'empty command', 'Failed to mark this issue as a duplicate because referenced issue was not found.' do
let(:other_project) { create(:project, :private) }
let(:issue_duplicate) { create(:issue, project: other_project) }
@@ -1049,7 +1287,7 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
- it_behaves_like 'empty command' do
+ it_behaves_like 'empty command', 'Failed to mark this issue as a duplicate because referenced issue was not found.' do
let(:content) { '/duplicate #{issue.to_reference}' }
let(:issuable) { issue }
end
@@ -1132,13 +1370,13 @@ describe QuickActions::InterpretService do
let(:service) { described_class.new(non_empty_project, developer)}
it 'updates target_branch if /target_branch command is executed' do
- _, updates = service.execute('/target_branch merge-test', merge_request)
+ _, updates, _ = service.execute('/target_branch merge-test', merge_request)
expect(updates).to eq(target_branch: 'merge-test')
end
it 'handles blanks around param' do
- _, updates = service.execute('/target_branch merge-test ', merge_request)
+ _, updates, _ = service.execute('/target_branch merge-test ', merge_request)
expect(updates).to eq(target_branch: 'merge-test')
end
@@ -1156,6 +1394,12 @@ describe QuickActions::InterpretService do
let(:issuable) { another_merge_request }
end
end
+
+ it 'returns the target_branch message' do
+ _, _, message = service.execute('/target_branch merge-test', merge_request)
+
+ expect(message).to eq('Set target branch to merge-test.')
+ end
end
context '/board_move command' do
@@ -1171,13 +1415,13 @@ describe QuickActions::InterpretService do
it 'populates remove_label_ids for all current board columns' do
issue.update!(label_ids: [todo.id, inprogress.id])
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates[:remove_label_ids]).to match_array([todo.id, inprogress.id])
end
it 'populates add_label_ids with the id of the given label' do
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates[:add_label_ids]).to eq([inreview.id])
end
@@ -1185,7 +1429,7 @@ describe QuickActions::InterpretService do
it 'does not include the given label id in remove_label_ids' do
issue.update!(label_ids: [todo.id, inreview.id])
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates[:remove_label_ids]).to match_array([todo.id])
end
@@ -1193,11 +1437,19 @@ describe QuickActions::InterpretService do
it 'does not remove label ids that are not lists on the board' do
issue.update!(label_ids: [todo.id, bug.id])
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates[:remove_label_ids]).to match_array([todo.id])
end
+ it 'returns board_move message' do
+ issue.update!(label_ids: [todo.id, inprogress.id])
+
+ _, _, message = service.execute(content, issue)
+
+ expect(message).to eq("Moved issue to ~#{inreview.id} column in the board.")
+ end
+
context 'if the project has multiple boards' do
let(:issuable) { issue }
@@ -1211,19 +1463,19 @@ describe QuickActions::InterpretService do
context 'if the given label does not exist' do
let(:issuable) { issue }
let(:content) { '/board_move ~"Fake Label"' }
- it_behaves_like 'empty command'
+ it_behaves_like 'empty command', 'Failed to move this issue because label was not found.'
end
context 'if multiple labels are given' do
let(:issuable) { issue }
let(:content) { %{/board_move ~"#{inreview.title}" ~"#{todo.title}"} }
- it_behaves_like 'empty command'
+ it_behaves_like 'empty command', 'Failed to move this issue because only a single label can be provided.'
end
context 'if the given label is not a list on the board' do
let(:issuable) { issue }
let(:content) { %{/board_move ~"#{bug.title}"} }
- it_behaves_like 'empty command'
+ it_behaves_like 'empty command', 'Failed to move this issue because label was not found.'
end
context 'if issuable is not an Issue' do
@@ -1292,10 +1544,16 @@ describe QuickActions::InterpretService do
end
it 'populates create_merge_request with branch_name and issue iid' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(create_merge_request: { branch_name: branch_name, issue_iid: issuable.iid })
end
+
+ it 'returns the create_merge_request message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Created branch '#{branch_name}' and a merge request to resolve this issue.")
+ end
end
end
@@ -1556,7 +1814,13 @@ describe QuickActions::InterpretService do
it 'uses the default branch name' do
_, explanations = service.explain(content, issue)
- expect(explanations).to eq(['Creates a branch and a merge request to resolve this issue'])
+ expect(explanations).to eq(['Creates a branch and a merge request to resolve this issue.'])
+ end
+
+ it 'returns the execution message using the default branch name' do
+ _, _, message = service.execute(content, issue)
+
+ expect(message).to eq('Created a branch and a merge request to resolve this issue.')
end
end
@@ -1566,7 +1830,13 @@ describe QuickActions::InterpretService do
it 'uses the given branch name' do
_, explanations = service.explain(content, issue)
- expect(explanations).to eq(["Creates branch 'foo' and a merge request to resolve this issue"])
+ expect(explanations).to eq(["Creates branch 'foo' and a merge request to resolve this issue."])
+ end
+
+ it 'returns the execution message using the given branch name' do
+ _, _, message = service.execute(content, issue)
+
+ expect(message).to eq("Created branch 'foo' and a merge request to resolve this issue.")
end
end
end
diff --git a/spec/services/submodules/update_service_spec.rb b/spec/services/submodules/update_service_spec.rb
index cf92350c1b2..47b31d4bcbf 100644
--- a/spec/services/submodules/update_service_spec.rb
+++ b/spec/services/submodules/update_service_spec.rb
@@ -142,7 +142,7 @@ describe Submodules::UpdateService do
let(:branch_name) { nil }
it_behaves_like 'returns error result' do
- let(:error_message) { 'You can only create or edit files when you are on a branch' }
+ let(:error_message) { 'Invalid parameters' }
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 93fe3290d8b..f46f9633c1c 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -332,7 +332,7 @@ describe SystemNoteService do
create(:merge_request, source_project: project, target_project: project)
end
- subject { described_class.merge_when_pipeline_succeeds(noteable, project, author, noteable.diff_head_commit) }
+ subject { described_class.merge_when_pipeline_succeeds(noteable, project, author, pipeline.sha) }
it_behaves_like 'a system note' do
let(:action) { 'merge' }
@@ -359,6 +359,22 @@ describe SystemNoteService do
end
end
+ describe '.abort_merge_when_pipeline_succeeds' do
+ let(:noteable) do
+ create(:merge_request, source_project: project, target_project: project)
+ end
+
+ subject { described_class.abort_merge_when_pipeline_succeeds(noteable, project, author, 'merge request was closed') }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'merge' }
+ end
+
+ it "posts the 'merge when pipeline succeeds' system note" do
+ expect(subject.note).to eq "aborted the automatic merge because merge request was closed"
+ end
+ end
+
describe '.change_title' do
let(:noteable) { create(:issue, project: project, title: 'Lorem ipsum') }
@@ -454,16 +470,32 @@ describe SystemNoteService do
end
describe '.new_issue_branch' do
- subject { described_class.new_issue_branch(noteable, project, author, "1-mepmep") }
+ let(:branch) { '1-mepmep' }
- it_behaves_like 'a system note' do
- let(:action) { 'branch' }
- end
+ subject { described_class.new_issue_branch(noteable, project, author, branch, branch_project: branch_project) }
- context 'when a branch is created from the new branch button' do
- it 'sets the note text' do
- expect(subject.note).to start_with("created branch [`1-mepmep`]")
+ shared_examples_for 'a system note for new issue branch' do
+ it_behaves_like 'a system note' do
+ let(:action) { 'branch' }
end
+
+ context 'when a branch is created from the new branch button' do
+ it 'sets the note text' do
+ expect(subject.note).to start_with("created branch [`#{branch}`]")
+ end
+ end
+ end
+
+ context 'branch_project is set' do
+ let(:branch_project) { create(:project, :repository) }
+
+ it_behaves_like 'a system note for new issue branch'
+ end
+
+ context 'branch_project is not set' do
+ let(:branch_project) { nil }
+
+ it_behaves_like 'a system note for new issue branch'
end
end
@@ -477,7 +509,31 @@ describe SystemNoteService do
end
it 'sets the new merge request note text' do
- expect(subject.note).to eq("created merge request #{merge_request.to_reference} to address this issue")
+ expect(subject.note).to eq("created merge request #{merge_request.to_reference(project)} to address this issue")
+ end
+ end
+
+ describe '.zoom_link_added' do
+ subject { described_class.zoom_link_added(issue, project, author) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'pinned_embed' }
+ end
+
+ it 'sets the zoom link added note text' do
+ expect(subject.note).to eq('added a Zoom call to this issue')
+ end
+ end
+
+ describe '.zoom_link_removed' do
+ subject { described_class.zoom_link_removed(issue, project, author) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'pinned_embed' }
+ end
+
+ it 'sets the zoom link removed note text' do
+ expect(subject.note).to eq('removed a Zoom call from this issue')
end
end
@@ -1132,7 +1188,7 @@ describe SystemNoteService do
end
it 'sets the note text' do
- expect(subject.note).to eq 'resolved all discussions'
+ expect(subject.note).to eq 'resolved all threads'
end
end
@@ -1159,16 +1215,30 @@ describe SystemNoteService do
end
it 'links to the diff in the system note' do
- expect(subject.note).to include('version 1')
-
diff_id = merge_request.merge_request_diff.id
line_code = change_position.line_code(project.repository)
- expect(subject.note).to include(diffs_project_merge_request_path(project, merge_request, diff_id: diff_id, anchor: line_code))
+ link = diffs_project_merge_request_path(project, merge_request, diff_id: diff_id, anchor: line_code)
+
+ expect(subject.note).to eq("changed this line in [version 1 of the diff](#{link})")
+ end
+
+ context 'discussion is on an image' do
+ let(:discussion) { create(:image_diff_note_on_merge_request, project: project).to_discussion }
+
+ it 'links to the diff in the system note' do
+ diff_id = merge_request.merge_request_diff.id
+ file_hash = change_position.file_hash
+ link = diffs_project_merge_request_path(project, merge_request, diff_id: diff_id, anchor: file_hash)
+
+ expect(subject.note).to eq("changed this file in [version 1 of the diff](#{link})")
+ end
end
end
- context 'when the change_position is invalid for the discussion' do
- let(:change_position) { project.commit(sample_commit.id) }
+ context 'when the change_position does not point to a valid version' do
+ before do
+ allow(merge_request).to receive(:version_params_for).and_return(nil)
+ end
it 'creates a new note in the discussion' do
# we need to completely rebuild the merge request object, or the `@discussions` on the merge request are not reloaded.
diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb
index 9adaee6481b..a309951bbcb 100644
--- a/spec/services/task_list_toggle_service_spec.rb
+++ b/spec/services/task_list_toggle_service_spec.rb
@@ -114,6 +114,23 @@ describe TaskListToggleService do
expect(toggler.execute).to be_falsey
end
+ it 'properly handles tasks in a blockquote' do
+ markdown =
+ <<-EOT.strip_heredoc
+ > > * [ ] Task 1
+ > * [x] Task 2
+ EOT
+
+ markdown_html = Banzai::Pipeline::FullPipeline.call(markdown, project: nil)[:output].to_html
+ toggler = described_class.new(markdown, markdown_html,
+ toggle_as_checked: true,
+ line_source: '> > * [ ] Task 1', line_number: 1)
+
+ expect(toggler.execute).to be_truthy
+ expect(toggler.updated_markdown.lines[0]).to eq "> > * [x] Task 1\n"
+ expect(toggler.updated_markdown_html).to include('disabled checked> Task 1')
+ end
+
it 'properly handles a GitLab blockquote' do
markdown =
<<-EOT.strip_heredoc
diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb
index 2a553e18807..ce809bbf6c5 100644
--- a/spec/services/todos/destroy/entity_leave_service_spec.rb
+++ b/spec/services/todos/destroy/entity_leave_service_spec.rb
@@ -176,7 +176,7 @@ describe Todos::Destroy::EntityLeaveService do
end
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let(:subgroup) { create(:group, :private, parent: group) }
let(:subgroup2) { create(:group, :private, parent: group) }
let(:subproject) { create(:project, group: subgroup) }
diff --git a/spec/services/todos/destroy/group_private_service_spec.rb b/spec/services/todos/destroy/group_private_service_spec.rb
index a1798686d7c..7dd495847b3 100644
--- a/spec/services/todos/destroy/group_private_service_spec.rb
+++ b/spec/services/todos/destroy/group_private_service_spec.rb
@@ -35,7 +35,7 @@ describe Todos::Destroy::GroupPrivateService do
expect(project_member.todos).to match_array([todo_project_member])
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let(:parent_group) { create(:group) }
let(:subgroup) { create(:group, :private, parent: group) }
let(:subproject) { create(:project, group: subgroup) }
diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb
index 23ea4e003f8..0678f54c195 100644
--- a/spec/services/update_snippet_service_spec.rb
+++ b/spec/services/update_snippet_service_spec.rb
@@ -40,6 +40,23 @@ describe UpdateSnippetService do
end
end
+ describe 'usage counter' do
+ let(:counter) { Gitlab::UsageDataCounters::SnippetCounter }
+ let(:snippet) { create_snippet(nil, @user, @opts) }
+
+ it 'increments count' do
+ expect do
+ update_snippet(nil, @admin, snippet, @opts)
+ end.to change { counter.read(:update) }.by 1
+ end
+
+ it 'does not increment count if create fails' do
+ expect do
+ update_snippet(nil, @admin, snippet, { title: '' })
+ end.not_to change { counter.read(:update) }
+ end
+ end
+
def create_snippet(project, user, opts)
CreateSnippetService.new(project, user, opts).execute
end
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index 0287a24808d..f5a914bb482 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -135,7 +135,7 @@ describe Users::RefreshAuthorizedProjectsService do
end
end
- context 'projects of subgroups of groups the user is a member of', :nested_groups do
+ context 'projects of subgroups of groups the user is a member of' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let!(:other_project) { create(:project, group: nested_group) }
@@ -163,7 +163,7 @@ describe Users::RefreshAuthorizedProjectsService do
end
end
- context 'projects shared with subgroups of groups the user is a member of', :nested_groups do
+ context 'projects shared with subgroups of groups the user is a member of' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:other_project) { create(:project) }
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 37bafc0c002..2a4368868d5 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -19,47 +19,74 @@ describe WebHookService do
let(:service_instance) { described_class.new(project_hook, data, :push_hooks) }
describe '#initialize' do
- it 'allow_local_requests is true if hook is a SystemHook' do
- instance = described_class.new(build(:system_hook), data, :system_hook)
- expect(instance.request_options[:allow_local_requests]).to be_truthy
+ before do
+ stub_application_setting(setting_name => setting)
end
- it 'allow_local_requests is false if hook is not a SystemHook' do
- %i(project_hook service_hook web_hook_log).each do |hook|
- instance = described_class.new(build(hook), data, hook)
- expect(instance.request_options[:allow_local_requests]).to be_falsey
+ shared_examples_for 'respects outbound network setting' do
+ context 'when local requests are allowed' do
+ let(:setting) { true }
+
+ it { expect(hook.request_options[:allow_local_requests]).to be_truthy }
+ end
+
+ context 'when local requests are not allowed' do
+ let(:setting) { false }
+
+ it { expect(hook.request_options[:allow_local_requests]).to be_falsey }
end
end
+
+ context 'when SystemHook' do
+ let(:setting_name) { :allow_local_requests_from_system_hooks }
+ let(:hook) { described_class.new(build(:system_hook), data, :system_hook) }
+
+ include_examples 'respects outbound network setting'
+ end
+
+ context 'when ProjectHook' do
+ let(:setting_name) { :allow_local_requests_from_web_hooks_and_services }
+ let(:hook) { described_class.new(build(:project_hook), data, :project_hook) }
+
+ include_examples 'respects outbound network setting'
+ end
end
describe '#execute' do
before do
project.hooks << [project_hook]
-
- WebMock.stub_request(:post, project_hook.url)
end
context 'when token is defined' do
let(:project_hook) { create(:project_hook, :token) }
it 'POSTs to the webhook URL' do
+ stub_full_request(project_hook.url, method: :post)
+
service_instance.execute
- expect(WebMock).to have_requested(:post, project_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)).with(
headers: headers.merge({ 'X-Gitlab-Token' => project_hook.token })
).once
end
end
it 'POSTs to the webhook URL' do
+ stub_full_request(project_hook.url, method: :post)
+
service_instance.execute
- expect(WebMock).to have_requested(:post, project_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)).with(
headers: headers
).once
end
it 'POSTs the data as JSON' do
+ stub_full_request(project_hook.url, method: :post)
+
service_instance.execute
- expect(WebMock).to have_requested(:post, project_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)).with(
headers: headers
).once
end
@@ -95,7 +122,7 @@ describe WebHookService do
end
it 'catches exceptions' do
- WebMock.stub_request(:post, project_hook.url).to_raise(StandardError.new('Some error'))
+ stub_full_request(project_hook.url, method: :post).to_raise(StandardError.new('Some error'))
expect { service_instance.execute }.to raise_error(StandardError)
end
@@ -105,20 +132,20 @@ describe WebHookService do
exceptions.each do |exception_class|
exception = exception_class.new('Exception message')
- WebMock.stub_request(:post, project_hook.url).to_raise(exception)
+ stub_full_request(project_hook.url, method: :post).to_raise(exception)
expect(service_instance.execute).to eq({ status: :error, message: exception.to_s })
expect { service_instance.execute }.not_to raise_error
end
end
it 'handles 200 status code' do
- WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')
+ stub_full_request(project_hook.url, method: :post).to_return(status: 200, body: 'Success')
expect(service_instance.execute).to include({ status: :success, http_status: 200, message: 'Success' })
end
it 'handles 2xx status codes' do
- WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: 'Success')
+ stub_full_request(project_hook.url, method: :post).to_return(status: 201, body: 'Success')
expect(service_instance.execute).to include({ status: :success, http_status: 201, message: 'Success' })
end
@@ -128,7 +155,7 @@ describe WebHookService do
context 'with success' do
before do
- WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')
+ stub_full_request(project_hook.url, method: :post).to_return(status: 200, body: 'Success')
service_instance.execute
end
@@ -145,7 +172,7 @@ describe WebHookService do
context 'with exception' do
before do
- WebMock.stub_request(:post, project_hook.url).to_raise(SocketError.new('Some HTTP Post error'))
+ stub_full_request(project_hook.url, method: :post).to_raise(SocketError.new('Some HTTP Post error'))
service_instance.execute
end
@@ -162,7 +189,7 @@ describe WebHookService do
context 'with unsafe response body' do
before do
- WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "\xBB")
+ stub_full_request(project_hook.url, method: :post).to_return(status: 200, body: "\xBB")
service_instance.execute
end
@@ -182,7 +209,7 @@ describe WebHookService do
let(:service_instance) { described_class.new(service_hook, data, 'service_hook') }
before do
- WebMock.stub_request(:post, service_hook.url).to_return(status: 200, body: 'Success')
+ stub_full_request(service_hook.url, method: :post).to_return(status: 200, body: 'Success')
end
it { expect { service_instance.execute }.not_to change(WebHookLog, :count) }
diff --git a/spec/services/wiki_pages/base_service_spec.rb b/spec/services/wiki_pages/base_service_spec.rb
new file mode 100644
index 00000000000..2e70246c6f2
--- /dev/null
+++ b/spec/services/wiki_pages/base_service_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe WikiPages::BaseService do
+ let(:project) { double('project') }
+ let(:user) { double('user') }
+
+ subject(:service) { described_class.new(project, user, {}) }
+
+ describe '#increment_usage' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+ error = counter::UnknownEvent
+
+ it 'raises an error on unknown events' do
+ expect { subject.send(:increment_usage, :bad_event) }.to raise_error error
+ end
+
+ context 'the event is valid' do
+ counter::KNOWN_EVENTS.each do |e|
+ it "updates the #{e} counter" do
+ expect { subject.send(:increment_usage, e) }.to change { counter.read(e) }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb
index 84510dcf700..ef03a2e9788 100644
--- a/spec/services/wiki_pages/create_service_spec.rb
+++ b/spec/services/wiki_pages/create_service_spec.rb
@@ -14,6 +14,10 @@ describe WikiPages::CreateService do
}
end
+ let(:bad_opts) do
+ { title: '' }
+ end
+
subject(:service) { described_class.new(project, user, opts) }
before do
@@ -36,5 +40,26 @@ describe WikiPages::CreateService do
service.execute
end
+
+ it 'counts wiki page creation' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+
+ expect { service.execute }.to change { counter.read(:create) }.by 1
+ end
+
+ context 'when the options are bad' do
+ subject(:service) { described_class.new(project, user, bad_opts) }
+
+ it 'does not count a creation event' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+
+ expect { service.execute }.not_to change { counter.read(:create) }
+ end
+
+ it 'reports the error' do
+ expect(service.execute).to be_invalid
+ .and have_attributes(errors: be_present)
+ end
+ end
end
end
diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb
index c74eac4dad6..350a7eb123b 100644
--- a/spec/services/wiki_pages/destroy_service_spec.rb
+++ b/spec/services/wiki_pages/destroy_service_spec.rb
@@ -20,5 +20,17 @@ describe WikiPages::DestroyService do
service.execute(page)
end
+
+ it 'increments the delete count' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+
+ expect { service.execute(page) }.to change { counter.read(:delete) }.by 1
+ end
+
+ it 'does not increment the delete count if the deletion failed' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+
+ expect { service.execute(nil) }.not_to change { counter.read(:delete) }
+ end
end
end
diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb
index 19866bd3bfc..d5f46e7b2db 100644
--- a/spec/services/wiki_pages/update_service_spec.rb
+++ b/spec/services/wiki_pages/update_service_spec.rb
@@ -16,6 +16,10 @@ describe WikiPages::UpdateService do
}
end
+ let(:bad_opts) do
+ { title: '' }
+ end
+
subject(:service) { described_class.new(project, user, opts) }
before do
@@ -39,5 +43,26 @@ describe WikiPages::UpdateService do
service.execute(page)
end
+
+ it 'counts edit events' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+
+ expect { service.execute page }.to change { counter.read(:update) }.by 1
+ end
+
+ context 'when the options are bad' do
+ subject(:service) { described_class.new(project, user, bad_opts) }
+
+ it 'does not count an edit event' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+
+ expect { service.execute page }.not_to change { counter.read(:update) }
+ end
+
+ it 'reports the error' do
+ expect(service.execute page).to be_invalid
+ .and have_attributes(errors: be_present)
+ end
+ end
end
end
diff --git a/spec/services/zoom_notes_service_spec.rb b/spec/services/zoom_notes_service_spec.rb
new file mode 100644
index 00000000000..419ecf3f374
--- /dev/null
+++ b/spec/services/zoom_notes_service_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ZoomNotesService do
+ describe '#execute' do
+ let(:issue) { OpenStruct.new(description: description) }
+ let(:project) { Object.new }
+ let(:user) { Object.new }
+ let(:description) { 'an issue description' }
+ let(:old_description) { nil }
+
+ subject { described_class.new(issue, project, user, old_description: old_description) }
+
+ shared_examples 'no notifications' do
+ it "doesn't create notifications" do
+ expect(SystemNoteService).not_to receive(:zoom_link_added)
+ expect(SystemNoteService).not_to receive(:zoom_link_removed)
+
+ subject.execute
+ end
+ end
+
+ it_behaves_like 'no notifications'
+
+ context 'when the zoom link exists in both description and old_description' do
+ let(:description) { 'a changed issue description https://zoom.us/j/123' }
+ let(:old_description) { 'an issue description https://zoom.us/j/123' }
+
+ it_behaves_like 'no notifications'
+ end
+
+ context "when the zoom link doesn't exist in both description and old_description" do
+ let(:description) { 'a changed issue description' }
+ let(:old_description) { 'an issue description' }
+
+ it_behaves_like 'no notifications'
+ end
+
+ context 'when description == old_description' do
+ let(:old_description) { 'an issue description' }
+
+ it_behaves_like 'no notifications'
+ end
+
+ context 'when the description contains a zoom link and old_description is nil' do
+ let(:description) { 'a changed issue description https://zoom.us/j/123' }
+
+ it 'creates a zoom_link_added notification' do
+ expect(SystemNoteService).to receive(:zoom_link_added).with(issue, project, user)
+ expect(SystemNoteService).not_to receive(:zoom_link_removed)
+
+ subject.execute
+ end
+ end
+
+ context 'when the zoom link has been added to the description' do
+ let(:description) { 'a changed issue description https://zoom.us/j/123' }
+ let(:old_description) { 'an issue description' }
+
+ it 'creates a zoom_link_added notification' do
+ expect(SystemNoteService).to receive(:zoom_link_added).with(issue, project, user)
+ expect(SystemNoteService).not_to receive(:zoom_link_removed)
+
+ subject.execute
+ end
+ end
+
+ context 'when the zoom link has been removed from the description' do
+ let(:description) { 'a changed issue description' }
+ let(:old_description) { 'an issue description https://zoom.us/j/123' }
+
+ it 'creates a zoom_link_removed notification' do
+ expect(SystemNoteService).not_to receive(:zoom_link_added).with(issue, project, user)
+ expect(SystemNoteService).to receive(:zoom_link_removed)
+
+ subject.execute
+ end
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 3bd2408dc72..bd504f1553b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -3,6 +3,7 @@ SimpleCovEnv.start!
ENV["RAILS_ENV"] = 'test'
ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true'
+ENV["RSPEC_ALLOW_INVALID_URLS"] = 'true'
require File.expand_path('../config/environment', __dir__)
require 'rspec/rails'
@@ -47,7 +48,10 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
quality_level = Quality::TestLevel.new
RSpec.configure do |config|
- config.use_transactional_fixtures = false
+ config.filter_run focus: true
+ config.run_all_when_everything_filtered = true
+
+ config.use_transactional_fixtures = true
config.use_instantiated_fixtures = false
config.fixture_path = Rails.root
@@ -103,6 +107,8 @@ RSpec.configure do |config|
config.include RedisHelpers
config.include Rails.application.routes.url_helpers, type: :routing
config.include PolicyHelpers, type: :policy
+ config.include MemoryUsageHelper
+ config.include ExpectRequestWithStatus, type: :request
if ENV['CI']
# This includes the first try, i.e. tests will be run 4 times before failing.
@@ -133,17 +139,21 @@ RSpec.configure do |config|
allow(Feature).to receive(:enabled?).with(flag).and_return(enabled)
end
+ allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enabled)
+
# The following can be removed when we remove the staged rollout strategy
# and we can just enable it using instance wide settings
# (ie. ApplicationSetting#auto_devops_enabled)
allow(Feature).to receive(:enabled?)
.with(:force_autodevops_on_by_default, anything)
.and_return(false)
+
+ Gitlab::ThreadMemoryCache.cache_backend.clear
end
- config.around(:example, :quarantine) do
+ config.around(:example, :quarantine) do |example|
# Skip tests in quarantine unless we explicitly focus on them.
- skip('In quarantine') unless config.inclusion_filter[:quarantine]
+ example.run if config.inclusion_filter[:quarantine]
end
config.before(:example, :request_store) do
@@ -252,18 +262,6 @@ RSpec.configure do |config|
Gitlab::CurrentSettings.clear_in_memory_application_settings!
end
- config.around(:each, :nested_groups) do |example|
- example.run if Group.supports_nested_objects?
- end
-
- config.around(:each, :postgresql) do |example|
- example.run if Gitlab::Database.postgresql?
- end
-
- config.around(:each, :mysql) do |example|
- example.run if Gitlab::Database.mysql?
- end
-
# This makes sure the `ApplicationController#can?` method is stubbed with the
# original implementation for all view specs.
config.before(:each, type: :view) do
@@ -287,6 +285,16 @@ RSpec.configure do |config|
config.before(:each, :https_pages_disabled) do |_|
allow(Gitlab.config.pages).to receive(:external_https).and_return(false)
end
+
+ # We can't use an `around` hook here because the wrapping transaction
+ # is not yet opened at the time that is triggered
+ config.prepend_before do
+ Gitlab::Database.set_open_transactions_baseline
+ end
+
+ config.append_after do
+ Gitlab::Database.reset_open_transactions_baseline
+ end
end
# add simpler way to match asset paths containing digest strings
diff --git a/spec/support/api/boards_shared_examples.rb b/spec/support/api/boards_shared_examples.rb
index 3abb5096a7a..b7aff32460d 100644
--- a/spec/support/api/boards_shared_examples.rb
+++ b/spec/support/api/boards_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'group and project boards' do |route_definition, ee = false|
let(:root_url) { route_definition.gsub(":id", board_parent.id.to_s) }
diff --git a/spec/support/api/issues_resolving_discussions_shared_examples.rb b/spec/support/api/issues_resolving_discussions_shared_examples.rb
index d2d6260dfa8..4c44f1bd103 100644
--- a/spec/support/api/issues_resolving_discussions_shared_examples.rb
+++ b/spec/support/api/issues_resolving_discussions_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'creating an issue resolving discussions through the API' do
it 'creates a new project issue' do
expect(response).to have_gitlab_http_status(:created)
diff --git a/spec/support/api/members_shared_examples.rb b/spec/support/api/members_shared_examples.rb
index 8d910e52eda..603efd4fc75 100644
--- a/spec/support/api/members_shared_examples.rb
+++ b/spec/support/api/members_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'a 404 response when source is private' do
before do
source.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb
index 63b719be03e..d6439f77408 100644
--- a/spec/support/api/milestones_shared_examples.rb
+++ b/spec/support/api/milestones_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'group and project milestones' do |route_definition|
let(:resource_route) { "#{route}/#{milestone.id}" }
let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) }
diff --git a/spec/support/api/repositories_shared_context.rb b/spec/support/api/repositories_shared_context.rb
index f1341804e56..346015106e3 100644
--- a/spec/support/api/repositories_shared_context.rb
+++ b/spec/support/api/repositories_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'disabled repository' do
before do
project.project_feature.update!(
diff --git a/spec/support/api/schema_matcher.rb b/spec/support/api/schema_matcher.rb
index 4cf34d43117..ebbd57c8115 100644
--- a/spec/support/api/schema_matcher.rb
+++ b/spec/support/api/schema_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SchemaPath
def self.expand(schema, dir = nil)
if Gitlab.ee? && dir.nil?
diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb
index 683234264a8..3786a8012f9 100644
--- a/spec/support/api/scopes/read_user_shared_examples.rb
+++ b/spec/support/api/scopes/read_user_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'allows the "read_user" scope' do |api_version|
let(:version) { api_version || 'v4' }
diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb
index 15037222630..3bd1b145433 100644
--- a/spec/support/api/time_tracking_shared_examples.rb
+++ b/spec/support/api/time_tracking_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'an unauthorized API user' do
it { is_expected.to eq(403) }
end
diff --git a/spec/support/banzai/reference_filter_shared_examples.rb b/spec/support/banzai/reference_filter_shared_examples.rb
index 476d80f3a93..27765652f28 100644
--- a/spec/support/banzai/reference_filter_shared_examples.rb
+++ b/spec/support/banzai/reference_filter_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specs for reference links containing HTML.
#
# Requires a reference:
diff --git a/spec/support/batch_loader.rb b/spec/support/batch_loader.rb
index bb790e660a6..0eb8f279d29 100644
--- a/spec/support/batch_loader.rb
+++ b/spec/support/batch_loader.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.after do
BatchLoader::Executor.clear_current
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 56ac208a025..4c688094352 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# rubocop:disable Style/GlobalVars
require 'capybara/rails'
require 'capybara/rspec'
@@ -45,6 +47,9 @@ Capybara.register_driver :chrome do |app|
# Explicitly set user-data-dir to prevent crashes. See https://gitlab.com/gitlab-org/gitlab-ce/issues/58882#note_179811508
options.add_argument("user-data-dir=/tmp/chrome") if ENV['CI'] || ENV['CI_SERVER']
+ # Chrome 75 defaults to W3C mode which doesn't allow console log access
+ options.add_option(:w3c, false)
+
Capybara::Selenium::Driver.new(
app,
browser: :chrome,
@@ -58,6 +63,7 @@ Capybara.javascript_driver = :chrome
Capybara.default_max_wait_time = timeout
Capybara.ignore_hidden_elements = true
Capybara.default_normalize_ws = true
+Capybara.enable_aria_label = true
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
diff --git a/spec/support/carrierwave.rb b/spec/support/carrierwave.rb
index b376822d530..8da55514c78 100644
--- a/spec/support/carrierwave.rb
+++ b/spec/support/carrierwave.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
CarrierWave.root = File.expand_path('tmp/tests/public', Rails.root)
RSpec.configure do |config|
diff --git a/spec/support/chunked_io/chunked_io_helpers.rb b/spec/support/chunked_io/chunked_io_helpers.rb
index fec1f951563..278f577f3cb 100644
--- a/spec/support/chunked_io/chunked_io_helpers.rb
+++ b/spec/support/chunked_io/chunked_io_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChunkedIOHelpers
def sample_trace_raw
@sample_trace_raw ||= File.read(expand_fixture_path('trace/sample_trace'))
diff --git a/spec/support/commit_trailers_spec_helper.rb b/spec/support/commit_trailers_spec_helper.rb
index efa317fd2f9..a958e259368 100644
--- a/spec/support/commit_trailers_spec_helper.rb
+++ b/spec/support/commit_trailers_spec_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CommitTrailersSpecHelper
extend ActiveSupport::Concern
diff --git a/spec/support/controllers/githubish_import_controller_shared_context.rb b/spec/support/controllers/githubish_import_controller_shared_context.rb
index e71994edec6..3706178ee34 100644
--- a/spec/support/controllers/githubish_import_controller_shared_context.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'a GitHub-ish import controller' do
let(:user) { create(:user) }
let(:token) { "asdasd12345" }
diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb
index 5bb1269a19d..718d9857b18 100644
--- a/spec/support/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specifications for behavior common to all objects with an email attribute.
# Takes a list of email-format attributes and requires:
# - subject { "the object with a attribute= setter" }
@@ -321,7 +323,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
end
- context 'user has chosen an existing nested namespace and name for the project', :postgresql do
+ context 'user has chosen an existing nested namespace and name for the project' do
let(:parent_namespace) { create(:group, name: 'foo') }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' }
@@ -340,7 +342,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
end
- context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen a non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do
@@ -371,7 +373,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
end
- context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo') }
diff --git a/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb b/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
index a0c77eecb61..d636c1cf6cd 100644
--- a/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
+++ b/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_context 'Ldap::OmniauthCallbacksController' do
diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
index 355555d9d19..b5149a0fcb1 100644
--- a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
+++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'authenticates sessionless user' do |path, format, params|
params ||= {}
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index 19b32c84d81..c57abbd96c6 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# rubocop:disable Metrics/AbcSize
# Note: The ABC size is large here because we have a method generating test cases with
@@ -50,7 +52,7 @@ module CycleAnalyticsHelpers
end
median_time_difference = time_differences.sort[2]
- expect(subject[phase].median).to be_within(5).of(median_time_difference)
+ expect(subject[phase].project_median).to be_within(5).of(median_time_difference)
end
context "when the data belongs to another project" do
@@ -80,7 +82,7 @@ module CycleAnalyticsHelpers
# Turn off the stub before checking assertions
allow(self).to receive(:project).and_call_original
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
@@ -103,7 +105,7 @@ module CycleAnalyticsHelpers
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
end
@@ -121,7 +123,7 @@ module CycleAnalyticsHelpers
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
end
@@ -138,7 +140,7 @@ module CycleAnalyticsHelpers
post_fn[self, data] if post_fn
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
end
@@ -146,7 +148,7 @@ module CycleAnalyticsHelpers
context "when none of the start / end conditions are matched" do
it "returns nil" do
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
end
diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb
index edd7de94203..f0dd6c52b74 100644
--- a/spec/support/database_cleaner.rb
+++ b/spec/support/database_cleaner.rb
@@ -26,31 +26,22 @@ RSpec.configure do |config|
end
config.append_after(:context) do
- DatabaseCleaner.clean_with(:deletion, cache_tables: false)
+ delete_from_all_tables!
end
- config.before do
- setup_database_cleaner
- DatabaseCleaner.strategy = :transaction
- end
+ config.around(:each, :delete) do |example|
+ self.class.use_transactional_tests = false
- config.before(:each, :js) do
- DatabaseCleaner.strategy = :deletion, { except: deletion_except_tables, cache_tables: false }
- end
+ example.run
- config.before(:each, :delete) do
- DatabaseCleaner.strategy = :deletion, { except: deletion_except_tables, cache_tables: false }
+ delete_from_all_tables!(except: deletion_except_tables)
end
- config.before(:each, :migration) do
- DatabaseCleaner.strategy = :deletion, { cache_tables: false }
- end
+ config.around(:each, :migration) do |example|
+ self.class.use_transactional_tests = false
- config.before do
- DatabaseCleaner.start
- end
+ example.run
- config.append_after do
- DatabaseCleaner.clean
+ delete_from_all_tables!
end
end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index c69fa322073..041ffa25535 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -1,4 +1,10 @@
+# frozen_string_literal: true
+
module DbCleaner
+ def delete_from_all_tables!(except: nil)
+ DatabaseCleaner.clean_with(:deletion, cache_tables: false, except: except)
+ end
+
def deletion_except_tables
[]
end
diff --git a/spec/support/external_authorization_service_helpers.rb b/spec/support/external_authorization_service_helpers.rb
index 79dd9a3d58e..f4214d800cf 100644
--- a/spec/support/external_authorization_service_helpers.rb
+++ b/spec/support/external_authorization_service_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExternalAuthorizationServiceHelpers
def enable_external_authorization_service_check
stub_application_setting(external_authorization_service_enabled: true)
diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb
index 0f8af2c5d6d..5590bf0fb7e 100644
--- a/spec/support/features/discussion_comments_shared_example.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -1,4 +1,6 @@
-shared_examples 'discussion comments' do |resource_name|
+# frozen_string_literal: true
+
+shared_examples 'thread comments' do |resource_name|
let(:form_selector) { '.js-main-target-form' }
let(:dropdown_selector) { "#{form_selector} .comment-type-dropdown" }
let(:toggle_selector) { "#{dropdown_selector} .dropdown-toggle" }
@@ -48,7 +50,7 @@ shared_examples 'discussion comments' do |resource_name|
find(toggle_selector).click
end
- it 'has a "Comment" item (selected by default) and "Start discussion" item' do
+ it 'has a "Comment" item (selected by default) and "Start thread" item' do
expect(page).to have_selector menu_selector
find("#{menu_selector} li", match: :first)
@@ -59,7 +61,7 @@ shared_examples 'discussion comments' do |resource_name|
expect(items.first).to have_selector '.fa-check'
expect(items.first['class']).to match 'droplab-item-selected'
- expect(items.last).to have_content 'Start discussion'
+ expect(items.last).to have_content 'Start thread'
expect(items.last).to have_content "Discuss a specific suggestion or question#{' that needs to be resolved' if resource_name == 'merge request'}."
expect(items.last).not_to have_selector '.fa-check'
expect(items.last['class']).not_to match 'droplab-item-selected'
@@ -103,7 +105,7 @@ shared_examples 'discussion comments' do |resource_name|
expect(find(dropdown_selector)).to have_content 'Comment'
end
- describe 'when selecting "Start discussion"' do
+ describe 'when selecting "Start thread"' do
before do
find("#{menu_selector} li", match: :first)
all("#{menu_selector} li").last.click
@@ -114,9 +116,9 @@ shared_examples 'discussion comments' do |resource_name|
# on issues page, the submit input is a <button>, on other pages it is <input>
if button.tag_name == 'button'
- expect(find(submit_selector)).to have_content 'Start discussion'
+ expect(find(submit_selector)).to have_content 'Start thread'
else
- expect(find(submit_selector).value).to eq 'Start discussion'
+ expect(find(submit_selector).value).to eq 'Start thread'
end
expect(page).not_to have_selector menu_selector
@@ -124,17 +126,17 @@ shared_examples 'discussion comments' do |resource_name|
if resource_name =~ /(issue|merge request)/
it 'updates the close button text' do
- expect(find(close_selector)).to have_content "Start discussion & close #{resource_name}"
+ expect(find(close_selector)).to have_content "Start thread & close #{resource_name}"
end
it 'typing does not change the close button text' do
find("#{form_selector} .note-textarea").send_keys('b')
- expect(find(close_selector)).to have_content "Start discussion & close #{resource_name}"
+ expect(find(close_selector)).to have_content "Start thread & close #{resource_name}"
end
end
- describe 'creating a discussion' do
+ describe 'creating a thread' do
before do
find(submit_selector).click
wait_for_requests
@@ -150,7 +152,7 @@ shared_examples 'discussion comments' do |resource_name|
wait_for_requests
end
- it 'clicking "Start discussion" will post a discussion' do
+ it 'clicking "Start thread" will post a thread' do
new_comment = all(comments_selector).last
expect(new_comment).to have_content 'a'
@@ -176,10 +178,10 @@ shared_examples 'discussion comments' do |resource_name|
if resource_name == 'merge request'
let(:note_id) { find("#{comments_selector} .note:first-child", match: :first)['data-note-id'] }
- let(:reply_id) { find("#{comments_selector} .note:last-child", match: :first)['data-note-id'] }
+ let(:reply_id) { find("#{comments_selector} .note:last-of-type", match: :first)['data-note-id'] }
it 'can be replied to after resolving' do
- click_button "Resolve discussion"
+ click_button "Resolve thread"
wait_for_requests
refresh
@@ -188,10 +190,10 @@ shared_examples 'discussion comments' do |resource_name|
submit_reply('to reply or not reply')
end
- it 'shows resolved discussion when toggled' do
+ it 'shows resolved thread when toggled' do
submit_reply('a')
- click_button "Resolve discussion"
+ click_button "Resolve thread"
wait_for_requests
expect(page).to have_selector(".note-row-#{note_id}", visible: true)
@@ -205,7 +207,7 @@ shared_examples 'discussion comments' do |resource_name|
end
if resource_name == 'issue'
- it "clicking 'Start discussion & close #{resource_name}' will post a discussion and close the #{resource_name}" do
+ it "clicking 'Start thread & close #{resource_name}' will post a thread and close the #{resource_name}" do
find(close_selector).click
find(comments_selector, match: :first)
@@ -224,7 +226,7 @@ shared_examples 'discussion comments' do |resource_name|
find(toggle_selector).click
end
- it 'has "Start discussion" selected' do
+ it 'has "Start thread" selected' do
find("#{menu_selector} li", match: :first)
items = all("#{menu_selector} li")
@@ -232,7 +234,7 @@ shared_examples 'discussion comments' do |resource_name|
expect(items.first).not_to have_selector '.fa-check'
expect(items.first['class']).not_to match 'droplab-item-selected'
- expect(items.last).to have_content 'Start discussion'
+ expect(items.last).to have_content 'Start thread'
expect(items.last).to have_selector '.fa-check'
expect(items.last['class']).to match 'droplab-item-selected'
end
@@ -277,7 +279,7 @@ shared_examples 'discussion comments' do |resource_name|
expect(items.first).to have_selector '.fa-check'
expect(items.first['class']).to match 'droplab-item-selected'
- expect(items.last).to have_content 'Start discussion'
+ expect(items.last).to have_content 'Start thread'
expect(items.last).not_to have_selector '.fa-check'
expect(items.last['class']).not_to match 'droplab-item-selected'
end
@@ -299,13 +301,13 @@ shared_examples 'discussion comments' do |resource_name|
expect(find("#{form_selector} .js-note-target-reopen")).to have_content "Comment & reopen #{resource_name}"
end
- it "should show a 'Start discussion & reopen #{resource_name}' button when 'Start discussion' is selected" do
+ it "should show a 'Start thread & reopen #{resource_name}' button when 'Start thread' is selected" do
find(toggle_selector).click
find("#{menu_selector} li", match: :first)
all("#{menu_selector} li").last.click
- expect(find("#{form_selector} .js-note-target-reopen")).to have_content "Start discussion & reopen #{resource_name}"
+ expect(find("#{form_selector} .js-note-target-reopen")).to have_content "Start thread & reopen #{resource_name}"
end
end
end
diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb
index 5d5a0a7b5d2..2f9208e6ed5 100644
--- a/spec/support/features/reportable_note_shared_examples.rb
+++ b/spec/support/features/reportable_note_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'reportable note' do |type|
diff --git a/spec/support/features/resolving_discussions_in_issues_shared_examples.rb b/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
index 38e5fb155a4..d4f8a87d0d8 100644
--- a/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
+++ b/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
@@ -1,4 +1,6 @@
-shared_examples 'creating an issue for a discussion' do
+# frozen_string_literal: true
+
+shared_examples 'creating an issue for a thread' do
it 'shows an issue with the title filled in' do
title_field = page.find_field('issue[title]')
diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb
index 0de92aedba5..c97eeba87db 100644
--- a/spec/support/features/rss_shared_examples.rb
+++ b/spec/support/features/rss_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples "an autodiscoverable RSS feed with current_user's feed token" do
it "has an RSS autodiscovery link tag with current_user's feed token" do
expect(page).to have_css("link[type*='atom+xml'][href*='feed_token=#{user.feed_token}']", visible: false)
diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb
index 01531864c1f..0f8ad2c6536 100644
--- a/spec/support/features/variable_list_shared_examples.rb
+++ b/spec/support/features/variable_list_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'variable list' do
it 'shows list of variables' do
page.within('.js-ci-variable-list-section') do
diff --git a/spec/support/forgery_protection.rb b/spec/support/forgery_protection.rb
index fa87d5fa881..1d6ea013292 100644
--- a/spec/support/forgery_protection.rb
+++ b/spec/support/forgery_protection.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ForgeryProtection
def with_forgery_protection
ActionController::Base.allow_forgery_protection = true
diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb
index 2fdbddd40c2..a1328ef0d13 100644
--- a/spec/support/google_api/cloud_platform_helpers.rb
+++ b/spec/support/google_api/cloud_platform_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module GoogleApi
module CloudPlatformHelpers
def stub_google_api_validate_token
diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb
index 4a9ce9beb78..aff0f87b6e4 100644
--- a/spec/support/helpers/api_helpers.rb
+++ b/spec/support/helpers/api_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ApiHelpers
# Public: Prepend a request path with the path to the API
#
@@ -30,11 +32,12 @@ module ApiHelpers
end
if query_string
- full_path << (path.index('?') ? '&' : '?')
- full_path << query_string
- end
+ separator = path.index('?') ? '&' : '?'
- full_path
+ full_path + separator + query_string
+ else
+ full_path
+ end
end
def expect_paginated_array_response(items)
diff --git a/spec/support/helpers/assets_helpers.rb b/spec/support/helpers/assets_helpers.rb
index 09bbf451671..fa24ad9ad2a 100644
--- a/spec/support/helpers/assets_helpers.rb
+++ b/spec/support/helpers/assets_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module AssetsHelpers
# In a CI environment the assets are not compiled, as there is a CI job
# `compile-assets` that compiles them in the prepare stage for all following
diff --git a/spec/support/helpers/bare_repo_operations.rb b/spec/support/helpers/bare_repo_operations.rb
index 3f4a4243cb6..099610f087d 100644
--- a/spec/support/helpers/bare_repo_operations.rb
+++ b/spec/support/helpers/bare_repo_operations.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'zlib'
class BareRepoOperations
diff --git a/spec/support/helpers/board_helpers.rb b/spec/support/helpers/board_helpers.rb
index b85fde222ea..683ee3e4bf2 100644
--- a/spec/support/helpers/board_helpers.rb
+++ b/spec/support/helpers/board_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module BoardHelpers
def click_card(card)
within card do
diff --git a/spec/support/helpers/capybara_helpers.rb b/spec/support/helpers/capybara_helpers.rb
index bcc2df44708..5abbc1e2951 100644
--- a/spec/support/helpers/capybara_helpers.rb
+++ b/spec/support/helpers/capybara_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CapybaraHelpers
# Execute a block a certain number of times before considering it a failure
#
diff --git a/spec/support/helpers/ci_artifact_metadata_generator.rb b/spec/support/helpers/ci_artifact_metadata_generator.rb
index ef638d59d2d..e02501565a9 100644
--- a/spec/support/helpers/ci_artifact_metadata_generator.rb
+++ b/spec/support/helpers/ci_artifact_metadata_generator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# frozen_sting_literal: true
# This generates fake CI metadata .gz for testing
diff --git a/spec/support/helpers/cookie_helper.rb b/spec/support/helpers/cookie_helper.rb
index 5ff7b0b68c9..ea4be12355b 100644
--- a/spec/support/helpers/cookie_helper.rb
+++ b/spec/support/helpers/cookie_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Helper for setting cookies in Selenium/WebDriver
#
module CookieHelper
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index 100e439ef44..575b2e779c5 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CycleAnalyticsHelpers
include GitHelpers
diff --git a/spec/support/helpers/database_connection_helpers.rb b/spec/support/helpers/database_connection_helpers.rb
index 763329499f0..10ea7b5de91 100644
--- a/spec/support/helpers/database_connection_helpers.rb
+++ b/spec/support/helpers/database_connection_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DatabaseConnectionHelpers
def run_with_new_database_connection
pool = ActiveRecord::Base.connection_pool
diff --git a/spec/support/helpers/devise_helpers.rb b/spec/support/helpers/devise_helpers.rb
index d32bc2424c0..70fc6a48414 100644
--- a/spec/support/helpers/devise_helpers.rb
+++ b/spec/support/helpers/devise_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DeviseHelpers
# explicitly tells Devise which mapping to use
# this is needed when we are testing a Devise controller bypassing the router
@@ -21,4 +23,16 @@ module DeviseHelpers
context.env
end
end
+
+ def with_omniauth_full_host(&block)
+ # The OmniAuth `full_host` parameter doesn't get set correctly (it gets set to something like `http://localhost`
+ # here), and causes integration tests to fail with 404s. We set the `full_host` by removing the request path (and
+ # anything after it) from the request URI.
+ omniauth_config_full_host = OmniAuth.config.full_host
+ OmniAuth.config.full_host = ->(request) { ActionDispatch::Request.new(request).base_url }
+
+ yield
+
+ OmniAuth.config.full_host = omniauth_config_full_host
+ end
end
diff --git a/spec/support/helpers/drag_to_helper.rb b/spec/support/helpers/drag_to_helper.rb
index 6d53ad0b602..2e9932f2e8a 100644
--- a/spec/support/helpers/drag_to_helper.rb
+++ b/spec/support/helpers/drag_to_helper.rb
@@ -1,6 +1,23 @@
+# frozen_string_literal: true
+
module DragTo
- def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body', duration: 1000)
- evaluate_script("simulateDrag({scrollable: $('#{scrollable}').get(0), duration: #{duration}, from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{from_index}}, to: {el: $('#{selector}').eq(#{list_to_index}).get(0), index: #{to_index}}});")
+ def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body', duration: 1000, perform_drop: true)
+ js = <<~JS
+ simulateDrag({
+ scrollable: document.querySelector('#{scrollable}'),
+ duration: #{duration},
+ from: {
+ el: document.querySelectorAll('#{selector}')[#{list_from_index}],
+ index: #{from_index}
+ },
+ to: {
+ el: document.querySelectorAll('#{selector}')[#{list_to_index}],
+ index: #{to_index}
+ },
+ performDrop: #{perform_drop}
+ });
+ JS
+ evaluate_script(js)
Timeout.timeout(Capybara.default_max_wait_time) do
loop while drag_active?
diff --git a/spec/support/helpers/dropzone_helper.rb b/spec/support/helpers/dropzone_helper.rb
index fe72d320fcf..a0f261b312e 100644
--- a/spec/support/helpers/dropzone_helper.rb
+++ b/spec/support/helpers/dropzone_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DropzoneHelper
# Provides a way to perform `attach_file` for a Dropzone-based file input
#
diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb
index a7175491fa0..024340310a1 100644
--- a/spec/support/helpers/email_helpers.rb
+++ b/spec/support/helpers/email_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module EmailHelpers
def sent_to_user(user, recipients: email_recipients)
recipients.count { |to| to == user.notification_email }
@@ -29,6 +31,10 @@ module EmailHelpers
expect(ActionMailer::Base.deliveries).to be_empty
end
+ def should_email_anyone
+ expect(ActionMailer::Base.deliveries).not_to be_empty
+ end
+
def email_recipients(kind: :to)
ActionMailer::Base.deliveries.flat_map(&kind)
end
@@ -37,19 +43,8 @@ module EmailHelpers
ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email) }
end
- def have_referable_subject(referable, include_project: true, include_group: false, reply: false)
- context = []
-
- context << referable.project.name if include_project && referable.project
- context << referable.project.group.name if include_group && referable.project.group
-
- prefix =
- if context.any?
- context.join(' | ') + ' | '
- else
- ''
- end
-
+ def have_referable_subject(referable, include_project: true, reply: false)
+ prefix = (include_project && referable.project ? "#{referable.project.name} | " : '').freeze
prefix = "Re: #{prefix}" if reply
suffix = "#{referable.title} (#{referable.to_reference})"
diff --git a/spec/support/helpers/exclusive_lease_helpers.rb b/spec/support/helpers/exclusive_lease_helpers.rb
index 383cc7dee81..77703e20602 100644
--- a/spec/support/helpers/exclusive_lease_helpers.rb
+++ b/spec/support/helpers/exclusive_lease_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExclusiveLeaseHelpers
def stub_exclusive_lease(key = nil, uuid = 'uuid', renew: false, timeout: nil)
key ||= instance_of(String)
diff --git a/spec/support/helpers/expect_next_instance_of.rb b/spec/support/helpers/expect_next_instance_of.rb
index b95046b2b42..749d2cb2a56 100644
--- a/spec/support/helpers/expect_next_instance_of.rb
+++ b/spec/support/helpers/expect_next_instance_of.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExpectNextInstanceOf
def expect_next_instance_of(klass, *new_args)
receive_new = receive(:new)
diff --git a/spec/support/helpers/expect_offense.rb b/spec/support/helpers/expect_offense.rb
index 35718ba90c5..76301fe19ff 100644
--- a/spec/support/helpers/expect_offense.rb
+++ b/spec/support/helpers/expect_offense.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rubocop/rspec/support'
# https://github.com/backus/rubocop-rspec/blob/master/spec/support/expect_offense.rb
diff --git a/spec/support/helpers/expect_request_with_status.rb b/spec/support/helpers/expect_request_with_status.rb
new file mode 100644
index 00000000000..0469a94e336
--- /dev/null
+++ b/spec/support/helpers/expect_request_with_status.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module ExpectRequestWithStatus
+ def expect_request_with_status(status)
+ expect do
+ yield
+
+ expect(response).to have_gitlab_http_status(status)
+ end
+ end
+end
diff --git a/spec/support/helpers/fake_blob_helpers.rb b/spec/support/helpers/fake_blob_helpers.rb
index 801ca8b7412..ef4740638ff 100644
--- a/spec/support/helpers/fake_blob_helpers.rb
+++ b/spec/support/helpers/fake_blob_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FakeBlobHelpers
class FakeBlob
include BlobLike
diff --git a/spec/support/helpers/fake_migration_classes.rb b/spec/support/helpers/fake_migration_classes.rb
index c7766df7a52..6c066b3b199 100644
--- a/spec/support/helpers/fake_migration_classes.rb
+++ b/spec/support/helpers/fake_migration_classes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration[4.2]
include Gitlab::Database::RenameReservedPathsMigration::V1
diff --git a/spec/support/helpers/fake_u2f_device.rb b/spec/support/helpers/fake_u2f_device.rb
index a7605cd483a..f765b277175 100644
--- a/spec/support/helpers/fake_u2f_device.rb
+++ b/spec/support/helpers/fake_u2f_device.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class FakeU2fDevice
attr_reader :name
@@ -32,6 +34,10 @@ class FakeU2fDevice
")
end
+ def fake_u2f_authentication
+ @page.execute_script("window.gl.u2fAuthenticate.renderAuthenticated('abc');")
+ end
+
private
def u2f_device(app_id)
diff --git a/spec/support/helpers/features/branches_helpers.rb b/spec/support/helpers/features/branches_helpers.rb
index df88fd425c9..2a50b41cb4e 100644
--- a/spec/support/helpers/features/branches_helpers.rb
+++ b/spec/support/helpers/features/branches_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These helpers allow you to manipulate with sorting features.
#
# Usage:
diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb
index 8a139fafac2..8c27f81930d 100644
--- a/spec/support/helpers/features/notes_helpers.rb
+++ b/spec/support/helpers/features/notes_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These helpers allow you to manipulate with notes.
#
# Usage:
@@ -21,6 +23,14 @@ module Spec
end
end
+ def edit_note(note_text_to_edit, new_note_text)
+ page.within('#notes-list li.note', text: note_text_to_edit) do
+ find('.js-note-edit').click
+ fill_in('note[note]', with: new_note_text)
+ find('.js-comment-button').click
+ end
+ end
+
def preview_note(text)
page.within('.js-main-target-form') do
filled_text = fill_in('note[note]', with: text)
diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb
index 003ecb251fe..a6428bf8573 100644
--- a/spec/support/helpers/features/sorting_helpers.rb
+++ b/spec/support/helpers/features/sorting_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These helpers allow you to manipulate with sorting features.
#
# Usage:
diff --git a/spec/support/helpers/filter_spec_helper.rb b/spec/support/helpers/filter_spec_helper.rb
index 721d359c2ee..95c24d76dcd 100644
--- a/spec/support/helpers/filter_spec_helper.rb
+++ b/spec/support/helpers/filter_spec_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Helper methods for Banzai filter specs
#
# Must be included into specs manually
diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb
index 34ef185ea27..39c818b1763 100644
--- a/spec/support/helpers/filtered_search_helpers.rb
+++ b/spec/support/helpers/filtered_search_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FilteredSearchHelpers
def filtered_search
page.find('.filtered-search')
diff --git a/spec/support/helpers/fixture_helpers.rb b/spec/support/helpers/fixture_helpers.rb
index 611d19f36a0..7b3b8ae5f7a 100644
--- a/spec/support/helpers/fixture_helpers.rb
+++ b/spec/support/helpers/fixture_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FixtureHelpers
def fixture_file(filename, dir: '')
return '' if filename.blank?
diff --git a/spec/support/helpers/git_http_helpers.rb b/spec/support/helpers/git_http_helpers.rb
index cd49bb148f2..de8bb9ac8e3 100644
--- a/spec/support/helpers/git_http_helpers.rb
+++ b/spec/support/helpers/git_http_helpers.rb
@@ -1,4 +1,10 @@
+# frozen_string_literal: true
+
+require_relative 'workhorse_helpers'
+
module GitHttpHelpers
+ include WorkhorseHelpers
+
def clone_get(project, options = {})
get "/#{project}/info/refs", params: { service: 'git-upload-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
diff --git a/spec/support/helpers/gitlab_verify_helpers.rb b/spec/support/helpers/gitlab_verify_helpers.rb
index 5df4bf24ec2..9901ce374ed 100644
--- a/spec/support/helpers/gitlab_verify_helpers.rb
+++ b/spec/support/helpers/gitlab_verify_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module GitlabVerifyHelpers
def collect_ranges(args = {})
verifier = described_class.new(args.merge(batch_size: 1))
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 1a09d48f4cd..d86371d70b9 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
module GraphqlHelpers
MutationDefinition = Struct.new(:query, :variables)
+ NoData = Class.new(StandardError)
+
# makes an underscored string look like a fieldname
# "merge_request" => "mergeRequest"
def self.fieldnamerize(underscored_field_name)
@@ -57,7 +61,8 @@ module GraphqlHelpers
end
def variables_for_mutation(name, input)
- graphql_input = input.map { |name, value| [GraphqlHelpers.fieldnamerize(name), value] }.to_h
+ graphql_input = prepare_input_for_mutation(input)
+
result = { input_variable_name_for_mutation(name) => graphql_input }
# Avoid trying to serialize multipart data into JSON
@@ -68,6 +73,18 @@ module GraphqlHelpers
end
end
+ # Recursively convert a Hash with Ruby-style keys to GraphQL fieldname-style keys
+ #
+ # prepare_input_for_mutation({ 'my_key' => 1 })
+ # => { 'myKey' => 1}
+ def prepare_input_for_mutation(input)
+ input.map do |name, value|
+ value = prepare_input_for_mutation(value) if value.is_a?(Hash)
+
+ [GraphqlHelpers.fieldnamerize(name), value]
+ end.to_h
+ end
+
def input_variable_name_for_mutation(mutation_name)
mutation_name = GraphqlHelpers.fieldnamerize(mutation_name)
mutation_field = GitlabSchema.mutation.fields[mutation_name]
@@ -143,8 +160,9 @@ module GraphqlHelpers
post_graphql(mutation.query, current_user: current_user, variables: mutation.variables)
end
+ # Raises an error if no data is found
def graphql_data
- json_response['data']
+ json_response['data'] || (raise NoData, graphql_errors)
end
def graphql_errors
@@ -158,8 +176,9 @@ module GraphqlHelpers
end
end
+ # Raises an error if no response is found
def graphql_mutation_response(mutation_name)
- graphql_data[GraphqlHelpers.fieldnamerize(mutation_name)]
+ graphql_data.fetch(GraphqlHelpers.fieldnamerize(mutation_name))
end
def nested_fields?(field)
diff --git a/spec/support/helpers/import_spec_helper.rb b/spec/support/helpers/import_spec_helper.rb
index d4eced724fa..d8fb2ba08af 100644
--- a/spec/support/helpers/import_spec_helper.rb
+++ b/spec/support/helpers/import_spec_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'ostruct'
# Helper methods for controller specs in the Import namespace
diff --git a/spec/support/helpers/input_helper.rb b/spec/support/helpers/input_helper.rb
index acbb42274ec..5136f8e9cb8 100644
--- a/spec/support/helpers/input_helper.rb
+++ b/spec/support/helpers/input_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# see app/assets/javascripts/test_utils/simulate_input.js
module InputHelper
diff --git a/spec/support/helpers/inspect_requests.rb b/spec/support/helpers/inspect_requests.rb
index 88ddc5c7f6c..4a1d9cb8539 100644
--- a/spec/support/helpers/inspect_requests.rb
+++ b/spec/support/helpers/inspect_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative './wait_for_requests'
module InspectRequests
diff --git a/spec/support/helpers/issue_helpers.rb b/spec/support/helpers/issue_helpers.rb
index ffd72515f37..82b954a92e2 100644
--- a/spec/support/helpers/issue_helpers.rb
+++ b/spec/support/helpers/issue_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module IssueHelpers
def visit_issues(project, opts = {})
visit project_issues_path project, opts
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index cdd7724cc13..7ec65318ec5 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'action_dispatch/testing/test_request'
require 'fileutils'
@@ -19,7 +21,7 @@ module JavaScriptFixturesHelpers
end
def fixture_root_path
- (Gitlab.ee? ? 'ee/' : '') + 'spec/javascripts/fixtures'
+ 'tmp/tests/frontend/fixtures' + (Gitlab.ee? ? '-ee' : '')
end
# Public: Removes all fixture files from given directory
diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb
index 7e955f3d593..57c33c81ea3 100644
--- a/spec/support/helpers/jira_service_helper.rb
+++ b/spec/support/helpers/jira_service_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module JiraServiceHelper
JIRA_URL = "http://jira.example.net".freeze
JIRA_API = JIRA_URL + "/rest/api/2"
diff --git a/spec/support/helpers/key_generator_helper.rb b/spec/support/helpers/key_generator_helper.rb
index d55d8312c65..59c8eeb3692 100644
--- a/spec/support/helpers/key_generator_helper.rb
+++ b/spec/support/helpers/key_generator_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Spec
module Support
module Helpers
@@ -33,7 +35,7 @@ module Spec
# Packs string components into an openssh-encoded pubkey.
def pack_pubkey_components(strings)
- (strings.map { |s| [s.length].pack('N') }).zip(strings).flatten.join
+ (strings.flat_map { |s| [s.length].pack('N') }).zip(strings).join
end
end
end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 3c7bcba2b42..538a5b8ef3c 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module KubernetesHelpers
include Gitlab::Kubernetes
@@ -199,6 +201,11 @@ module KubernetesHelpers
.to_return(kube_response({}))
end
+ def stub_kubeclient_put_role(api_url, name, namespace: 'default')
+ WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{name}")
+ .to_return(kube_response({}))
+ end
+
def kube_v1_secret_body(**options)
{
"kind" => "SecretList",
diff --git a/spec/support/helpers/ldap_helpers.rb b/spec/support/helpers/ldap_helpers.rb
index 66ca5d7f0a3..dce8a3803f5 100644
--- a/spec/support/helpers/ldap_helpers.rb
+++ b/spec/support/helpers/ldap_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module LdapHelpers
def ldap_adapter(provider = 'ldapmain', ldap = double(:ldap))
::Gitlab::Auth::LDAP::Adapter.new(provider, ldap)
diff --git a/spec/support/helpers/live_debugger.rb b/spec/support/helpers/live_debugger.rb
index 911eb48a8ca..d6091035b59 100644
--- a/spec/support/helpers/live_debugger.rb
+++ b/spec/support/helpers/live_debugger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'io/console'
module LiveDebugger
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index 0bb2d2510c2..2b508ee6f2c 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative 'devise_helpers'
module LoginHelpers
@@ -87,12 +89,17 @@ module LoginHelpers
click_link "oauth-login-#{provider}"
end
+ def fake_successful_u2f_authentication
+ allow(U2fRegistration).to receive(:authenticate).and_return(true)
+ FakeU2fDevice.new(page, nil).fake_u2f_authentication
+ end
+
def mock_auth_hash_with_saml_xml(provider, uid, email, saml_response)
response_object = { document: saml_xml(saml_response) }
mock_auth_hash(provider, uid, email, response_object: response_object)
end
- def mock_auth_hash(provider, uid, email, response_object: nil)
+ def configure_mock_auth(provider, uid, email, response_object: nil)
# The mock_auth configuration allows you to set per-provider (or default)
# authentication hashes to return during integration testing.
OmniAuth.config.mock_auth[provider.to_sym] = OmniAuth::AuthHash.new({
@@ -118,6 +125,11 @@ module LoginHelpers
response_object: response_object
}
})
+ end
+
+ def mock_auth_hash(provider, uid, email, response_object: nil)
+ configure_mock_auth(provider, uid, email, response_object: response_object)
+
original_env_config_omniauth_auth = Rails.application.env_config['omniauth.auth']
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider.to_sym]
diff --git a/spec/support/helpers/markdown_feature.rb b/spec/support/helpers/markdown_feature.rb
index 96401379cf0..eea03fb9325 100644
--- a/spec/support/helpers/markdown_feature.rb
+++ b/spec/support/helpers/markdown_feature.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This is a helper class used by the GitLab Markdown feature spec
#
# Because the feature spec only cares about the output of the Markdown, and the
diff --git a/spec/support/helpers/memory_usage_helper.rb b/spec/support/helpers/memory_usage_helper.rb
new file mode 100644
index 00000000000..984ea8cc571
--- /dev/null
+++ b/spec/support/helpers/memory_usage_helper.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module MemoryUsageHelper
+ extend ActiveSupport::Concern
+
+ def gather_memory_data(csv_path)
+ write_csv_entry(csv_path,
+ {
+ example_group_path: TestEnv.topmost_example_group[:location],
+ example_group_description: TestEnv.topmost_example_group[:description],
+ time: Time.current,
+ job_name: ENV['CI_JOB_NAME']
+ }.merge(get_memory_usage))
+ end
+
+ def write_csv_entry(path, entry)
+ CSV.open(path, "a", headers: entry.keys, write_headers: !File.exist?(path)) do |file|
+ file << entry.values
+ end
+ end
+
+ def get_memory_usage
+ output, status = Gitlab::Popen.popen(%w(free -m))
+ abort "`free -m` return code is #{status}: #{output}" unless status.zero?
+
+ result = output.split("\n")[1].split(" ")[1..-1]
+ attrs = %i(m_total m_used m_free m_shared m_buffers_cache m_available).freeze
+
+ attrs.zip(result).to_h
+ end
+
+ included do |config|
+ config.after(:all) do
+ gather_memory_data(ENV['MEMORY_TEST_PATH']) if ENV['MEMORY_TEST_PATH']
+ end
+ end
+end
diff --git a/spec/support/helpers/merge_request_diff_helpers.rb b/spec/support/helpers/merge_request_diff_helpers.rb
index 3b49d0b3319..49beecc6d4b 100644
--- a/spec/support/helpers/merge_request_diff_helpers.rb
+++ b/spec/support/helpers/merge_request_diff_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MergeRequestDiffHelpers
def click_diff_line(line_holder, diff_side = nil)
line = get_line_components(line_holder, diff_side)
diff --git a/spec/support/helpers/merge_request_helpers.rb b/spec/support/helpers/merge_request_helpers.rb
index 772adff4626..3c359bc9353 100644
--- a/spec/support/helpers/merge_request_helpers.rb
+++ b/spec/support/helpers/merge_request_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MergeRequestHelpers
def visit_merge_requests(project, opts = {})
visit project_merge_requests_path project, opts
diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb
index 1511a2f6b49..0e86b6dfda7 100644
--- a/spec/support/helpers/metrics_dashboard_helpers.rb
+++ b/spec/support/helpers/metrics_dashboard_helpers.rb
@@ -18,6 +18,14 @@ module MetricsDashboardHelpers
project.repository.refresh_method_caches([:metrics_dashboard])
end
+ def system_dashboard_path
+ Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH
+ end
+
+ def business_metric_title
+ PrometheusMetricEnums.group_details[:business][:group_title]
+ end
+
shared_examples_for 'misconfigured dashboard service response' do |status_code|
it 'returns an appropriate message and status code' do
result = service_call
diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb
index cc1a28cb264..2727ab7fb1e 100644
--- a/spec/support/helpers/migrations_helpers.rb
+++ b/spec/support/helpers/migrations_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MigrationsHelpers
def active_record_base
ActiveRecord::Base
@@ -18,8 +20,12 @@ module MigrationsHelpers
ActiveRecord::Migrator.migrations_paths
end
+ def migration_context
+ ActiveRecord::MigrationContext.new(migrations_paths)
+ end
+
def migrations
- ActiveRecord::Migrator.migrations(migrations_paths)
+ migration_context.migrations
end
def clear_schema_cache!
@@ -96,8 +102,7 @@ module MigrationsHelpers
def schema_migrate_down!
disable_migrations_output do
- ActiveRecord::Migrator.migrate(migrations_paths,
- migration_schema_version)
+ migration_context.down(migration_schema_version)
end
reset_column_in_all_models
@@ -107,7 +112,7 @@ module MigrationsHelpers
reset_column_in_all_models
disable_migrations_output do
- ActiveRecord::Migrator.migrate(migrations_paths)
+ migration_context.up
end
reset_column_in_all_models
@@ -123,7 +128,7 @@ module MigrationsHelpers
end
def migrate!
- ActiveRecord::Migrator.up(migrations_paths) do |migration|
+ migration_context.up do |migration|
migration.name == described_class.name
end
end
diff --git a/spec/support/helpers/mobile_helpers.rb b/spec/support/helpers/mobile_helpers.rb
index 4230d315d9b..94dbd2fb1b7 100644
--- a/spec/support/helpers/mobile_helpers.rb
+++ b/spec/support/helpers/mobile_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MobileHelpers
def resize_screen_xs
resize_window(575, 768)
diff --git a/spec/support/helpers/note_interaction_helpers.rb b/spec/support/helpers/note_interaction_helpers.rb
index 79a0aa174b1..a4322618cd3 100644
--- a/spec/support/helpers/note_interaction_helpers.rb
+++ b/spec/support/helpers/note_interaction_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module NoteInteractionHelpers
def open_more_actions_dropdown(note)
note_element = find("#note_#{note.id}")
diff --git a/spec/support/helpers/notification_helpers.rb b/spec/support/helpers/notification_helpers.rb
index 44c2051598c..16ecb338f6e 100644
--- a/spec/support/helpers/notification_helpers.rb
+++ b/spec/support/helpers/notification_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module NotificationHelpers
extend self
diff --git a/spec/support/helpers/position_tracer_helpers.rb b/spec/support/helpers/position_tracer_helpers.rb
new file mode 100644
index 00000000000..bbf6e06dd40
--- /dev/null
+++ b/spec/support/helpers/position_tracer_helpers.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module PositionTracerHelpers
+ def diff_refs(base_commit, head_commit)
+ Gitlab::Diff::DiffRefs.new(base_sha: base_commit.id, head_sha: head_commit.id)
+ end
+
+ def position(attrs = {})
+ attrs.reverse_merge!(
+ diff_refs: old_diff_refs
+ )
+ Gitlab::Diff::Position.new(attrs)
+ end
+
+ def expect_new_position(attrs, result = subject)
+ aggregate_failures("expect new position #{attrs.inspect}") do
+ if attrs.nil?
+ expect(result[:outdated]).to be_truthy
+ else
+ new_position = result[:position]
+
+ expect(result[:outdated]).to be_falsey
+ expect(new_position).not_to be_nil
+ expect(new_position.diff_refs).to eq(new_diff_refs)
+
+ attrs.each do |attr, value|
+ expect(new_position.send(attr)).to eq(value)
+ end
+ end
+ end
+ end
+
+ def expect_change_position(attrs, result = subject)
+ aggregate_failures("expect change position #{attrs.inspect}") do
+ change_position = result[:position]
+
+ expect(result[:outdated]).to be_truthy
+
+ if attrs.nil? || attrs.empty?
+ expect(change_position).to be_nil
+ else
+ expect(change_position).not_to be_nil
+ expect(change_position.diff_refs).to eq(change_diff_refs)
+
+ attrs.each do |attr, value|
+ expect(change_position.send(attr)).to eq(value)
+ end
+ end
+ end
+ end
+
+ def create_branch(new_name, branch_name)
+ CreateBranchService.new(project, current_user).execute(new_name, branch_name)
+ end
+
+ def create_file(branch_name, file_name, content)
+ Files::CreateService.new(
+ project,
+ current_user,
+ start_branch: branch_name,
+ branch_name: branch_name,
+ commit_message: "Create file",
+ file_path: file_name,
+ file_content: content
+ ).execute
+ project.commit(branch_name)
+ end
+
+ def update_file(branch_name, file_name, content)
+ Files::UpdateService.new(
+ project,
+ current_user,
+ start_branch: branch_name,
+ branch_name: branch_name,
+ commit_message: "Update file",
+ file_path: file_name,
+ file_content: content
+ ).execute
+ project.commit(branch_name)
+ end
+
+ def delete_file(branch_name, file_name)
+ Files::DeleteService.new(
+ project,
+ current_user,
+ start_branch: branch_name,
+ branch_name: branch_name,
+ commit_message: "Delete file",
+ file_path: file_name
+ ).execute
+ project.commit(branch_name)
+ end
+end
diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb
index bcb11a09b36..b2d22853e4c 100644
--- a/spec/support/helpers/project_forks_helper.rb
+++ b/spec/support/helpers/project_forks_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ProjectForksHelper
def fork_project(project, user = nil, params = {})
Gitlab::GitalyClient.allow_n_plus_1_calls do
diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb
index db662836013..7c03746a395 100644
--- a/spec/support/helpers/prometheus_helpers.rb
+++ b/spec/support/helpers/prometheus_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module PrometheusHelpers
def prometheus_memory_query(environment_slug)
%{avg(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / 2^20}
@@ -74,6 +76,14 @@ module PrometheusHelpers
WebMock.stub_request(:any, /prometheus.example.com/)
end
+ def stub_any_prometheus_request_with_response(status: 200, headers: {}, body: nil)
+ stub_any_prometheus_request.to_return({
+ status: status,
+ headers: { 'Content-Type' => 'application/json' }.merge(headers),
+ body: body || prometheus_values_body.to_json
+ })
+ end
+
def stub_all_prometheus_requests(environment_slug, body: nil, status: 200)
stub_prometheus_request(
prometheus_query_with_time_url(prometheus_memory_query(environment_slug), Time.now.utc),
diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb
index f77b43391dd..d936dc6de41 100644
--- a/spec/support/helpers/query_recorder.rb
+++ b/spec/support/helpers/query_recorder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveRecord
class QueryRecorder
attr_reader :log, :skip_cached, :cached
diff --git a/spec/support/helpers/quick_actions_helpers.rb b/spec/support/helpers/quick_actions_helpers.rb
index 361190aa352..cb853f5363f 100644
--- a/spec/support/helpers/quick_actions_helpers.rb
+++ b/spec/support/helpers/quick_actions_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QuickActionsHelpers
def write_note(text)
Sidekiq::Testing.fake! do
diff --git a/spec/support/helpers/rake_helpers.rb b/spec/support/helpers/rake_helpers.rb
index 7d8d7750bf3..d8f354a69da 100644
--- a/spec/support/helpers/rake_helpers.rb
+++ b/spec/support/helpers/rake_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RakeHelpers
def run_rake_task(task_name, *args)
Rake::Task[task_name].reenable
diff --git a/spec/support/helpers/reactive_caching_helpers.rb b/spec/support/helpers/reactive_caching_helpers.rb
index b76b53db0b9..aa9d3b3a199 100644
--- a/spec/support/helpers/reactive_caching_helpers.rb
+++ b/spec/support/helpers/reactive_caching_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ReactiveCachingHelpers
def reactive_cache_key(subject, *qualifiers)
([subject.class.reactive_cache_key.call(subject)].flatten + qualifiers).join(':')
@@ -10,7 +12,7 @@ module ReactiveCachingHelpers
def stub_reactive_cache(subject = nil, data = nil, *qualifiers)
allow(ReactiveCachingWorker).to receive(:perform_async)
allow(ReactiveCachingWorker).to receive(:perform_in)
- write_reactive_cache(subject, data, *qualifiers) unless data.nil?
+ write_reactive_cache(subject, data, *qualifiers) unless subject.nil?
end
def synchronous_reactive_cache(subject)
diff --git a/spec/support/helpers/redis_without_keys.rb b/spec/support/helpers/redis_without_keys.rb
index 6220167dee6..e030f1028f7 100644
--- a/spec/support/helpers/redis_without_keys.rb
+++ b/spec/support/helpers/redis_without_keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Redis
ForbiddenCommand = Class.new(StandardError)
diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb
index 9f27502aa52..f96a01d15b5 100644
--- a/spec/support/helpers/reference_parser_helpers.rb
+++ b/spec/support/helpers/reference_parser_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ReferenceParserHelpers
def empty_html_link
Nokogiri::HTML.fragment('<a></a>').children[0]
diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb
index 44d95a029af..ca4d2acbf2c 100644
--- a/spec/support/helpers/repo_helpers.rb
+++ b/spec/support/helpers/repo_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RepoHelpers
extend self
diff --git a/spec/support/helpers/routes_helpers.rb b/spec/support/helpers/routes_helpers.rb
index c4129606418..2a7cd81cbe3 100644
--- a/spec/support/helpers/routes_helpers.rb
+++ b/spec/support/helpers/routes_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RoutesHelpers
def fake_routes(&block)
@routes = @routes.dup
diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb
index abbbb636d66..815337f8615 100644
--- a/spec/support/helpers/search_helpers.rb
+++ b/spec/support/helpers/search_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SearchHelpers
def select_filter(name)
find(:xpath, "//ul[contains(@class, 'search-filter')]//a[contains(.,'#{name}')]").click
diff --git a/spec/support/helpers/seed_repo.rb b/spec/support/helpers/seed_repo.rb
index 71f1a86b0c1..20738b45129 100644
--- a/spec/support/helpers/seed_repo.rb
+++ b/spec/support/helpers/seed_repo.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This file is generated by generate-seed-repo-rb. Do not edit this file manually.
#
# Seed repo:
diff --git a/spec/support/helpers/select2_helper.rb b/spec/support/helpers/select2_helper.rb
index 87672c8896d..9c42c2b0d8b 100644
--- a/spec/support/helpers/select2_helper.rb
+++ b/spec/support/helpers/select2_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative 'wait_for_requests'
# Select2 ajax programmatic helper
diff --git a/spec/support/helpers/selection_helper.rb b/spec/support/helpers/selection_helper.rb
index b4725b137b2..a5f9ca76f6e 100644
--- a/spec/support/helpers/selection_helper.rb
+++ b/spec/support/helpers/selection_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SelectionHelper
def select_element(selector)
find(selector)
diff --git a/spec/support/helpers/smime_helper.rb b/spec/support/helpers/smime_helper.rb
new file mode 100644
index 00000000000..656b3e196ba
--- /dev/null
+++ b/spec/support/helpers/smime_helper.rb
@@ -0,0 +1,55 @@
+module SmimeHelper
+ include OpenSSL
+
+ INFINITE_EXPIRY = 1000.years
+ SHORT_EXPIRY = 30.minutes
+
+ def generate_root
+ issue(signed_by: nil, expires_in: INFINITE_EXPIRY, certificate_authority: true)
+ end
+
+ def generate_cert(root_ca:, expires_in: SHORT_EXPIRY)
+ issue(signed_by: root_ca, expires_in: expires_in, certificate_authority: false)
+ end
+
+ # returns a hash { key:, cert: } containing a generated key, cert pair
+ def issue(email_address: 'test@example.com', signed_by:, expires_in:, certificate_authority:)
+ key = OpenSSL::PKey::RSA.new(4096)
+ public_key = key.public_key
+
+ subject = if certificate_authority
+ X509::Name.parse("/CN=EU")
+ else
+ X509::Name.parse("/CN=#{email_address}")
+ end
+
+ cert = X509::Certificate.new
+ cert.subject = subject
+
+ cert.issuer = signed_by&.fetch(:cert, nil)&.subject || subject
+
+ cert.not_before = Time.now
+ cert.not_after = expires_in.from_now
+ cert.public_key = public_key
+ cert.serial = 0x0
+ cert.version = 2
+
+ extension_factory = X509::ExtensionFactory.new
+ if certificate_authority
+ extension_factory.subject_certificate = cert
+ extension_factory.issuer_certificate = cert
+ cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
+ cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
+ cert.add_extension(extension_factory.create_extension('keyUsage', 'cRLSign,keyCertSign', true))
+ else
+ cert.add_extension(extension_factory.create_extension('subjectAltName', "email:#{email_address}", false))
+ cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE', true))
+ cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature,keyEncipherment', true))
+ cert.add_extension(extension_factory.create_extension('extendedKeyUsage', 'clientAuth,emailProtection', false))
+ end
+
+ cert.sign(signed_by&.fetch(:key, nil) || key, Digest::SHA256.new)
+
+ { key: key, cert: cert }
+ end
+end
diff --git a/spec/support/helpers/sorting_helper.rb b/spec/support/helpers/sorting_helper.rb
index e505a6b7258..3801d25fb63 100644
--- a/spec/support/helpers/sorting_helper.rb
+++ b/spec/support/helpers/sorting_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Helper allows you to sort items
#
# Params
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index c372a3f0e49..dec7898d8d2 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'active_support/core_ext/hash/transform_values'
require 'active_support/hash_with_indifferent_access'
require 'active_support/dependencies'
@@ -28,6 +30,10 @@ module StubConfiguration
allow(Gitlab.config.gitlab).to receive_messages(to_settings(messages))
end
+ def stub_config(messages)
+ allow(Gitlab.config).to receive_messages(to_settings(messages))
+ end
+
def stub_default_url_options(host: "localhost", protocol: "http")
url_options = { host: host, protocol: protocol }
allow(Rails.application.routes).to receive(:default_url_options).and_return(url_options)
@@ -65,6 +71,10 @@ module StubConfiguration
allow(Gitlab.config.artifacts).to receive_messages(to_settings(messages))
end
+ def stub_pages_setting(messages)
+ allow(Gitlab.config.pages).to receive_messages(to_settings(messages))
+ end
+
def stub_storage_settings(messages)
messages.deep_stringify_keys!
diff --git a/spec/support/helpers/stub_env.rb b/spec/support/helpers/stub_env.rb
index 1c2f474a015..8107ffc939f 100644
--- a/spec/support/helpers/stub_env.rb
+++ b/spec/support/helpers/stub_env.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
module StubENV
def stub_env(key_or_hash, value = nil)
diff --git a/spec/support/helpers/stub_feature_flags.rb b/spec/support/helpers/stub_feature_flags.rb
index 48258692304..6c3efff7262 100644
--- a/spec/support/helpers/stub_feature_flags.rb
+++ b/spec/support/helpers/stub_feature_flags.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubFeatureFlags
# Stub Feature flags with `flag_name: true/false`
#
diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb
index 4cb3b18df85..7d10cffe920 100644
--- a/spec/support/helpers/stub_gitlab_calls.rb
+++ b/spec/support/helpers/stub_gitlab_calls.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubGitlabCalls
def stub_gitlab_calls
stub_user
@@ -20,6 +22,10 @@ module StubGitlabCalls
allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file) { ci_yaml }
end
+ def stub_pipeline_modified_paths(pipeline, modified_paths)
+ allow(pipeline).to receive(:modified_paths).and_return(modified_paths)
+ end
+
def stub_repository_ci_yaml_file(sha:, path: '.gitlab-ci.yml')
allow_any_instance_of(Repository)
.to receive(:gitlab_ci_yml_for).with(sha, path)
diff --git a/spec/support/helpers/stub_gitlab_data.rb b/spec/support/helpers/stub_gitlab_data.rb
index fa402f35b95..ed518393c03 100644
--- a/spec/support/helpers/stub_gitlab_data.rb
+++ b/spec/support/helpers/stub_gitlab_data.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubGitlabData
def gitlab_ci_yaml
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
diff --git a/spec/support/helpers/stub_metrics.rb b/spec/support/helpers/stub_metrics.rb
index 64983fdf222..e347955efbb 100644
--- a/spec/support/helpers/stub_metrics.rb
+++ b/spec/support/helpers/stub_metrics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubMetrics
def authentication_metrics
Gitlab::Auth::Activity
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index d31f9908714..e5b8bb712bb 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubObjectStorage
def stub_object_storage_uploader(
config:,
diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb
index 5cad35282c0..473f07dd413 100644
--- a/spec/support/helpers/stub_requests.rb
+++ b/spec/support/helpers/stub_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubRequests
IP_ADDRESS_STUB = '8.8.8.9'.freeze
@@ -26,6 +28,19 @@ module StubRequests
.and_return([addr])
end
+ def stub_all_dns(url, ip_address:)
+ url = URI(url)
+ port = 80 # arbitarily chosen, does not matter as we are not going to connect
+ socket = Socket.sockaddr_in(port, ip_address)
+ addr = Addrinfo.new(socket)
+
+ # See Gitlab::UrlBlocker
+ allow(Addrinfo).to receive(:getaddrinfo).and_call_original
+ allow(Addrinfo).to receive(:getaddrinfo)
+ .with(url.hostname, anything, nil, :STREAM)
+ .and_return([addr])
+ end
+
def stubbed_hostname(url, hostname: IP_ADDRESS_STUB)
url = parse_url(url)
url.hostname = hostname
diff --git a/spec/support/helpers/stub_worker.rb b/spec/support/helpers/stub_worker.rb
index 58b7ee93dff..cac839ed5fe 100644
--- a/spec/support/helpers/stub_worker.rb
+++ b/spec/support/helpers/stub_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
module StubWorker
def stub_worker(queue:)
diff --git a/spec/support/helpers/terms_helper.rb b/spec/support/helpers/terms_helper.rb
index a00ec14138b..a61bae18f9a 100644
--- a/spec/support/helpers/terms_helper.rb
+++ b/spec/support/helpers/terms_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TermsHelper
def enforce_terms
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index e63099d89b7..a4acf76e1a3 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
require 'rspec/mocks'
require 'toml-rb'
module TestEnv
+ extend ActiveSupport::Concern
extend self
ComponentFailedToInstallError = Class.new(StandardError)
@@ -108,6 +111,12 @@ module TestEnv
setup_forked_repo
end
+ included do |config|
+ config.append_before do
+ set_current_example_group
+ end
+ end
+
def disable_mailer
allow_any_instance_of(NotificationService).to receive(:mailer)
.and_return(double.as_null_object)
@@ -123,7 +132,7 @@ module TestEnv
# Keeps gitlab-shell and gitlab-test
def clean_test_path
Dir[TMP_TEST_PATH].each do |entry|
- unless File.basename(entry) =~ /\A(gitaly|gitlab-(shell|test|test_bare|test-fork|test-fork_bare))\z/
+ unless test_dirs.include?(File.basename(entry))
FileUtils.rm_rf(entry)
end
end
@@ -134,14 +143,6 @@ module TestEnv
FileUtils.mkdir_p(artifacts_path)
end
- def clean_gitlab_test_path
- Dir[TMP_TEST_PATH].each do |entry|
- unless test_dirs.include?(File.basename(entry))
- FileUtils.rm_rf(entry)
- end
- end
- end
-
def setup_gitlab_shell
component_timed_setup('GitLab Shell',
install_dir: Gitlab.config.gitlab_shell.path,
@@ -297,11 +298,27 @@ module TestEnv
FileUtils.rm_rf(path)
end
+ def current_example_group
+ Thread.current[:current_example_group]
+ end
+
+ # looking for a top-level `describe`
+ def topmost_example_group
+ example_group = current_example_group
+ example_group = example_group[:parent_example_group] until example_group[:parent_example_group].nil?
+ example_group
+ end
+
private
+ def set_current_example_group
+ Thread.current[:current_example_group] = ::RSpec.current_example.metadata[:example_group]
+ end
+
# These are directories that should be preserved at cleanup time
def test_dirs
@test_dirs ||= %w[
+ frontend
gitaly
gitlab-shell
gitlab-test
@@ -346,10 +363,7 @@ module TestEnv
# Try to reset without fetching to avoid using the network.
unless reset.call
raise 'Could not fetch test seed repository.' unless system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} fetch origin))
-
- # Before we used Git clone's --mirror option, bare repos could end up
- # with missing refs, clearing them and retrying should fix the issue.
- clean_gitlab_test_path && init unless reset.call
+ raise "Could not update test seed repository, please delete #{repo_path} and try again" unless reset.call
end
end
diff --git a/spec/support/helpers/upload_helpers.rb b/spec/support/helpers/upload_helpers.rb
index 5eead80c935..60e14a8673b 100644
--- a/spec/support/helpers/upload_helpers.rb
+++ b/spec/support/helpers/upload_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fileutils'
module UploadHelpers
diff --git a/spec/support/helpers/wait_for_requests.rb b/spec/support/helpers/wait_for_requests.rb
index 45b9faa0fea..3bb2f7c5b51 100644
--- a/spec/support/helpers/wait_for_requests.rb
+++ b/spec/support/helpers/wait_for_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WaitForRequests
extend self
diff --git a/spec/support/helpers/wait_helpers.rb b/spec/support/helpers/wait_helpers.rb
index 7e8e25798e8..a8c4408db59 100644
--- a/spec/support/helpers/wait_helpers.rb
+++ b/spec/support/helpers/wait_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WaitHelpers
extend self
diff --git a/spec/support/helpers/wiki_helpers.rb b/spec/support/helpers/wiki_helpers.rb
index 8165403cb60..06cea728b42 100644
--- a/spec/support/helpers/wiki_helpers.rb
+++ b/spec/support/helpers/wiki_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WikiHelpers
extend self
diff --git a/spec/support/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb
index ef1f9f68671..4488e5f227e 100644
--- a/spec/support/helpers/workhorse_helpers.rb
+++ b/spec/support/helpers/workhorse_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WorkhorseHelpers
extend self
diff --git a/spec/support/http_io/http_io_helpers.rb b/spec/support/http_io/http_io_helpers.rb
index 42144870eb5..0193db81fa9 100644
--- a/spec/support/http_io/http_io_helpers.rb
+++ b/spec/support/http_io/http_io_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module HttpIOHelpers
def stub_remote_url_206(url, file_path)
WebMock.stub_request(:get, url)
diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb
index 2542a59bb00..ac6840dbcfc 100644
--- a/spec/support/import_export/common_util.rb
+++ b/spec/support/import_export/common_util.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ImportExport
module CommonUtil
def setup_symlink(tmpdir, symlink_name)
diff --git a/spec/support/import_export/configuration_helper.rb b/spec/support/import_export/configuration_helper.rb
index b4164cff922..122df7f27f0 100644
--- a/spec/support/import_export/configuration_helper.rb
+++ b/spec/support/import_export/configuration_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ConfigurationHelper
# Returns a list of models from hashes/arrays contained in +project_tree+
def names_from_tree(project_tree)
diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb
index 388b88f0331..f862a9bc1a4 100644
--- a/spec/support/import_export/export_file_helper.rb
+++ b/spec/support/import_export/export_file_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require './spec/support/import_export/configuration_helper'
module ExportFileHelper
diff --git a/spec/support/inspect_squelch.rb b/spec/support/inspect_squelch.rb
index 8ee6732370b..90475204889 100644
--- a/spec/support/inspect_squelch.rb
+++ b/spec/support/inspect_squelch.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This class can generate a lot of output if it fails,
# so squelch the instance variable output.
class ActiveSupport::Cache::NullStore
diff --git a/spec/support/issuables_requiring_filter_shared_examples.rb b/spec/support/issuables_requiring_filter_shared_examples.rb
index 71bcc82ee58..ee25df00dfb 100644
--- a/spec/support/issuables_requiring_filter_shared_examples.rb
+++ b/spec/support/issuables_requiring_filter_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuables requiring filter' do |action|
it "doesn't load any issuables if no filter is set" do
expect_any_instance_of(described_class).not_to receive(:issuables_collection)
diff --git a/spec/support/json_response.rb b/spec/support/json_response.rb
index 210b0e6d867..55bdce0cfe9 100644
--- a/spec/support/json_response.rb
+++ b/spec/support/json_response.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
- config.include_context 'JSON response'
+ config.include_context 'JSON response', type: :controller
config.include_context 'JSON response', type: :request
config.include_context 'JSON response', :api
end
diff --git a/spec/support/matchers/abort_matcher.rb b/spec/support/matchers/abort_matcher.rb
new file mode 100644
index 00000000000..64fed2ca069
--- /dev/null
+++ b/spec/support/matchers/abort_matcher.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :abort_execution do
+ match do |code_block|
+ @captured_stderr = StringIO.new
+ original_stderr = $stderr
+ $stderr = @captured_stderr
+
+ code_block.call
+
+ false
+ rescue SystemExit => e
+ captured = @captured_stderr.string.chomp
+ @actual_exit_code = e.status
+ break false unless e.status == 1
+
+ if @message
+ if @message.is_a? String
+ @message == captured
+ elsif @message.is_a? Regexp
+ @message.match?(captured)
+ else
+ raise ArgumentError, 'with_message must be either a String or a Regular Expression'
+ end
+ end
+
+ ensure
+ $stderr = original_stderr
+ end
+
+ chain :with_message do |message|
+ @message = message
+ end
+
+ failure_message do |block|
+ unless @actual_exit_code
+ break "expected #{block} to abort with '#{@message}' but didnt call abort."
+ end
+
+ if @actual_exit_code != 1
+ break "expected #{block} to abort with: '#{@message}' but exited with success instead."
+ end
+
+ "expected #{block} to abort with: '#{@message}' \n but received: '#{@captured_stderr.string.chomp}' instead."
+ end
+
+ supports_block_expectations
+end
diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb
index e6899e2d23c..c9ff777f604 100644
--- a/spec/support/matchers/access_matchers.rb
+++ b/spec/support/matchers/access_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# AccessMatchers
#
# The custom matchers contained in this module are used to test a user's access
diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb
index 429401a5da8..401bf6c196e 100644
--- a/spec/support/matchers/access_matchers_for_controller.rb
+++ b/spec/support/matchers/access_matchers_for_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# AccessMatchersForController
#
# For testing authorize_xxx in controller.
diff --git a/spec/support/matchers/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb
index f4127efc6ae..c38aa7ad6a6 100644
--- a/spec/support/matchers/background_migrations_matchers.rb
+++ b/spec/support/matchers/background_migrations_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_scheduled_delayed_migration do |delay, *expected|
match do |migration|
BackgroundMigrationWorker.jobs.any? do |job|
diff --git a/spec/support/matchers/be_a_binary_string.rb b/spec/support/matchers/be_a_binary_string.rb
index f041ae76167..6195c6c7554 100644
--- a/spec/support/matchers/be_a_binary_string.rb
+++ b/spec/support/matchers/be_a_binary_string.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_a_binary_string do |_|
match do |actual|
actual.is_a?(String) && actual.encoding == Encoding.find('ASCII-8BIT')
diff --git a/spec/support/matchers/be_like_time.rb b/spec/support/matchers/be_like_time.rb
index 1f27390eab7..b449f7a7ffb 100644
--- a/spec/support/matchers/be_like_time.rb
+++ b/spec/support/matchers/be_like_time.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_like_time do |expected|
match do |actual|
expect(actual).to be_within(1.second).of(expected)
diff --git a/spec/support/matchers/be_url.rb b/spec/support/matchers/be_url.rb
index f8096af1b22..69171f53891 100644
--- a/spec/support/matchers/be_url.rb
+++ b/spec/support/matchers/be_url.rb
@@ -1,5 +1,11 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_url do |_|
match do |actual|
URI.parse(actual) rescue false
end
end
+
+# looks better when used like:
+# expect(thing).to receive(:method).with(a_valid_url)
+RSpec::Matchers.alias_matcher :a_valid_url, :be_url
diff --git a/spec/support/matchers/be_utf8.rb b/spec/support/matchers/be_utf8.rb
index ea806352422..4fa55539a49 100644
--- a/spec/support/matchers/be_utf8.rb
+++ b/spec/support/matchers/be_utf8.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_utf8 do |_|
match do |actual|
actual.is_a?(String) && actual.encoding == Encoding.find('UTF-8')
diff --git a/spec/support/matchers/be_valid_commit.rb b/spec/support/matchers/be_valid_commit.rb
index 3696e4d5f03..b59339de622 100644
--- a/spec/support/matchers/be_valid_commit.rb
+++ b/spec/support/matchers/be_valid_commit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_valid_commit do
match do |actual|
actual &&
diff --git a/spec/support/matchers/disallow_request_matchers.rb b/spec/support/matchers/disallow_request_matchers.rb
index db4d90e4fd0..a161e3660cd 100644
--- a/spec/support/matchers/disallow_request_matchers.rb
+++ b/spec/support/matchers/disallow_request_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :disallow_request do
match do |middleware|
alert = middleware.env['rack.session'].to_hash
diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb
index cd042401f3a..40cf85eb8e5 100644
--- a/spec/support/matchers/exceed_query_limit.rb
+++ b/spec/support/matchers/exceed_query_limit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExceedQueryLimitHelpers
def with_threshold(threshold)
@threshold = threshold
diff --git a/spec/support/matchers/execute_check.rb b/spec/support/matchers/execute_check.rb
index 7232fad52fb..d3c0751f0dc 100644
--- a/spec/support/matchers/execute_check.rb
+++ b/spec/support/matchers/execute_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :execute_check do |expected|
match do |actual|
expect(actual).to eq(SystemCheck)
diff --git a/spec/support/matchers/gitaly_matchers.rb b/spec/support/matchers/gitaly_matchers.rb
index ebfabcd8f24..c2b3ebe3422 100644
--- a/spec/support/matchers/gitaly_matchers.rb
+++ b/spec/support/matchers/gitaly_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :gitaly_request_with_path do |storage_name, relative_path|
match do |actual|
repository = actual.repository
@@ -9,6 +11,6 @@ end
RSpec::Matchers.define :gitaly_request_with_params do |params|
match do |actual|
- params.reduce(true) { |r, (key, val)| r && actual.send(key) == val }
+ params.reduce(true) { |r, (key, val)| r && actual[key.to_s] == val }
end
end
diff --git a/spec/support/matchers/gitlab_git_matchers.rb b/spec/support/matchers/gitlab_git_matchers.rb
index c840cd4bf2d..aea1603db05 100644
--- a/spec/support/matchers/gitlab_git_matchers.rb
+++ b/spec/support/matchers/gitlab_git_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :gitlab_git_repository_with do |values|
match do |actual|
actual.is_a?(Gitlab::Git::Repository) &&
diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb
index 7894484f590..4d48b4b5389 100644
--- a/spec/support/matchers/graphql_matchers.rb
+++ b/spec/support/matchers/graphql_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :require_graphql_authorizations do |*expected|
match do |field|
expect(field.metadata[:authorize]).to eq(*expected)
diff --git a/spec/support/matchers/have_emoji.rb b/spec/support/matchers/have_emoji.rb
index 23fb8e9c1c4..273bd0b7f40 100644
--- a/spec/support/matchers/have_emoji.rb
+++ b/spec/support/matchers/have_emoji.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_emoji do |emoji_name|
match do |actual|
expect(actual).to have_selector("gl-emoji[data-name='#{emoji_name}']")
diff --git a/spec/support/matchers/have_gitlab_http_status.rb b/spec/support/matchers/have_gitlab_http_status.rb
index e7e418cdde4..13a64a58218 100644
--- a/spec/support/matchers/have_gitlab_http_status.rb
+++ b/spec/support/matchers/have_gitlab_http_status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_gitlab_http_status do |expected|
match do |actual|
expect(actual).to have_http_status(expected)
diff --git a/spec/support/matchers/have_issuable_counts.rb b/spec/support/matchers/have_issuable_counts.rb
index 92cf3de5448..049cfc022fb 100644
--- a/spec/support/matchers/have_issuable_counts.rb
+++ b/spec/support/matchers/have_issuable_counts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_issuable_counts do |opts|
expected_counts = opts.map do |state, count|
"#{state.to_s.humanize} #{count}"
diff --git a/spec/support/matchers/include_module.rb b/spec/support/matchers/include_module.rb
index 0a78af1e90e..9b6970cf061 100644
--- a/spec/support/matchers/include_module.rb
+++ b/spec/support/matchers/include_module.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :include_module do |expected|
match do
described_class.included_modules.include?(expected)
diff --git a/spec/support/matchers/issuable_matchers.rb b/spec/support/matchers/issuable_matchers.rb
index 62f510b0fbd..743f0b8c932 100644
--- a/spec/support/matchers/issuable_matchers.rb
+++ b/spec/support/matchers/issuable_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".md"|
match do |actual|
node = find("#{parent} h#{level} a#user-content-#{id}")
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index ec4ec6f4038..12e8fa83a60 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# MarkdownMatchers
#
# Custom matchers for our custom HTML::Pipeline filters. These are used to test
diff --git a/spec/support/matchers/match_file.rb b/spec/support/matchers/match_file.rb
index d1888b3376a..4e522b52912 100644
--- a/spec/support/matchers/match_file.rb
+++ b/spec/support/matchers/match_file.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :match_file do |expected|
match do |actual|
expect(Digest::MD5.hexdigest(actual)).to eq(Digest::MD5.hexdigest(File.read(expected)))
diff --git a/spec/support/matchers/match_ids.rb b/spec/support/matchers/match_ids.rb
index 1cb6b74acac..7bc41949937 100644
--- a/spec/support/matchers/match_ids.rb
+++ b/spec/support/matchers/match_ids.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :match_ids do |*expected|
match do |actual|
actual_ids = map_ids(actual)
diff --git a/spec/support/matchers/metric_counter_matcher.rb b/spec/support/matchers/metric_counter_matcher.rb
index 22d5cd17e3f..f0d52b9b149 100644
--- a/spec/support/matchers/metric_counter_matcher.rb
+++ b/spec/support/matchers/metric_counter_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :increment do |counter|
match do |adapter|
expect(adapter.send(counter))
diff --git a/spec/support/matchers/navigation_matcher.rb b/spec/support/matchers/navigation_matcher.rb
index 63f59b9654c..ad73c96031e 100644
--- a/spec/support/matchers/navigation_matcher.rb
+++ b/spec/support/matchers/navigation_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_active_navigation do |expected|
match do |page|
expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1)
diff --git a/spec/support/matchers/pagination_matcher.rb b/spec/support/matchers/pagination_matcher.rb
index 9a7697e2bfc..a3e9c3b8474 100644
--- a/spec/support/matchers/pagination_matcher.rb
+++ b/spec/support/matchers/pagination_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :include_pagination_headers do |expected|
match do |actual|
expect(actual.headers).to include('X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page', 'Link')
diff --git a/spec/support/matchers/query_matcher.rb b/spec/support/matchers/query_matcher.rb
index bb0feca7c43..3e47fe241bc 100644
--- a/spec/support/matchers/query_matcher.rb
+++ b/spec/support/matchers/query_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :make_queries_matching do |matcher, expected_count = nil|
supports_block_expectations
diff --git a/spec/support/matchers/satisfy_matchers.rb b/spec/support/matchers/satisfy_matchers.rb
index 585915bac93..dd1920ee2b2 100644
--- a/spec/support/matchers/satisfy_matchers.rb
+++ b/spec/support/matchers/satisfy_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These matchers are a syntactic hack to provide more readable expectations for
# an Enumerable object.
#
diff --git a/spec/support/matchers/security_header_matcher.rb b/spec/support/matchers/security_header_matcher.rb
index f8518d13ebb..760b1fddd06 100644
--- a/spec/support/matchers/security_header_matcher.rb
+++ b/spec/support/matchers/security_header_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :include_security_headers do |expected|
match do |actual|
expect(actual.headers).to include('X-Content-Type-Options')
diff --git a/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb b/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb
index 016bcfa9b1b..656be3b6d4d 100644
--- a/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb
+++ b/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MigrationsHelpers
module TrackUntrackedUploadsHelpers
PUBLIC_DIR = File.join(Rails.root, 'tmp', 'tests', 'public')
diff --git a/spec/support/omni_auth.rb b/spec/support/omni_auth.rb
index 0b1af4052ff..64aa3855a6f 100644
--- a/spec/support/omni_auth.rb
+++ b/spec/support/omni_auth.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
OmniAuth.config.test_mode = true
diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb
index de21e808932..4e006edb7da 100644
--- a/spec/support/prometheus/additional_metrics_shared_examples.rb
+++ b/spec/support/prometheus/additional_metrics_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'additional metrics query' do
include Prometheus::MetricBuilders
@@ -48,7 +50,7 @@ RSpec.shared_examples 'additional metrics query' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
let(:environment) { create(:environment, slug: 'environment-slug', project: project) }
- let(:kube_namespace) { project.deployment_platform.kubernetes_namespace_for(project) }
+ let(:kube_namespace) { environment.deployment_namespace }
it_behaves_like 'query context containing environment slug and filter'
diff --git a/spec/support/prometheus/metric_builders.rb b/spec/support/prometheus/metric_builders.rb
index c8d056d3fc8..512e32a44d0 100644
--- a/spec/support/prometheus/metric_builders.rb
+++ b/spec/support/prometheus/metric_builders.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Prometheus
module MetricBuilders
def simple_query(suffix = 'a', **opts)
diff --git a/spec/support/protected_tags/access_control_ce_shared_examples.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb
index 71eec9f3217..8666c19481c 100644
--- a/spec/support/protected_tags/access_control_ce_shared_examples.rb
+++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples "protected tags > access control > CE" do
ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)|
it "allows creating protected tags that #{access_type_name} can create" do
diff --git a/spec/support/redis/redis_helpers.rb b/spec/support/redis/redis_helpers.rb
index 0457e8487d8..7c571738a01 100644
--- a/spec/support/redis/redis_helpers.rb
+++ b/spec/support/redis/redis_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RedisHelpers
# config/README.md
diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb
index 6aa59960092..7e47cdae866 100644
--- a/spec/support/redis/redis_shared_examples.rb
+++ b/spec/support/redis/redis_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples "redis_shared_examples" do
include StubENV
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index b38c5dfe60b..1c9f9e5161e 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative "helpers/stub_configuration"
require_relative "helpers/stub_metrics"
require_relative "helpers/stub_object_storage"
diff --git a/spec/support/seed.rb b/spec/support/seed.rb
index bea2e9c3044..36cb819763b 100644
--- a/spec/support/seed.rb
+++ b/spec/support/seed.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.include SeedHelper, :seed_helper
diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb
index b0bf942aa09..27f6d0570b6 100644
--- a/spec/support/services/clusters/create_service_shared.rb
+++ b/spec/support/services/clusters/create_service_shared.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'valid cluster create params' do
let(:params) do
{
@@ -30,23 +32,56 @@ shared_context 'invalid cluster create params' do
end
shared_examples 'create cluster service success' do
- it 'creates a cluster object and performs a worker' do
- expect(ClusterProvisionWorker).to receive(:perform_async)
-
- expect { subject }
- .to change { Clusters::Cluster.count }.by(1)
- .and change { Clusters::Providers::Gcp.count }.by(1)
-
- expect(subject.name).to eq('test-cluster')
- expect(subject.user).to eq(user)
- expect(subject.project).to eq(project)
- expect(subject.provider.gcp_project_id).to eq('gcp-project')
- expect(subject.provider.zone).to eq('us-central1-a')
- expect(subject.provider.num_nodes).to eq(1)
- expect(subject.provider.machine_type).to eq('machine_type-a')
- expect(subject.provider.access_token).to eq(access_token)
- expect(subject.provider).to be_legacy_abac
- expect(subject.platform).to be_nil
+ context 'namespace per environment feature is enabled' do
+ before do
+ stub_feature_flags(kubernetes_namespace_per_environment: true)
+ end
+
+ it 'creates a cluster object and performs a worker' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+
+ expect { subject }
+ .to change { Clusters::Cluster.count }.by(1)
+ .and change { Clusters::Providers::Gcp.count }.by(1)
+
+ expect(subject.name).to eq('test-cluster')
+ expect(subject.user).to eq(user)
+ expect(subject.project).to eq(project)
+ expect(subject.provider.gcp_project_id).to eq('gcp-project')
+ expect(subject.provider.zone).to eq('us-central1-a')
+ expect(subject.provider.num_nodes).to eq(1)
+ expect(subject.provider.machine_type).to eq('machine_type-a')
+ expect(subject.provider.access_token).to eq(access_token)
+ expect(subject.provider).to be_legacy_abac
+ expect(subject.platform).to be_nil
+ expect(subject.namespace_per_environment).to eq true
+ end
+ end
+
+ context 'namespace per environment feature is disabled' do
+ before do
+ stub_feature_flags(kubernetes_namespace_per_environment: false)
+ end
+
+ it 'creates a cluster object and performs a worker' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+
+ expect { subject }
+ .to change { Clusters::Cluster.count }.by(1)
+ .and change { Clusters::Providers::Gcp.count }.by(1)
+
+ expect(subject.name).to eq('test-cluster')
+ expect(subject.user).to eq(user)
+ expect(subject.project).to eq(project)
+ expect(subject.provider.gcp_project_id).to eq('gcp-project')
+ expect(subject.provider.zone).to eq('us-central1-a')
+ expect(subject.provider.num_nodes).to eq(1)
+ expect(subject.provider.machine_type).to eq('machine_type-a')
+ expect(subject.provider.access_token).to eq(access_token)
+ expect(subject.provider).to be_legacy_abac
+ expect(subject.platform).to be_nil
+ expect(subject.namespace_per_environment).to eq false
+ end
end
end
diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
index 8b4cffaac19..4c3644e6724 100644
--- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
+++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specifications for behavior common to all objects with executable attributes.
# It can take a `default_params`.
diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/services/issuable_update_service_shared_examples.rb
index ffbce6c42bf..5e5acd0e40a 100644
--- a/spec/support/services/issuable_update_service_shared_examples.rb
+++ b/spec/support/services/issuable_update_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuable update service' do
def update_issuable(opts)
described_class.new(project, user, opts).execute(open_issuable)
diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
index 1284415da1f..65236f13e27 100644
--- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class, fields|
diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb
index 1d2a4856724..bbe442f07b0 100644
--- a/spec/support/setup_builds_storage.rb
+++ b/spec/support/setup_builds_storage.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
def builds_path
Rails.root.join('tmp/tests/builds')
diff --git a/spec/support/shared_contexts/email_shared_context.rb b/spec/support/shared_contexts/email_shared_context.rb
index 9d806fc524d..b4d061a8215 100644
--- a/spec/support/shared_contexts/email_shared_context.rb
+++ b/spec/support/shared_contexts/email_shared_context.rb
@@ -1,4 +1,4 @@
-require 'gitlab/email/receiver'
+# frozen_string_literal: true
shared_context :email_shared_context do
let(:mail_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
diff --git a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
index a0d994c4d8d..38f6011646e 100644
--- a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.shared_context 'GroupProjectsFinder context' do
diff --git a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
index b8a9554f55f..26ab6fbd400 100644
--- a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.shared_context 'IssuesFinder context' do
diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
index ab6687f1d07..ef1e65d2577 100644
--- a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests context' do
diff --git a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
index 9e1f89ee0ed..d6404b2ee4b 100644
--- a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.shared_context 'UsersFinder#execute filter by project context' do
diff --git a/spec/support/shared_contexts/json_response_shared_context.rb b/spec/support/shared_contexts/json_response_shared_context.rb
index df5fc288089..bd37c97ed35 100644
--- a/spec/support/shared_contexts/json_response_shared_context.rb
+++ b/spec/support/shared_contexts/json_response_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'JSON response' do
let(:json_response) { JSON.parse(response.body) }
end
diff --git a/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb b/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb
index 05424d08b9d..276ebf973c8 100644
--- a/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb
+++ b/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'merge request allowing collaboration' do
include ProjectForksHelper
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index b4808ac0068..b89723b1e1a 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -7,7 +7,7 @@ RSpec.shared_context 'GroupPolicy context' do
let(:maintainer) { create(:user) }
let(:owner) { create(:user) }
let(:admin) { create(:admin) }
- let(:group) { create(:group, :private) }
+ let(:group) { create(:group, :private, :owner_subgroup_creation_only) }
let(:guest_permissions) do
%i[
@@ -16,7 +16,7 @@ RSpec.shared_context 'GroupPolicy context' do
read_group_merge_requests
]
end
- let(:reporter_permissions) { [:admin_label] }
+ let(:reporter_permissions) { %i[admin_label read_container_image] }
let(:developer_permissions) { [:admin_milestone] }
let(:maintainer_permissions) do
%i[
@@ -31,7 +31,8 @@ RSpec.shared_context 'GroupPolicy context' do
:admin_group_member,
:change_visibility_level,
:set_note_created_at,
- (Gitlab::Database.postgresql? ? :create_subgroup : nil)
+ :create_subgroup,
+ :read_statistics
].compact
end
diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb
index 0c3a24d206f..4d176ab5fca 100644
--- a/spec/support/shared_contexts/services_shared_context.rb
+++ b/spec/support/shared_contexts/services_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
Service.available_services_names.each do |service|
shared_context service do
let(:dashed_service) { service.dasherize }
diff --git a/spec/support/shared_contexts/url_shared_context.rb b/spec/support/shared_contexts/url_shared_context.rb
index 1b1f67daac3..560cd500ecd 100644
--- a/spec/support/shared_contexts/url_shared_context.rb
+++ b/spec/support/shared_contexts/url_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'invalid urls' do
let(:urls_with_CRLF) do
["http://127.0.0.1:333/pa\rth",
diff --git a/spec/support/shared_examples/application_setting_examples.rb b/spec/support/shared_examples/application_setting_examples.rb
index e7ec24c5b7e..a43d2a75082 100644
--- a/spec/support/shared_examples/application_setting_examples.rb
+++ b/spec/support/shared_examples/application_setting_examples.rb
@@ -1,62 +1,150 @@
# frozen_string_literal: true
+RSpec.shared_examples 'string of domains' do |attribute|
+ it 'sets single domain' do
+ setting.method("#{attribute}_raw=").call('example.com')
+ expect(setting.method(attribute).call).to eq(['example.com'])
+ end
+
+ it 'sets multiple domains with spaces' do
+ setting.method("#{attribute}_raw=").call('example.com *.example.com')
+ expect(setting.method(attribute).call).to eq(['example.com', '*.example.com'])
+ end
+
+ it 'sets multiple domains with newlines and a space' do
+ setting.method("#{attribute}_raw=").call("example.com\n *.example.com")
+ expect(setting.method(attribute).call).to eq(['example.com', '*.example.com'])
+ end
+
+ it 'sets multiple domains with commas' do
+ setting.method("#{attribute}_raw=").call("example.com, *.example.com")
+ expect(setting.method(attribute).call).to eq(['example.com', '*.example.com'])
+ end
+
+ it 'sets multiple domains with semicolon' do
+ setting.method("#{attribute}_raw=").call("example.com; *.example.com")
+ expect(setting.method(attribute).call).to contain_exactly('example.com', '*.example.com')
+ end
+
+ it 'sets multiple domains with mixture of everything' do
+ setting.method("#{attribute}_raw=").call("example.com; *.example.com\n test.com\sblock.com yes.com")
+ expect(setting.method(attribute).call).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com')
+ end
+
+ it 'removes duplicates' do
+ setting.method("#{attribute}_raw=").call("example.com; example.com; 127.0.0.1; 127.0.0.1")
+ expect(setting.method(attribute).call).to contain_exactly('example.com', '127.0.0.1')
+ end
+
+ it 'does not fail with garbage values' do
+ setting.method("#{attribute}_raw=").call("example;34543:garbage:fdh5654;")
+ expect(setting.method(attribute).call).to contain_exactly('example', '34543:garbage:fdh5654')
+ end
+
+ it 'does not raise error with nil' do
+ setting.method("#{attribute}_raw=").call(nil)
+ expect(setting.method(attribute).call).to eq([])
+ end
+end
+
RSpec.shared_examples 'application settings examples' do
context 'restricted signup domains' do
- it 'sets single domain' do
- setting.domain_whitelist_raw = 'example.com'
- expect(setting.domain_whitelist).to eq(['example.com'])
- end
+ it_behaves_like 'string of domains', :domain_whitelist
+ end
- it 'sets multiple domains with spaces' do
- setting.domain_whitelist_raw = 'example.com *.example.com'
- expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
- end
+ context 'blacklisted signup domains' do
+ it_behaves_like 'string of domains', :domain_blacklist
- it 'sets multiple domains with newlines and a space' do
- setting.domain_whitelist_raw = "example.com\n *.example.com"
- expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
+ it 'sets multiple domain with file' do
+ setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt'))
+ expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar')
end
+ end
+
+ context 'outbound_local_requests_whitelist' do
+ it_behaves_like 'string of domains', :outbound_local_requests_whitelist
- it 'sets multiple domains with commas' do
- setting.domain_whitelist_raw = "example.com, *.example.com"
- expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
+ it 'clears outbound_local_requests_whitelist_arrays memoization' do
+ setting.outbound_local_requests_whitelist_raw = 'example.com'
+
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ [], ['example.com']
+ )
+
+ setting.outbound_local_requests_whitelist_raw = 'gitlab.com'
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ [], ['gitlab.com']
+ )
end
end
- context 'blacklisted signup domains' do
- it 'sets single domain' do
- setting.domain_blacklist_raw = 'example.com'
- expect(setting.domain_blacklist).to contain_exactly('example.com')
+ context 'outbound_local_requests_whitelist_arrays' do
+ it 'separates the IPs and domains' do
+ setting.outbound_local_requests_whitelist = [
+ '192.168.1.1', '127.0.0.0/28', 'www.example.com', 'example.com',
+ '::ffff:a00:2', '1:0:0:0:0:0:0:0/124', 'subdomain.example.com'
+ ]
+
+ ip_whitelist = [
+ IPAddr.new('192.168.1.1'), IPAddr.new('127.0.0.0/8'),
+ IPAddr.new('::ffff:a00:2'), IPAddr.new('1:0:0:0:0:0:0:0/124')
+ ]
+ domain_whitelist = ['www.example.com', 'example.com', 'subdomain.example.com']
+
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ ip_whitelist, domain_whitelist
+ )
end
+ end
- it 'sets multiple domains with spaces' do
- setting.domain_blacklist_raw = 'example.com *.example.com'
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
- end
+ context 'add_to_outbound_local_requests_whitelist' do
+ it 'adds entry to outbound_local_requests_whitelist' do
+ setting.outbound_local_requests_whitelist = ['example.com']
- it 'sets multiple domains with newlines and a space' do
- setting.domain_blacklist_raw = "example.com\n *.example.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
- end
+ setting.add_to_outbound_local_requests_whitelist(
+ ['example.com', '127.0.0.1', 'gitlab.com']
+ )
- it 'sets multiple domains with commas' do
- setting.domain_blacklist_raw = "example.com, *.example.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+ expect(setting.outbound_local_requests_whitelist).to contain_exactly(
+ 'example.com',
+ '127.0.0.1',
+ 'gitlab.com'
+ )
end
- it 'sets multiple domains with semicolon' do
- setting.domain_blacklist_raw = "example.com; *.example.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+ it 'clears outbound_local_requests_whitelist_arrays memoization' do
+ setting.outbound_local_requests_whitelist = ['example.com']
+
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ [],
+ ['example.com']
+ )
+
+ setting.add_to_outbound_local_requests_whitelist(
+ ['example.com', 'gitlab.com']
+ )
+
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ [],
+ ['example.com', 'gitlab.com']
+ )
end
- it 'sets multiple domains with mixture of everything' do
- setting.domain_blacklist_raw = "example.com; *.example.com\n test.com\sblock.com yes.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com')
+ it 'does not raise error with nil' do
+ setting.outbound_local_requests_whitelist = nil
+
+ setting.add_to_outbound_local_requests_whitelist(['gitlab.com'])
+
+ expect(setting.outbound_local_requests_whitelist).to contain_exactly('gitlab.com')
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ [], ['gitlab.com']
+ )
end
- it 'sets multiple domain with file' do
- setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt'))
- expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar')
+ it 'does not raise error with nil' do
+ setting.outbound_local_requests_whitelist = nil
+
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly([], [])
end
end
diff --git a/spec/support/shared_examples/award_emoji_todo_shared_examples.rb b/spec/support/shared_examples/award_emoji_todo_shared_examples.rb
new file mode 100644
index 00000000000..88ad37d232f
--- /dev/null
+++ b/spec/support/shared_examples/award_emoji_todo_shared_examples.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+# Shared examples to that test code that creates AwardEmoji also mark Todos
+# as done.
+#
+# The examples expect these to be defined in the calling spec:
+# - `subject` the callable code that executes the creation of an AwardEmoji
+# - `user`
+# - `project`
+RSpec.shared_examples 'creating award emojis marks Todos as done' do
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ project.add_developer(user)
+ end
+
+ where(:type, :expectation) do
+ :issue | true
+ :merge_request | true
+ :project_snippet | false
+ end
+
+ with_them do
+ let(:project) { awardable.project }
+ let(:awardable) { create(type) }
+ let!(:todo) { create(:todo, target: awardable, project: project, user: user) }
+
+ it do
+ subject
+
+ expect(todo.reload.done?).to eq(expectation)
+ end
+ end
+
+ # Notes have more complicated rules than other Todoables
+ describe 'for notes' do
+ let!(:todo) { create(:todo, target: awardable.noteable, project: project, user: user) }
+
+ context 'regular Notes' do
+ let(:awardable) { create(:note, project: project) }
+
+ it 'marks the Todo as done' do
+ subject
+
+ expect(todo.reload.done?).to eq(true)
+ end
+ end
+
+ context 'PersonalSnippet Notes' do
+ let(:awardable) { create(:note, noteable: create(:personal_snippet, author: user)) }
+
+ it 'does not mark the Todo as done' do
+ subject
+
+ expect(todo.reload.done?).to eq(false)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
new file mode 100644
index 00000000000..76d82649c5f
--- /dev/null
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+shared_examples_for 'multiple issue boards' do
+ dropdown_selector = '.js-boards-selector .dropdown-menu'
+
+ context 'authorized user' do
+ before do
+ parent.add_maintainer(user)
+
+ login_as(user)
+
+ visit boards_path
+ wait_for_requests
+ end
+
+ it 'shows current board name' do
+ page.within('.boards-switcher') do
+ expect(page).to have_content(board.name)
+ end
+ end
+
+ it 'shows a list of boards' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ expect(page).to have_content(board.name)
+ expect(page).to have_content(board2.name)
+ end
+ end
+
+ it 'switches current board' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ click_link board2.name
+ end
+
+ wait_for_requests
+
+ page.within('.boards-switcher') do
+ expect(page).to have_content(board2.name)
+ end
+ end
+
+ it 'creates new board without detailed configuration' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ click_button 'Create new board'
+ end
+
+ fill_in 'board-new-name', with: 'This is a new board'
+ click_button 'Create board'
+ wait_for_requests
+
+ expect(page).to have_button('This is a new board')
+ end
+
+ it 'deletes board' do
+ click_button board.name
+
+ wait_for_requests
+
+ page.within(dropdown_selector) do
+ click_button 'Delete board'
+ end
+
+ expect(page).to have_content('Are you sure you want to delete this board?')
+ click_button 'Delete'
+
+ click_button board2.name
+ page.within(dropdown_selector) do
+ expect(page).not_to have_content(board.name)
+ expect(page).to have_content(board2.name)
+ end
+ end
+
+ it 'adds a list to the none default board' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ click_link board2.name
+ end
+
+ wait_for_requests
+
+ page.within('.boards-switcher') do
+ expect(page).to have_content(board2.name)
+ end
+
+ click_button 'Add list'
+
+ wait_for_requests
+
+ page.within '.dropdown-menu-issues-board-new' do
+ click_link planning.title
+ end
+
+ wait_for_requests
+
+ expect(page).to have_selector('.board', count: 3)
+
+ click_button board2.name
+
+ page.within(dropdown_selector) do
+ click_link board.name
+ end
+
+ wait_for_requests
+
+ expect(page).to have_selector('.board', count: 2)
+ end
+
+ it 'maintains sidebar state over board switch' do
+ assert_boards_nav_active
+
+ find('.boards-switcher').click
+ wait_for_requests
+ click_link board2.name
+
+ assert_boards_nav_active
+ end
+ end
+
+ context 'unauthorized user' do
+ before do
+ visit boards_path
+ wait_for_requests
+ end
+
+ it 'does not show action links' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ expect(page).not_to have_content('Create new board')
+ expect(page).not_to have_content('Delete board')
+ end
+ end
+ end
+
+ def assert_boards_nav_active
+ expect(find('.nav-sidebar .active .active')).to have_selector('a', text: 'Boards')
+ end
+end
diff --git a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
index dc97a39f051..dcc92dda950 100644
--- a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
+++ b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'chat slash commands service' do
describe "Associations" do
it { is_expected.to respond_to :token }
@@ -91,6 +93,19 @@ RSpec.shared_examples 'chat slash commands service' do
subject.trigger(params)
end
+
+ context 'when user is blocked' do
+ before do
+ chat_name.user.block
+ end
+
+ it 'blocks command execution' do
+ expect_any_instance_of(Gitlab::SlashCommands::Command).not_to receive(:execute)
+
+ result = subject.trigger(params)
+ expect(result).to include(text: /^Whoops! This action is not allowed/)
+ end
+ end
end
end
end
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index ab0550e2613..e2b4b50d41d 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'common trace features' do
describe '#html' do
before do
@@ -5,11 +7,11 @@ shared_examples_for 'common trace features' do
end
it "returns formatted html" do
- expect(trace.html).to eq("<span class=\"\">12<br/><span class=\"\">34</span></span>")
+ expect(trace.html).to eq("<span>12<br/>34</span>")
end
it "returns last line of formatted html" do
- expect(trace.html(last_lines: 1)).to eq("<span class=\"\">34</span>")
+ expect(trace.html(last_lines: 1)).to eq("<span>34</span>")
end
end
@@ -720,6 +722,58 @@ shared_examples_for 'trace with enabled live trace feature' do
end
end
+ describe '#archived_trace_exist?' do
+ subject { trace.archived_trace_exist? }
+
+ context 'when trace does not exist' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when archived trace exists' do
+ before do
+ create(:ci_job_artifact, :trace, job: build)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when live trace exists' do
+ before do
+ Gitlab::Ci::Trace::ChunkedIO.new(build) do |stream|
+ stream.write('abc')
+ end
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#live_trace_exist?' do
+ subject { trace.live_trace_exist? }
+
+ context 'when trace does not exist' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when archived trace exists' do
+ before do
+ create(:ci_job_artifact, :trace, job: build)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when live trace exists' do
+ before do
+ Gitlab::Ci::Trace::ChunkedIO.new(build) do |stream|
+ stream.write('abc')
+ end
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
describe '#archive!' do
subject { trace.archive! }
diff --git a/spec/support/shared_examples/common_system_notes_examples.rb b/spec/support/shared_examples/common_system_notes_examples.rb
index da5a4f3e319..75f93a32d78 100644
--- a/spec/support/shared_examples/common_system_notes_examples.rb
+++ b/spec/support/shared_examples/common_system_notes_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'system note creation' do |update_params, note_text|
subject { described_class.new(project, user).execute(issuable, old_labels: []) }
diff --git a/spec/support/shared_examples/container_repositories_shared_examples.rb b/spec/support/shared_examples/container_repositories_shared_examples.rb
new file mode 100644
index 00000000000..946b130fca2
--- /dev/null
+++ b/spec/support/shared_examples/container_repositories_shared_examples.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+shared_examples 'rejected container repository access' do |user_type, status|
+ context "for #{user_type}" do
+ let(:api_user) { users[user_type] }
+
+ it "returns #{status}" do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ end
+ end
+end
+
+shared_examples 'returns repositories for allowed users' do |user_type, scope|
+ context "for #{user_type}" do
+ it 'returns a list of repositories' do
+ subject
+
+ expect(json_response.length).to eq(2)
+ expect(json_response.map { |repository| repository['id'] }).to contain_exactly(
+ root_repository.id, test_repository.id)
+ expect(response.body).not_to include('tags')
+ end
+
+ it 'returns a matching schema' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('registry/repositories')
+ end
+
+ context 'with tags param' do
+ let(:url) { "/#{scope}s/#{object.id}/registry/repositories?tags=true" }
+
+ before do
+ stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA latest), with_manifest: true)
+ stub_container_registry_tags(repository: test_repository.path, tags: %w(rootA latest), with_manifest: true)
+ end
+
+ it 'returns a list of repositories and their tags' do
+ subject
+
+ expect(json_response.length).to eq(2)
+ expect(json_response.map { |repository| repository['id'] }).to contain_exactly(
+ root_repository.id, test_repository.id)
+ expect(response.body).to include('tags')
+ end
+
+ it 'returns a matching schema' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('registry/repositories')
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb
index 8dd78fd0a25..d8a1ae83f61 100644
--- a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'disabled when using an external authorization service' do
@@ -6,7 +8,7 @@ shared_examples 'disabled when using an external authorization service' do
it 'works when the feature is not enabled' do
subject
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'renders a 404 with a message when the feature is enabled' do
diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index 0acc9e2a836..26ed86bfe26 100644
--- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuable notes filter' do
let(:params) do
if issuable_parent.is_a?(Project)
@@ -39,14 +41,22 @@ shared_examples 'issuable notes filter' do
get :discussions, params: params.merge(notes_filter: notes_filter)
- expect(user.reload.notes_filter_for(issuable)).to eq(0)
+ expect(user.reload.notes_filter_for(issuable)).to eq(UserPreference::NOTES_FILTERS[:all_notes])
+ end
+
+ it 'does not set notes filter when persist_filter param is false' do
+ notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
+
+ get :discussions, params: params.merge(notes_filter: notes_filter, persist_filter: false)
+
+ expect(user.reload.notes_filter_for(issuable)).to eq(UserPreference::NOTES_FILTERS[:all_notes])
end
it 'returns only user comments' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable)
get :discussions, params: params
- discussions = JSON.parse(response.body)
+ discussions = json_response
expect(discussions.count).to eq(1)
expect(discussions.first["notes"].first["system"]).to be(false)
@@ -56,7 +66,7 @@ shared_examples 'issuable notes filter' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable)
get :discussions, params: params
- discussions = JSON.parse(response.body)
+ discussions = json_response
expect(discussions.count).to eq(1)
expect(discussions.first["notes"].first["system"]).to be(true)
diff --git a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
index eb051166a69..d89eded6e69 100644
--- a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
@@ -1,13 +1,15 @@
+# frozen_string_literal: true
+
shared_examples 'set sort order from user preference' do
describe '#set_sort_order_from_user_preference' do
- # There is no issuable_sorting_field defined in any CE controllers yet,
+ # There is no sorting_field defined in any CE controllers yet,
# however any other field present in user_preferences table can be used for testing.
context 'when database is in read-only mode' do
it 'does not update user preference' do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
- expect_any_instance_of(UserPreference).not_to receive(:update).with({ controller.send(:issuable_sorting_field) => sorting_param })
+ expect_any_instance_of(UserPreference).not_to receive(:update).with({ controller.send(:sorting_field) => sorting_param })
get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param }
end
@@ -17,7 +19,7 @@ shared_examples 'set sort order from user preference' do
it 'updates user preference' do
allow(Gitlab::Database).to receive(:read_only?).and_return(false)
- expect_any_instance_of(UserPreference).to receive(:update).with({ controller.send(:issuable_sorting_field) => sorting_param })
+ expect_any_instance_of(UserPreference).to receive(:update).with({ controller.send(:sorting_field) => sorting_param })
get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param }
end
diff --git a/spec/support/shared_examples/controllers/todos_shared_examples.rb b/spec/support/shared_examples/controllers/todos_shared_examples.rb
index bafd9bac8d0..f3f9abb7da2 100644
--- a/spec/support/shared_examples/controllers/todos_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/todos_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'todos actions' do
context 'when authorized' do
before do
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index 59708173716..39d13cccb13 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'handle uploads' do
let(:user) { create(:user) }
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
@@ -74,6 +76,16 @@ shared_examples 'handle uploads' do
UploadService.new(model, jpg, uploader_class).execute
end
+ context 'when accessing a specific upload via different model' do
+ it 'responds with status 404' do
+ params.merge!(other_params)
+
+ show_upload
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
context "when the model is public" do
before do
model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
diff --git a/spec/support/shared_examples/controllers/variables_shared_examples.rb b/spec/support/shared_examples/controllers/variables_shared_examples.rb
index e80722857ec..78666e677ef 100644
--- a/spec/support/shared_examples/controllers/variables_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/variables_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'GET #show lists all variables' do
it 'renders the variables as json' do
subject
diff --git a/spec/support/shared_examples/cycle_analytics_stage_examples.rb b/spec/support/shared_examples/cycle_analytics_stage_examples.rb
new file mode 100644
index 00000000000..151f5325e84
--- /dev/null
+++ b/spec/support/shared_examples/cycle_analytics_stage_examples.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+shared_examples_for 'cycle analytics stage' do
+ let(:valid_params) do
+ {
+ name: 'My Stage',
+ parent: parent,
+ start_event_identifier: :merge_request_created,
+ end_event_identifier: :merge_request_merged
+ }
+ end
+
+ describe 'validation' do
+ it 'is valid' do
+ expect(described_class.new(valid_params)).to be_valid
+ end
+
+ it 'validates presence of parent' do
+ stage = described_class.new(valid_params.except(:parent))
+
+ expect(stage).not_to be_valid
+ expect(stage.errors.details[parent_name]).to eq([{ error: :blank }])
+ end
+
+ it 'validates presence of start_event_identifier' do
+ stage = described_class.new(valid_params.except(:start_event_identifier))
+
+ expect(stage).not_to be_valid
+ expect(stage.errors.details[:start_event_identifier]).to eq([{ error: :blank }])
+ end
+
+ it 'validates presence of end_event_identifier' do
+ stage = described_class.new(valid_params.except(:end_event_identifier))
+
+ expect(stage).not_to be_valid
+ expect(stage.errors.details[:end_event_identifier]).to eq([{ error: :blank }])
+ end
+
+ it 'is invalid when end_event is not allowed for the given start_event' do
+ invalid_params = valid_params.merge(
+ start_event_identifier: :merge_request_merged,
+ end_event_identifier: :merge_request_created
+ )
+ stage = described_class.new(invalid_params)
+
+ expect(stage).not_to be_valid
+ expect(stage.errors.details[:end_event]).to eq([{ error: :not_allowed_for_the_given_start_event }])
+ end
+ end
+
+ describe '#subject_model' do
+ it 'infers the model from the start event' do
+ stage = described_class.new(valid_params)
+
+ expect(stage.subject_model).to eq(MergeRequest)
+ end
+ end
+
+ describe '#start_event' do
+ it 'builds start_event object based on start_event_identifier' do
+ stage = described_class.new(start_event_identifier: 'merge_request_created')
+
+ expect(stage.start_event).to be_a_kind_of(Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated)
+ end
+ end
+
+ describe '#end_event' do
+ it 'builds end_event object based on end_event_identifier' do
+ stage = described_class.new(end_event_identifier: 'merge_request_merged')
+
+ expect(stage.end_event).to be_a_kind_of(Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
index 4e45e2921e7..60c8899d349 100644
--- a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
+++ b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'dirty submit form' do |selector_args|
selectors = selector_args.is_a?(Array) ? selector_args : [selector_args]
diff --git a/spec/support/shared_examples/discussions_provider_shared_examples.rb b/spec/support/shared_examples/discussions_provider_shared_examples.rb
new file mode 100644
index 00000000000..77cf1ac3f51
--- /dev/null
+++ b/spec/support/shared_examples/discussions_provider_shared_examples.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+shared_examples 'discussions provider' do
+ it 'returns the expected discussions' do
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: requested_iid }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('entities/discussions')
+
+ expect(json_response.size).to eq(expected_discussion_count)
+ expect(json_response.pluck('id')).to eq(expected_discussion_ids)
+ end
+end
diff --git a/spec/support/shared_examples/email_format_shared_examples.rb b/spec/support/shared_examples/email_format_shared_examples.rb
index b924a208e71..22d6c2b38e3 100644
--- a/spec/support/shared_examples/email_format_shared_examples.rb
+++ b/spec/support/shared_examples/email_format_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specifications for behavior common to all objects with an email attribute.
# Takes a list of email-format attributes and requires:
# - subject { "the object with a attribute= setter" }
diff --git a/spec/support/shared_examples/fast_destroy_all.rb b/spec/support/shared_examples/fast_destroy_all.rb
index a8079b6d864..a64259c03f2 100644
--- a/spec/support/shared_examples/fast_destroy_all.rb
+++ b/spec/support/shared_examples/fast_destroy_all.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'fast destroyable' do
describe 'Forbid #destroy and #destroy_all' do
it 'does not delete database rows and associted external data' do
diff --git a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb
index 221926aaf7e..f24e47f4638 100644
--- a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb
+++ b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'comment on merge request file' do
it 'adds a comment' do
click_diff_line(find("[id='#{sample_commit.line_code}']"))
@@ -16,7 +18,7 @@ shared_examples 'comment on merge request file' do
visit(merge_request_path(merge_request))
page.within('.notes .discussion') do
- expect(page).to have_content("#{user.name} #{user.to_reference} started a discussion")
+ expect(page).to have_content("#{user.name} #{user.to_reference} started a thread")
expect(page).to have_content(sample_commit.line_code_path)
expect(page).to have_content('Line is wrong')
end
diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
index ec1b1754cf0..c0db4cdde72 100644
--- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'a creatable merge request' do
include WaitForRequests
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index a6121fcc50a..964c80007b0 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'an editable merge request' do
it 'updates merge request', :js do
find('.js-assignee-search').click
diff --git a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
index 96c821b26f7..09a48533ee3 100644
--- a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issue sidebar stays collapsed on mobile' do
before do
resize_screen_xs
diff --git a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
index c92c7f603d6..63ed37cde03 100644
--- a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
+++ b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuable user dropdown behaviors' do
include FilteredSearchHelpers
diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
index d87e5fcaa88..8e1d24c4be2 100644
--- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
+++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'Maintainer manages access requests' do
let(:user) { create(:user) }
let(:maintainer) { create(:user) }
diff --git a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
index 64c3b80136d..51559c0b110 100644
--- a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'project features apply to issuables' do |klass|
let(:described_class) { klass }
diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
index a8f2c2e7a5a..db83d6f0793 100644
--- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples "protected branches > access control > CE" do
ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)|
it "allows creating protected branches that #{access_type_name} can push to" do
@@ -6,7 +8,7 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master')
find(".js-allowed-to-merge").click
- within('.qa-allowed-to-merge-dropdown') do
+ within('.rspec-allowed-to-merge-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
@@ -32,13 +34,13 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master')
find(".js-allowed-to-merge").click
- within('.qa-allowed-to-merge-dropdown') do
+ within('.rspec-allowed-to-merge-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
find(".js-allowed-to-push").click
- within('.qa-allowed-to-push-dropdown') do
+ within('.rspec-allowed-to-push-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
@@ -78,7 +80,7 @@ shared_examples "protected branches > access control > CE" do
end
find(".js-allowed-to-push").click
- within('.qa-allowed-to-push-dropdown') do
+ within('.rspec-allowed-to-push-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
@@ -95,13 +97,13 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master')
find(".js-allowed-to-merge").click
- within('.qa-allowed-to-merge-dropdown') do
+ within('.rspec-allowed-to-merge-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
find(".js-allowed-to-push").click
- within('.qa-allowed-to-push-dropdown') do
+ within('.rspec-allowed-to-push-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
diff --git a/spec/support/shared_examples/features/search_shared_examples.rb b/spec/support/shared_examples/features/search_shared_examples.rb
index 25ebbf011d5..e27d6700cbf 100644
--- a/spec/support/shared_examples/features/search_shared_examples.rb
+++ b/spec/support/shared_examples/features/search_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'top right search form' do
it 'does not show top right search form' do
expect(page).not_to have_selector('.search')
diff --git a/spec/support/shared_examples/file_finder.rb b/spec/support/shared_examples/file_finder.rb
index 0dc351b5149..984a06ccd1a 100644
--- a/spec/support/shared_examples/file_finder.rb
+++ b/spec/support/shared_examples/file_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'file finder' do
let(:query) { 'files' }
let(:search_results) { subject.find(query) }
diff --git a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb
index d7e17cc0b70..b8b0079e36d 100644
--- a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb
+++ b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'a finder with external authorization service' do
diff --git a/spec/support/shared_examples/gitlab_verify.rb b/spec/support/shared_examples/gitlab_verify.rb
index 560913ca92f..721ea3b4c88 100644
--- a/spec/support/shared_examples/gitlab_verify.rb
+++ b/spec/support/shared_examples/gitlab_verify.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'Gitlab::Verify::BatchVerifier subclass' do
describe 'batching' do
let(:first_batch) { objects[0].id..objects[0].id }
diff --git a/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb
index 713f0a879c1..145c476c7f7 100644
--- a/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'issuable state' do
it 'exposes all the existing issuable states' do
expect(described_class.values.keys).to include(*%w[opened closed locked])
diff --git a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
new file mode 100644
index 00000000000..f2e1a95345b
--- /dev/null
+++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a Note mutation that does not create a Note' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { Note.count }
+ end
+end
+
+RSpec.shared_examples 'a Note mutation that creates a Note' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { Note.count }.by(1)
+ end
+end
+
+RSpec.shared_examples 'a Note mutation when the user does not have permission' do
+ it_behaves_like 'a Note mutation that does not create a Note'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+end
+
+RSpec.shared_examples 'a Note mutation when there are active record validation errors' do |model: Note|
+ before do
+ expect_next_instance_of(model) do |note|
+ expect(note).to receive(:valid?).at_least(:once).and_return(false)
+ expect(note).to receive_message_chain(
+ :errors,
+ :full_messages
+ ).and_return(['Error 1', 'Error 2'])
+ end
+ end
+
+ it_behaves_like 'a Note mutation that does not create a Note'
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ['Error 1', 'Error 2']
+
+ it 'returns an empty Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('note')
+ expect(mutation_response['note']).to be_nil
+ end
+end
+
+RSpec.shared_examples 'a Note mutation when the given resource id is not for a Noteable' do
+ let(:noteable) { create(:label, project: project) }
+
+ it_behaves_like 'a Note mutation that does not create a Note'
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: ['Cannot add notes to this resource']
+end
+
+RSpec.shared_examples 'a Note mutation when the given resource id is not for a Note' do
+ let(:note) { create(:issue) }
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: ['Resource is not a note']
+end
diff --git a/spec/support/shared_examples/group_members_shared_example.rb b/spec/support/shared_examples/group_members_shared_example.rb
index 547c83c7955..4f7d496741d 100644
--- a/spec/support/shared_examples/group_members_shared_example.rb
+++ b/spec/support/shared_examples/group_members_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'members and requesters associations' do
describe '#members_and_requesters' do
it 'includes members and requesters' do
diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb
index 01bee603274..17f495ebe46 100644
--- a/spec/support/shared_examples/helm_generated_script.rb
+++ b/spec/support/shared_examples/helm_generated_script.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'helm commands' do
describe '#generate_script' do
let(:helm_setup) do
diff --git a/spec/support/shared_examples/issuable_shared_examples.rb b/spec/support/shared_examples/issuable_shared_examples.rb
index d97b21f71cd..3460a8ba297 100644
--- a/spec/support/shared_examples/issuable_shared_examples.rb
+++ b/spec/support/shared_examples/issuable_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'cache counters invalidator' do
it 'invalidates counter cache for assignees' do
expect_any_instance_of(User).to receive(:invalidate_merge_request_cache_counts)
diff --git a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
index 244f4766a84..52d90b5f183 100644
--- a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
+++ b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
include ProjectForksHelper
diff --git a/spec/support/shared_examples/issue_tracker_service_shared_example.rb b/spec/support/shared_examples/issue_tracker_service_shared_example.rb
index a6ab03cb808..0a483fd30ba 100644
--- a/spec/support/shared_examples/issue_tracker_service_shared_example.rb
+++ b/spec/support/shared_examples/issue_tracker_service_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr|
it { is_expected.to allow_value('https://example.com').for(url_attr) }
diff --git a/spec/support/shared_examples/ldap_shared_examples.rb b/spec/support/shared_examples/ldap_shared_examples.rb
index 52c34e78965..0a70ce7ea0c 100644
--- a/spec/support/shared_examples/ldap_shared_examples.rb
+++ b/spec/support/shared_examples/ldap_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'normalizes a DN' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb
index f326e502092..22e5698825d 100644
--- a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb
+++ b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'redirecting a legacy path' do |source, target|
include RSpec::Rails::RequestExampleGroup
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
index dcf7c1a90c2..2cbc0c2bdf2 100644
--- a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'backfill migration for project repositories' do |storage|
describe '#perform' do
let(:storage_versions) { storage == :legacy ? [nil, 0] : [1, 2] }
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb
new file mode 100644
index 00000000000..91bf804978d
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+shared_examples 'a redis usage counter' do |thing, event|
+ describe ".count(#{event})", :clean_gitlab_redis_shared_state do
+ it "increments the #{thing} #{event} counter by 1" do
+ expect do
+ described_class.count(event)
+ end.to change { described_class.read(event) }.by 1
+ end
+ end
+
+ describe ".read(#{event})", :clean_gitlab_redis_shared_state do
+ event_count = 5
+
+ it "returns the total number of #{event} events" do
+ event_count.times do
+ described_class.count(event)
+ end
+
+ expect(described_class.read(event)).to eq(event_count)
+ end
+ end
+end
+
+shared_examples 'a redis usage counter with totals' do |prefix, events|
+ describe 'totals', :clean_gitlab_redis_shared_state do
+ before do
+ events.each do |k, n|
+ n.times do
+ described_class.count(k)
+ end
+ end
+ end
+
+ let(:expected_totals) do
+ events.transform_keys { |k| "#{prefix}_#{k}".to_sym }
+ end
+
+ it 'can report all totals' do
+ expect(described_class.totals).to include(expected_totals)
+ end
+ end
+
+ # Override these let-bindings to adjust the unknown events tests
+ let(:unknown_event) { described_class::UnknownEvent }
+ let(:bad_event) { :wibble }
+
+ describe 'unknown events' do
+ it 'cannot increment' do
+ expect { described_class.count(bad_event) }.to raise_error unknown_event
+ end
+
+ it 'cannot read' do
+ expect { described_class.read(bad_event) }.to raise_error unknown_event
+ end
+ end
+end
diff --git a/spec/support/shared_examples/malicious_regexp_shared_examples.rb b/spec/support/shared_examples/malicious_regexp_shared_examples.rb
index a86050e2cf2..96c02260d53 100644
--- a/spec/support/shared_examples/malicious_regexp_shared_examples.rb
+++ b/spec/support/shared_examples/malicious_regexp_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'timeout'
shared_examples 'malicious regexp' do
diff --git a/spec/support/shared_examples/mentionable_shared_examples.rb b/spec/support/shared_examples/mentionable_shared_examples.rb
index 1226841f24c..93a8c4709a6 100644
--- a/spec/support/shared_examples/mentionable_shared_examples.rb
+++ b/spec/support/shared_examples/mentionable_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specifications for behavior common to all Mentionable implementations.
# Requires a shared context containing:
# - subject { "the mentionable implementation" }
@@ -76,6 +78,30 @@ shared_examples 'a mentionable' do
expect(refs).to include(ext_commit)
end
+ context 'when there are cached markdown fields' do
+ before do
+ if subject.is_a?(CacheMarkdownField)
+ subject.refresh_markdown_cache
+ end
+ end
+
+ it 'sends in cached markdown fields when appropriate' do
+ if subject.is_a?(CacheMarkdownField)
+ expect_next_instance_of(Gitlab::ReferenceExtractor) do |ext|
+ attrs = subject.class.mentionable_attrs.collect(&:first) & subject.cached_markdown_fields.markdown_fields
+ attrs.each do |field|
+ expect(ext).to receive(:analyze).with(subject.send(field), hash_including(rendered: anything))
+ end
+ end
+
+ expect(subject).not_to receive(:refresh_markdown_cache)
+ expect(subject).to receive(:cached_markdown_fields).at_least(:once).and_call_original
+
+ subject.all_references(author)
+ end
+ end
+ end
+
it 'creates cross-reference notes' do
mentioned_objects = [mentioned_issue, mentioned_mr, mentioned_commit,
ext_issue, ext_mr, ext_commit]
@@ -98,6 +124,33 @@ shared_examples 'an editable mentionable' do
[create(:issue, project: project), create(:issue, project: ext_proj)]
end
+ context 'when there are cached markdown fields' do
+ before do
+ if subject.is_a?(CacheMarkdownField)
+ subject.refresh_markdown_cache
+ end
+ end
+
+ it 'refreshes markdown cache if necessary' do
+ subject.save!
+
+ set_mentionable_text.call('This is a text')
+
+ if subject.is_a?(CacheMarkdownField)
+ expect_next_instance_of(Gitlab::ReferenceExtractor) do |ext|
+ subject.cached_markdown_fields.markdown_fields.each do |field|
+ expect(ext).to receive(:analyze).with(subject.send(field), hash_including(rendered: anything))
+ end
+ end
+
+ expect(subject).to receive(:refresh_markdown_cache)
+ expect(subject).to receive(:cached_markdown_fields).at_least(:once).and_call_original
+
+ subject.all_references(author)
+ end
+ end
+ end
+
it 'creates new cross-reference notes when the mentionable text is edited' do
subject.save
subject.create_cross_references!
diff --git a/spec/support/shared_examples/milestone_tabs_examples.rb b/spec/support/shared_examples/milestone_tabs_examples.rb
index 8b757586941..bda4b978737 100644
--- a/spec/support/shared_examples/milestone_tabs_examples.rb
+++ b/spec/support/shared_examples/milestone_tabs_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'milestone tabs' do
def go(path, extra_params = {})
params =
diff --git a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
index a248f60d23e..b837ca87256 100644
--- a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
+++ b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples_for 'AtomicInternalId' do |validate_presence: true|
diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_service_shared_examples.rb
index 0a302e7d030..b6a3d50d14a 100644
--- a/spec/support/shared_examples/models/chat_service_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
shared_examples_for "chat service" do |service_name|
@@ -220,7 +222,8 @@ shared_examples_for "chat service" do |service_name|
context "with not default branch" do
let(:pipeline) do
- create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch")
+ create(:ci_pipeline, :failed, project: project,
+ sha: project.commit.sha, ref: "not-the-default-branch")
end
context "when notify_only_default_branch enabled" do
diff --git a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
index d6490a808ce..8e58cc7ba22 100644
--- a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'cluster application core specs' do |application_name|
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_presence_of(:cluster) }
diff --git a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb
index bd3661471f8..7ddb3b11c85 100644
--- a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'cluster application helm specs' do |application_name|
let(:application) { create(application_name) }
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index 4525c03837f..5341aacb445 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'cluster application status specs' do |application_name|
describe '#status' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
diff --git a/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb b/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb
new file mode 100644
index 00000000000..8b298c5c974
--- /dev/null
+++ b/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+shared_examples_for 'a valid diff positionable note' do |factory_on_commit|
+ context 'for commit' do
+ let(:project) { create(:project, :repository) }
+ let(:commit) { project.commit(sample_commit.id) }
+ let(:commit_id) { commit.id }
+ let(:diff_refs) { commit.diff_refs }
+
+ let(:position) do
+ Gitlab::Diff::Position.new(
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: diff_refs
+ )
+ end
+
+ subject { build(factory_on_commit, commit_id: commit_id, position: position) }
+
+ context 'position diff refs matches commit diff refs' do
+ it 'is valid' do
+ expect(subject).to be_valid
+ expect(subject.errors).not_to have_key(:commit_id)
+ end
+ end
+
+ context 'position diff refs does not match commit diff refs' do
+ let(:diff_refs) do
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: "not_existing_sha",
+ head_sha: "existing_sha"
+ )
+ end
+
+ it 'is invalid' do
+ expect(subject).to be_invalid
+ expect(subject.errors).to have_key(:commit_id)
+ end
+ end
+
+ context 'commit does not exist' do
+ let(:commit_id) { 'non-existing' }
+
+ it 'is invalid' do
+ expect(subject).to be_invalid
+ expect(subject.errors).to have_key(:commit_id)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb
index a4762b68858..7ea2bb265cc 100644
--- a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb
+++ b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This shared example requires a `builder` and `user` variable
shared_examples 'issuable hook data' do |kind|
let(:data) { builder.build(user: user) }
diff --git a/spec/support/shared_examples/models/members_notifications_shared_example.rb b/spec/support/shared_examples/models/members_notifications_shared_example.rb
index ef5cea3f2a5..050d710f1de 100644
--- a/spec/support/shared_examples/models/members_notifications_shared_example.rb
+++ b/spec/support/shared_examples/models/members_notifications_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'members notifications' do |entity_type|
let(:notification_service) { double('NotificationService').as_null_object }
diff --git a/spec/support/shared_examples/models/project_hook_data_shared_examples.rb b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb
index f0264878811..03d10c10e3c 100644
--- a/spec/support/shared_examples/models/project_hook_data_shared_examples.rb
+++ b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'project hook data with deprecateds' do |project_key: :project|
it 'contains project data' do
expect(data[project_key][:name]).to eq(project.name)
diff --git a/spec/support/shared_examples/models/services_fields_shared_examples.rb b/spec/support/shared_examples/models/services_fields_shared_examples.rb
new file mode 100644
index 00000000000..6fbd0da9383
--- /dev/null
+++ b/spec/support/shared_examples/models/services_fields_shared_examples.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+shared_examples 'issue tracker fields' do
+ let(:title) { 'custom title' }
+ let(:description) { 'custom description' }
+ let(:url) { 'http://issue_tracker.example.com' }
+
+ context 'when data are stored in the properties' do
+ describe '#update' do
+ before do
+ service.update(title: 'new_title', description: 'new description')
+ end
+
+ it 'removes title and description from properties' do
+ expect(service.reload.properties).not_to include('title', 'description')
+ end
+
+ it 'stores title & description in services table' do
+ expect(service.read_attribute(:title)).to eq('new_title')
+ expect(service.read_attribute(:description)).to eq('new description')
+ end
+ end
+
+ describe 'reading fields' do
+ it 'returns correct values' do
+ expect(service.title).to eq(title)
+ expect(service.description).to eq(description)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
index 1b09c3dd636..e03435cafe8 100644
--- a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
+++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
@@ -25,16 +25,23 @@ shared_examples_for 'UpdateProjectStatistics' do
.to change { reload_stat }
.by(delta)
end
+
+ it 'schedules a namespace statistics worker' do
+ expect(Namespaces::ScheduleAggregationWorker)
+ .to receive(:perform_async).once
+
+ subject.save!
+ end
end
context 'when updating' do
+ let(:delta) { 42 }
+
before do
subject.save!
end
it 'updates project statistics' do
- delta = 42
-
expect(ProjectStatistics)
.to receive(:increment_statistic)
.and_call_original
@@ -45,6 +52,28 @@ shared_examples_for 'UpdateProjectStatistics' do
.to change { reload_stat }
.by(delta)
end
+
+ it 'schedules a namespace statistics worker' do
+ expect(Namespaces::ScheduleAggregationWorker)
+ .to receive(:perform_async).once
+
+ subject.write_attribute(statistic_attribute, read_attribute + delta)
+ subject.save!
+ end
+
+ it 'avoids N + 1 queries' do
+ subject.write_attribute(statistic_attribute, read_attribute + delta)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ subject.save!
+ end
+
+ subject.write_attribute(statistic_attribute, read_attribute + delta)
+
+ expect do
+ subject.save!
+ end.not_to exceed_query_limit(control_count)
+ end
end
context 'when destroying' do
@@ -59,11 +88,18 @@ shared_examples_for 'UpdateProjectStatistics' do
.to receive(:increment_statistic)
.and_call_original
- expect { subject.destroy }
+ expect { subject.destroy! }
.to change { reload_stat }
.by(delta)
end
+ it 'schedules a namespace statistics worker' do
+ expect(Namespaces::ScheduleAggregationWorker)
+ .to receive(:perform_async).once
+
+ subject.destroy!
+ end
+
context 'when it is destroyed from the project level' do
it 'does not update the project statistics' do
expect(ProjectStatistics)
@@ -72,6 +108,14 @@ shared_examples_for 'UpdateProjectStatistics' do
project.update(pending_delete: true)
project.destroy!
end
+
+ it 'does not schedule a namespace statistics worker' do
+ expect(Namespaces::ScheduleAggregationWorker)
+ .not_to receive(:perform_async)
+
+ project.update(pending_delete: true)
+ project.destroy!
+ end
end
end
end
diff --git a/spec/support/shared_examples/models/with_uploads_shared_examples.rb b/spec/support/shared_examples/models/with_uploads_shared_examples.rb
index 43033a2d256..eb1ade03017 100644
--- a/spec/support/shared_examples/models/with_uploads_shared_examples.rb
+++ b/spec/support/shared_examples/models/with_uploads_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples_for 'model with uploads' do |supports_fileuploads|
diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb
index e64c7e37a0c..ca031df000e 100644
--- a/spec/support/shared_examples/notify_shared_examples.rb
+++ b/spec/support/shared_examples/notify_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'gitlab email notification' do
set(:group) { create(:group) }
set(:subgroup) { create(:group, parent: group) }
@@ -42,42 +44,17 @@ shared_examples 'an email sent from GitLab' do
end
shared_examples 'an email sent to a user' do
- let(:group_notification_email) { 'user+group@example.com' }
-
it 'is sent to user\'s global notification email address' do
expect(subject).to deliver_to(recipient.notification_email)
end
- context 'that is part of a project\'s group' do
- it 'is sent to user\'s group notification email address when set' do
- create(:notification_setting, user: recipient, source: project.group, notification_email: group_notification_email)
- expect(subject).to deliver_to(group_notification_email)
- end
-
- it 'is sent to user\'s global notification email address when no group email set' do
- create(:notification_setting, user: recipient, source: project.group, notification_email: '')
- expect(subject).to deliver_to(recipient.notification_email)
- end
- end
+ context 'with group notification email' do
+ it 'is sent to user\'s group notification email' do
+ group_notification_email = 'user+group@example.com'
- context 'when project is in a sub-group', :nested_groups do
- before do
- project.update!(group: subgroup)
- end
-
- it 'is sent to user\'s subgroup notification email address when set' do
- # Set top-level group notification email address to make sure it doesn't get selected
create(:notification_setting, user: recipient, source: group, notification_email: group_notification_email)
- subgroup_notification_email = 'user+subgroup@example.com'
- create(:notification_setting, user: recipient, source: subgroup, notification_email: subgroup_notification_email)
-
- expect(subject).to deliver_to(subgroup_notification_email)
- end
-
- it 'is sent to user\'s group notification email address when set and subgroup email address not set' do
- create(:notification_setting, user: recipient, source: subgroup, notification_email: '')
- expect(subject).to deliver_to(recipient.notification_email)
+ expect(subject).to deliver_to(group_notification_email)
end
end
end
diff --git a/spec/support/shared_examples/policies/clusterable_shared_examples.rb b/spec/support/shared_examples/policies/clusterable_shared_examples.rb
index d99f94c76c3..0b427c23256 100644
--- a/spec/support/shared_examples/policies/clusterable_shared_examples.rb
+++ b/spec/support/shared_examples/policies/clusterable_shared_examples.rb
@@ -13,7 +13,11 @@ shared_examples 'clusterable policies' do
clusterable.add_developer(current_user)
end
+ it { expect_disallowed(:read_cluster) }
it { expect_disallowed(:add_cluster) }
+ it { expect_disallowed(:create_cluster) }
+ it { expect_disallowed(:update_cluster) }
+ it { expect_disallowed(:admin_cluster) }
end
context 'with a maintainer' do
@@ -22,15 +26,11 @@ shared_examples 'clusterable policies' do
end
context 'with no clusters' do
+ it { expect_allowed(:read_cluster) }
it { expect_allowed(:add_cluster) }
- end
-
- context 'with an existing cluster' do
- before do
- cluster
- end
-
- it { expect_disallowed(:add_cluster) }
+ it { expect_allowed(:create_cluster) }
+ it { expect_allowed(:update_cluster) }
+ it { expect_allowed(:admin_cluster) }
end
end
end
diff --git a/spec/support/shared_examples/position_formatters.rb b/spec/support/shared_examples/position_formatters.rb
index ffc9456dbc7..30b6b8d24f0 100644
--- a/spec/support/shared_examples/position_formatters.rb
+++ b/spec/support/shared_examples/position_formatters.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for "position formatter" do
let(:formatter) { described_class.new(attrs) }
diff --git a/spec/support/shared_examples/project_latest_successful_build_for_examples.rb b/spec/support/shared_examples/project_latest_successful_build_for_examples.rb
new file mode 100644
index 00000000000..a9bd23e9fc9
--- /dev/null
+++ b/spec/support/shared_examples/project_latest_successful_build_for_examples.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+shared_examples 'latest successful build for sha or ref' do
+ context 'with many builds' do
+ let(:other_pipeline) { create_pipeline(project) }
+ let(:other_build) { create_build(other_pipeline, 'test') }
+ let(:build_name) { other_build.name }
+
+ before do
+ pipeline1 = create_pipeline(project)
+ pipeline2 = create_pipeline(project)
+ create_build(pipeline1, 'test')
+ create_build(pipeline1, 'test2')
+ create_build(pipeline2, 'test2')
+ end
+
+ it 'gives the latest builds from latest pipeline' do
+ expect(subject).to eq(other_build)
+ end
+ end
+
+ context 'with succeeded pipeline' do
+ let!(:build) { create_build }
+ let(:build_name) { build.name }
+
+ context 'standalone pipeline' do
+ it 'returns builds for ref for default_branch' do
+ expect(subject).to eq(build)
+ end
+
+ context 'with nonexistent build' do
+ let(:build_name) { 'TAIL' }
+
+ it 'returns empty relation if the build cannot be found' do
+ expect(subject).to be_nil
+ end
+ end
+ end
+
+ context 'with some pending pipeline' do
+ before do
+ create_build(create_pipeline(project, 'pending'))
+ end
+
+ it 'gives the latest build from latest pipeline' do
+ expect(subject).to eq(build)
+ end
+ end
+ end
+
+ context 'with pending pipeline' do
+ let!(:pending_build) { create_build(pipeline) }
+ let(:build_name) { pending_build.name }
+
+ before do
+ pipeline.update(status: 'pending')
+ end
+
+ it 'returns empty relation' do
+ expect(subject).to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
index b337a1c18d8..f5a86e4dc2c 100644
--- a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
@@ -5,7 +5,7 @@ shared_examples 'tag quick action' do
it 'tags this commit' do
add_note("/tag #{tag_name} #{tag_message}")
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content %{Tagged this commit to #{tag_name} with "#{tag_message}".}
expect(page).to have_content "tagged commit #{truncated_commit_sha}"
expect(page).to have_content tag_name
diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
index a79a61bc708..6e7eb78261a 100644
--- a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
@@ -68,7 +68,7 @@ shared_examples 'close quick action' do |issuable_type|
it "does not close the #{issuable_type}" do
add_note('/close')
- expect(page).not_to have_content 'Commands applied'
+ expect(page).not_to have_content "Closed this #{issuable.to_ability_name.humanize(capitalize: false)}."
expect(issuable).to be_open
end
end
diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
index 34dba5dbc31..3e9ee9a633f 100644
--- a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
@@ -2,8 +2,14 @@
shared_examples 'create_merge_request quick action' do
context 'create a merge request starting from an issue' do
- def expect_mr_quickaction(success)
- expect(page).to have_content 'Commands applied'
+ def expect_mr_quickaction(success, branch_name = nil)
+ command_message = if branch_name
+ "Created branch '#{branch_name}' and a merge request to resolve this issue"
+ else
+ "Created a branch and a merge request to resolve this issue"
+ end
+
+ expect(page).to have_content command_message
if success
expect(page).to have_content 'created merge request'
@@ -13,19 +19,21 @@ shared_examples 'create_merge_request quick action' do
end
it "doesn't create a merge request when the branch name is invalid" do
- add_note("/create_merge_request invalid branch name")
+ branch_name = 'invalid branch name'
+ add_note("/create_merge_request #{branch_name}")
wait_for_requests
- expect_mr_quickaction(false)
+ expect_mr_quickaction(false, branch_name)
end
it "doesn't create a merge request when a branch with that name already exists" do
- add_note("/create_merge_request feature")
+ branch_name = 'feature'
+ add_note("/create_merge_request #{branch_name}")
wait_for_requests
- expect_mr_quickaction(false)
+ expect_mr_quickaction(false, branch_name)
end
it 'creates a new merge request using issue iid and title as branch name when the branch name is empty' do
@@ -46,7 +54,7 @@ shared_examples 'create_merge_request quick action' do
branch_name = '1-feature'
add_note("/create_merge_request #{branch_name}")
- expect_mr_quickaction(true)
+ expect_mr_quickaction(true, branch_name)
created_mr = project.merge_requests.last
expect(created_mr.source_branch).to eq(branch_name)
diff --git a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
index 633c7135fbc..3834b8b2b87 100644
--- a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
@@ -9,7 +9,6 @@ shared_examples 'duplicate quick action' do
add_note("/duplicate ##{original_issue.to_reference}")
expect(page).not_to have_content "/duplicate #{original_issue.to_reference}"
- expect(page).to have_content 'Commands applied'
expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
expect(issue.reload).to be_closed
@@ -28,7 +27,6 @@ shared_examples 'duplicate quick action' do
it 'does not create a note, and does not mark the issue as a duplicate' do
add_note("/duplicate ##{original_issue.to_reference}")
- expect(page).not_to have_content 'Commands applied'
expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
expect(issue.reload).to be_open
diff --git a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
index a0b0d888769..bebc8509d53 100644
--- a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
@@ -12,7 +12,7 @@ shared_examples 'move quick action' do
it 'moves the issue' do
add_note("/move #{target_project.full_path}")
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "Moved this issue to #{target_project.full_path}."
expect(issue.reload).to be_closed
visit project_issue_path(target_project, issue)
@@ -29,7 +29,7 @@ shared_examples 'move quick action' do
wait_for_requests
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "Moved this issue to #{project_unauthorized.full_path}."
expect(issue.reload).to be_open
end
end
@@ -40,7 +40,7 @@ shared_examples 'move quick action' do
wait_for_requests
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "Failed to move this issue because target project doesn't exist."
expect(issue.reload).to be_open
end
end
@@ -56,7 +56,7 @@ shared_examples 'move quick action' do
shared_examples 'applies the commands to issues in both projects, target and source' do
it "applies quick actions" do
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "Moved this issue to #{target_project.full_path}."
expect(issue.reload).to be_closed
visit project_issue_path(target_project, issue)
@@ -89,5 +89,54 @@ shared_examples 'move quick action' do
it_behaves_like 'applies the commands to issues in both projects, target and source'
end
end
+
+ context 'when editing comments' do
+ let(:target_project) { create(:project, :public) }
+
+ before do
+ target_project.add_maintainer(user)
+
+ sign_in(user)
+ visit project_issue_path(project, issue)
+ wait_for_all_requests
+ end
+
+ it 'moves the issue after quickcommand note was updated' do
+ # misspelled quick action
+ add_note("test note.\n/mvoe #{target_project.full_path}")
+
+ expect(issue.reload).not_to be_closed
+
+ edit_note("/mvoe #{target_project.full_path}", "test note.\n/move #{target_project.full_path}")
+ wait_for_all_requests
+
+ expect(page).to have_content 'test note.'
+ expect(issue.reload).to be_closed
+
+ visit project_issue_path(target_project, issue)
+ wait_for_all_requests
+
+ expect(page).to have_content 'Issues 1'
+ end
+
+ it 'deletes the note if it was updated to just contain a command' do
+ # missspelled quick action
+ add_note("test note.\n/mvoe #{target_project.full_path}")
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(issue.reload).not_to be_closed
+
+ edit_note("/mvoe #{target_project.full_path}", "/move #{target_project.full_path}")
+ wait_for_all_requests
+
+ expect(page).not_to have_content "/move #{target_project.full_path}"
+ expect(issue.reload).to be_closed
+
+ visit project_issue_path(target_project, issue)
+ wait_for_all_requests
+
+ expect(page).to have_content 'Issues 1'
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
index c454ddc4bba..ac7c17915de 100644
--- a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
@@ -10,7 +10,7 @@ shared_examples 'merge quick action' do
it 'merges the MR' do
add_note("/merge")
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content 'Scheduled to merge this merge request when the pipeline succeeds.'
expect(merge_request.reload).to be_merged
end
diff --git a/spec/support/shared_examples/reference_parser_shared_examples.rb b/spec/support/shared_examples/reference_parser_shared_examples.rb
index baf8bcc04b8..d903c0f10e0 100644
--- a/spec/support/shared_examples/reference_parser_shared_examples.rb
+++ b/spec/support/shared_examples/reference_parser_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples "referenced feature visibility" do |*related_features|
let(:feature_fields) do
related_features.map { |feature| (feature + "_access_level").to_sym }
diff --git a/spec/support/shared_examples/relative_positioning_shared_examples.rb b/spec/support/shared_examples/relative_positioning_shared_examples.rb
new file mode 100644
index 00000000000..b7382cea93c
--- /dev/null
+++ b/spec/support/shared_examples/relative_positioning_shared_examples.rb
@@ -0,0 +1,283 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a class that supports relative positioning' do
+ let(:item1) { create(factory, default_params) }
+ let(:item2) { create(factory, default_params) }
+ let(:new_item) { create(factory, default_params) }
+
+ def create_item(params)
+ create(factory, params.merge(default_params))
+ end
+
+ def create_items_with_positions(positions)
+ positions.map do |position|
+ create_item(relative_position: position)
+ end
+ end
+
+ describe '.move_nulls_to_end' do
+ it 'moves items with null relative_position to the end' do
+ item1.update!(relative_position: nil)
+ item2.update!(relative_position: nil)
+
+ described_class.move_nulls_to_end([item1, item2])
+
+ expect(item2.prev_relative_position).to eq item1.relative_position
+ expect(item1.prev_relative_position).to eq nil
+ expect(item2.next_relative_position).to eq nil
+ end
+
+ it 'moves the item near the start position when there are no existing positions' do
+ item1.update!(relative_position: nil)
+
+ described_class.move_nulls_to_end([item1])
+
+ expect(item1.relative_position).to eq(described_class::START_POSITION + described_class::IDEAL_DISTANCE)
+ end
+
+ it 'does not perform any moves if all items have their relative_position set' do
+ item1.update!(relative_position: 1)
+
+ expect(item1).not_to receive(:save)
+
+ described_class.move_nulls_to_end([item1])
+ end
+ end
+
+ describe '#max_relative_position' do
+ it 'returns maximum position' do
+ expect(item1.max_relative_position).to eq item2.relative_position
+ end
+ end
+
+ describe '#prev_relative_position' do
+ it 'returns previous position if there is an item above' do
+ item1.update(relative_position: 5)
+ item2.update(relative_position: 15)
+
+ expect(item2.prev_relative_position).to eq item1.relative_position
+ end
+
+ it 'returns nil if there is no item above' do
+ expect(item1.prev_relative_position).to eq nil
+ end
+ end
+
+ describe '#next_relative_position' do
+ it 'returns next position if there is an item below' do
+ item1.update(relative_position: 5)
+ item2.update(relative_position: 15)
+
+ expect(item1.next_relative_position).to eq item2.relative_position
+ end
+
+ it 'returns nil if there is no item below' do
+ expect(item2.next_relative_position).to eq nil
+ end
+ end
+
+ describe '#move_before' do
+ it 'moves item before' do
+ [item2, item1].each(&:move_to_end)
+
+ item1.move_before(item2)
+
+ expect(item1.relative_position).to be < item2.relative_position
+ end
+ end
+
+ describe '#move_after' do
+ it 'moves item after' do
+ [item1, item2].each(&:move_to_end)
+
+ item1.move_after(item2)
+
+ expect(item1.relative_position).to be > item2.relative_position
+ end
+ end
+
+ describe '#move_to_end' do
+ before do
+ [item1, item2].each do |item1|
+ item1.move_to_end && item1.save
+ end
+ end
+
+ it 'moves item to the end' do
+ new_item.move_to_end
+
+ expect(new_item.relative_position).to be > item2.relative_position
+ end
+ end
+
+ describe '#move_between' do
+ before do
+ [item1, item2].each do |item1|
+ item1.move_to_end && item1.save
+ end
+ end
+
+ it 'positions item between two other' do
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to be > item1.relative_position
+ expect(new_item.relative_position).to be < item2.relative_position
+ end
+
+ it 'positions item between on top' do
+ new_item.move_between(nil, item1)
+
+ expect(new_item.relative_position).to be < item1.relative_position
+ end
+
+ it 'positions item between to end' do
+ new_item.move_between(item2, nil)
+
+ expect(new_item.relative_position).to be > item2.relative_position
+ end
+
+ it 'positions items even when after and before positions are the same' do
+ item2.update relative_position: item1.relative_position
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to be > item1.relative_position
+ expect(item1.relative_position).to be < item2.relative_position
+ end
+
+ it 'positions items between other two if distance is 1' do
+ item2.update relative_position: item1.relative_position + 1
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to be > item1.relative_position
+ expect(item1.relative_position).to be < item2.relative_position
+ end
+
+ it 'positions item in the middle of other two if distance is big enough' do
+ item1.update relative_position: 6000
+ item2.update relative_position: 10000
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to eq(8000)
+ end
+
+ it 'positions item closer to the middle if we are at the very top' do
+ item2.update relative_position: 6000
+
+ new_item.move_between(nil, item2)
+
+ expect(new_item.relative_position).to eq(6000 - RelativePositioning::IDEAL_DISTANCE)
+ end
+
+ it 'positions item closer to the middle if we are at the very bottom' do
+ new_item.update relative_position: 1
+ item1.update relative_position: 6000
+ item2.destroy
+
+ new_item.move_between(item1, nil)
+
+ expect(new_item.relative_position).to eq(6000 + RelativePositioning::IDEAL_DISTANCE)
+ end
+
+ it 'positions item in the middle of other two if distance is not big enough' do
+ item1.update relative_position: 100
+ item2.update relative_position: 400
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to eq(250)
+ end
+
+ it 'positions item in the middle of other two is there is no place' do
+ item1.update relative_position: 100
+ item2.update relative_position: 101
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to be_between(item1.relative_position, item2.relative_position)
+ end
+
+ it 'uses rebalancing if there is no place' do
+ item1.update relative_position: 100
+ item2.update relative_position: 101
+ item3 = create_item(relative_position: 102)
+ new_item.update relative_position: 103
+
+ new_item.move_between(item2, item3)
+ new_item.save!
+
+ expect(new_item.relative_position).to be_between(item2.relative_position, item3.relative_position)
+ expect(item1.reload.relative_position).not_to eq(100)
+ end
+
+ it 'positions item right if we pass none-sequential parameters' do
+ item1.update relative_position: 99
+ item2.update relative_position: 101
+ item3 = create_item(relative_position: 102)
+ new_item.update relative_position: 103
+
+ new_item.move_between(item1, item3)
+ new_item.save!
+
+ expect(new_item.relative_position).to be(100)
+ end
+
+ it 'avoids N+1 queries when rebalancing other items' do
+ items = create_items_with_positions([100, 101, 102])
+
+ count = ActiveRecord::QueryRecorder.new do
+ new_item.move_between(items[-2], items[-1])
+ end
+
+ items = create_items_with_positions([150, 151, 152, 153, 154])
+
+ expect { new_item.move_between(items[-2], items[-1]) }.not_to exceed_query_limit(count)
+ end
+ end
+
+ describe '#move_sequence_before' do
+ it 'moves the whole sequence of items to the middle of the nearest gap' do
+ items = create_items_with_positions([90, 100, 101, 102])
+
+ items.last.move_sequence_before
+ items.last.save!
+
+ positions = items.map { |item| item.reload.relative_position }
+ expect(positions).to eq([90, 95, 96, 102])
+ end
+
+ it 'finds a gap if there are unused positions' do
+ items = create_items_with_positions([100, 101, 102])
+
+ items.last.move_sequence_before
+ items.last.save!
+
+ positions = items.map { |item| item.reload.relative_position }
+ expect(positions).to eq([50, 51, 102])
+ end
+ end
+
+ describe '#move_sequence_after' do
+ it 'moves the whole sequence of items to the middle of the nearest gap' do
+ items = create_items_with_positions([100, 101, 102, 110])
+
+ items.first.move_sequence_after
+ items.first.save!
+
+ positions = items.map { |item| item.reload.relative_position }
+ expect(positions).to eq([100, 105, 106, 110])
+ end
+
+ it 'finds a gap if there are unused positions' do
+ items = create_items_with_positions([100, 101, 102])
+
+ items.first.move_sequence_after
+ items.first.save!
+
+ positions = items.map { |item| item.reload.relative_position }
+ expect(positions).to eq([100, 601, 602])
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb
index 8a7fcf856a1..776a0bdd29e 100644
--- a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'custom attributes endpoints' do |attributable_name|
let!(:custom_attribute1) { attributable.custom_attributes.create key: 'foo', value: 'foo' }
let!(:custom_attribute2) { attributable.custom_attributes.create key: 'bar', value: 'bar' }
diff --git a/spec/support/shared_examples/requests/api/diff_discussions.rb b/spec/support/shared_examples/requests/api/diff_discussions.rb
index 366c2955359..76c6c93964a 100644
--- a/spec/support/shared_examples/requests/api/diff_discussions.rb
+++ b/spec/support/shared_examples/requests/api/diff_discussions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name|
describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do
it "includes diff discussions" do
diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb
index c3132c41f5b..a36bc2dc9b5 100644
--- a/spec/support/shared_examples/requests/api/discussions.rb
+++ b/spec/support/shared_examples/requests/api/discussions.rb
@@ -1,3 +1,59 @@
+# frozen_string_literal: true
+
+shared_examples 'with cross-reference system notes' do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:new_merge_request) { create(:merge_request) }
+ let(:commit) { new_merge_request.project.commit }
+ let!(:note) { create(:system_note, noteable: merge_request, project: project, note: cross_reference) }
+ let!(:note_metadata) { create(:system_note_metadata, note: note, action: 'cross_reference') }
+ let(:cross_reference) { "test commit #{commit.to_reference(project)}" }
+ let(:pat) { create(:personal_access_token, user: user) }
+
+ before do
+ project.add_developer(user)
+ new_merge_request.project.add_developer(user)
+
+ hidden_merge_request = create(:merge_request)
+ new_cross_reference = "test commit #{hidden_merge_request.project.commit}"
+ new_note = create(:system_note, noteable: merge_request, project: project, note: new_cross_reference)
+ create(:system_note_metadata, note: new_note, action: 'cross_reference')
+ end
+
+ it 'returns only the note that the user should see' do
+ get api(url, user, personal_access_token: pat)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.count).to eq(1)
+ expect(notes_in_response.count).to eq(1)
+
+ parsed_note = notes_in_response.first
+ expect(parsed_note['id']).to eq(note.id)
+ expect(parsed_note['body']).to eq(cross_reference)
+ expect(parsed_note['system']).to be true
+ end
+
+ it 'avoids Git calls and N+1 SQL queries', :request_store do
+ expect_any_instance_of(Repository).not_to receive(:find_commit).with(commit.id)
+
+ control = ActiveRecord::QueryRecorder.new do
+ get api(url, user, personal_access_token: pat)
+ end
+
+ expect(response).to have_gitlab_http_status(200)
+
+ RequestStore.clear!
+
+ new_note = create(:system_note, noteable: merge_request, project: project, note: cross_reference)
+ create(:system_note_metadata, note: new_note, action: 'cross_reference')
+
+ RequestStore.clear!
+
+ expect { get api(url, user, personal_access_token: pat) }.not_to exceed_query_limit(control)
+ expect(response).to have_gitlab_http_status(200)
+ end
+end
+
shared_examples 'discussions API' do |parent_type, noteable_type, id_name, can_reply_to_individual_notes: false|
describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do
it "returns an array of discussions" do
diff --git a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
index 96d59e0c472..9fe6288d53f 100644
--- a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
+++ b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuable participants endpoint' do
let(:area) { entity.class.name.underscore.pluralize }
diff --git a/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb b/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb
new file mode 100644
index 00000000000..90c1ed8d09b
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+def get_issue
+ json_response.is_a?(Array) ? json_response.detect {|issue| issue['id'] == target_issue.id} : json_response
+end
+
+shared_examples 'accessible merge requests count' do
+ it 'returns anonymous accessible merge requests count' do
+ get api(api_url), params: { scope: 'all' }
+
+ issue = get_issue
+ expect(issue).not_to be_nil
+ expect(issue['merge_requests_count']).to eq(1)
+ end
+
+ it 'returns guest accessible merge requests count' do
+ get api(api_url, guest), params: { scope: 'all' }
+
+ issue = get_issue
+ expect(issue).not_to be_nil
+ expect(issue['merge_requests_count']).to eq(1)
+ end
+
+ it 'returns reporter accessible merge requests count' do
+ get api(api_url, user), params: { scope: 'all' }
+
+ issue = get_issue
+ expect(issue).not_to be_nil
+ expect(issue['merge_requests_count']).to eq(2)
+ end
+
+ it 'returns admin accessible merge requests count' do
+ get api(api_url, admin), params: { scope: 'all' }
+
+ issue = get_issue
+ expect(issue).not_to be_nil
+ expect(issue['merge_requests_count']).to eq(2)
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/notes.rb b/spec/support/shared_examples/requests/api/notes.rb
index 57eefd5ef01..354ae7288b1 100644
--- a/spec/support/shared_examples/requests/api/notes.rb
+++ b/spec/support/shared_examples/requests/api/notes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do
context 'sorting' do
diff --git a/spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb b/spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb
new file mode 100644
index 00000000000..dfd07176b1c
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb
@@ -0,0 +1,235 @@
+# frozen_string_literal: true
+
+shared_examples 'pipelines visibility table' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:ci_user) { create(:user) }
+ let(:api_user) { user_role && ci_user }
+
+ let(:pipelines_api_path) do
+ "/projects/#{project.id}/pipelines"
+ end
+
+ let(:response_200) do
+ a_collection_containing_exactly(
+ a_hash_including('sha', 'ref', 'status', 'web_url', 'id' => pipeline.id)
+ )
+ end
+
+ let(:response_40x) do
+ a_hash_including('message')
+ end
+
+ let(:expected_response) do
+ if response_status == 200
+ response_200
+ else
+ response_40x
+ end
+ end
+
+ let(:api_response) { json_response }
+
+ let(:visibility_levels) do
+ {
+ private: Gitlab::VisibilityLevel::PRIVATE,
+ internal: Gitlab::VisibilityLevel::INTERNAL,
+ public: Gitlab::VisibilityLevel::PUBLIC
+ }
+ end
+
+ let(:builds_access_levels) do
+ {
+ enabled: ProjectFeature::ENABLED,
+ private: ProjectFeature::PRIVATE
+ }
+ end
+
+ let(:project_attributes) do
+ {
+ visibility_level: visibility_levels[visibility_level],
+ public_builds: public_builds
+ }
+ end
+
+ let(:project_feature_attributes) do
+ {
+ builds_access_level: builds_access_levels[builds_access_level]
+ }
+ end
+
+ where(:visibility_level, :builds_access_level, :public_builds, :is_admin, :user_role, :response_status) do
+ :private | :enabled | true | true | :non_member | 200
+ :private | :enabled | true | true | :guest | 200
+ :private | :enabled | true | true | :reporter | 200
+ :private | :enabled | true | true | :developer | 200
+ :private | :enabled | true | true | :maintainer | 200
+
+ :private | :enabled | true | false | nil | 404
+ :private | :enabled | true | false | :non_member | 404
+ :private | :enabled | true | false | :guest | 200
+ :private | :enabled | true | false | :reporter | 200
+ :private | :enabled | true | false | :developer | 200
+ :private | :enabled | true | false | :maintainer | 200
+
+ :private | :enabled | false | true | :non_member | 200
+ :private | :enabled | false | true | :guest | 200
+ :private | :enabled | false | true | :reporter | 200
+ :private | :enabled | false | true | :developer | 200
+ :private | :enabled | false | true | :maintainer | 200
+
+ :private | :enabled | false | false | nil | 404
+ :private | :enabled | false | false | :non_member | 404
+ :private | :enabled | false | false | :guest | 403
+ :private | :enabled | false | false | :reporter | 200
+ :private | :enabled | false | false | :developer | 200
+ :private | :enabled | false | false | :maintainer | 200
+
+ :private | :private | true | true | :non_member | 200
+ :private | :private | true | true | :guest | 200
+ :private | :private | true | true | :reporter | 200
+ :private | :private | true | true | :developer | 200
+ :private | :private | true | true | :maintainer | 200
+
+ :private | :private | true | false | nil | 404
+ :private | :private | true | false | :non_member | 404
+ :private | :private | true | false | :guest | 200
+ :private | :private | true | false | :reporter | 200
+ :private | :private | true | false | :developer | 200
+ :private | :private | true | false | :maintainer | 200
+
+ :private | :private | false | true | :non_member | 200
+ :private | :private | false | true | :guest | 200
+ :private | :private | false | true | :reporter | 200
+ :private | :private | false | true | :developer | 200
+ :private | :private | false | true | :maintainer | 200
+
+ :private | :private | false | false | nil | 404
+ :private | :private | false | false | :non_member | 404
+ :private | :private | false | false | :guest | 403
+ :private | :private | false | false | :reporter | 200
+ :private | :private | false | false | :developer | 200
+ :private | :private | false | false | :maintainer | 200
+
+ :internal | :enabled | true | true | :non_member | 200
+ :internal | :enabled | true | true | :guest | 200
+ :internal | :enabled | true | true | :reporter | 200
+ :internal | :enabled | true | true | :developer | 200
+ :internal | :enabled | true | true | :maintainer | 200
+
+ :internal | :enabled | true | false | nil | 404
+ :internal | :enabled | true | false | :non_member | 200
+ :internal | :enabled | true | false | :guest | 200
+ :internal | :enabled | true | false | :reporter | 200
+ :internal | :enabled | true | false | :developer | 200
+ :internal | :enabled | true | false | :maintainer | 200
+
+ :internal | :enabled | false | true | :non_member | 200
+ :internal | :enabled | false | true | :guest | 200
+ :internal | :enabled | false | true | :reporter | 200
+ :internal | :enabled | false | true | :developer | 200
+ :internal | :enabled | false | true | :maintainer | 200
+
+ :internal | :enabled | false | false | nil | 404
+ :internal | :enabled | false | false | :non_member | 403
+ :internal | :enabled | false | false | :guest | 403
+ :internal | :enabled | false | false | :reporter | 200
+ :internal | :enabled | false | false | :developer | 200
+ :internal | :enabled | false | false | :maintainer | 200
+
+ :internal | :private | true | true | :non_member | 200
+ :internal | :private | true | true | :guest | 200
+ :internal | :private | true | true | :reporter | 200
+ :internal | :private | true | true | :developer | 200
+ :internal | :private | true | true | :maintainer | 200
+
+ :internal | :private | true | false | nil | 404
+ :internal | :private | true | false | :non_member | 403
+ :internal | :private | true | false | :guest | 200
+ :internal | :private | true | false | :reporter | 200
+ :internal | :private | true | false | :developer | 200
+ :internal | :private | true | false | :maintainer | 200
+
+ :internal | :private | false | true | :non_member | 200
+ :internal | :private | false | true | :guest | 200
+ :internal | :private | false | true | :reporter | 200
+ :internal | :private | false | true | :developer | 200
+ :internal | :private | false | true | :maintainer | 200
+
+ :internal | :private | false | false | nil | 404
+ :internal | :private | false | false | :non_member | 403
+ :internal | :private | false | false | :guest | 403
+ :internal | :private | false | false | :reporter | 200
+ :internal | :private | false | false | :developer | 200
+ :internal | :private | false | false | :maintainer | 200
+
+ :public | :enabled | true | true | :non_member | 200
+ :public | :enabled | true | true | :guest | 200
+ :public | :enabled | true | true | :reporter | 200
+ :public | :enabled | true | true | :developer | 200
+ :public | :enabled | true | true | :maintainer | 200
+
+ :public | :enabled | true | false | nil | 200
+ :public | :enabled | true | false | :non_member | 200
+ :public | :enabled | true | false | :guest | 200
+ :public | :enabled | true | false | :reporter | 200
+ :public | :enabled | true | false | :developer | 200
+ :public | :enabled | true | false | :maintainer | 200
+
+ :public | :enabled | false | true | :non_member | 200
+ :public | :enabled | false | true | :guest | 200
+ :public | :enabled | false | true | :reporter | 200
+ :public | :enabled | false | true | :developer | 200
+ :public | :enabled | false | true | :maintainer | 200
+
+ :public | :enabled | false | false | nil | 403
+ :public | :enabled | false | false | :non_member | 403
+ :public | :enabled | false | false | :guest | 403
+ :public | :enabled | false | false | :reporter | 200
+ :public | :enabled | false | false | :developer | 200
+ :public | :enabled | false | false | :maintainer | 200
+
+ :public | :private | true | true | :non_member | 200
+ :public | :private | true | true | :guest | 200
+ :public | :private | true | true | :reporter | 200
+ :public | :private | true | true | :developer | 200
+ :public | :private | true | true | :maintainer | 200
+
+ :public | :private | true | false | nil | 403
+ :public | :private | true | false | :non_member | 403
+ :public | :private | true | false | :guest | 200
+ :public | :private | true | false | :reporter | 200
+ :public | :private | true | false | :developer | 200
+ :public | :private | true | false | :maintainer | 200
+
+ :public | :private | false | true | :non_member | 200
+ :public | :private | false | true | :guest | 200
+ :public | :private | false | true | :reporter | 200
+ :public | :private | false | true | :developer | 200
+ :public | :private | false | true | :maintainer | 200
+
+ :public | :private | false | false | nil | 403
+ :public | :private | false | false | :non_member | 403
+ :public | :private | false | false | :guest | 403
+ :public | :private | false | false | :reporter | 200
+ :public | :private | false | false | :developer | 200
+ :public | :private | false | false | :maintainer | 200
+ end
+
+ with_them do
+ before do
+ ci_user.update!(admin: is_admin) if user_role
+
+ project.update!(project_attributes)
+ project.project_feature.update!(project_feature_attributes)
+ project.add_role(ci_user, user_role) if user_role && user_role != :non_member
+
+ get api(pipelines_api_path, api_user)
+ end
+
+ it do
+ expect(response).to have_gitlab_http_status(response_status)
+ expect(api_response).to match(expected_response)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/resolvable_discussions.rb b/spec/support/shared_examples/requests/api/resolvable_discussions.rb
index 7e2416b23f3..42054a273f3 100644
--- a/spec/support/shared_examples/requests/api/resolvable_discussions.rb
+++ b/spec/support/shared_examples/requests/api/resolvable_discussions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_name|
describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do
it "resolves discussion if resolved is true" do
diff --git a/spec/support/shared_examples/requests/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb
index ebfc5fed3bb..eebed7e42c1 100644
--- a/spec/support/shared_examples/requests/api/status_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specs for status checking.
#
# Requires an API request:
diff --git a/spec/support/shared_examples/requests/graphql_shared_examples.rb b/spec/support/shared_examples/requests/graphql_shared_examples.rb
index 04140cad3f0..2a38d56141a 100644
--- a/spec/support/shared_examples/requests/graphql_shared_examples.rb
+++ b/spec/support/shared_examples/requests/graphql_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'a working graphql query' do
diff --git a/spec/support/shared_examples/resource_label_events_api.rb b/spec/support/shared_examples/resource_label_events_api.rb
new file mode 100644
index 00000000000..945cb8d9f2c
--- /dev/null
+++ b/spec/support/shared_examples/resource_label_events_api.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+shared_examples 'resource_label_events API' do |parent_type, eventable_type, id_name|
+ describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events" do
+ it "returns an array of resource label events" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(event.id)
+ end
+
+ it "returns a 404 error when eventable id not found" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/12345/resource_label_events", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it "returns 404 when not authorized" do
+ parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ private_user = create(:user)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", private_user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events/:event_id" do
+ it "returns a resource label event by id" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/#{event.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['id']).to eq(event.id)
+ end
+
+ it "returns a 404 error if resource label event not found" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/12345", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/serializers/note_entity_examples.rb b/spec/support/shared_examples/serializers/note_entity_examples.rb
index ec208aba2a9..bfcaa2f1bd5 100644
--- a/spec/support/shared_examples/serializers/note_entity_examples.rb
+++ b/spec/support/shared_examples/serializers/note_entity_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'note entity' do
subject { entity.as_json }
diff --git a/spec/support/shared_examples/services/boards/boards_create_service.rb b/spec/support/shared_examples/services/boards/boards_create_service.rb
index 5bdc04f660f..19818a6091b 100644
--- a/spec/support/shared_examples/services/boards/boards_create_service.rb
+++ b/spec/support/shared_examples/services/boards/boards_create_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'boards create service' do
context 'when parent does not have a board' do
it 'creates a new board' do
diff --git a/spec/support/shared_examples/services/boards/boards_list_service.rb b/spec/support/shared_examples/services/boards/boards_list_service.rb
index e0d5a7c61f2..566e5050f8e 100644
--- a/spec/support/shared_examples/services/boards/boards_list_service.rb
+++ b/spec/support/shared_examples/services/boards/boards_list_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'boards list service' do
context 'when parent does not have a board' do
it 'creates a new parent board' do
diff --git a/spec/support/shared_examples/services/boards/issues_list_service.rb b/spec/support/shared_examples/services/boards/issues_list_service.rb
index 8b879cef084..75733c774ef 100644
--- a/spec/support/shared_examples/services/boards/issues_list_service.rb
+++ b/spec/support/shared_examples/services/boards/issues_list_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issues list service' do
it 'delegates search to IssuesFinder' do
params = { board_id: board.id, id: list1.id }
diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb
index 5359831f8f8..d3fa8084185 100644
--- a/spec/support/shared_examples/services/boards/issues_move_service.rb
+++ b/spec/support/shared_examples/services/boards/issues_move_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issues move service' do |group|
shared_examples 'updating timestamps' do
it 'updates updated_at' do
diff --git a/spec/support/shared_examples/services/boards/lists_destroy_service.rb b/spec/support/shared_examples/services/boards/lists_destroy_service.rb
index 62b6ffe1836..95725078f9d 100644
--- a/spec/support/shared_examples/services/boards/lists_destroy_service.rb
+++ b/spec/support/shared_examples/services/boards/lists_destroy_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'lists destroy service' do
context 'when list type is label' do
it 'removes list from board' do
diff --git a/spec/support/shared_examples/services/boards/lists_list_service.rb b/spec/support/shared_examples/services/boards/lists_list_service.rb
index 0a8220111ab..29784f6da08 100644
--- a/spec/support/shared_examples/services/boards/lists_list_service.rb
+++ b/spec/support/shared_examples/services/boards/lists_list_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'lists list service' do
context 'when the board has a backlog list' do
let!(:backlog_list) { create(:backlog_list, board: board) }
diff --git a/spec/support/shared_examples/services/boards/lists_move_service.rb b/spec/support/shared_examples/services/boards/lists_move_service.rb
index 2cdb968a45d..0b3bfd8e2a8 100644
--- a/spec/support/shared_examples/services/boards/lists_move_service.rb
+++ b/spec/support/shared_examples/services/boards/lists_move_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'lists move service' do
let!(:planning) { create(:list, board: board, position: 0) }
let!(:development) { create(:list, board: board, position: 1) }
diff --git a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
index 02de47a96dd..1e0ac8b7615 100644
--- a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'check ingress ip executions' do |app_name|
describe '#execute' do
let(:application) { create(app_name, :installed) }
diff --git a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb
index b8db35a6ef9..1c3fa5644d3 100644
--- a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'gitlab projects import validations' do
context 'with an invalid path' do
let(:path) { '/invalid-path/' }
diff --git a/spec/support/shared_examples/services/notification_service_shared_examples.rb b/spec/support/shared_examples/services/notification_service_shared_examples.rb
new file mode 100644
index 00000000000..dd338ea47c7
--- /dev/null
+++ b/spec/support/shared_examples/services/notification_service_shared_examples.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+# Note that we actually update the attribute on the target_project/group, rather than
+# using `allow`. This is because there are some specs where, based on how the notification
+# is done, using an `allow` doesn't change the correct object.
+shared_examples 'project emails are disabled' do
+ let(:target_project) { notification_target.is_a?(Project) ? notification_target : notification_target.project }
+
+ before do
+ reset_delivered_emails!
+ target_project.clear_memoization(:emails_disabled)
+ end
+
+ it 'sends no emails with project emails disabled' do
+ target_project.update_attribute(:emails_disabled, true)
+
+ notification_trigger
+
+ should_not_email_anyone
+ end
+
+ it 'sends emails to someone' do
+ target_project.update_attribute(:emails_disabled, false)
+
+ notification_trigger
+
+ should_email_anyone
+ end
+end
+
+shared_examples 'group emails are disabled' do
+ let(:target_group) { notification_target.is_a?(Group) ? notification_target : notification_target.project.group }
+
+ before do
+ reset_delivered_emails!
+ target_group.clear_memoization(:emails_disabled)
+ end
+
+ it 'sends no emails with group emails disabled' do
+ target_group.update_attribute(:emails_disabled, true)
+
+ notification_trigger
+
+ should_not_email_anyone
+ end
+
+ it 'sends emails to someone' do
+ target_group.update_attribute(:emails_disabled, false)
+
+ notification_trigger
+
+ should_email_anyone
+ end
+end
diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
index 36c486dbdd6..8ce94064dc3 100644
--- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f }
RSpec.shared_examples 'slack or mattermost notifications' do
@@ -452,7 +454,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
context 'only notify for the default branch' do
context 'when enabled' do
let(:pipeline) do
- create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch')
+ create(:ci_pipeline, :failed, project: project, sha: project.commit.sha, ref: 'not-the-default-branch')
end
before do
@@ -470,7 +472,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
context 'when disabled' do
let(:pipeline) do
- create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch')
+ create(:ci_pipeline, :failed, project: project, sha: project.commit.sha, ref: 'not-the-default-branch')
end
before do
diff --git a/spec/support/shared_examples/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/snippet_visibility_shared_examples.rb
index 833c31a57cb..b5321c6db34 100644
--- a/spec/support/shared_examples/snippet_visibility_shared_examples.rb
+++ b/spec/support/shared_examples/snippet_visibility_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'snippet visibility' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/support/shared_examples/snippets_shared_examples.rb b/spec/support/shared_examples/snippets_shared_examples.rb
index 85f0facd5c3..5c35617bd36 100644
--- a/spec/support/shared_examples/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/snippets_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These shared examples expect a `snippets` array of snippets
RSpec.shared_examples 'paginated snippets' do |remote: false|
it "is limited to #{Snippet.default_per_page} items per page" do
diff --git a/spec/support/shared_examples/taskable_shared_examples.rb b/spec/support/shared_examples/taskable_shared_examples.rb
index 4056ff06b84..f04f509f3d2 100644
--- a/spec/support/shared_examples/taskable_shared_examples.rb
+++ b/spec/support/shared_examples/taskable_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specs for task state functionality for issues and merge requests.
#
# Requires a context containing:
@@ -105,4 +107,25 @@ shared_examples 'a Taskable' do
expect(subject.task_status_short).to match('1 task')
end
end
+
+ describe 'with tasks in blockquotes' do
+ before do
+ subject.description = <<-EOT.strip_heredoc
+ > - [ ] Task a
+ > > - [x] Task a.1
+
+ >>>
+ 1. [ ] Task 1
+ 1. [x] Task 2
+ >>>
+ EOT
+ end
+
+ it 'returns the correct task status' do
+ expect(subject.task_status).to match('2 of')
+ expect(subject.task_status).to match('4 tasks completed')
+ expect(subject.task_status_short).to match('2/')
+ expect(subject.task_status_short).to match('4 tasks')
+ end
+ end
end
diff --git a/spec/support/shared_examples/throttled_touch.rb b/spec/support/shared_examples/throttled_touch.rb
index eba990d4037..aaaa590862d 100644
--- a/spec/support/shared_examples/throttled_touch.rb
+++ b/spec/support/shared_examples/throttled_touch.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'throttled touch' do
describe '#touch' do
it 'updates the updated_at timestamp' do
diff --git a/spec/support/shared_examples/unique_ip_check_shared_examples.rb b/spec/support/shared_examples/unique_ip_check_shared_examples.rb
index e5c8ac6a004..65d86ddee9e 100644
--- a/spec/support/shared_examples/unique_ip_check_shared_examples.rb
+++ b/spec/support/shared_examples/unique_ip_check_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'unique ips sign in limit' do
include StubENV
before do
diff --git a/spec/support/shared_examples/update_invalid_issuable.rb b/spec/support/shared_examples/update_invalid_issuable.rb
index 64568de424e..b7ac08372f9 100644
--- a/spec/support/shared_examples/update_invalid_issuable.rb
+++ b/spec/support/shared_examples/update_invalid_issuable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'update invalid issuable' do |klass|
let(:params) do
{
@@ -38,7 +40,7 @@ shared_examples 'update invalid issuable' do |klass|
put :update, params: params
expect(response.status).to eq(409)
- expect(JSON.parse(response.body)).to have_key('errors')
+ expect(json_response).to have_key('errors')
end
end
diff --git a/spec/support/shared_examples/updating_mentions_shared_examples.rb b/spec/support/shared_examples/updating_mentions_shared_examples.rb
index 5e3f19ba19e..ef385f94cc2 100644
--- a/spec/support/shared_examples/updating_mentions_shared_examples.rb
+++ b/spec/support/shared_examples/updating_mentions_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'updating mentions' do |service_class|
let(:mentioned_user) { create(:user) }
let(:service_class) { service_class }
diff --git a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
index 1190863d88e..9263aaff89a 100644
--- a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples "matches the method pattern" do |method|
let(:target) { subject }
let(:args) { nil }
diff --git a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
index 1bd176280c5..5d605dd811b 100644
--- a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'with storage' do |store, **stub_params|
before do
subject.object_store = store
diff --git a/spec/support/shared_examples/url_validator_examples.rb b/spec/support/shared_examples/url_validator_examples.rb
index 25277ccd9aa..c5a775fefb6 100644
--- a/spec/support/shared_examples/url_validator_examples.rb
+++ b/spec/support/shared_examples/url_validator_examples.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'url validator examples' do |schemes|
- let(:validator) { described_class.new(attributes: [:link_url], **options) }
- let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
+ describe '#validate' do
+ let(:validator) { described_class.new(attributes: [:link_url], **options) }
+ let(:badge) { build(:badge, link_url: 'http://www.example.com') }
- subject { validator.validate(badge) }
+ subject { validator.validate(badge) }
- describe '#validate' do
context 'with no options' do
let(:options) { {} }
@@ -40,3 +42,52 @@ RSpec.shared_examples 'url validator examples' do |schemes|
end
end
end
+
+RSpec.shared_examples 'public url validator examples' do |setting|
+ let(:validator) { described_class.new(attributes: [:link_url]) }
+ let(:badge) { build(:badge, link_url: 'http://www.example.com') }
+
+ subject { validator.validate(badge) }
+
+ context 'by default' do
+ it 'blocks urls pointing to localhost' do
+ badge.link_url = 'https://127.0.0.1'
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+
+ it 'blocks urls pointing to the local network' do
+ badge.link_url = 'https://192.168.1.1'
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+ end
+
+ context 'when local requests are allowed' do
+ let!(:settings) { create(:application_setting) }
+
+ before do
+ stub_application_setting(setting)
+ end
+
+ it 'does not block urls pointing to localhost' do
+ badge.link_url = 'https://127.0.0.1'
+
+ subject
+
+ expect(badge.errors).not_to be_present
+ end
+
+ it 'does not block urls pointing to the local network' do
+ badge.link_url = 'https://192.168.1.1'
+
+ subject
+
+ expect(badge.errors).not_to be_present
+ end
+ end
+end
diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb
index d1a765f27b9..585c458a64e 100644
--- a/spec/support/sidekiq.rb
+++ b/spec/support/sidekiq.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'sidekiq/testing/inline'
# If Sidekiq::Testing.inline! is used, SQL transactions done inside
diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb
index 55212355daa..95f0f971787 100644
--- a/spec/support/stored_repositories.rb
+++ b/spec/support/stored_repositories.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.before(:each, :broken_storage) do
allow(Gitlab::GitalyClient).to receive(:call) do
diff --git a/spec/support/test_reports/test_reports_helper.rb b/spec/support/test_reports/test_reports_helper.rb
index 45c6e04dbf3..6ba50c83b25 100644
--- a/spec/support/test_reports/test_reports_helper.rb
+++ b/spec/support/test_reports/test_reports_helper.rb
@@ -1,36 +1,38 @@
+# frozen_string_literal: true
+
module TestReportsHelper
- def create_test_case_rspec_success
+ def create_test_case_rspec_success(name = 'test_spec')
Gitlab::Ci::Reports::TestCase.new(
name: 'Test#sum when a is 1 and b is 3 returns summary',
- classname: 'spec.test_spec',
+ classname: "spec.#{name}",
file: './spec/test_spec.rb',
execution_time: 1.11,
status: Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS)
end
- def create_test_case_rspec_failed
+ def create_test_case_rspec_failed(name = 'test_spec')
Gitlab::Ci::Reports::TestCase.new(
- name: 'Test#sum when a is 2 and b is 2 returns summary',
- classname: 'spec.test_spec',
+ name: 'Test#sum when a is 1 and b is 3 returns summary',
+ classname: "spec.#{name}",
file: './spec/test_spec.rb',
execution_time: 2.22,
system_output: sample_rspec_failed_message,
status: Gitlab::Ci::Reports::TestCase::STATUS_FAILED)
end
- def create_test_case_rspec_skipped
+ def create_test_case_rspec_skipped(name = 'test_spec')
Gitlab::Ci::Reports::TestCase.new(
name: 'Test#sum when a is 3 and b is 3 returns summary',
- classname: 'spec.test_spec',
+ classname: "spec.#{name}",
file: './spec/test_spec.rb',
execution_time: 3.33,
status: Gitlab::Ci::Reports::TestCase::STATUS_SKIPPED)
end
- def create_test_case_rspec_error
+ def create_test_case_rspec_error(name = 'test_spec')
Gitlab::Ci::Reports::TestCase.new(
name: 'Test#sum when a is 4 and b is 4 returns summary',
- classname: 'spec.test_spec',
+ classname: "spec.#{name}",
file: './spec/test_spec.rb',
execution_time: 4.44,
status: Gitlab::Ci::Reports::TestCase::STATUS_ERROR)
@@ -48,34 +50,34 @@ module TestReportsHelper
EOF
end
- def create_test_case_java_success
+ def create_test_case_java_success(name = 'addTest')
Gitlab::Ci::Reports::TestCase.new(
- name: 'addTest',
+ name: name,
classname: 'CalculatorTest',
execution_time: 5.55,
status: Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS)
end
- def create_test_case_java_failed
+ def create_test_case_java_failed(name = 'addTest')
Gitlab::Ci::Reports::TestCase.new(
- name: 'subtractTest',
+ name: name,
classname: 'CalculatorTest',
execution_time: 6.66,
system_output: sample_java_failed_message,
status: Gitlab::Ci::Reports::TestCase::STATUS_FAILED)
end
- def create_test_case_java_skipped
+ def create_test_case_java_skipped(name = 'addTest')
Gitlab::Ci::Reports::TestCase.new(
- name: 'multiplyTest',
+ name: name,
classname: 'CalculatorTest',
execution_time: 7.77,
status: Gitlab::Ci::Reports::TestCase::STATUS_SKIPPED)
end
- def create_test_case_java_error
+ def create_test_case_java_error(name = 'addTest')
Gitlab::Ci::Reports::TestCase.new(
- name: 'divideTest',
+ name: name,
classname: 'CalculatorTest',
execution_time: 8.88,
status: Gitlab::Ci::Reports::TestCase::STATUS_ERROR)
diff --git a/spec/support/trace/trace_helpers.rb b/spec/support/trace/trace_helpers.rb
index c7802bbcb94..9255715ff71 100644
--- a/spec/support/trace/trace_helpers.rb
+++ b/spec/support/trace/trace_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TraceHelpers
def create_legacy_trace(build, content)
File.open(legacy_trace_path(build), 'wb') { |stream| stream.write(content) }
diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb
index 9ac7e7fc515..32b88edc2df 100644
--- a/spec/support/webmock.rb
+++ b/spec/support/webmock.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'webmock'
require 'webmock/rspec'
diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb
index 19794227d9f..4aee6d005a8 100644
--- a/spec/tasks/gitlab/cleanup_rake_spec.rb
+++ b/spec/tasks/gitlab/cleanup_rake_spec.rb
@@ -156,4 +156,63 @@ describe 'gitlab:cleanup rake tasks' do
end
end
end
+
+ describe 'gitlab:cleanup:orphan_job_artifact_files' do
+ subject(:rake_task) { run_rake_task('gitlab:cleanup:orphan_job_artifact_files') }
+
+ it 'runs the task without errors' do
+ expect(Gitlab::Cleanup::OrphanJobArtifactFiles)
+ .to receive(:new).and_call_original
+
+ expect { rake_task }.not_to raise_error
+ end
+
+ context 'with DRY_RUN set to false' do
+ before do
+ stub_env('DRY_RUN', 'false')
+ end
+
+ it 'passes dry_run correctly' do
+ expect(Gitlab::Cleanup::OrphanJobArtifactFiles)
+ .to receive(:new)
+ .with(limit: anything,
+ dry_run: false,
+ niceness: anything,
+ logger: anything)
+ .and_call_original
+
+ rake_task
+ end
+ end
+ end
+
+ context 'sessions' do
+ describe 'gitlab:cleanup:sessions:active_sessions_lookup_keys', :clean_gitlab_redis_shared_state do
+ subject(:rake_task) { run_rake_task('gitlab:cleanup:sessions:active_sessions_lookup_keys') }
+
+ let!(:user) { create(:user) }
+ let(:existing_session_id) { '5' }
+
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set("session:user:gitlab:#{user.id}:#{existing_session_id}",
+ Marshal.dump(true))
+ redis.sadd("session:lookup:user:gitlab:#{user.id}", (1..10).to_a)
+ end
+ end
+
+ it 'runs the task without errors' do
+ expect { rake_task }.not_to raise_error
+ end
+
+ it 'removes expired active session lookup keys' do
+ Gitlab::Redis::SharedState.with do |redis|
+ lookup_key = "session:lookup:user:gitlab:#{user.id}"
+ expect { subject }.to change { redis.scard(lookup_key) }.from(10).to(1)
+ expect(redis.smembers("session:lookup:user:gitlab:#{user.id}")).to(
+ eql([existing_session_id]))
+ end
+ end
+ end
+ end
end
diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
deleted file mode 100644
index 8d1cff7a261..00000000000
--- a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'spec_helper'
-require 'rake'
-
-describe 'gitlab:mail_google_schema_whitelisting rake task' do
- before :all do
- Rake.application.rake_require "tasks/gitlab/helpers"
- Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting"
- # empty task as env is already loaded
- Rake::Task.define_task :environment
- end
-
- describe 'call' do
- before do
- # avoid writing task output to spec progress
- allow($stdout).to receive :write
- end
-
- let :run_rake_task do
- Rake::Task["gitlab:mail_google_schema_whitelisting"].reenable
- Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting"
- end
-
- it 'runs the task without errors' do
- expect { run_rake_task }.not_to raise_error
- end
- end
-end
diff --git a/spec/tasks/gitlab/storage_rake_spec.rb b/spec/tasks/gitlab/storage_rake_spec.rb
index 4b04d9cec39..0e47408fc72 100644
--- a/spec/tasks/gitlab/storage_rake_spec.rb
+++ b/spec/tasks/gitlab/storage_rake_spec.rb
@@ -50,7 +50,7 @@ describe 'rake gitlab:storage:*', :sidekiq do
expect(Project).not_to receive(:with_unmigrated_storage)
- expect { run_rake_task(task) }.to output(/This task requires database write access. Exiting./).to_stderr
+ expect { run_rake_task(task) }.to abort_execution.with_message(/This task requires database write access. Exiting./)
end
end
end
@@ -96,7 +96,7 @@ describe 'rake gitlab:storage:*', :sidekiq do
expect(Project).not_to receive(:with_unmigrated_storage)
- expect { run_rake_task(task) }.to output(/There is already a rollback operation in progress/).to_stderr
+ expect { run_rake_task(task) }.to abort_execution.with_message(/There is already a rollback operation in progress/)
end
end
end
@@ -105,14 +105,23 @@ describe 'rake gitlab:storage:*', :sidekiq do
it 'does nothing' do
expect(::HashedStorage::MigratorWorker).not_to receive(:perform_async)
- run_rake_task(task)
+ expect { run_rake_task(task) }.to abort_execution.with_message('There are no projects requiring storage migration. Nothing to do!')
end
end
context 'with 3 legacy projects' do
let(:projects) { create_list(:project, 3, :legacy_storage) }
- it_behaves_like "handles custom BATCH env var", ::HashedStorage::MigratorWorker
+ it 'enqueues migrations and count projects correctly' do
+ projects.map(&:id).sort.tap do |ids|
+ stub_env('ID_FROM', ids[0])
+ stub_env('ID_TO', ids[1])
+ end
+
+ expect { run_rake_task(task) }.to output(/Enqueuing migration of 2 projects in batches/).to_stdout
+ end
+
+ it_behaves_like 'handles custom BATCH env var', ::HashedStorage::MigratorWorker
end
context 'with same id in range' do
@@ -120,7 +129,7 @@ describe 'rake gitlab:storage:*', :sidekiq do
stub_env('ID_FROM', 99999)
stub_env('ID_TO', 99999)
- expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=99999/).to_stderr
+ expect { run_rake_task(task) }.to abort_execution.with_message(/There are no projects requiring storage migration with ID=99999/)
end
it 'displays a message when project exists but its already migrated' do
@@ -128,7 +137,7 @@ describe 'rake gitlab:storage:*', :sidekiq do
stub_env('ID_FROM', project.id)
stub_env('ID_TO', project.id)
- expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=#{project.id}/).to_stderr
+ expect { run_rake_task(task) }.to abort_execution.with_message(/There are no projects requiring storage migration with ID=#{project.id}/)
end
it 'enqueues migration when project can be found' do
@@ -153,7 +162,7 @@ describe 'rake gitlab:storage:*', :sidekiq do
expect(Project).not_to receive(:with_unmigrated_storage)
- expect { run_rake_task(task) }.to output(/There is already a migration operation in progress/).to_stderr
+ expect { run_rake_task(task) }.to abort_execution.with_message(/There is already a migration operation in progress/)
end
end
end
@@ -162,13 +171,22 @@ describe 'rake gitlab:storage:*', :sidekiq do
it 'does nothing' do
expect(::HashedStorage::RollbackerWorker).not_to receive(:perform_async)
- run_rake_task(task)
+ expect { run_rake_task(task) }.to abort_execution.with_message('There are no projects that can have storage rolledback. Nothing to do!')
end
end
context 'with 3 hashed projects' do
let(:projects) { create_list(:project, 3) }
+ it 'enqueues migrations and count projects correctly' do
+ projects.map(&:id).sort.tap do |ids|
+ stub_env('ID_FROM', ids[0])
+ stub_env('ID_TO', ids[1])
+ end
+
+ expect { run_rake_task(task) }.to output(/Enqueuing rollback of 2 projects in batches/).to_stdout
+ end
+
it_behaves_like "handles custom BATCH env var", ::HashedStorage::RollbackerWorker
end
end
diff --git a/spec/tasks/gitlab/update_templates_rake_spec.rb b/spec/tasks/gitlab/update_templates_rake_spec.rb
new file mode 100644
index 00000000000..14b4ad5e3d8
--- /dev/null
+++ b/spec/tasks/gitlab/update_templates_rake_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+describe 'gitlab:update_project_templates rake task' do
+ let!(:tmpdir) { Dir.mktmpdir }
+
+ before do
+ Rake.application.rake_require 'tasks/gitlab/update_templates'
+ create(:admin)
+
+ allow(Gitlab::ProjectTemplate)
+ .to receive(:archive_directory)
+ .and_return(Pathname.new(tmpdir))
+
+ # Gitlab::HTTP resolves the domain to an IP prior to WebMock taking effect, hence the wildcard
+ stub_request(:get, %r{^https://.*/api/v4/projects/gitlab-org%2Fproject-templates%2Frails/repository/commits\?page=1&per_page=1})
+ .to_return(
+ status: 200,
+ body: [{ id: '67812735b83cb42710f22dc98d73d42c8bf4d907' }].to_json,
+ headers: { 'Content-Type' => 'application/json' }
+ )
+ end
+
+ after do
+ FileUtils.rm_rf(tmpdir)
+ end
+
+ it 'updates valid project templates' do
+ expect { run_rake_task('gitlab:update_project_templates', ['rails']) }
+ .to change { Dir.entries(tmpdir) }
+ .by(['rails.tar.gz'])
+ end
+end
diff --git a/spec/tasks/migrate/schema_check_rake_spec.rb b/spec/tasks/migrate/schema_check_rake_spec.rb
new file mode 100644
index 00000000000..1097a43cd8a
--- /dev/null
+++ b/spec/tasks/migrate/schema_check_rake_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rake'
+
+describe 'schema_version_check rake task' do
+ include StubENV
+
+ before :all do
+ Rake.application.rake_require 'active_record/railties/databases'
+ Rake.application.rake_require 'tasks/migrate/schema_check'
+
+ # empty task as env is already loaded
+ Rake::Task.define_task :environment
+ end
+
+ before do
+ allow(ActiveRecord::Migrator).to receive(:current_version).and_return(Gitlab::Database::MIN_SCHEMA_VERSION)
+
+ # Ensure our check can re-run each time
+ Rake::Task[:schema_version_check].reenable
+ end
+
+ it 'allows migrations on databases meeting the min schema version requirement' do
+ expect { run_rake_task('schema_version_check') }.not_to raise_error
+ end
+
+ it 'raises an error when schema version is too old to migrate' do
+ allow(ActiveRecord::Migrator).to receive(:current_version).and_return(25)
+ expect { run_rake_task('schema_version_check') }.to raise_error(RuntimeError, /current database version is too old to be migrated/)
+ end
+
+ it 'skips running validation when passed the skip env variable' do
+ stub_env('SKIP_SCHEMA_VERSION_CHECK', 'true')
+ allow(ActiveRecord::Migrator).to receive(:current_version).and_return(25)
+ expect { run_rake_task('schema_version_check') }.not_to raise_error
+ end
+
+ it 'allows migrations on fresh databases' do
+ allow(ActiveRecord::Migrator).to receive(:current_version).and_return(0)
+ expect { run_rake_task('schema_version_check') }.not_to raise_error
+ end
+
+ def run_rake_task(task_name)
+ Rake::Task[task_name].reenable
+ Rake.application.invoke_task task_name
+ end
+end
diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb
index e474a714b10..5ee0a10f38d 100644
--- a/spec/uploaders/file_mover_spec.rb
+++ b/spec/uploaders/file_mover_spec.rb
@@ -3,79 +3,139 @@ require 'spec_helper'
describe FileMover do
include FileMoverHelpers
+ let(:user) { create(:user) }
let(:filename) { 'banana_sample.gif' }
- let(:temp_file_path) { File.join('uploads/-/system/temp', 'secret55', filename) }
+ let(:secret) { 'secret55' }
+ let(:temp_file_path) { File.join("uploads/-/system/user/#{user.id}", secret, filename) }
let(:temp_description) do
"test ![banana_sample](/#{temp_file_path}) "\
"same ![banana_sample](/#{temp_file_path}) "
end
- let(:file_path) { File.join('uploads/-/system/personal_snippet', snippet.id.to_s, 'secret55', filename) }
+ let(:file_path) { File.join('uploads/-/system/personal_snippet', snippet.id.to_s, secret, filename) }
let(:snippet) { create(:personal_snippet, description: temp_description) }
- subject { described_class.new(temp_file_path, snippet).execute }
+ let(:tmp_uploader) do
+ PersonalFileUploader.new(user, secret: secret)
+ end
+
+ let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') }
+ subject { described_class.new(temp_file_path, from_model: user, to_model: snippet).execute }
describe '#execute' do
- before do
- expect(FileUtils).to receive(:mkdir_p).with(a_string_including(File.dirname(file_path)))
- expect(FileUtils).to receive(:move).with(a_string_including(temp_file_path), a_string_including(file_path))
- allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:exists?).and_return(true)
- allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:size).and_return(10)
+ let(:tmp_upload) { tmp_uploader.upload }
- stub_file_mover(temp_file_path)
+ before do
+ tmp_uploader.store!(file)
end
- context 'when move and field update successful' do
- it 'updates the description correctly' do
- subject
+ context 'local storage' do
+ before do
+ allow(FileUtils).to receive(:mkdir_p).with(a_string_including(File.dirname(file_path)))
+ allow(FileUtils).to receive(:move).with(a_string_including(temp_file_path), a_string_including(file_path))
+ allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:exists?).and_return(true)
+ allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:size).and_return(10)
- expect(snippet.reload.description)
- .to eq(
- "test ![banana_sample](/uploads/-/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif) "\
- "same ![banana_sample](/uploads/-/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif) "
- )
+ stub_file_mover(temp_file_path)
end
- it 'creates a new update record' do
- expect { subject }.to change { Upload.count }.by(1)
+ context 'when move and field update successful' do
+ it 'updates the description correctly' do
+ subject
+
+ expect(snippet.reload.description)
+ .to eq("test ![banana_sample](/uploads/-/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif) "\
+ "same ![banana_sample](/uploads/-/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif) ")
+ end
+
+ it 'updates existing upload record' do
+ expect { subject }
+ .to change { tmp_upload.reload.attributes.values_at('model_id', 'model_type') }
+ .from([user.id, 'User']).to([snippet.id, 'Snippet'])
+ end
+
+ it 'schedules a background migration' do
+ expect_any_instance_of(PersonalFileUploader).to receive(:schedule_background_upload).once
+
+ subject
+ end
end
- it 'schedules a background migration' do
- expect_any_instance_of(PersonalFileUploader).to receive(:schedule_background_upload).once
+ context 'when update_markdown fails' do
+ before do
+ expect(FileUtils).to receive(:move).with(a_string_including(file_path), a_string_including(temp_file_path))
+ end
- subject
+ subject { described_class.new(file_path, :non_existing_field, from_model: user, to_model: snippet).execute }
+
+ it 'does not update the description' do
+ subject
+
+ expect(snippet.reload.description)
+ .to eq("test ![banana_sample](/uploads/-/system/user/#{user.id}/secret55/banana_sample.gif) "\
+ "same ![banana_sample](/uploads/-/system/user/#{user.id}/secret55/banana_sample.gif) ")
+ end
+
+ it 'does not change the upload record' do
+ expect { subject }
+ .not_to change { tmp_upload.reload.attributes.values_at('model_id', 'model_type') }
+ end
end
end
- context 'when update_markdown fails' do
+ context 'when tmp uploader is not local storage' do
before do
- expect(FileUtils).to receive(:move).with(a_string_including(file_path), a_string_including(temp_file_path))
+ stub_uploads_object_storage(uploader: PersonalFileUploader)
+ allow_any_instance_of(PersonalFileUploader).to receive(:file_storage?) { false }
end
- subject { described_class.new(file_path, snippet, :non_existing_field).execute }
+ after do
+ FileUtils.rm_f(File.join('personal_snippet', snippet.id.to_s, secret, filename))
+ end
- it 'does not update the description' do
- subject
+ context 'when move and field update successful' do
+ it 'updates the description correctly' do
+ subject
- expect(snippet.reload.description)
- .to eq(
- "test ![banana_sample](/uploads/-/system/temp/secret55/banana_sample.gif) "\
- "same ![banana_sample](/uploads/-/system/temp/secret55/banana_sample.gif) "
- )
+ expect(snippet.reload.description)
+ .to eq("test ![banana_sample](/uploads/-/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif) "\
+ "same ![banana_sample](/uploads/-/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif) ")
+ end
+
+ it 'creates new target upload record an delete the old upload' do
+ expect { subject }
+ .to change { Upload.last.attributes.values_at('model_id', 'model_type') }
+ .from([user.id, 'User']).to([snippet.id, 'Snippet'])
+
+ expect(Upload.count).to eq(1)
+ end
end
- it 'does not create a new update record' do
- expect { subject }.not_to change { Upload.count }
+ context 'when update_markdown fails' do
+ subject { described_class.new(file_path, :non_existing_field, from_model: user, to_model: snippet).execute }
+
+ it 'does not update the description' do
+ subject
+
+ expect(snippet.reload.description)
+ .to eq("test ![banana_sample](/uploads/-/system/user/#{user.id}/secret55/banana_sample.gif) "\
+ "same ![banana_sample](/uploads/-/system/user/#{user.id}/secret55/banana_sample.gif) ")
+ end
+
+ it 'does not change the upload record' do
+ expect { subject }
+ .to change { Upload.last.attributes.values_at('model_id', 'model_type') }.from([user.id, 'User'])
+ end
end
end
end
context 'security' do
context 'when relative path is involved' do
- let(:temp_file_path) { File.join('uploads/-/system/temp', '..', 'another_subdir_of_temp') }
+ let(:temp_file_path) { File.join("uploads/-/system/user/#{user.id}", '..', 'another_subdir_of_temp') }
it 'does not trigger move if path is outside designated directory' do
- stub_file_mover('uploads/-/system/another_subdir_of_temp')
+ stub_file_mover("uploads/-/system/user/#{user.id}/another_subdir_of_temp")
expect(FileUtils).not_to receive(:move)
subject
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index 185c62491ce..04206de3dc6 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -184,40 +184,37 @@ describe FileUploader do
end
end
- describe '#cache!' do
- subject do
- uploader.store!(uploaded_file)
- end
+ context 'when remote file is used' do
+ let(:temp_file) { Tempfile.new("test") }
- context 'when remote file is used' do
- let(:temp_file) { Tempfile.new("test") }
+ let!(:fog_connection) do
+ stub_uploads_object_storage(described_class)
+ end
- let!(:fog_connection) do
- stub_uploads_object_storage(described_class)
- end
+ let(:filename) { "my file.txt" }
+ let(:uploaded_file) do
+ UploadedFile.new(temp_file.path, filename: filename, remote_id: "test/123123")
+ end
- let(:uploaded_file) do
- UploadedFile.new(temp_file.path, filename: "my file.txt", remote_id: "test/123123")
- end
+ let!(:fog_file) do
+ fog_connection.directories.new(key: 'uploads').files.create(
+ key: 'tmp/uploads/test/123123',
+ body: 'content'
+ )
+ end
- let!(:fog_file) do
- fog_connection.directories.new(key: 'uploads').files.create(
- key: 'tmp/uploads/test/123123',
- body: 'content'
- )
- end
+ before do
+ FileUtils.touch(temp_file)
- before do
- FileUtils.touch(temp_file)
- end
+ uploader.store!(uploaded_file)
+ end
- after do
- FileUtils.rm_f(temp_file)
- end
+ after do
+ FileUtils.rm_f(temp_file)
+ end
+ describe '#cache!' do
it 'file is stored remotely in permament location with sanitized name' do
- subject
-
expect(uploader).to be_exists
expect(uploader).not_to be_cached
expect(uploader).not_to be_file_storage
@@ -228,5 +225,18 @@ describe FileUploader do
expect(uploader.object_store).to eq(described_class::Store::REMOTE)
end
end
+
+ describe '#to_h' do
+ subject { uploader.to_h }
+
+ let(:filename) { 'my+file.txt' }
+
+ it 'generates URL using original file name instead of filename returned by object storage' do
+ # GCS returns a URL with a `+` instead of `%2B`
+ allow(uploader.file).to receive(:url).and_return('https://storage.googleapis.com/gitlab-test-uploads/@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b/64c5065e62100b1a12841644256a98be/my+file.txt')
+
+ expect(subject[:url]).to end_with(filename)
+ end
+ end
end
end
diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb
index 42352f9b9f8..6134137d2b7 100644
--- a/spec/uploaders/records_uploads_spec.rb
+++ b/spec/uploaders/records_uploads_spec.rb
@@ -85,6 +85,27 @@ describe RecordsUploads do
expect { existing.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect(Upload.count).to eq(1)
end
+
+ it 'does not affect other uploads with different model but the same path' do
+ project = create(:project)
+ other_project = create(:project)
+
+ uploader = RecordsUploadsExampleUploader.new(other_project)
+
+ upload_for_project = Upload.create!(
+ path: File.join('uploads', 'rails_sample.jpg'),
+ size: 512.kilobytes,
+ model: project,
+ uploader: uploader.class.to_s
+ )
+
+ uploader.store!(upload_fixture('rails_sample.jpg'))
+
+ upload_for_project_fresh = Upload.find(upload_for_project.id)
+
+ expect(upload_for_project).to eq(upload_for_project_fresh)
+ expect(Upload.count).to eq(2)
+ end
end
describe '#destroy_upload callback' do
diff --git a/spec/validators/color_validator_spec.rb b/spec/validators/color_validator_spec.rb
new file mode 100644
index 00000000000..e5a38ac9372
--- /dev/null
+++ b/spec/validators/color_validator_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ColorValidator do
+ using RSpec::Parameterized::TableSyntax
+
+ subject do
+ Class.new do
+ include ActiveModel::Model
+ include ActiveModel::Validations
+ attr_accessor :color
+ validates :color, color: true
+ end.new
+ end
+
+ where(:color, :is_valid) do
+ '#000abc' | true
+ '#aaa' | true
+ '#BBB' | true
+ '#cCc' | true
+ '#ffff' | false
+ '#000111222' | false
+ 'invalid' | false
+ '000' | false
+ end
+
+ with_them do
+ it 'only accepts valid colors' do
+ subject.color = color
+
+ expect(subject.valid?).to eq(is_valid)
+ end
+ end
+
+ it 'fails fast for long invalid string' do
+ subject.color = '#' + ('0' * 50_000) + 'xxx'
+
+ expect do
+ Timeout.timeout(5.seconds) { subject.valid? }
+ end.not_to raise_error
+ end
+end
diff --git a/spec/validators/public_url_validator_spec.rb b/spec/validators/public_url_validator_spec.rb
index f6364fb1dd5..3cbf1002730 100644
--- a/spec/validators/public_url_validator_spec.rb
+++ b/spec/validators/public_url_validator_spec.rb
@@ -2,27 +2,5 @@ require 'spec_helper'
describe PublicUrlValidator do
include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes]
-
- context 'by default' do
- let(:validator) { described_class.new(attributes: [:link_url]) }
- let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
-
- subject { validator.validate(badge) }
-
- it 'blocks urls pointing to localhost' do
- badge.link_url = 'https://127.0.0.1'
-
- subject
-
- expect(badge.errors).to be_present
- end
-
- it 'blocks urls pointing to the local network' do
- badge.link_url = 'https://192.168.1.1'
-
- subject
-
- expect(badge.errors).to be_present
- end
- end
+ include_examples 'public url validator examples', allow_local_requests_from_web_hooks_and_services: true
end
diff --git a/spec/validators/qualified_domain_array_validator_spec.rb b/spec/validators/qualified_domain_array_validator_spec.rb
new file mode 100644
index 00000000000..6beb4c67f6f
--- /dev/null
+++ b/spec/validators/qualified_domain_array_validator_spec.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe QualifiedDomainArrayValidator do
+ class TestClass
+ include ActiveModel::Validations
+
+ attr_accessor :domain_array
+
+ def initialize(domain_array)
+ self.domain_array = domain_array
+ end
+ end
+
+ let!(:record) do
+ TestClass.new(['gitlab.com'])
+ end
+
+ subject { validator.validate(record) }
+
+ shared_examples 'can be nil' do
+ it 'allows when attribute is nil' do
+ record.domain_array = nil
+
+ subject
+
+ expect(record.errors).to be_empty
+ end
+ end
+
+ shared_examples 'can be blank' do
+ it 'allows when attribute is blank' do
+ record.domain_array = []
+
+ subject
+
+ expect(record.errors).to be_empty
+ end
+ end
+
+ describe 'validations' do
+ let(:validator) { described_class.new(attributes: [:domain_array]) }
+
+ it_behaves_like 'can be blank'
+
+ it 'returns error when attribute is nil' do
+ record.domain_array = nil
+
+ subject
+
+ expect(record.errors).to be_present
+ expect(record.errors.first[1]).to eq('entries cannot be nil')
+ end
+
+ it 'allows when domain is valid' do
+ subject
+
+ expect(record.errors).to be_empty
+ end
+
+ it 'returns error when domain contains unicode' do
+ record.domain_array = ['ğitlab.com']
+
+ subject
+
+ expect(record.errors).to be_present
+ expect(record.errors.first[1]).to eq 'unicode domains should use IDNA encoding'
+ end
+
+ it 'returns error when entry is larger than 255 chars' do
+ record.domain_array = ['a' * 256]
+
+ subject
+
+ expect(record.errors).to be_present
+ expect(record.errors.first[1]).to eq 'entries cannot be larger than 255 characters'
+ end
+
+ it 'returns error when entry contains HTML tags' do
+ record.domain_array = ['gitlab.com<h1>something</h1>']
+
+ subject
+
+ expect(record.errors).to be_present
+ expect(record.errors.first[1]).to eq 'entries cannot contain HTML tags'
+ end
+ end
+
+ context 'when allow_nil is set to true' do
+ let(:validator) { described_class.new(attributes: [:domain_array], allow_nil: true) }
+
+ it_behaves_like 'can be nil'
+ it_behaves_like 'can be blank'
+ end
+
+ context 'when allow_blank is set to true' do
+ let(:validator) { described_class.new(attributes: [:domain_array], allow_blank: true) }
+
+ it_behaves_like 'can be nil'
+ it_behaves_like 'can be blank'
+ end
+end
diff --git a/spec/validators/system_hook_url_validator_spec.rb b/spec/validators/system_hook_url_validator_spec.rb
new file mode 100644
index 00000000000..02384bbd1ce
--- /dev/null
+++ b/spec/validators/system_hook_url_validator_spec.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SystemHookUrlValidator do
+ include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes]
+ include_examples 'public url validator examples', allow_local_requests_from_system_hooks: true
+end
diff --git a/spec/views/dashboard/projects/_blank_state_admin_welcome.haml.rb b/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb
index 2f58eec86dc..2f58eec86dc 100644
--- a/spec/views/dashboard/projects/_blank_state_admin_welcome.haml.rb
+++ b/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb
diff --git a/spec/views/dashboard/projects/_nav.html.haml.rb b/spec/views/dashboard/projects/_nav.html.haml_spec.rb
index f6a8ca13040..cbdd3c0acc3 100644
--- a/spec/views/dashboard/projects/_nav.html.haml.rb
+++ b/spec/views/dashboard/projects/_nav.html.haml_spec.rb
@@ -4,7 +4,7 @@ describe 'dashboard/projects/_nav.html.haml' do
it 'highlights All tab by default' do
render
- expect(rendered).to have_css('li.active a', text: 'All')
+ expect(rendered).to have_css('a.active', text: 'All')
end
it 'highlights Personal tab personal param is present' do
@@ -12,6 +12,6 @@ describe 'dashboard/projects/_nav.html.haml' do
render
- expect(rendered).to have_css('li.active a', text: 'Personal')
+ expect(rendered).to have_css('a.active', text: 'Personal')
end
end
diff --git a/spec/views/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb
index 29e15960fb8..47804411b9d 100644
--- a/spec/views/groups/edit.html.haml_spec.rb
+++ b/spec/views/groups/edit.html.haml_spec.rb
@@ -35,7 +35,7 @@ describe 'groups/edit.html.haml' do
it_behaves_like '"Share with group lock" setting', { disabled: false, checked: false }
end
- context 'for a subgroup', :nested_groups do
+ context 'for a subgroup' do
let!(:subgroup) { create(:group, parent: root_group) }
let(:sub_owner) { create(:user) }
let(:test_group) { subgroup }
diff --git a/spec/views/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb
index cbb4199954a..d7f24950e6f 100644
--- a/spec/views/layouts/_head.html.haml_spec.rb
+++ b/spec/views/layouts/_head.html.haml_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'layouts/_head' do
+ include StubConfiguration
+
before do
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
end
@@ -70,6 +72,41 @@ describe 'layouts/_head' do
expect(rendered).to match('<link rel="stylesheet" media="all" href="/stylesheets/highlight/themes/solarised-light.css" />')
end
+ context 'when an asset_host is set and snowplow url is set' do
+ let(:asset_host) { 'http://test.host' }
+
+ before do
+ allow(ActionController::Base).to receive(:asset_host).and_return(asset_host)
+ allow(Gitlab::CurrentSettings).to receive(:snowplow_enabled?).and_return(true)
+ allow(Gitlab::CurrentSettings).to receive(:snowplow_collector_hostname).and_return('www.snow.plow')
+ end
+
+ it 'add a snowplow script tag with asset host' do
+ render
+ expect(rendered).to match('http://test.host/assets/snowplow/')
+ expect(rendered).to match('window.snowplow')
+ expect(rendered).to match('www.snow.plow')
+ end
+ end
+
+ context 'when a Piwik config is set' do
+ let(:piwik_host) { 'piwik.example.com' }
+
+ before do
+ stub_config(extra: {
+ piwik_url: piwik_host,
+ piwik_site_id: 12345
+ })
+ end
+
+ it 'add a Piwik Javascript' do
+ render
+
+ expect(rendered).to match(/<script.*>.*var u="\/\/#{piwik_host}\/".*<\/script>/m)
+ expect(rendered).to match(%r(<noscript>.*<img src="//#{piwik_host}/piwik.php.*</noscript>))
+ end
+ end
+
def stub_helper_with_safe_string(method)
allow_any_instance_of(PageLayoutHelper).to receive(method)
.and_return(%q{foo" http-equiv="refresh}.html_safe)
diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
index 2e19d0cec26..26e429ac5d0 100644
--- a/spec/views/layouts/header/_new_dropdown.haml_spec.rb
+++ b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
@@ -28,7 +28,7 @@ describe 'layouts/header/_new_dropdown' do
)
end
- it 'has a "New subgroup" link', :nested_groups do
+ it 'has a "New subgroup" link' do
render
expect(rendered).to have_link(
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index 2befbcb3370..b627b9dba59 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -21,7 +21,7 @@ describe 'layouts/nav/sidebar/_project' do
end
end
- describe 'container registry tab' do
+ describe 'packages tab' do
before do
stub_container_registry_config(enabled: true)
@@ -31,24 +31,17 @@ describe 'layouts/nav/sidebar/_project' do
.and_return('projects/registry/repositories')
end
- it 'has both Registry and Repository tabs' do
- render
-
- expect(rendered).to have_text 'Repository'
- expect(rendered).to have_text 'Registry'
- end
-
it 'highlights sidebar item and flyout' do
render
expect(rendered).to have_css('.sidebar-top-level-items > li.active', count: 1)
- expect(rendered).to have_css('.is-fly-out-only > li.active', count: 1)
+ expect(rendered).to have_css('.sidebar-sub-level-items > li.fly-out-top-item.active', count: 1)
end
it 'highlights container registry tab' do
render
- expect(rendered).to have_css('.sidebar-top-level-items > li.active', text: 'Registry')
+ expect(rendered).to have_css('.sidebar-sub-level-items > li:not(.fly-out-top-item).active', text: 'Container Registry')
end
end
diff --git a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
index 623237b111a..bf633a118ca 100644
--- a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
+++ b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
@@ -28,7 +28,7 @@ describe 'notify/pipeline_failed_email.html.haml' do
expect(rendered).to have_content "Your pipeline has failed"
expect(rendered).to have_content pipeline.project.name
- expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub!(/\s+/, ' ')
+ expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ')
expect(rendered).to have_content pipeline.commit.author_name
expect(rendered).to have_content "##{pipeline.id}"
expect(rendered).to have_content pipeline.user.name
@@ -45,7 +45,7 @@ describe 'notify/pipeline_failed_email.html.haml' do
expect(rendered).to have_content "Your pipeline has failed"
expect(rendered).to have_content pipeline.project.name
- expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub!(/\s+/, ' ')
+ expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ')
expect(rendered).to have_content pipeline.commit.author_name
expect(rendered).to have_content "##{pipeline.id}"
expect(rendered).to have_content "by API"
diff --git a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
index 81245239eba..060274eb56a 100644
--- a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
+++ b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
@@ -30,7 +30,7 @@ describe 'notify/pipeline_failed_email.text.erb' do
expect(rendered).to have_content('Your pipeline has failed')
expect(rendered).to have_content(pipeline.project.name)
- expect(rendered).to have_content(pipeline.git_commit_message.truncate(50).gsub!(/\s+/, ' '))
+ expect(rendered).to have_content(pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' '))
expect(rendered).to have_content(pipeline.commit.author_name)
expect(rendered).to have_content("##{pipeline.id}")
expect(rendered).to have_content(pipeline.user.name)
diff --git a/spec/views/notify/pipeline_success_email.html.haml_spec.rb b/spec/views/notify/pipeline_success_email.html.haml_spec.rb
index a876bf13e11..46a6c908863 100644
--- a/spec/views/notify/pipeline_success_email.html.haml_spec.rb
+++ b/spec/views/notify/pipeline_success_email.html.haml_spec.rb
@@ -28,7 +28,7 @@ describe 'notify/pipeline_success_email.html.haml' do
expect(rendered).to have_content "Your pipeline has passed"
expect(rendered).to have_content pipeline.project.name
- expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub!(/\s+/, ' ')
+ expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ')
expect(rendered).to have_content pipeline.commit.author_name
expect(rendered).to have_content "##{pipeline.id}"
expect(rendered).to have_content pipeline.user.name
@@ -45,7 +45,7 @@ describe 'notify/pipeline_success_email.html.haml' do
expect(rendered).to have_content "Your pipeline has passed"
expect(rendered).to have_content pipeline.project.name
- expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub!(/\s+/, ' ')
+ expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ')
expect(rendered).to have_content pipeline.commit.author_name
expect(rendered).to have_content "##{pipeline.id}"
expect(rendered).to have_content "by API"
diff --git a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb
index 54ec4f32856..54ec4f32856 100644
--- a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb
+++ b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb
diff --git a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
index 0206928a211..88c4b52b3a6 100644
--- a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
@@ -12,6 +12,7 @@ describe 'projects/merge_requests/creations/_new_submit.html.haml' do
assign(:hidden_commit_count, 0)
assign(:total_commit_count, merge_request.commits.count)
assign(:project, merge_request.target_project)
+ assign(:target_project, merge_request.target_project)
assign(:mr_presenter, merge_request.present(current_user: merge_request.author))
allow(view).to receive(:can?).and_return(true)
diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
index 529afa03f9c..0a3a46210ed 100644
--- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
@@ -23,6 +23,7 @@ describe 'projects/merge_requests/edit.html.haml' do
before do
assign(:project, project)
+ assign(:target_project, project)
assign(:merge_request, closed_merge_request)
assign(:mr_presenter, closed_merge_request.present(current_user: user))
diff --git a/spec/views/projects/pages_domains/show.html.haml_spec.rb b/spec/views/projects/pages_domains/show.html.haml_spec.rb
new file mode 100644
index 00000000000..da27a04bfe9
--- /dev/null
+++ b/spec/views/projects/pages_domains/show.html.haml_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe 'projects/pages_domains/show' do
+ let(:project) { create(:project, :repository) }
+
+ before do
+ assign(:project, project)
+ assign(:domain, domain)
+ end
+
+ context 'when auto_ssl is enabled' do
+ context 'when domain is disabled' do
+ let(:domain) { create(:pages_domain, :disabled, project: project, auto_ssl_enabled: true) }
+
+ it 'shows verification warning' do
+ render
+
+ expect(rendered).to have_content("A Let's Encrypt SSL certificate can not be obtained until your domain is verified.")
+ end
+ end
+
+ context 'when certificate is absent' do
+ let(:domain) { create(:pages_domain, :without_key, :without_certificate, project: project, auto_ssl_enabled: true) }
+
+ it 'shows alert about time of obtaining certificate' do
+ render
+
+ expect(rendered).to have_content("GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later.")
+ end
+ end
+
+ context 'when certificate is present' do
+ let(:domain) { create(:pages_domain, :letsencrypt, project: project) }
+
+ it 'shows certificate info' do
+ render
+
+ # test just a random part of cert represenations(X509v3 Subject Key Identifier:)
+ expect(rendered).to have_content("C6:5F:56:4B:10:69:AC:1D:33:D2:26:C9:B3:7A:D7:12:4D:3E:F7:90")
+ end
+ end
+ end
+
+ context 'when auto_ssl is disabled' do
+ context 'when certificate is present' do
+ let(:domain) { create(:pages_domain, project: project) }
+
+ it 'shows certificate info' do
+ render
+
+ # test just a random part of cert represenations(X509v3 Subject Key Identifier:)
+ expect(rendered).to have_content("C6:5F:56:4B:10:69:AC:1D:33:D2:26:C9:B3:7A:D7:12:4D:3E:F7:90")
+ end
+ end
+
+ context 'when certificate is absent' do
+ let(:domain) { create(:pages_domain, :without_certificate, :without_key, project: project) }
+
+ it 'shows missing certificate' do
+ render
+
+ expect(rendered).to have_content("missing")
+ end
+ end
+ end
+end
diff --git a/spec/views/search/_filter.html.haml_spec.rb b/spec/views/search/_filter.html.haml_spec.rb
new file mode 100644
index 00000000000..d2cd636f8c6
--- /dev/null
+++ b/spec/views/search/_filter.html.haml_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'search/_filter' do
+ context 'when the search page is opened' do
+ it 'displays the correct elements' do
+ render
+
+ expect(rendered).to have_selector('label[for="dashboard_search_group"]')
+ expect(rendered).to have_selector('button#dashboard_search_group')
+
+ expect(rendered).to have_selector('label[for="dashboard_search_project"]')
+ expect(rendered).to have_selector('button#dashboard_search_project')
+ end
+ end
+end
diff --git a/spec/views/search/_form.html.haml_spec.rb b/spec/views/search/_form.html.haml_spec.rb
new file mode 100644
index 00000000000..69f40895d86
--- /dev/null
+++ b/spec/views/search/_form.html.haml_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'search/_form' do
+ context 'when the search page is opened' do
+ it 'displays the correct elements' do
+ render
+
+ expect(rendered).to have_selector('.search-field-holder.form-group')
+ expect(rendered).to have_selector('label[for="dashboard_search"]')
+ end
+ end
+end
diff --git a/spec/views/search/show.html.haml_spec.rb b/spec/views/search/show.html.haml_spec.rb
new file mode 100644
index 00000000000..483b913f2cc
--- /dev/null
+++ b/spec/views/search/show.html.haml_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'search/show' do
+ let(:search_term) { nil }
+
+ before do
+ stub_template "search/_category.html.haml" => 'Category Partial'
+ stub_template "search/_results.html.haml" => 'Results Partial'
+
+ @search_term = search_term
+
+ render
+ end
+
+ context 'when the search page is opened' do
+ it 'displays the title' do
+ expect(rendered).to have_selector('h1.page-title', text: 'Search')
+ expect(rendered).not_to have_selector('h1.page-title code')
+ end
+
+ it 'does not render partials' do
+ expect(rendered).not_to render_template('search/_category')
+ expect(rendered).not_to render_template('search/_results')
+ end
+ end
+
+ context 'when search term is supplied' do
+ let(:search_term) { 'Search Foo' }
+
+ it 'renders partials' do
+ expect(rendered).to render_template('search/_category')
+ expect(rendered).to render_template('search/_results')
+ end
+ end
+end
diff --git a/spec/views/shared/_label_row.html.haml.rb b/spec/views/shared/_label_row.html.haml_spec.rb
index a58d5efc1e3..4cce13aa37c 100644
--- a/spec/views/shared/_label_row.html.haml.rb
+++ b/spec/views/shared/_label_row.html.haml_spec.rb
@@ -7,9 +7,20 @@ describe 'shared/_label_row.html.haml' do
}
label_types.each do |label_type, label_factory|
- let!(:label) { create(label_factory) }
+ let!(:label) do
+ label_record = create(label_factory)
+ label_record.present(issuable_subject: label_record.subject)
+ end
context "for a #{label_type}" do
+ before do
+ if label.project_label?
+ @project = label.project
+ else
+ @group = label.group
+ end
+ end
+
it 'has a non-linked label title' do
render 'shared/label_row', label: label
diff --git a/spec/views/shared/milestones/_issuable.html.haml.rb b/spec/views/shared/milestones/_issuable.html.haml_spec.rb
index 0a3f877cae0..0a3f877cae0 100644
--- a/spec/views/shared/milestones/_issuable.html.haml.rb
+++ b/spec/views/shared/milestones/_issuable.html.haml_spec.rb
diff --git a/spec/views/shared/milestones/_issuables.html.haml.rb b/spec/views/shared/milestones/_issuables.html.haml_spec.rb
index cbbb984935f..24b55338db3 100644
--- a/spec/views/shared/milestones/_issuables.html.haml.rb
+++ b/spec/views/shared/milestones/_issuables.html.haml_spec.rb
@@ -6,7 +6,7 @@ describe 'shared/milestones/_issuables.html.haml' do
before do
allow(view).to receive_messages(title: nil, id: nil, show_project_name: nil,
show_full_project_name: nil, dom_class: '',
- issuables: double(size: issuables_size).as_null_object)
+ issuables: double(length: issuables_size).as_null_object)
stub_template 'shared/milestones/_issuable.html.haml' => ''
end
diff --git a/spec/views/shared/milestones/_top.html.haml.rb b/spec/views/shared/milestones/_top.html.haml_spec.rb
index 516d81c87ac..f2ee8be5857 100644
--- a/spec/views/shared/milestones/_top.html.haml.rb
+++ b/spec/views/shared/milestones/_top.html.haml_spec.rb
@@ -7,6 +7,7 @@ describe 'shared/milestones/_top.html.haml' do
before do
allow(milestone).to receive(:milestones) { [] }
+ allow(milestone).to receive(:milestone) { milestone }
end
it 'renders a deprecation message for a legacy milestone' do
diff --git a/spec/workers/archive_trace_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb
index 368ed3f3db1..44f7be15201 100644
--- a/spec/workers/archive_trace_worker_spec.rb
+++ b/spec/workers/archive_trace_worker_spec.rb
@@ -11,7 +11,7 @@ describe ArchiveTraceWorker do
it 'executes service' do
expect_any_instance_of(Ci::ArchiveTraceService)
- .to receive(:execute).with(job)
+ .to receive(:execute).with(job, anything)
subject
end
diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb
index 746c858609f..e5be8ce0423 100644
--- a/spec/workers/background_migration_worker_spec.rb
+++ b/spec/workers/background_migration_worker_spec.rb
@@ -55,21 +55,13 @@ describe BackgroundMigrationWorker, :sidekiq, :clean_gitlab_redis_shared_state d
end
describe '#healthy_database?' do
- context 'using MySQL', :mysql do
- it 'returns true' do
- expect(worker.healthy_database?).to eq(true)
- end
- end
-
- context 'using PostgreSQL', :postgresql do
- context 'when replication lag is too great' do
- it 'returns false' do
- allow(Postgresql::ReplicationSlot)
- .to receive(:lag_too_great?)
- .and_return(true)
+ context 'when replication lag is too great' do
+ it 'returns false' do
+ allow(Postgresql::ReplicationSlot)
+ .to receive(:lag_too_great?)
+ .and_return(true)
- expect(worker.healthy_database?).to eq(false)
- end
+ expect(worker.healthy_database?).to eq(false)
end
context 'when replication lag is small enough' do
diff --git a/spec/workers/ci/archive_traces_cron_worker_spec.rb b/spec/workers/ci/archive_traces_cron_worker_spec.rb
index eca6cf5235f..01232e2a58b 100644
--- a/spec/workers/ci/archive_traces_cron_worker_spec.rb
+++ b/spec/workers/ci/archive_traces_cron_worker_spec.rb
@@ -5,6 +5,8 @@ require 'spec_helper'
describe Ci::ArchiveTracesCronWorker do
subject { described_class.new.perform }
+ let(:finished_at) { 1.day.ago }
+
before do
stub_feature_flags(ci_enable_live_trace: true)
end
@@ -28,20 +30,26 @@ describe Ci::ArchiveTracesCronWorker do
end
context 'when a job succeeded' do
- let!(:build) { create(:ci_build, :success, :trace_live) }
+ let!(:build) { create(:ci_build, :success, :trace_live, finished_at: finished_at) }
it_behaves_like 'archives trace'
it 'executes service' do
expect_any_instance_of(Ci::ArchiveTraceService)
- .to receive(:execute).with(build)
+ .to receive(:execute).with(build, anything)
subject
end
+ context 'when the job finished recently' do
+ let(:finished_at) { 1.hour.ago }
+
+ it_behaves_like 'does not archive trace'
+ end
+
context 'when a trace had already been archived' do
let!(:build) { create(:ci_build, :success, :trace_live, :trace_artifact) }
- let!(:build2) { create(:ci_build, :success, :trace_live) }
+ let!(:build2) { create(:ci_build, :success, :trace_live, finished_at: finished_at) }
it 'continues to archive live traces' do
subject
@@ -52,7 +60,7 @@ describe Ci::ArchiveTracesCronWorker do
end
context 'when an unexpected exception happened during archiving' do
- let!(:build) { create(:ci_build, :success, :trace_live) }
+ let!(:build) { create(:ci_build, :success, :trace_live, finished_at: finished_at) }
before do
allow(Gitlab::Sentry).to receive(:track_exception)
@@ -60,7 +68,10 @@ describe Ci::ArchiveTracesCronWorker do
end
it 'puts a log' do
- expect(Rails.logger).to receive(:error).with("Failed to archive trace. id: #{build.id} message: Unexpected error")
+ expect(Sidekiq.logger).to receive(:warn).with(
+ class: described_class.name,
+ message: "Failed to archive trace. message: Unexpected error.",
+ job_id: build.id)
subject
end
@@ -68,7 +79,7 @@ describe Ci::ArchiveTracesCronWorker do
end
context 'when a job was cancelled' do
- let!(:build) { create(:ci_build, :canceled, :trace_live) }
+ let!(:build) { create(:ci_build, :canceled, :trace_live, finished_at: finished_at) }
it_behaves_like 'archives trace'
end
diff --git a/spec/workers/cluster_configure_worker_spec.rb b/spec/workers/cluster_configure_worker_spec.rb
deleted file mode 100644
index 975088f3ee6..00000000000
--- a/spec/workers/cluster_configure_worker_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe ClusterConfigureWorker, '#perform' do
- let(:worker) { described_class.new }
-
- shared_examples 'configured cluster' do
- it 'creates a namespace' do
- expect(Clusters::RefreshService).to receive(:create_or_update_namespaces_for_cluster).with(cluster).once
-
- worker.perform(cluster.id)
- end
- end
-
- shared_examples 'unconfigured cluster' do
- it 'does not create a namespace' do
- expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_cluster)
-
- worker.perform(cluster.id)
- end
- end
-
- context 'group cluster' do
- let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
- let(:group) { cluster.group }
-
- context 'when group has a project' do
- let!(:project) { create(:project, group: group) }
-
- it_behaves_like 'unconfigured cluster'
- end
-
- context 'when group has project in a sub-group' do
- let!(:subgroup) { create(:group, parent: group) }
- let!(:project) { create(:project, group: subgroup) }
-
- it_behaves_like 'unconfigured cluster'
- end
- end
-
- context 'when provider type is gcp' do
- let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
-
- it_behaves_like 'configured cluster'
- end
-
- context 'when provider type is user' do
- let!(:cluster) { create(:cluster, :project, :provided_by_user) }
-
- it_behaves_like 'configured cluster'
- end
-
- context 'when cluster is not managed' do
- let(:cluster) { create(:cluster, :not_managed) }
-
- it 'does not configure the cluster' do
- expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_cluster)
-
- described_class.new.perform(cluster.id)
- end
- end
-
- context 'when cluster does not exist' do
- it 'does not provision a cluster' do
- expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:execute)
-
- described_class.new.perform(123)
- end
- end
-end
diff --git a/spec/workers/cluster_project_configure_worker_spec.rb b/spec/workers/cluster_project_configure_worker_spec.rb
deleted file mode 100644
index 2ac9d0f61b4..00000000000
--- a/spec/workers/cluster_project_configure_worker_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe ClusterProjectConfigureWorker, '#perform' do
- let(:worker) { described_class.new }
- let(:cluster) { create(:cluster, :project) }
-
- it 'configures the cluster' do
- expect(Clusters::RefreshService).to receive(:create_or_update_namespaces_for_project)
-
- described_class.new.perform(cluster.projects.first.id)
- end
-end
diff --git a/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb b/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb
new file mode 100644
index 00000000000..b069b080531
--- /dev/null
+++ b/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Namespaces::PruneAggregationSchedulesWorker, '#perform', :clean_gitlab_redis_shared_state do
+ include ExclusiveLeaseHelpers
+
+ let(:namespaces) { create_list(:namespace, 5, :with_aggregation_schedule) }
+ let(:timeout) { Namespace::AggregationSchedule::DEFAULT_LEASE_TIMEOUT }
+
+ subject(:worker) { described_class.new }
+
+ before do
+ allow(Namespaces::RootStatisticsWorker)
+ .to receive(:perform_async).and_return(nil)
+
+ allow(Namespaces::RootStatisticsWorker)
+ .to receive(:perform_in).and_return(nil)
+
+ namespaces.each do |namespace|
+ lease_key = "namespace:namespaces_root_statistics:#{namespace.id}"
+ stub_exclusive_lease(lease_key, timeout: timeout)
+ end
+ end
+
+ it 'schedules a worker per pending aggregation' do
+ expect(Namespaces::RootStatisticsWorker)
+ .to receive(:perform_async).exactly(5).times
+
+ expect(Namespaces::RootStatisticsWorker)
+ .to receive(:perform_in).exactly(5).times
+
+ worker.perform
+ end
+end
diff --git a/spec/workers/namespaces/root_statistics_worker_spec.rb b/spec/workers/namespaces/root_statistics_worker_spec.rb
new file mode 100644
index 00000000000..6bbdfe03ceb
--- /dev/null
+++ b/spec/workers/namespaces/root_statistics_worker_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Namespaces::RootStatisticsWorker, '#perform' do
+ let(:group) { create(:group, :with_aggregation_schedule) }
+
+ subject(:worker) { described_class.new }
+
+ context 'with a namespace' do
+ it 'executes refresher service' do
+ expect_any_instance_of(Namespaces::StatisticsRefresherService)
+ .to receive(:execute)
+
+ worker.perform(group.id)
+ end
+
+ it 'deletes namespace aggregated schedule row' do
+ worker.perform(group.id)
+
+ expect(group.reload.aggregation_schedule).to be_nil
+ end
+
+ context 'when something goes wrong when updating' do
+ before do
+ allow_any_instance_of(Namespaces::StatisticsRefresherService)
+ .to receive(:execute)
+ .and_raise(Namespaces::StatisticsRefresherService::RefresherError, 'error')
+ end
+
+ it 'does not delete the aggregation schedule' do
+ worker.perform(group.id)
+
+ expect(group.reload.aggregation_schedule).to be_present
+ end
+
+ it 'logs the error' do
+ # A Namespace::RootStatisticsWorker is scheduled when
+ # a Namespace::AggregationSchedule is created, so having
+ # create(:group, :with_aggregation_schedule), will execute
+ # another worker
+ allow_any_instance_of(Namespace::AggregationSchedule)
+ .to receive(:schedule_root_storage_statistics).and_return(nil)
+
+ expect(Gitlab::SidekiqLogger).to receive(:error).once
+
+ worker.perform(group.id)
+ end
+ end
+ end
+
+ context 'with no namespace' do
+ before do
+ group.destroy
+ end
+
+ it 'does not execute the refresher service' do
+ expect_any_instance_of(Namespaces::StatisticsRefresherService)
+ .not_to receive(:execute)
+
+ worker.perform(group.id)
+ end
+ end
+
+ context 'with a namespace with no aggregation scheduled' do
+ before do
+ group.aggregation_schedule.destroy
+ end
+
+ it 'does not execute the refresher service' do
+ expect_any_instance_of(Namespaces::StatisticsRefresherService)
+ .not_to receive(:execute)
+
+ worker.perform(group.id)
+ end
+ end
+end
diff --git a/spec/workers/namespaces/schedule_aggregation_worker_spec.rb b/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
new file mode 100644
index 00000000000..be722f451e0
--- /dev/null
+++ b/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Namespaces::ScheduleAggregationWorker, '#perform', :clean_gitlab_redis_shared_state do
+ let(:group) { create(:group) }
+
+ subject(:worker) { described_class.new }
+
+ context 'when group is the root ancestor' do
+ context 'when aggregation schedule exists' do
+ it 'does not create a new one' do
+ stub_aggregation_schedule_statistics
+
+ Namespace::AggregationSchedule.safe_find_or_create_by!(namespace_id: group.id)
+
+ expect do
+ worker.perform(group.id)
+ end.not_to change(Namespace::AggregationSchedule, :count)
+ end
+ end
+
+ context 'when aggregation schedule does not exist' do
+ it 'creates one' do
+ stub_aggregation_schedule_statistics
+
+ expect do
+ worker.perform(group.id)
+ end.to change(Namespace::AggregationSchedule, :count).by(1)
+
+ expect(group.aggregation_schedule).to be_present
+ end
+ end
+ end
+
+ context 'when group is not the root ancestor' do
+ let(:parent_group) { create(:group) }
+ let(:group) { create(:group, parent: parent_group) }
+
+ it 'creates an aggregation schedule for the root' do
+ stub_aggregation_schedule_statistics
+
+ worker.perform(group.id)
+
+ expect(parent_group.aggregation_schedule).to be_present
+ end
+ end
+
+ context 'when namespace does not exist' do
+ it 'logs the error' do
+ expect(Gitlab::SidekiqLogger).to receive(:error).once
+
+ worker.perform(12345)
+ end
+ end
+
+ def stub_aggregation_schedule_statistics
+ # Namespace::Aggregations are deleted by
+ # Namespace::AggregationSchedule::schedule_root_storage_statistics,
+ # which is executed async. Stubing the service so instances are not deleted
+ # while still running the specs.
+ expect_next_instance_of(Namespace::AggregationSchedule) do |aggregation_schedule|
+ expect(aggregation_schedule)
+ .to receive(:schedule_root_storage_statistics)
+ end
+ end
+end
diff --git a/spec/workers/pipeline_process_worker_spec.rb b/spec/workers/pipeline_process_worker_spec.rb
index d33cf72e51e..ac677e3b555 100644
--- a/spec/workers/pipeline_process_worker_spec.rb
+++ b/spec/workers/pipeline_process_worker_spec.rb
@@ -12,6 +12,17 @@ describe PipelineProcessWorker do
described_class.new.perform(pipeline.id)
end
+
+ context 'when build_ids are passed' do
+ let(:build) { create(:ci_build, pipeline: pipeline, name: 'my-build') }
+
+ it 'processes pipeline with a list of builds' do
+ expect_any_instance_of(Ci::Pipeline).to receive(:process!)
+ .with([build.id])
+
+ described_class.new.perform(pipeline.id, [build.id])
+ end
+ end
end
context 'when pipeline does not exist' do
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 39f1beb4efa..c8a0c22b0e8 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -14,6 +14,10 @@ describe PostReceive do
create(:project, :repository, auto_cancel_pending_pipelines: 'disabled')
end
+ def perform(changes: base64_changes)
+ described_class.new.perform(gl_repository, key_id, changes)
+ end
+
context "as a sidekiq worker" do
it "responds to #perform" do
expect(described_class.new).to respond_to(:perform)
@@ -28,18 +32,41 @@ describe PostReceive do
it "returns false and logs an error" do
expect(Gitlab::GitLogger).to receive(:error).with("POST-RECEIVE: #{error_message}")
- expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be(false)
+ expect(perform).to be(false)
end
end
describe "#process_project_changes" do
+ context 'with an empty project' do
+ let(:empty_project) { create(:project, :empty_repo) }
+ let(:changes) { "123456 789012 refs/heads/tést1\n" }
+
+ before do
+ allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(empty_project.owner)
+ allow(Gitlab::GlRepository).to receive(:parse).and_return([empty_project, Gitlab::GlRepository::PROJECT])
+ end
+
+ it 'expire the status cache' do
+ expect(empty_project.repository).to receive(:expire_status_cache)
+
+ perform
+ end
+
+ it 'schedules a cache update for commit count and size' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(empty_project.id, [], [:repository_size, :commit_count], true)
+
+ perform
+ end
+ end
+
context 'empty changes' do
it "does not call any PushService but runs after project hooks" do
expect(Git::BranchPushService).not_to receive(:new)
expect(Git::TagPushService).not_to receive(:new)
expect_next_instance_of(SystemHooksService) { |service| expect(service).to receive(:execute_hooks) }
- described_class.new.perform(gl_repository, key_id, "")
+ perform(changes: "")
end
end
@@ -50,40 +77,118 @@ describe PostReceive do
expect(Git::BranchPushService).not_to receive(:new)
expect(Git::TagPushService).not_to receive(:new)
- expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be false
+ expect(perform).to be false
end
end
context 'with changes' do
before do
allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner)
+ allow(Gitlab::GlRepository).to receive(:parse).and_return([project, Gitlab::GlRepository::PROJECT])
end
context "branches" do
- let(:changes) { "123456 789012 refs/heads/tést" }
+ let(:changes) do
+ <<~EOF
+ 123456 789012 refs/heads/tést1
+ 123456 789012 refs/heads/tést2
+ EOF
+ end
- it "calls Git::BranchPushService" do
- expect_next_instance_of(Git::BranchPushService) do |service|
+ it 'expires the branches cache' do
+ expect(project.repository).to receive(:expire_branches_cache).once
+
+ perform
+ end
+
+ it 'expires the status cache' do
+ expect(project).to receive(:empty_repo?).and_return(true)
+ expect(project.repository).to receive(:expire_status_cache)
+
+ perform
+ end
+
+ it 'calls Git::BranchPushService' do
+ expect_any_instance_of(Git::BranchPushService) do |service|
expect(service).to receive(:execute).and_return(true)
end
expect(Git::TagPushService).not_to receive(:new)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
+ end
+
+ it 'schedules a cache update for repository size only' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(project.id, [], [:repository_size], true)
+
+ perform
+ end
+
+ context 'with a default branch' do
+ let(:changes) do
+ <<~EOF
+ 123456 789012 refs/heads/tést1
+ 123456 789012 refs/heads/tést2
+ 678912 123455 refs/heads/#{project.default_branch}
+ EOF
+ end
+
+ it 'schedules a cache update for commit count and size' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(project.id, [], [:repository_size, :commit_count], true)
+
+ perform
+ end
end
end
context "tags" do
- let(:changes) { "123456 789012 refs/tags/tag" }
+ let(:changes) do
+ <<~EOF
+ 654321 210987 refs/tags/tag1
+ 654322 210986 refs/tags/tag2
+ 654323 210985 refs/tags/tag3
+ 654324 210984 refs/tags/tag4
+ 654325 210983 refs/tags/tag5
+ EOF
+ end
+
+ before do
+ expect(Gitlab::GlRepository).to receive(:parse).and_return([project, Gitlab::GlRepository::PROJECT])
+ end
+
+ it 'does not expire branches cache' do
+ expect(project.repository).not_to receive(:expire_branches_cache)
+
+ perform
+ end
+
+ it "only invalidates tags once" do
+ expect(project.repository).to receive(:repository_event).exactly(5).times.with(:push_tag).and_call_original
+ expect(project.repository).to receive(:expire_caches_for_tags).once.and_call_original
+ expect(project.repository).to receive(:expire_tags_cache).once.and_call_original
+
+ perform
+ end
it "calls Git::TagPushService" do
- expect(Git::BranchPushService).not_to receive(:execute)
+ expect(Git::BranchPushService).not_to receive(:new)
- expect_next_instance_of(Git::TagPushService) do |service|
+ expect_any_instance_of(Git::TagPushService) do |service|
expect(service).to receive(:execute).and_return(true)
end
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ expect(Git::BranchPushService).not_to receive(:new)
+
+ perform
+ end
+
+ it 'schedules a single ProjectCacheWorker update' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(project.id, [], [:repository_size], true)
+
+ perform
end
end
@@ -94,7 +199,7 @@ describe PostReceive do
expect(Git::BranchPushService).not_to receive(:new)
expect(Git::TagPushService).not_to receive(:new)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
end
end
@@ -111,7 +216,7 @@ describe PostReceive do
let(:changes_count) { changes.lines.count }
- subject { described_class.new.perform(gl_repository, key_id, base64_changes) }
+ subject { perform }
context "with valid .gitlab-ci.yml" do
before do
@@ -180,7 +285,13 @@ describe PostReceive do
it 'calls SystemHooksService' do
expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(fake_hook_data, :repository_update_hooks).and_return(true)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
+ end
+
+ it 'increments the usage data counter of pushes event' do
+ counter = Gitlab::UsageDataCounters::SourceCodeCounter
+
+ expect { perform }.to change { counter.read(:pushes) }.by(1)
end
end
end
@@ -197,7 +308,7 @@ describe PostReceive do
# a second to ensure we see changes.
Timecop.freeze(1.second.from_now) do
expect do
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
project.reload
end.to change(project, :last_activity_at)
.and change(project, :last_repository_updated_at)
@@ -208,7 +319,8 @@ describe PostReceive do
context "webhook" do
it "fetches the correct project" do
expect(Project).to receive(:find_by).with(id: project.id.to_s)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+
+ perform
end
it "does not run if the author is not in the project" do
@@ -218,16 +330,18 @@ describe PostReceive do
expect(project).not_to receive(:execute_hooks)
- expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be_falsey
+ expect(perform).to be_falsey
end
it "asks the project to trigger all hooks" do
+ create(:project_hook, push_events: true, tag_push_events: true, project: project)
+ create(:custom_issue_tracker_service, push_events: true, merge_requests_events: false, project: project)
allow(Project).to receive(:find_by).and_return(project)
expect(project).to receive(:execute_hooks).twice
expect(project).to receive(:execute_services).twice
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
end
it "enqueues a UpdateMergeRequestsWorker job" do
@@ -235,7 +349,7 @@ describe PostReceive do
expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
end
end
end
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 47bac63511e..eb1d3c364ac 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
describe ProcessCommitWorker do
- include ProjectForksHelper
-
let(:worker) { described_class.new }
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
@@ -35,44 +33,6 @@ describe ProcessCommitWorker do
worker.perform(project.id, user.id, commit.to_hash)
end
-
- context 'when the project is forked' do
- context 'when commit already exists in the upstream project' do
- it 'does not process the commit message' do
- forked = fork_project(project, user, repository: true)
-
- expect(worker).not_to receive(:process_commit_message)
-
- worker.perform(forked.id, user.id, forked.commit.to_hash)
- end
- end
-
- context 'when the commit does not exist in the upstream project' do
- it 'processes the commit message' do
- empty_project = create(:project, :public)
- forked = fork_project(empty_project, user, repository: true)
-
- TestEnv.copy_repo(forked,
- bare_repo: TestEnv.factory_repo_path_bare,
- refs: TestEnv::BRANCH_SHA)
-
- expect(worker).to receive(:process_commit_message)
-
- worker.perform(forked.id, user.id, forked.commit.to_hash)
- end
- end
-
- context 'when the upstream project no longer exists' do
- it 'processes the commit message' do
- forked = fork_project(project, user, repository: true)
- project.destroy!
-
- expect(worker).to receive(:process_commit_message)
-
- worker.perform(forked.id, user.id, forked.commit.to_hash)
- end
- end
- end
end
describe '#process_commit_message' do
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
index 51afb076da1..7f3c4881b89 100644
--- a/spec/workers/project_cache_worker_spec.rb
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -36,6 +36,11 @@ describe ProjectCacheWorker do
end
context 'with an existing project' do
+ before do
+ lease_key = "namespace:namespaces_root_statistics:#{project.namespace_id}"
+ stub_exclusive_lease_taken(lease_key, timeout: Namespace::AggregationSchedule::DEFAULT_LEASE_TIMEOUT)
+ end
+
it 'refreshes the method caches' do
expect_any_instance_of(Repository).to receive(:refresh_method_caches)
.with(%i(readme))
@@ -44,6 +49,16 @@ describe ProjectCacheWorker do
worker.perform(project.id, %w(readme))
end
+ context 'with statistics disabled' do
+ let(:statistics) { [] }
+
+ it 'does not update the project statistics' do
+ expect(worker).not_to receive(:update_statistics)
+
+ worker.perform(project.id, [], [], false)
+ end
+ end
+
context 'with statistics' do
let(:statistics) { %w(repository_size) }
@@ -81,6 +96,10 @@ describe ProjectCacheWorker do
expect(UpdateProjectStatisticsWorker).not_to receive(:perform_in)
+ expect(Namespaces::ScheduleAggregationWorker)
+ .not_to receive(:perform_async)
+ .with(project.namespace_id)
+
worker.update_statistics(project, statistics)
end
end
@@ -98,6 +117,11 @@ describe ProjectCacheWorker do
.with(lease_timeout, project.id, statistics)
.and_call_original
+ expect(Namespaces::ScheduleAggregationWorker)
+ .to receive(:perform_async)
+ .with(project.namespace_id)
+ .twice
+
worker.update_statistics(project, statistics)
end
end
diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb
index 4de51ecb3e9..66d517332ba 100644
--- a/spec/workers/repository_update_remote_mirror_worker_spec.rb
+++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb
@@ -2,99 +2,70 @@
require 'rails_helper'
-describe RepositoryUpdateRemoteMirrorWorker do
+describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do
subject { described_class.new }
- let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
+ let(:remote_mirror) { create(:remote_mirror) }
let(:scheduled_time) { Time.now - 5.minutes }
around do |example|
Timecop.freeze(Time.now) { example.run }
end
- describe '#perform' do
- context 'with status none' do
- before do
- remote_mirror.update(update_status: 'none')
- end
-
- it 'sets status as finished when update remote mirror service executes successfully' do
- expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :success)
-
- expect { subject.perform(remote_mirror.id, Time.now) }.to change { remote_mirror.reload.update_status }.to('finished')
- end
-
- it 'resets the notification flag upon success' do
- expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :success)
- remote_mirror.update_column(:error_notification_sent, true)
-
- expect { subject.perform(remote_mirror.id, Time.now) }.to change { remote_mirror.reload.error_notification_sent }.to(false)
- end
-
- it 'sets status as failed when update remote mirror service executes with errors' do
- error_message = 'fail!'
-
- expect_next_instance_of(Projects::UpdateRemoteMirrorService) do |service|
- expect(service).to receive(:execute).with(remote_mirror).and_return(status: :error, message: error_message)
- end
+ def expect_mirror_service_to_return(mirror, result, tries = 0)
+ expect_next_instance_of(Projects::UpdateRemoteMirrorService) do |service|
+ expect(service).to receive(:execute).with(mirror, tries).and_return(result)
+ end
+ end
- # Mock the finder so that it returns an object we can set expectations on
- expect_next_instance_of(RemoteMirrorFinder) do |finder|
- expect(finder).to receive(:execute).and_return(remote_mirror)
- end
- expect(remote_mirror).to receive(:mark_as_failed).with(error_message)
+ describe '#perform' do
+ it 'calls out to the service to perform the update' do
+ expect_mirror_service_to_return(remote_mirror, status: :success)
- expect do
- subject.perform(remote_mirror.id, Time.now)
- end.to raise_error(RepositoryUpdateRemoteMirrorWorker::UpdateError, error_message)
- end
+ subject.perform(remote_mirror.id, scheduled_time)
+ end
- it 'does nothing if last_update_started_at is higher than the time the job was scheduled in' do
- remote_mirror.update(last_update_started_at: Time.now)
+ it 'does not do anything if the mirror was already updated' do
+ remote_mirror.update(last_update_started_at: Time.now, update_status: :finished)
- expect_any_instance_of(RemoteMirror).to receive(:updated_since?).with(scheduled_time).and_return(true)
- expect_any_instance_of(Projects::UpdateRemoteMirrorService).not_to receive(:execute).with(remote_mirror)
+ expect(Projects::UpdateRemoteMirrorService).not_to receive(:new)
- expect(subject.perform(remote_mirror.id, scheduled_time)).to be_nil
- end
+ subject.perform(remote_mirror.id, scheduled_time)
end
- context 'with unexpected error' do
- it 'marks mirror as failed' do
- allow_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_raise(RuntimeError)
+ it 'schedules a retry when the mirror is marked for retrying' do
+ remote_mirror = create(:remote_mirror, update_status: :to_retry)
+ expect_mirror_service_to_return(remote_mirror, status: :error, message: 'Retry!')
- expect do
- subject.perform(remote_mirror.id, Time.now)
- end.to raise_error(RepositoryUpdateRemoteMirrorWorker::UpdateError)
- expect(remote_mirror.reload.update_status).to eq('failed')
- end
- end
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(remote_mirror.backoff_delay, remote_mirror.id, scheduled_time, 1)
- context 'with another worker already running' do
- before do
- remote_mirror.update(update_status: 'started')
- end
-
- it 'raises RemoteMirrorUpdateAlreadyInProgressError' do
- expect do
- subject.perform(remote_mirror.id, Time.now)
- end.to raise_error(RepositoryUpdateRemoteMirrorWorker::UpdateAlreadyInProgressError)
- end
+ subject.perform(remote_mirror.id, scheduled_time)
end
- context 'with status failed' do
- before do
- remote_mirror.update(update_status: 'failed')
+ it 'clears the lease if there was an unexpected exception' do
+ expect_next_instance_of(Projects::UpdateRemoteMirrorService) do |service|
+ expect(service).to receive(:execute).with(remote_mirror, 1).and_raise('Unexpected!')
end
+ expect { subject.perform(remote_mirror.id, Time.now, 1) }.to raise_error('Unexpected!')
- it 'sets status as finished if last_update_started_at is higher than the time the job was scheduled in' do
- remote_mirror.update(last_update_started_at: Time.now)
+ lease = Gitlab::ExclusiveLease.new("#{described_class.name}:#{remote_mirror.id}", timeout: 1.second)
- expect_any_instance_of(RemoteMirror).to receive(:updated_since?).with(scheduled_time).and_return(false)
- expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :success)
+ expect(lease.try_obtain).not_to be_nil
+ end
- expect { subject.perform(remote_mirror.id, scheduled_time) }.to change { remote_mirror.reload.update_status }.to('finished')
- end
+ it 'retries 3 times for the worker to finish before rescheduling' do
+ expect(subject).to receive(:in_lock)
+ .with("#{described_class.name}:#{remote_mirror.id}",
+ retries: 3,
+ ttl: remote_mirror.max_runtime,
+ sleep_sec: described_class::LOCK_WAIT_TIME)
+ .and_raise(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
+ expect(described_class).to receive(:perform_in)
+ .with(remote_mirror.backoff_delay, remote_mirror.id, scheduled_time, 0)
+
+ subject.perform(remote_mirror.id, scheduled_time)
end
end
end
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
index 72de62f1188..c3d577e2dae 100644
--- a/spec/workers/stuck_ci_jobs_worker_spec.rb
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -7,8 +7,6 @@ describe StuckCiJobsWorker do
let!(:runner) { create :ci_runner }
let!(:job) { create :ci_build, runner: runner }
- let(:trace_lease_key) { "trace:write:lock:#{job.id}" }
- let(:trace_lease_uuid) { SecureRandom.uuid }
let(:worker_lease_key) { StuckCiJobsWorker::EXCLUSIVE_LEASE_KEY }
let(:worker_lease_uuid) { SecureRandom.uuid }
@@ -16,7 +14,6 @@ describe StuckCiJobsWorker do
before do
stub_exclusive_lease(worker_lease_key, worker_lease_uuid)
- stub_exclusive_lease(trace_lease_key, trace_lease_uuid)
job.update!(status: status, updated_at: updated_at)
end
@@ -195,7 +192,6 @@ describe StuckCiJobsWorker do
end
it 'cancels exclusive leases after worker perform' do
- expect_to_cancel_exclusive_lease(trace_lease_key, trace_lease_uuid)
expect_to_cancel_exclusive_lease(worker_lease_key, worker_lease_uuid)
worker.perform