summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb7
-rw-r--r--spec/controllers/admin/gitaly_servers_controller_spec.rb15
-rw-r--r--spec/controllers/admin/hooks_controller_spec.rb6
-rw-r--r--spec/controllers/dashboard/groups_controller_spec.rb20
-rw-r--r--spec/controllers/groups/children_controller_spec.rb24
-rw-r--r--spec/controllers/groups_controller_spec.rb24
-rw-r--r--spec/controllers/import/gitlab_projects_controller_spec.rb38
-rw-r--r--spec/controllers/omniauth_callbacks_controller_spec.rb75
-rw-r--r--spec/controllers/projects/avatars_controller_spec.rb8
-rw-r--r--spec/controllers/projects/clusters/gcp_controller_spec.rb7
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb8
-rw-r--r--spec/controllers/projects/hooks_controller_spec.rb26
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb78
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/merge_requests/creations_controller_spec.rb42
-rw-r--r--spec/controllers/projects/todos_controller_spec.rb4
-rw-r--r--spec/factories/commits.rb2
-rw-r--r--spec/factories/deploy_keys_projects.rb4
-rw-r--r--spec/factories/deployments.rb3
-rw-r--r--spec/factories/events.rb2
-rw-r--r--spec/factories/issues.rb2
-rw-r--r--spec/factories/keys.rb4
-rw-r--r--spec/factories/merge_requests.rb2
-rw-r--r--spec/factories/notes.rb2
-rw-r--r--spec/factories/project_wikis.rb2
-rw-r--r--spec/factories/protected_branches.rb1
-rw-r--r--spec/factories/redirect_routes.rb15
-rw-r--r--spec/factories/sent_notifications.rb2
-rw-r--r--spec/factories/snippets.rb1
-rw-r--r--spec/factories/subscriptions.rb2
-rw-r--r--spec/factories/timelogs.rb2
-rw-r--r--spec/factories/todos.rb4
-rw-r--r--spec/features/admin/admin_builds_spec.rb16
-rw-r--r--spec/features/admin/admin_deploy_keys_spec.rb14
-rw-r--r--spec/features/admin/admin_hooks_spec.rb65
-rw-r--r--spec/features/atom/users_spec.rb4
-rw-r--r--spec/features/boards/boards_spec.rb11
-rw-r--r--spec/features/boards/sidebar_spec.rb10
-rw-r--r--spec/features/commits_spec.rb2
-rw-r--r--spec/features/copy_as_gfm_spec.rb2
-rw-r--r--spec/features/cycle_analytics_spec.rb1
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb22
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb7
-rw-r--r--spec/features/global_search_spec.rb2
-rw-r--r--spec/features/groups/issues_spec.rb69
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb1
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb9
-rw-r--r--spec/features/issues/spam_issues_spec.rb3
-rw-r--r--spec/features/merge_request/user_assigns_themselves_spec.rb49
-rw-r--r--spec/features/merge_request/user_awards_emoji_spec.rb (renamed from spec/features/merge_requests/award_spec.rb)6
-rw-r--r--spec/features/merge_request/user_cherry_picks_spec.rb (renamed from spec/features/merge_requests/cherry_pick_spec.rb)22
-rw-r--r--spec/features/merge_request/user_creates_image_diff_notes_spec.rb (renamed from spec/features/merge_requests/image_diff_notes_spec.rb)39
-rw-r--r--spec/features/merge_request/user_creates_mr_spec.rb31
-rw-r--r--spec/features/merge_request/user_customizes_merge_commit_message_spec.rb (renamed from spec/features/merge_requests/merge_commit_message_toggle_spec.rb)15
-rw-r--r--spec/features/merge_request/user_edits_mr_spec.rb11
-rw-r--r--spec/features/merge_request/user_locks_discussion_spec.rb (renamed from spec/features/merge_requests/discussion_lock_spec.rb)6
-rw-r--r--spec/features/merge_request/user_merges_immediately_spec.rb (renamed from spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb)22
-rw-r--r--spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb (renamed from spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb)51
-rw-r--r--spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb (renamed from spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb)82
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb (renamed from spec/features/merge_requests/user_posts_diff_notes_spec.rb)15
-rw-r--r--spec/features/merge_request/user_posts_notes_spec.rb (renamed from spec/features/merge_requests/user_posts_notes_spec.rb)8
-rw-r--r--spec/features/merge_request/user_resolves_conflicts_spec.rb (renamed from spec/features/merge_requests/conflicts_spec.rb)17
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb (renamed from spec/features/merge_requests/diff_notes_resolve_spec.rb)33
-rw-r--r--spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb (renamed from spec/features/merge_requests/resolve_outdated_diff_discussions.rb)2
-rw-r--r--spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb26
-rw-r--r--spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb (renamed from spec/features/merge_requests/diff_notes_avatars_spec.rb)8
-rw-r--r--spec/features/merge_request/user_sees_closing_issues_message_spec.rb (renamed from spec/features/merge_requests/closes_issues_spec.rb)10
-rw-r--r--spec/features/merge_request/user_sees_deleted_target_branch_spec.rb22
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb56
-rw-r--r--spec/features/merge_request/user_sees_diff_spec.rb (renamed from spec/features/merge_requests/diffs_spec.rb)4
-rw-r--r--spec/features/merge_request/user_sees_discussions_spec.rb (renamed from spec/features/merge_requests/discussion_spec.rb)19
-rw-r--r--spec/features/merge_request/user_sees_empty_state_spec.rb30
-rw-r--r--spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb (renamed from spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb)24
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb (renamed from spec/features/merge_requests/widget_spec.rb)4
-rw-r--r--spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb (renamed from spec/features/merge_requests/mini_pipeline_graph_spec.rb)28
-rw-r--r--spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb24
-rw-r--r--spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb (renamed from spec/features/merge_requests/deleted_source_branch_spec.rb)20
-rw-r--r--spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb35
-rw-r--r--spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb34
-rw-r--r--spec/features/merge_request/user_sees_pipelines_spec.rb (renamed from spec/features/merge_requests/pipelines_spec.rb)32
-rw-r--r--spec/features/merge_request/user_sees_system_notes_spec.rb (renamed from spec/features/merge_requests/user_sees_system_notes_spec.rb)6
-rw-r--r--spec/features/merge_request/user_sees_versions_spec.rb (renamed from spec/features/merge_requests/versions_spec.rb)45
-rw-r--r--spec/features/merge_request/user_sees_wip_help_message_spec.rb (renamed from spec/features/merge_requests/wip_message_spec.rb)8
-rw-r--r--spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb (renamed from spec/features/merge_requests/create_new_mr_spec.rb)11
-rw-r--r--spec/features/merge_request/user_toggles_whitespace_changes_spec.rb (renamed from spec/features/merge_requests/toggle_whitespace_changes_spec.rb)13
-rw-r--r--spec/features/merge_request/user_uses_slash_commands_spec.rb (renamed from spec/features/merge_requests/user_uses_slash_commands_spec.rb)19
-rw-r--r--spec/features/merge_requests/assign_issues_spec.rb51
-rw-r--r--spec/features/merge_requests/create_new_mr_from_fork_spec.rb89
-rw-r--r--spec/features/merge_requests/created_from_fork_spec.rb94
-rw-r--r--spec/features/merge_requests/edit_mr_spec.rb73
-rw-r--r--spec/features/merge_requests/filter_by_labels_spec.rb93
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb97
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb337
-rw-r--r--spec/features/merge_requests/filters_generic_behavior_spec.rb81
-rw-r--r--spec/features/merge_requests/form_spec.rb301
-rw-r--r--spec/features/merge_requests/reset_filters_spec.rb136
-rw-r--r--spec/features/merge_requests/target_branch_spec.rb33
-rw-r--r--spec/features/merge_requests/toggler_behavior_spec.rb28
-rw-r--r--spec/features/merge_requests/user_filters_by_assignees_spec.rb36
-rw-r--r--spec/features/merge_requests/user_filters_by_labels_spec.rb49
-rw-r--r--spec/features/merge_requests/user_filters_by_milestones_spec.rb62
-rw-r--r--spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb38
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb4
-rw-r--r--spec/features/merge_requests/user_mass_updates_spec.rb (renamed from spec/features/merge_requests/update_merge_requests_spec.rb)20
-rw-r--r--spec/features/merge_requests/widget_deployments_spec.rb59
-rw-r--r--spec/features/oauth_login_spec.rb3
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb12
-rw-r--r--spec/features/projects/blobs/edit_spec.rb22
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb38
-rw-r--r--spec/features/projects/clusters/user_spec.rb2
-rw-r--r--spec/features/projects/clusters_spec.rb6
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb15
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb2
-rw-r--r--spec/features/projects/jobs_spec.rb28
-rw-r--r--spec/features/projects/members/share_with_group_spec.rb2
-rw-r--r--spec/features/projects/merge_requests/list_spec.rb44
-rw-r--r--spec/features/projects/merge_requests/user_manages_subscription_spec.rb20
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb6
-rw-r--r--spec/features/projects/tree/create_directory_spec.rb2
-rw-r--r--spec/features/projects/tree/create_file_spec.rb2
-rw-r--r--spec/features/projects/tree/upload_file_spec.rb2
-rw-r--r--spec/features/user_can_display_performance_bar_spec.rb26
-rw-r--r--spec/features/user_page_spec.rb107
-rw-r--r--spec/finders/group_descendants_finder_spec.rb20
-rw-r--r--spec/finders/group_projects_finder_spec.rb76
-rw-r--r--spec/finders/issues_finder_spec.rb44
-rw-r--r--spec/finders/merge_requests_finder_spec.rb41
-rw-r--r--spec/finders/milestones_finder_spec.rb41
-rw-r--r--spec/fixtures/api/schemas/entities/issue.json1
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json1
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/pipelines.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/user/basic.json4
-rw-r--r--spec/fixtures/emails/attachment.eml28
-rw-r--r--spec/helpers/application_helper_spec.rb6
-rw-r--r--spec/helpers/blob_helper_spec.rb7
-rw-r--r--spec/helpers/diff_helper_spec.rb10
-rw-r--r--spec/helpers/groups_helper_spec.rb4
-rw-r--r--spec/helpers/issuables_helper_spec.rb35
-rw-r--r--spec/helpers/labels_helper_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb22
-rw-r--r--spec/helpers/version_check_helper_spec.rb2
-rw-r--r--spec/initializers/gollum_spec.rb62
-rw-r--r--spec/initializers/grape_route_helpers_fix_spec.rb14
-rw-r--r--spec/initializers/settings_spec.rb2
-rw-r--r--spec/javascripts/api_spec.js177
-rw-r--r--spec/javascripts/behaviors/secret_values_spec.js110
-rw-r--r--spec/javascripts/blob/notebook/index_spec.js6
-rw-r--r--spec/javascripts/blob/viewer/index_spec.js36
-rw-r--r--spec/javascripts/boards/board_card_spec.js2
-rw-r--r--spec/javascripts/boards/board_list_spec.js20
-rw-r--r--spec/javascripts/boards/board_new_issue_spec.js2
-rw-r--r--spec/javascripts/boards/boards_store_spec.js2
-rw-r--r--spec/javascripts/boards/issue_card_spec.js6
-rw-r--r--spec/javascripts/boards/list_spec.js4
-rw-r--r--spec/javascripts/boards/mock_data.js1
-rw-r--r--spec/javascripts/boards/utils/query_data_spec.js27
-rw-r--r--spec/javascripts/clusters/clusters_bundle_spec.js24
-rw-r--r--spec/javascripts/clusters/clusters_index_spec.js58
-rw-r--r--spec/javascripts/commit/commit_pipeline_status_component_spec.js104
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_spec.js6
-rw-r--r--spec/javascripts/commit_merge_requests_spec.js60
-rw-r--r--spec/javascripts/commits_spec.js43
-rw-r--r--spec/javascripts/create_item_dropdown_spec.js183
-rw-r--r--spec/javascripts/cycle_analytics/banner_spec.js3
-rw-r--r--spec/javascripts/cycle_analytics/total_time_component_spec.js6
-rw-r--r--spec/javascripts/deploy_keys/components/app_spec.js1
-rw-r--r--spec/javascripts/deploy_keys/components/key_spec.js20
-rw-r--r--spec/javascripts/environments/environment_item_spec.js4
-rw-r--r--spec/javascripts/environments/environments_app_spec.js1
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js1
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js1
-rw-r--r--spec/javascripts/fixtures/clusters.rb15
-rw-r--r--spec/javascripts/fixtures/create_item_dropdown.html.haml13
-rw-r--r--spec/javascripts/fixtures/projects.json2
-rw-r--r--spec/javascripts/fixtures/search.rb18
-rw-r--r--spec/javascripts/flash_spec.js12
-rw-r--r--spec/javascripts/fly_out_nav_spec.js32
-rw-r--r--spec/javascripts/groups/components/app_spec.js66
-rw-r--r--spec/javascripts/groups/components/item_actions_spec.js40
-rw-r--r--spec/javascripts/helpers/class_spec_helper_spec.js20
-rw-r--r--spec/javascripts/helpers/user_mock_data_helper.js2
-rw-r--r--spec/javascripts/integrations/integration_settings_form_spec.js128
-rw-r--r--spec/javascripts/issuable_spec.js26
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js35
-rw-r--r--spec/javascripts/issue_show/components/fields/description_template_spec.js2
-rw-r--r--spec/javascripts/issue_show/components/form_spec.js2
-rw-r--r--spec/javascripts/job_spec.js26
-rw-r--r--spec/javascripts/jobs/header_spec.js34
-rw-r--r--spec/javascripts/jobs/job_details_mediator_spec.js8
-rw-r--r--spec/javascripts/jobs/mock_data.js8
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js39
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js6
-rw-r--r--spec/javascripts/lib/utils/users_cache_spec.js4
-rw-r--r--spec/javascripts/merge_request_notes_spec.js1
-rw-r--r--spec/javascripts/merge_request_spec.js22
-rw-r--r--spec/javascripts/mini_pipeline_graph_dropdown_spec.js73
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js2
-rw-r--r--spec/javascripts/notebook/cells/markdown_spec.js12
-rw-r--r--spec/javascripts/notebook/cells/output/html_sanitize_tests.js66
-rw-r--r--spec/javascripts/notebook/cells/output/html_spec.js29
-rw-r--r--spec/javascripts/notes/components/comment_form_spec.js10
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js1
-rw-r--r--spec/javascripts/notes/components/note_form_spec.js9
-rw-r--r--spec/javascripts/notes/components/noteable_note_spec.js2
-rw-r--r--spec/javascripts/notes/mock_data.js4
-rw-r--r--spec/javascripts/notes_spec.js1
-rw-r--r--spec/javascripts/oauth_remember_me_spec.js2
-rw-r--r--spec/javascripts/pager_spec.js81
-rw-r--r--spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js (renamed from spec/javascripts/abuse_reports_spec.js)2
-rw-r--r--spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js63
-rw-r--r--spec/javascripts/pages/milestones/shared/components/delete_milestone_modal_spec.js95
-rw-r--r--spec/javascripts/pipelines/async_button_spec.js6
-rw-r--r--spec/javascripts/pipelines/empty_state_spec.js4
-rw-r--r--spec/javascripts/pipelines/graph/job_component_spec.js8
-rw-r--r--spec/javascripts/pipelines/graph/stage_column_component_spec.js15
-rw-r--r--spec/javascripts/pipelines/pipeline_details_mediator_spec.js1
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js1
-rw-r--r--spec/javascripts/pipelines/pipelines_table_row_spec.js7
-rw-r--r--spec/javascripts/pipelines/pipelines_table_spec.js5
-rw-r--r--spec/javascripts/pipelines/stage_spec.js1
-rw-r--r--spec/javascripts/registry/components/app_spec.js3
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js2
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_item_spec.js2
-rw-r--r--spec/javascripts/repo/components/new_dropdown/modal_spec.js6
-rw-r--r--spec/javascripts/repo/components/new_dropdown/upload_spec.js6
-rw-r--r--spec/javascripts/repo/components/repo_commit_section_spec.js8
-rw-r--r--spec/javascripts/repo/components/repo_file_spec.js8
-rw-r--r--spec/javascripts/repo/components/repo_tab_spec.js4
-rw-r--r--spec/javascripts/repo/components/repo_tabs_spec.js2
-rw-r--r--spec/javascripts/repo/stores/actions/file_spec.js31
-rw-r--r--spec/javascripts/repo/stores/actions_spec.js59
-rw-r--r--spec/javascripts/repo/stores/mutations/file_spec.js2
-rw-r--r--spec/javascripts/repo/stores/mutations/tree_spec.js2
-rw-r--r--spec/javascripts/search_spec.js40
-rw-r--r--spec/javascripts/sidebar/mock_data.js36
-rw-r--r--spec/javascripts/sidebar/sidebar_assignees_spec.js1
-rw-r--r--spec/javascripts/sidebar/sidebar_mediator_spec.js1
-rw-r--r--spec/javascripts/sidebar/sidebar_move_issue_spec.js1
-rw-r--r--spec/javascripts/sidebar/sidebar_store_spec.js6
-rw-r--r--spec/javascripts/sidebar/subscriptions_spec.js12
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js9
-rw-r--r--spec/javascripts/smart_interval_spec.js1
-rw-r--r--spec/javascripts/test_bundle.js12
-rw-r--r--spec/javascripts/todos_spec.js2
-rw-r--r--spec/javascripts/toggle_buttons_spec.js120
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js58
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js79
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js269
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js79
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js131
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js44
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js37
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js57
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js36
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js106
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js138
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js123
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js33
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js131
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js208
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_merging_spec.js34
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js53
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js11
-rw-r--r--spec/javascripts/vue_shared/components/clipboard_button_spec.js31
-rw-r--r--spec/javascripts/vue_shared/components/confirmation_input_spec.js63
-rw-r--r--spec/javascripts/vue_shared/components/loading_icon_spec.js3
-rw-r--r--spec/javascripts/vue_shared/components/markdown/field_spec.js6
-rw-r--r--spec/javascripts/vue_shared/components/modal_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js9
-rw-r--r--spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js77
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js3
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js1
-rw-r--r--spec/javascripts/zen_mode_spec.js2
-rw-r--r--spec/lib/backup/manager_spec.rb6
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb65
-rw-r--r--spec/lib/banzai/filter/snippet_reference_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb8
-rw-r--r--spec/lib/gitaly/server_spec.rb30
-rw-r--r--spec/lib/gitlab/auth/blocked_user_tracker_spec.rb53
-rw-r--r--spec/lib/gitlab/auth/user_auth_finders_spec.rb10
-rw-r--r--spec/lib/gitlab/auth_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb50
-rw-r--r--spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb11
-rw-r--r--spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb4
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb10
-rw-r--r--spec/lib/gitlab/bare_repository_import/importer_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/ansi2html_spec.rb55
-rw-r--r--spec/lib/gitlab/ci/config/entry/key_spec.rb62
-rw-r--r--spec/lib/gitlab/ci/status/build/action_spec.rb8
-rw-r--r--spec/lib/gitlab/database/migration_helpers_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/git/attributes_at_ref_parser_spec.rb28
-rw-r--r--spec/lib/gitlab/git/attributes_parser_spec.rb (renamed from spec/lib/gitlab/git/attributes_spec.rb)53
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb70
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb78
-rw-r--r--spec/lib/gitlab/git/gitlab_projects_spec.rb104
-rw-r--r--spec/lib/gitlab/git/info_attributes_spec.rb43
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb346
-rw-r--r--spec/lib/gitlab/git/rev_list_spec.rb11
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb20
-rw-r--r--spec/lib/gitlab/git_access_spec.rb14
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb23
-rw-r--r--spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb54
-rw-r--r--spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb33
-rw-r--r--spec/lib/gitlab/gitaly_client/health_check_service_spec.rb41
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/remote_service_spec.rb14
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb25
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb6
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb24
-rw-r--r--spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb4
-rw-r--r--spec/lib/gitlab/health_checks/gitaly_check_spec.rb57
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb57
-rw-r--r--spec/lib/gitlab/import_export/project.group.json2
-rw-r--r--spec/lib/gitlab/import_export/project.json22
-rw-r--r--spec/lib/gitlab/import_export/project.light.json1
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml5
-rw-r--r--spec/lib/gitlab/insecure_key_fingerprint_spec.rb18
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb19
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb10
-rw-r--r--spec/lib/gitlab/metrics/method_call_spec.rb44
-rw-r--r--spec/lib/gitlab/metrics/methods_spec.rb137
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb22
-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.rb7
-rw-r--r--spec/lib/gitlab/metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb30
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb2
-rw-r--r--spec/lib/gitlab/popen/runner_spec.rb139
-rw-r--r--spec/lib/gitlab/popen_spec.rb16
-rw-r--r--spec/lib/gitlab/profiler_spec.rb156
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb39
-rw-r--r--spec/lib/gitlab/regex_spec.rb3
-rw-r--r--spec/lib/gitlab/search_results_spec.rb58
-rw-r--r--spec/lib/gitlab/shell_spec.rb378
-rw-r--r--spec/lib/gitlab/slash_commands/issue_search_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/override_spec.rb158
-rw-r--r--spec/lib/gitlab/utils_spec.rb16
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb14
-rw-r--r--spec/mailers/notify_spec.rb51
-rw-r--r--spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb2
-rw-r--r--spec/migrations/calculate_conv_dev_index_percentages_spec.rb2
-rw-r--r--spec/migrations/fix_wrongly_renamed_routes_spec.rb65
-rw-r--r--spec/migrations/migrate_issues_to_ghost_user_spec.rb6
-rw-r--r--spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb2
-rw-r--r--spec/migrations/migrate_user_project_view_spec.rb2
-rw-r--r--spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb43
-rw-r--r--spec/migrations/remove_duplicate_mr_events_spec.rb2
-rw-r--r--spec/migrations/remove_empty_fork_networks_spec.rb13
-rw-r--r--spec/migrations/remove_soft_removed_objects_spec.rb77
-rw-r--r--spec/migrations/rename_more_reserved_project_names_spec.rb4
-rw-r--r--spec/migrations/rename_reserved_project_names_spec.rb4
-rw-r--r--spec/migrations/rename_users_with_renamed_namespace_spec.rb2
-rw-r--r--spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb6
-rw-r--r--spec/migrations/update_retried_for_ci_build_spec.rb2
-rw-r--r--spec/migrations/update_upload_paths_to_system_spec.rb58
-rw-r--r--spec/models/ci/build_spec.rb9
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb1
-rw-r--r--spec/models/ci/pipeline_spec.rb7
-rw-r--r--spec/models/commit_range_spec.rb6
-rw-r--r--spec/models/commit_spec.rb39
-rw-r--r--spec/models/concerns/avatarable_spec.rb39
-rw-r--r--spec/models/concerns/discussion_on_diff_spec.rb10
-rw-r--r--spec/models/concerns/triggerable_hooks_spec.rb43
-rw-r--r--spec/models/deploy_keys_project_spec.rb2
-rw-r--r--spec/models/hooks/system_hook_spec.rb3
-rw-r--r--spec/models/hooks/web_hook_spec.rb6
-rw-r--r--spec/models/issue_spec.rb5
-rw-r--r--spec/models/member_spec.rb4
-rw-r--r--spec/models/merge_request_diff_spec.rb22
-rw-r--r--spec/models/merge_request_spec.rb193
-rw-r--r--spec/models/namespace_spec.rb11
-rw-r--r--spec/models/pages_domain_spec.rb2
-rw-r--r--spec/models/project_group_link_spec.rb2
-rw-r--r--spec/models/project_services/jira_service_spec.rb27
-rw-r--r--spec/models/project_services/microsoft_teams_service_spec.rb4
-rw-r--r--spec/models/project_spec.rb45
-rw-r--r--spec/models/project_statistics_spec.rb6
-rw-r--r--spec/models/push_event_spec.rb26
-rw-r--r--spec/models/repository_spec.rb146
-rw-r--r--spec/models/route_spec.rb121
-rw-r--r--spec/models/service_spec.rb34
-rw-r--r--spec/models/user_spec.rb18
-rw-r--r--spec/models/wiki_page_spec.rb21
-rw-r--r--spec/policies/ci/pipeline_schedule_policy_spec.rb14
-rw-r--r--spec/policies/project_policy_spec.rb2
-rw-r--r--spec/requests/api/applications_spec.rb86
-rw-r--r--spec/requests/api/commit_statuses_spec.rb1
-rw-r--r--spec/requests/api/commits_spec.rb37
-rw-r--r--spec/requests/api/deploy_keys_spec.rb12
-rw-r--r--spec/requests/api/deployments_spec.rb54
-rw-r--r--spec/requests/api/helpers_spec.rb6
-rw-r--r--spec/requests/api/internal_spec.rb89
-rw-r--r--spec/requests/api/issues_spec.rb13
-rw-r--r--spec/requests/api/jobs_spec.rb56
-rw-r--r--spec/requests/api/members_spec.rb25
-rw-r--r--spec/requests/api/merge_requests_spec.rb71
-rw-r--r--spec/requests/api/project_milestones_spec.rb40
-rw-r--r--spec/requests/api/project_snippets_spec.rb13
-rw-r--r--spec/requests/api/projects_spec.rb26
-rw-r--r--spec/requests/api/protected_branches_spec.rb6
-rw-r--r--spec/requests/api/runner_spec.rb1
-rw-r--r--spec/requests/api/services_spec.rb4
-rw-r--r--spec/requests/api/system_hooks_spec.rb20
-rw-r--r--spec/requests/api/v3/builds_spec.rb38
-rw-r--r--spec/requests/api/v3/commits_spec.rb33
-rw-r--r--spec/requests/api/v3/deploy_keys_spec.rb2
-rw-r--r--spec/requests/api/v3/members_spec.rb10
-rw-r--r--spec/requests/api/v3/merge_requests_spec.rb26
-rw-r--r--spec/requests/lfs_http_spec.rb4
-rw-r--r--spec/rubocop/cop/gitlab/predicate_memoization_spec.rb100
-rw-r--r--spec/rubocop/cop/line_break_around_conditional_block_spec.rb411
-rw-r--r--spec/serializers/deploy_key_entity_spec.rb15
-rw-r--r--spec/services/check_gcp_project_billing_service_spec.rb21
-rw-r--r--spec/services/files/update_service_spec.rb12
-rw-r--r--spec/services/issues/close_service_spec.rb2
-rw-r--r--spec/services/issues/move_service_spec.rb16
-rw-r--r--spec/services/issues/update_service_spec.rb3
-rw-r--r--spec/services/labels/promote_service_spec.rb13
-rw-r--r--spec/services/merge_requests/build_service_spec.rb26
-rw-r--r--spec/services/merge_requests/close_service_spec.rb2
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb19
-rw-r--r--spec/services/merge_requests/create_service_spec.rb61
-rw-r--r--spec/services/merge_requests/ff_merge_service_spec.rb3
-rw-r--r--spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb2
-rw-r--r--spec/services/merge_requests/rebase_service_spec.rb108
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb25
-rw-r--r--spec/services/merge_requests/reopen_service_spec.rb2
-rw-r--r--spec/services/merge_requests/update_service_spec.rb3
-rw-r--r--spec/services/notification_service_spec.rb68
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb15
-rw-r--r--spec/services/projects/gitlab_projects_import_service_spec.rb31
-rw-r--r--spec/services/projects/transfer_service_spec.rb1
-rw-r--r--spec/services/system_hooks_service_spec.rb13
-rw-r--r--spec/services/system_note_service_spec.rb7
-rw-r--r--spec/services/test_hooks/system_service_spec.rb20
-rw-r--r--spec/services/users/destroy_service_spec.rb2
-rw-r--r--spec/spec_helper.rb14
-rw-r--r--spec/support/cycle_analytics_helpers.rb31
-rw-r--r--spec/support/db_cleaner.rb26
-rw-r--r--spec/support/devise_helpers.rb15
-rw-r--r--spec/support/email_helpers.rb4
-rw-r--r--spec/support/features/discussion_comments_shared_example.rb8
-rw-r--r--spec/support/filtered_search_helpers.rb2
-rwxr-xr-xspec/support/generate-seed-repo-rb1
-rw-r--r--spec/support/google_api/cloud_platform_helpers.rb47
-rw-r--r--spec/support/javascript_fixtures_helpers.rb1
-rw-r--r--spec/support/login_helpers.rb7
-rw-r--r--spec/support/matchers/access_matchers_for_controller.rb1
-rw-r--r--spec/support/project_forks_helper.rb4
-rw-r--r--spec/support/select2_helper.rb1
-rw-r--r--spec/support/services_shared_context.rb8
-rw-r--r--spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb99
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb140
-rw-r--r--spec/support/slack_mattermost_notifications_shared_examples.rb1
-rw-r--r--spec/support/stub_env.rb1
-rw-r--r--spec/support/test_env.rb11
-rw-r--r--spec/support/wait_for_requests.rb1
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/git_rake_spec.rb2
-rw-r--r--spec/tasks/gitlab/gitaly_rake_spec.rb6
-rw-r--r--spec/tasks/gitlab/task_helpers_spec.rb1
-rw-r--r--spec/tasks/gitlab/uploads_rake_spec.rb27
-rw-r--r--spec/uploaders/job_artifact_uploader_spec.rb2
-rw-r--r--spec/views/projects/buttons/_dropdown.html.haml_spec.rb39
-rw-r--r--spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb47
-rw-r--r--spec/views/projects/pipelines_settings/_show.html.haml_spec.rb8
-rw-r--r--spec/workers/check_gcp_project_billing_worker_spec.rb2
-rw-r--r--spec/workers/gitlab_shell_worker_spec.rb12
-rw-r--r--spec/workers/new_issue_worker_spec.rb5
-rw-r--r--spec/workers/new_merge_request_worker_spec.rb6
-rw-r--r--spec/workers/repository_fork_worker_spec.rb8
-rw-r--r--spec/workers/repository_import_worker_spec.rb15
487 files changed, 10097 insertions, 4418 deletions
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 2565622f8df..cc1b1e5039e 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -51,6 +51,13 @@ describe Admin::ApplicationSettingsController do
sign_in(admin)
end
+ it 'updates the password_authentication_enabled_for_git setting' do
+ put :update, application_setting: { password_authentication_enabled_for_git: "0" }
+
+ expect(response).to redirect_to(admin_application_settings_path)
+ expect(ApplicationSetting.current.password_authentication_enabled_for_git).to eq(false)
+ end
+
it 'updates the default_project_visibility for string value' do
put :update, application_setting: { default_project_visibility: "20" }
diff --git a/spec/controllers/admin/gitaly_servers_controller_spec.rb b/spec/controllers/admin/gitaly_servers_controller_spec.rb
new file mode 100644
index 00000000000..b7378aa37d0
--- /dev/null
+++ b/spec/controllers/admin/gitaly_servers_controller_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe Admin::GitalyServersController do
+ describe '#index' do
+ before do
+ sign_in(create(:admin))
+ end
+
+ it 'shows the gitaly servers page' do
+ get :index
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+end
diff --git a/spec/controllers/admin/hooks_controller_spec.rb b/spec/controllers/admin/hooks_controller_spec.rb
index e6ba596117a..d2c1e634930 100644
--- a/spec/controllers/admin/hooks_controller_spec.rb
+++ b/spec/controllers/admin/hooks_controller_spec.rb
@@ -11,11 +11,13 @@ describe Admin::HooksController do
it 'sets all parameters' do
hook_params = {
enable_ssl_verification: true,
+ token: "TEST TOKEN",
+ url: "http://example.com",
+
push_events: true,
tag_push_events: true,
repository_update_events: true,
- token: "TEST TOKEN",
- url: "http://example.com"
+ merge_requests_events: true
}
post :create, hook: hook_params
diff --git a/spec/controllers/dashboard/groups_controller_spec.rb b/spec/controllers/dashboard/groups_controller_spec.rb
index fb9d3efbac0..7f2eaf95165 100644
--- a/spec/controllers/dashboard/groups_controller_spec.rb
+++ b/spec/controllers/dashboard/groups_controller_spec.rb
@@ -20,4 +20,24 @@ 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
+ 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) }
+ let!(:other_group) { create(:group, name: 'other') }
+
+ before do
+ top_level_result.add_master(user)
+ top_level_a.add_master(user)
+ end
+
+ it 'renders only groups the user is a member of when searching hierarchy correctly' do
+ get :index, filter: 'chef', format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ all_groups = [top_level_result, top_level_a, sub_level_result_a]
+ expect(assigns(:groups)).to contain_exactly(*all_groups)
+ end
+ end
end
diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb
index cb1b460fc0e..22d3076c269 100644
--- a/spec/controllers/groups/children_controller_spec.rb
+++ b/spec/controllers/groups/children_controller_spec.rb
@@ -160,6 +160,30 @@ describe Groups::ChildrenController do
expect(json_response).to eq([])
end
+ it 'succeeds if multiple pages contain matching subgroups' do
+ create(:group, parent: group, name: 'subgroup-filter-1')
+ create(:group, parent: group, name: 'subgroup-filter-2')
+
+ # Creating the group-to-nest first so it would be loaded into the
+ # relation first before it's parents, this is what would cause the
+ # crash in: https://gitlab.com/gitlab-org/gitlab-ce/issues/40785.
+ #
+ # If we create the parent groups first, those would be loaded into the
+ # collection first, and the pagination would cut off the actual search
+ # result. In this case the hierarchy can be rendered without crashing,
+ # it's just incomplete.
+ group_to_nest = create(:group, parent: group, name: 'subsubgroup-filter-3')
+ subgroup = create(:group, parent: group)
+ 3.times do |i|
+ subgroup = create(:group, parent: subgroup)
+ end
+ group_to_nest.update!(parent: subgroup)
+
+ get :index, group_id: group.to_param, filter: 'filter', per_page: 3, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
it 'includes pagination headers' do
2.times { |i| create(:group, :public, parent: public_subgroup, name: "filterme#{i}") }
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index a9cfd964dd5..492fed42d31 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -85,6 +85,30 @@ describe GroupsController do
end
end
+ describe 'GET #activity' do
+ render_views
+
+ before do
+ sign_in(user)
+ project
+ end
+
+ context 'as json' do
+ it 'includes all projects in event feed' do
+ 3.times do
+ project = create(:project, group: group)
+ create(:event, project: project)
+ end
+
+ get :activity, id: group.to_param, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['count']).to eq(3)
+ expect(assigns(:projects).limit_value).to be_nil
+ end
+ end
+ end
+
describe 'POST #create' do
context 'when creating subgroups', :nested_groups do
[true, false].each do |can_create_group_status|
diff --git a/spec/controllers/import/gitlab_projects_controller_spec.rb b/spec/controllers/import/gitlab_projects_controller_spec.rb
new file mode 100644
index 00000000000..8759d3c0b97
--- /dev/null
+++ b/spec/controllers/import/gitlab_projects_controller_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Import::GitlabProjectsController do
+ set(:namespace) { create(:namespace) }
+ set(:user) { namespace.owner }
+ let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'POST create' do
+ context 'with an invalid path' do
+ it 'redirects with an error' do
+ post :create, namespace_id: namespace.id, path: '/test', file: file
+
+ expect(flash[:alert]).to start_with('Project could not be imported')
+ expect(response).to have_gitlab_http_status(302)
+ end
+
+ it 'redirects with an error when a relative path is used' do
+ post :create, namespace_id: namespace.id, path: '../test', file: file
+
+ expect(flash[:alert]).to start_with('Project could not be imported')
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ context 'with a valid path' do
+ it 'redirects to the new project path' do
+ post :create, namespace_id: namespace.id, path: 'test', file: file
+
+ expect(flash[:notice]).to include('is being imported')
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb
new file mode 100644
index 00000000000..c639ad32ec6
--- /dev/null
+++ b/spec/controllers/omniauth_callbacks_controller_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe OmniauthCallbacksController do
+ include LoginHelpers
+
+ let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: provider) }
+ let(:provider) { :github }
+
+ before do
+ mock_auth_hash(provider.to_s, 'my-uid', user.email)
+ stub_omniauth_provider(provider, context: request)
+ end
+
+ it 'allows sign in' do
+ post provider
+
+ expect(request.env['warden']).to be_authenticated
+ end
+
+ shared_context 'sign_up' do
+ let(:user) { double(email: 'new@example.com') }
+
+ before do
+ stub_omniauth_setting(block_auto_created_users: false)
+ end
+ end
+
+ context 'sign up' do
+ include_context 'sign_up'
+
+ it 'is allowed' do
+ post provider
+
+ expect(request.env['warden']).to be_authenticated
+ end
+ end
+
+ context 'when OAuth is disabled' do
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ settings = Gitlab::CurrentSettings.current_application_settings
+ settings.update(disabled_oauth_sign_in_sources: [provider.to_s])
+ end
+
+ it 'prevents login via POST' do
+ post provider
+
+ expect(request.env['warden']).not_to be_authenticated
+ end
+
+ it 'shows warning when attempting login' do
+ post provider
+
+ expect(response).to redirect_to new_user_session_path
+ expect(flash[:alert]).to eq('Signing in using GitHub has been disabled')
+ end
+
+ it 'allows linking the disabled provider' do
+ user.identities.destroy_all
+ sign_in(user)
+
+ expect { post provider }.to change { user.reload.identities.count }.by(1)
+ end
+
+ context 'sign up' do
+ include_context 'sign_up'
+
+ it 'is prevented' do
+ post provider
+
+ expect(request.env['warden']).not_to be_authenticated
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb
index 3bbe168f6d5..6a41c4d23ea 100644
--- a/spec/controllers/projects/avatars_controller_spec.rb
+++ b/spec/controllers/projects/avatars_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Projects::AvatarsController do
- let(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let(:project) { create(:project, :repository, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
let(:user) { create(:user) }
before do
@@ -10,6 +10,12 @@ describe Projects::AvatarsController do
controller.instance_variable_set(:@project, project)
end
+ it 'GET #show' do
+ get :show, namespace_id: project.namespace.id, project_id: project.id
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
it 'removes avatar from DB by calling destroy' do
delete :destroy, namespace_id: project.namespace.id, project_id: project.id
expect(project.avatar.present?).to be_falsey
diff --git a/spec/controllers/projects/clusters/gcp_controller_spec.rb b/spec/controllers/projects/clusters/gcp_controller_spec.rb
index be19fa93183..775f9db1c6e 100644
--- a/spec/controllers/projects/clusters/gcp_controller_spec.rb
+++ b/spec/controllers/projects/clusters/gcp_controller_spec.rb
@@ -137,11 +137,14 @@ describe Projects::Clusters::GcpController do
context 'when access token is valid' do
before do
stub_google_api_validate_token
+ allow_any_instance_of(described_class).to receive(:authorize_google_project_billing)
end
context 'when google project billing is enabled' do
before do
- stub_google_project_billing_status
+ redis_double = double
+ allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double)
+ allow(redis_double).to receive(:get).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for('token')).and_return('true')
end
it 'creates a new cluster' do
@@ -158,7 +161,7 @@ describe Projects::Clusters::GcpController do
it 'renders the cluster form with an error' do
go
- expect(response).to set_flash[:error]
+ expect(response).to set_flash[:alert]
expect(response).to render_template('new')
end
end
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 73fb90d73ec..55ed276f96b 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -41,15 +41,21 @@ describe Projects::CommitsController do
context "when the ref name ends in .atom" do
context "when the ref does not exist with the suffix" do
- it "renders as atom" do
+ before do
get(:show,
namespace_id: project.namespace,
project_id: project,
id: "master.atom")
+ end
+ it "renders as atom" do
expect(response).to be_success
expect(response.content_type).to eq('application/atom+xml')
end
+
+ it 'renders summary with type=html' do
+ expect(response.body).to include('<summary type="html">')
+ end
end
context "when the ref exists with the suffix" do
diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb
index aba70c6d4c1..2d473d5bf52 100644
--- a/spec/controllers/projects/hooks_controller_spec.rb
+++ b/spec/controllers/projects/hooks_controller_spec.rb
@@ -18,4 +18,30 @@ describe Projects::HooksController do
)
end
end
+
+ describe '#create' do
+ it 'sets all parameters' do
+ hook_params = {
+ enable_ssl_verification: true,
+ token: "TEST TOKEN",
+ url: "http://example.com",
+
+ push_events: true,
+ tag_push_events: true,
+ merge_requests_events: true,
+ issues_events: true,
+ confidential_issues_events: true,
+ note_events: true,
+ job_events: true,
+ pipeline_events: true,
+ wiki_page_events: true
+ }
+
+ post :create, namespace_id: project.namespace, project_id: project, hook: hook_params
+
+ expect(response).to have_http_status(302)
+ expect(ProjectHook.all.size).to eq(1)
+ expect(ProjectHook.first).to have_attributes(hook_params)
+ end
+ end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 6b7db947216..9656e7f7e74 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -102,6 +102,18 @@ describe Projects::IssuesController do
expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
end
+
+ it 'does not use pagination if disabled' do
+ allow(controller).to receive(:pagination_disabled?).and_return(true)
+
+ get :index,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ page: (last_page + 1).to_param
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(assigns(:issues).size).to eq(2)
+ end
end
end
@@ -301,6 +313,53 @@ describe Projects::IssuesController do
end
end
+ describe 'GET #realtime_changes' do
+ def go(id:)
+ get :realtime_changes,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: id
+ end
+
+ context 'when an issue was edited' do
+ before do
+ project.add_developer(user)
+
+ issue.update!(last_edited_by: user, last_edited_at: issue.created_at + 1.minute)
+
+ sign_in(user)
+ end
+
+ 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)
+ end
+ end
+
+ context 'when an issue was edited by a deleted user' do
+ let(:deleted_user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+
+ issue.update!(last_edited_by: deleted_user, last_edited_at: Time.now)
+
+ deleted_user.destroy
+ sign_in(user)
+ end
+
+ it 'returns 200' do
+ go(id: issue.iid)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+
describe 'Confidential Issues' do
let(:project) { create(:project_empty_repo, :public) }
let(:assignee) { create(:assignee) }
@@ -589,25 +648,6 @@ describe Projects::IssuesController do
project_id: project,
id: id
end
-
- context 'when an issue was edited by a deleted user' do
- let(:deleted_user) { create(:user) }
-
- before do
- project.add_developer(user)
-
- issue.update!(last_edited_by: deleted_user, last_edited_at: Time.now)
-
- deleted_user.destroy
- sign_in(user)
- end
-
- it 'returns 200' do
- go(id: issue.iid)
-
- expect(response).to have_gitlab_http_status(200)
- end
- end
end
describe 'GET #edit' do
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 01f69e81103..f3e303bb0fe 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -137,8 +137,8 @@ describe Projects::JobsController do
it 'exposes needed information' do
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['raw_path']).to match(/jobs\/\d+\/raw\z/)
- expect(json_response.dig('merge_request', 'path')).to match(/merge_requests\/\d+\z/)
+ expect(json_response['raw_path']).to match(%r{jobs/\d+/raw\z})
+ expect(json_response.dig('merge_request', 'path')).to match(%r{merge_requests/\d+\z})
expect(json_response['new_issue_path'])
.to include('/issues/new')
end
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
index 7e2366847f4..92db7284e0e 100644
--- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
@@ -4,6 +4,16 @@ describe Projects::MergeRequests::CreationsController do
let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:fork_project) { create(:forked_project_with_submodules) }
+ let(:get_diff_params) do
+ {
+ namespace_id: fork_project.namespace.to_param,
+ project_id: fork_project,
+ merge_request: {
+ source_branch: 'remove-submodule',
+ target_branch: 'master'
+ }
+ }
+ end
before do
fork_project.add_master(user)
@@ -13,18 +23,23 @@ describe Projects::MergeRequests::CreationsController do
describe 'GET new' do
context 'merge request that removes a submodule' do
- render_views
-
it 'renders new merge request widget template' do
- get :new,
- namespace_id: fork_project.namespace.to_param,
- project_id: fork_project,
- merge_request: {
- source_branch: 'remove-submodule',
- target_branch: 'master'
- }
+ get :new, get_diff_params
+
+ expect(response).to be_success
+ end
+ end
+ end
+
+ describe 'GET diffs' do
+ context 'when merge request cannot be created' do
+ it 'does not assign diffs var' do
+ allow_any_instance_of(MergeRequest).to receive(:can_be_created).and_return(false)
+
+ get :diffs, get_diff_params.merge(format: 'json')
expect(response).to be_success
+ expect(assigns[:diffs]).to be_nil
end
end
end
@@ -37,14 +52,7 @@ describe Projects::MergeRequests::CreationsController do
end
it 'renders JSON including serialized pipelines' do
- get :pipelines,
- namespace_id: fork_project.namespace.to_param,
- project_id: fork_project,
- merge_request: {
- source_branch: 'remove-submodule',
- target_branch: 'master'
- },
- format: :json
+ get :pipelines, get_diff_params.merge(format: 'json')
expect(response).to be_ok
expect(json_response).to have_key 'pipelines'
diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb
index e2524be7724..1ce7e84bef9 100644
--- a/spec/controllers/projects/todos_controller_spec.rb
+++ b/spec/controllers/projects/todos_controller_spec.rb
@@ -36,7 +36,7 @@ describe Projects::TodosController do
expect(response).to have_gitlab_http_status(200)
expect(json_response['count']).to eq 1
- expect(json_response['delete_path']).to match(/\/dashboard\/todos\/\d{1}/)
+ expect(json_response['delete_path']).to match(%r{/dashboard/todos/\d{1}})
end
end
@@ -104,7 +104,7 @@ describe Projects::TodosController do
expect(response).to have_gitlab_http_status(200)
expect(json_response['count']).to eq 1
- expect(json_response['delete_path']).to match(/\/dashboard\/todos\/\d{1}/)
+ expect(json_response['delete_path']).to match(%r{/dashboard/todos/\d{1}})
end
end
diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb
index 84a8bc56640..d5d819d862a 100644
--- a/spec/factories/commits.rb
+++ b/spec/factories/commits.rb
@@ -23,7 +23,7 @@ FactoryBot.define do
end
after(:build) do |commit, evaluator|
- allow(commit).to receive(:author).and_return(evaluator.author || build(:author))
+ allow(commit).to receive(:author).and_return(evaluator.author || build_stubbed(:author))
end
trait :without_author do
diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb
index 30a6d468ed3..4350652fb79 100644
--- a/spec/factories/deploy_keys_projects.rb
+++ b/spec/factories/deploy_keys_projects.rb
@@ -2,5 +2,9 @@ FactoryBot.define do
factory :deploy_keys_project do
deploy_key
project
+
+ trait :write_access do
+ can_push true
+ end
end
end
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index 9d7d5e56611..cac56695319 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -3,13 +3,14 @@ FactoryBot.define do
sha '97de212e80737a608d939f648d959671fb0a0142'
ref 'master'
tag false
- user
+ user nil
project nil
deployable factory: :ci_build
environment factory: :environment
after(:build) do |deployment, evaluator|
deployment.project ||= deployment.environment.project
+ deployment.user ||= deployment.project.creator
unless deployment.project.repository_exists?
allow(deployment.project.repository).to receive(:create_ref)
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
index ed275243ac9..5798b81ecad 100644
--- a/spec/factories/events.rb
+++ b/spec/factories/events.rb
@@ -1,7 +1,7 @@
FactoryBot.define do
factory :event do
project
- author factory: :user
+ author(factory: :user) { project.creator }
action Event::JOINED
trait(:created) { action Event::CREATED }
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 71dc169c6a2..998080a3dd5 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -1,8 +1,8 @@
FactoryBot.define do
factory :issue do
title { generate(:title) }
- author
project
+ author { project.creator }
trait :confidential do
confidential true
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index 552b4b7e06e..f0c43f3d6f5 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -15,10 +15,6 @@ FactoryBot.define do
factory :another_deploy_key, class: 'DeployKey'
end
- factory :write_access_key, class: 'DeployKey' do
- can_push true
- end
-
factory :rsa_key_2048 do
key do
<<~KEY.delete("\n")
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 40558c88d15..d26cb0c3417 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -1,9 +1,9 @@
FactoryBot.define do
factory :merge_request do
title { generate(:title) }
- author
association :source_project, :repository, factory: :project
target_project { source_project }
+ author { source_project.creator }
# $ git log --pretty=oneline feature..master
# 5937ac0a7beb003549fc5fd26fc247adbce4a52e Add submodule from gitlab.com
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 0889c5090fb..3f4e408b3a6 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -6,7 +6,7 @@ FactoryBot.define do
factory :note do
project
note { generate(:title) }
- author
+ author { project&.creator || create(:user) }
on_issue
factory :note_on_commit, traits: [:on_commit]
diff --git a/spec/factories/project_wikis.rb b/spec/factories/project_wikis.rb
index 89d8248f9f4..db2eb4fc863 100644
--- a/spec/factories/project_wikis.rb
+++ b/spec/factories/project_wikis.rb
@@ -3,7 +3,7 @@ FactoryBot.define do
skip_create
project
- user factory: :user
+ user { project.creator }
initialize_with { new(project, user) }
end
end
diff --git a/spec/factories/protected_branches.rb b/spec/factories/protected_branches.rb
index 39460834d06..60956511834 100644
--- a/spec/factories/protected_branches.rb
+++ b/spec/factories/protected_branches.rb
@@ -53,6 +53,7 @@ FactoryBot.define do
if evaluator.default_access_level && evaluator.default_push_level
protected_branch.push_access_levels.new(access_level: Gitlab::Access::MASTER)
end
+
if evaluator.default_access_level && evaluator.default_merge_level
protected_branch.merge_access_levels.new(access_level: Gitlab::Access::MASTER)
end
diff --git a/spec/factories/redirect_routes.rb b/spec/factories/redirect_routes.rb
new file mode 100644
index 00000000000..c29c81c5df9
--- /dev/null
+++ b/spec/factories/redirect_routes.rb
@@ -0,0 +1,15 @@
+FactoryBot.define do
+ factory :redirect_route do
+ sequence(:path) { |n| "redirect#{n}" }
+ source factory: :group
+ permanent false
+
+ trait :permanent do
+ permanent true
+ end
+
+ trait :temporary do
+ permanent false
+ end
+ end
+end
diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb
index 80872067233..b0174dd06b7 100644
--- a/spec/factories/sent_notifications.rb
+++ b/spec/factories/sent_notifications.rb
@@ -1,7 +1,7 @@
FactoryBot.define do
factory :sent_notification do
project
- recipient factory: :user
+ recipient { project.creator }
noteable { create(:issue, project: project) }
reply_key { SentNotification.reply_key }
end
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index 2ab9a56d255..dc12b562108 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -21,6 +21,7 @@ FactoryBot.define do
factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
project
+ author { project.creator }
end
factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb
index a4bc4e87b0a..8f7ab74ec70 100644
--- a/spec/factories/subscriptions.rb
+++ b/spec/factories/subscriptions.rb
@@ -1,7 +1,7 @@
FactoryBot.define do
factory :subscription do
- user
project
+ user { project.creator }
subscribable factory: :issue
end
end
diff --git a/spec/factories/timelogs.rb b/spec/factories/timelogs.rb
index af34b0681e2..b45f06b9a0a 100644
--- a/spec/factories/timelogs.rb
+++ b/spec/factories/timelogs.rb
@@ -3,7 +3,7 @@
FactoryBot.define do
factory :timelog do
time_spent 3600
- user
issue
+ user { issue.project.creator }
end
end
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index 6a6de665dd1..94f8caedfa6 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -1,8 +1,8 @@
FactoryBot.define do
factory :todo do
project
- author
- user
+ author { project.creator }
+ user { project.creator }
target factory: :issue
action { Todo::ASSIGNED }
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index e020579f71e..51b42d1b43b 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -21,7 +21,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_selector('.row-content-block', text: 'All jobs')
expect(page.all('.build-link').size).to eq(4)
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
@@ -31,7 +31,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_content 'No jobs to show'
- expect(page).not_to have_link 'Cancel all'
+ expect(page).not_to have_button 'Stop all jobs'
end
end
end
@@ -51,7 +51,7 @@ describe 'Admin Builds' do
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).not_to have_content(build3.id)
expect(page.find('.build-link')).not_to have_content(build4.id)
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
@@ -63,7 +63,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'Pending')
expect(page).to have_content 'No jobs to show'
- expect(page).not_to have_link 'Cancel all'
+ expect(page).not_to have_button 'Stop all jobs'
end
end
end
@@ -83,7 +83,7 @@ describe 'Admin Builds' do
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).not_to have_content(build3.id)
expect(page.find('.build-link')).not_to have_content(build4.id)
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
@@ -95,7 +95,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page).to have_content 'No jobs to show'
- expect(page).not_to have_link 'Cancel all'
+ expect(page).not_to have_button 'Stop all jobs'
end
end
end
@@ -113,7 +113,7 @@ describe 'Admin Builds' do
expect(page.find('.build-link')).not_to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).to have_content(build3.id)
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
@@ -125,7 +125,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page).to have_content 'No jobs to show'
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
end
diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb
index 241c7cbc34e..cb96830cb7c 100644
--- a/spec/features/admin/admin_deploy_keys_spec.rb
+++ b/spec/features/admin/admin_deploy_keys_spec.rb
@@ -17,6 +17,16 @@ RSpec.describe 'admin deploy keys' do
end
end
+ it 'shows all the projects the deploy key has write access' do
+ write_key = create(:deploy_keys_project, :write_access, deploy_key: deploy_key)
+
+ visit admin_deploy_keys_path
+
+ page.within(find('.deploy-keys-list', match: :first)) do
+ expect(page).to have_content(write_key.project.full_name)
+ end
+ end
+
describe 'create a new deploy key' do
let(:new_ssh_key) { attributes_for(:key)[:key] }
@@ -28,14 +38,12 @@ RSpec.describe 'admin deploy keys' do
it 'creates a new deploy key' do
fill_in 'deploy_key_title', with: 'laptop'
fill_in 'deploy_key_key', with: new_ssh_key
- check 'deploy_key_can_push'
click_button 'Create'
expect(current_path).to eq admin_deploy_keys_path
page.within(find('.deploy-keys-list', match: :first)) do
expect(page).to have_content('laptop')
- expect(page).to have_content('Yes')
end
end
end
@@ -48,14 +56,12 @@ RSpec.describe 'admin deploy keys' do
it 'updates an existing deploy key' do
fill_in 'deploy_key_title', with: 'new-title'
- check 'deploy_key_can_push'
click_button 'Save changes'
expect(current_path).to eq admin_deploy_keys_path
page.within(find('.deploy-keys-list', match: :first)) do
expect(page).to have_content('new-title')
- expect(page).to have_content('Yes')
end
end
end
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index eec44549a03..f266f2ecc54 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -1,11 +1,10 @@
require 'spec_helper'
-describe 'Admin::Hooks', :js do
- before do
- @project = create(:project)
- sign_in(create(:admin))
+describe 'Admin::Hooks' do
+ let(:user) { create(:admin) }
- @system_hook = create(:system_hook)
+ before do
+ sign_in(user)
end
describe 'GET /admin/hooks' do
@@ -13,15 +12,17 @@ describe 'Admin::Hooks', :js do
visit admin_root_path
page.within '.nav-sidebar' do
- click_on 'Hooks'
+ click_on 'System Hooks', match: :first
end
expect(current_path).to eq(admin_hooks_path)
end
it 'has hooks list' do
+ system_hook = create(:system_hook)
+
visit admin_hooks_path
- expect(page).to have_content(@system_hook.url)
+ expect(page).to have_content(system_hook.url)
end
end
@@ -43,6 +44,10 @@ describe 'Admin::Hooks', :js do
describe 'Update existing hook' do
let(:new_url) { generate(:url) }
+ before do
+ create(:system_hook)
+ end
+
it 'updates existing hook' do
visit admin_hooks_path
@@ -57,7 +62,11 @@ describe 'Admin::Hooks', :js do
end
end
- describe 'Remove existing hook' do
+ describe 'Remove existing hook', :js do
+ before do
+ create(:system_hook)
+ end
+
context 'removes existing hook' do
it 'from hooks list page' do
visit admin_hooks_path
@@ -76,7 +85,8 @@ describe 'Admin::Hooks', :js do
describe 'Test', :js do
before do
- WebMock.stub_request(:post, @system_hook.url)
+ system_hook = create(:system_hook)
+ WebMock.stub_request(:post, system_hook.url)
visit admin_hooks_path
find('.hook-test-button.dropdown').click
@@ -85,4 +95,41 @@ describe 'Admin::Hooks', :js do
it { expect(current_path).to eq(admin_hooks_path) }
end
+
+ context 'Merge request hook' do
+ describe 'New Hook' do
+ let(:url) { generate(:url) }
+
+ it 'adds new hook' do
+ visit admin_hooks_path
+
+ fill_in 'hook_url', with: url
+ uncheck 'Repository update events'
+ check 'Merge request events'
+
+ expect { click_button 'Add system hook' }.to change(SystemHook, :count).by(1)
+ expect(current_path).to eq(admin_hooks_path)
+ expect(page).to have_content(url)
+ end
+ end
+
+ describe 'Test', :js do
+ before do
+ system_hook = create(:system_hook)
+ WebMock.stub_request(:post, system_hook.url)
+ end
+
+ it 'succeeds if the user has a repository with a merge request' do
+ project = create(:project, :repository)
+ create(:project_member, user: user, project: project)
+ create(:merge_request, source_project: project)
+
+ visit admin_hooks_path
+ find('.hook-test-button.dropdown').click
+ click_link 'Merge requests events'
+
+ expect(page).to have_content 'Hook executed successfully'
+ end
+ end
+ end
end
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 782f42aab04..2d074c115dd 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -64,7 +64,7 @@ describe "User Feed" do
end
it 'has XHTML summaries in issue descriptions' do
- expect(body).to match /<hr ?\/>/
+ expect(body).to match %r{<hr ?/>}
end
it 'has XHTML summaries in notes' do
@@ -72,7 +72,7 @@ describe "User Feed" do
end
it 'has XHTML summaries in merge request descriptions' do
- expect(body).to match /Here is the fix: <a[^>]*><img[^>]*\/><\/a>/
+ expect(body).to match %r{Here is the fix: <a[^>]*><img[^>]*/></a>}
end
it 'has push event commit ID' do
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 3876d1c76d7..3d13f806b11 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -69,6 +69,7 @@ describe 'Issue Boards', :js do
let!(:backlog) { create(:label, project: project, name: 'Backlog') }
let!(:closed) { create(:label, project: project, name: 'Closed') }
let!(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') }
+ let!(:a_plus) { create(:label, project: project, name: 'A+') }
let!(:list1) { create(:list, board: board, label: planning, position: 0) }
let!(:list2) { create(:list, board: board, label: development, position: 1) }
@@ -83,6 +84,7 @@ describe 'Issue Boards', :js do
let!(:issue7) { create(:labeled_issue, project: project, title: 'ggg', description: '777', labels: [development], relative_position: 2) }
let!(:issue8) { create(:closed_issue, project: project, title: 'hhh', description: '888') }
let!(:issue9) { create(:labeled_issue, project: project, title: 'iii', description: '999', labels: [planning, testing, bug, accepting], relative_position: 1) }
+ let!(:issue10) { create(:labeled_issue, project: project, title: 'issue +', description: 'A+ great issue', labels: [a_plus]) }
before do
visit project_board_path(project, board)
@@ -400,6 +402,15 @@ describe 'Issue Boards', :js do
wait_for_empty_boards((3..4))
end
+ it 'filters by label with encoded character' do
+ set_filter("label", a_plus.title)
+ click_filter_link(a_plus.title)
+ submit_filter
+
+ wait_for_board_cards(1, 1)
+ wait_for_empty_boards((2..4))
+ end
+
it 'filters by label with space after reload' do
set_filter("label", "\"#{accepting.title}")
click_filter_link(accepting.title)
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 205900615c4..b2dbfcd0031 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -334,14 +334,14 @@ describe 'Issue Boards', :js do
wait_for_requests
page.within('.subscriptions') do
- click_button 'Subscribe'
+ find('.js-issuable-subscribe-button button:not(.is-checked)').click
wait_for_requests
- expect(page).to have_content('Unsubscribe')
+ expect(page).to have_css('.js-issuable-subscribe-button button.is-checked')
end
end
- it 'has "Unsubscribe" button when already subscribed' do
+ it 'has checked subscription toggle when already subscribed' do
create(:subscription, user: user, project: project, subscribable: issue2, subscribed: true)
visit project_board_path(project, board)
wait_for_requests
@@ -350,10 +350,10 @@ describe 'Issue Boards', :js do
wait_for_requests
page.within('.subscriptions') do
- click_button 'Unsubscribe'
+ find('.js-issuable-subscribe-button button.is-checked').click
wait_for_requests
- expect(page).to have_content('Subscribe')
+ expect(page).to have_css('.js-issuable-subscribe-button button:not(.is-checked)')
end
end
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index a28b8905b65..62a2ec55b00 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -194,7 +194,7 @@ describe 'Commits' do
end
it 'includes the committed_date for each commit' do
- commits = project.repository.commits(branch_name)
+ commits = project.repository.commits(branch_name, limit: 40)
commits.each do |commit|
expect(page).to have_content("authored #{commit.authored_date.strftime("%b %d, %Y")}")
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index d8f1a919522..f82ed6300cc 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -750,7 +750,7 @@ describe 'Copy as GFM', :js do
js = <<-JS.strip_heredoc
(function(selector) {
var els = document.querySelectorAll(selector);
- var htmls = _.map(els, function(el) { return el.outerHTML; });
+ var htmls = [].slice.call(els).map(function(el) { return el.outerHTML; });
return htmls.join("\\n");
})("#{escape_javascript(selector)}")
JS
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index d36954954b6..510677ecf56 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -113,6 +113,7 @@ feature 'Cycle Analytics', :js do
context "as a guest" do
before do
+ project.add_developer(user)
project.add_guest(guest)
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 991d360ccaf..744041ac425 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -44,36 +44,38 @@ feature 'Dashboard Merge Requests' do
context 'merge requests exist' do
let!(:assigned_merge_request) do
- create(:merge_request, assignee: current_user, target_project: project, source_project: project)
+ create(:merge_request,
+ assignee: current_user,
+ source_project: project,
+ author: create(:user))
end
let!(:assigned_merge_request_from_fork) do
create(:merge_request,
source_branch: 'markdown', assignee: current_user,
- target_project: public_project, source_project: forked_project
- )
+ target_project: public_project, source_project: forked_project,
+ author: create(:user))
end
let!(:authored_merge_request) do
create(:merge_request,
- source_branch: 'markdown', author: current_user,
- target_project: project, source_project: project
- )
+ source_branch: 'markdown',
+ source_project: project,
+ author: current_user)
end
let!(:authored_merge_request_from_fork) do
create(:merge_request,
source_branch: 'feature_conflict',
author: current_user,
- target_project: public_project, source_project: forked_project
- )
+ target_project: public_project, source_project: forked_project)
end
let!(:other_merge_request) do
create(:merge_request,
source_branch: 'fix',
- target_project: project, source_project: project
- )
+ source_project: project,
+ author: create(:user))
end
before do
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index 1dd7547a7fc..31862b2e8f4 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -112,13 +112,6 @@ feature 'Expand and collapse diffs', :js do
wait_for_requests
end
- it 'makes a request to get the content' do
- ajax_uris = evaluate_script('ajaxUris')
-
- expect(ajax_uris).not_to be_empty
- expect(ajax_uris.first).to include('large_diff.md')
- end
-
it 'shows the diff content' do
expect(large_diff).to have_selector('.code')
expect(large_diff).not_to have_selector('.nothing-here-block')
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
index 4f575613848..f8c4db1403c 100644
--- a/spec/features/global_search_spec.rb
+++ b/spec/features/global_search_spec.rb
@@ -22,7 +22,7 @@ feature 'Global search' do
click_button "Go"
select_filter("Issues")
- expect(page).to have_selector('.gl-pagination .page', count: 2)
+ expect(page).to have_selector('.gl-pagination .next')
end
end
end
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index cdf7aceb13c..450bc0ff8cf 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -3,40 +3,61 @@ require 'spec_helper'
feature 'Group issues page' do
include FilteredSearchHelpers
- let(:path) { issues_group_path(group) }
- let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
+ context 'with shared examples' do
+ let(:path) { issues_group_path(group) }
+ let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
- include_examples 'project features apply to issuables', Issue
+ include_examples 'project features apply to issuables', Issue
- context 'rss feed' do
- let(:access_level) { ProjectFeature::ENABLED }
+ context 'rss feed' do
+ let(:access_level) { ProjectFeature::ENABLED }
- context 'when signed in' do
- let(:user) { user_in_group }
+ context 'when signed in' do
+ let(:user) { user_in_group }
- it_behaves_like "it has an RSS button with current_user's RSS token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
- end
+ it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ end
- context 'when signed out' do
- let(:user) { nil }
+ context 'when signed out' do
+ let(:user) { nil }
- it_behaves_like "it has an RSS button without an RSS token"
- it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ it_behaves_like "it has an RSS button without an RSS token"
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
+ end
end
- end
- context 'assignee', :js do
- let(:access_level) { ProjectFeature::ENABLED }
- let(:user) { user_in_group }
- let(:user2) { user_outside_group }
- let(:path) { issues_group_path(group) }
+ context 'assignee', :js do
+ let(:access_level) { ProjectFeature::ENABLED }
+ let(:user) { user_in_group }
+ let(:user2) { user_outside_group }
+ let(:path) { issues_group_path(group) }
+
+ it 'filters by only group users' do
+ filtered_search.set('assignee:')
- it 'filters by only group users' do
- filtered_search.set('assignee:')
+ expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name)
+ expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name)
+ end
+ end
+ end
- expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name)
- expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name)
+ context 'issues list', :nested_groups do
+ let(:group) { create(:group)}
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project) { create(:project, :public, group: group)}
+ let(:subgroup_project) { create(:project, :public, group: subgroup)}
+ let!(:issue) { create(:issue, project: project, title: 'root group issue') }
+ let!(:subgroup_issue) { create(:issue, project: subgroup_project, title: 'subgroup issue') }
+
+ it 'returns all group and subgroup issues' do
+ visit issues_group_path(group)
+
+ page.within('.issuable-list') do
+ expect(page).to have_selector('li.issue', count: 2)
+ expect(page).to have_content('root group issue')
+ expect(page).to have_content('subgroup issue')
+ end
end
end
end
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 587ece22ec7..cf283119f36 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -377,6 +377,7 @@ feature 'Issues > Labels bulk assignment' do
items.map do |item|
click_link item
end
+
if unmark
items.map do |item|
# Make sure we are unmarking the item no matter the state it has currently
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index a5c9d0bde5d..64b4f9e7e67 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -8,6 +8,7 @@ feature 'Issue Sidebar' do
let(:issue) { create(:issue, project: project) }
let!(:user) { create(:user)}
let!(:label) { create(:label, project: project, title: 'bug') }
+ let!(:xss_label) { create(:label, project: project, title: '&lt;script&gt;alert("xss");&lt;&#x2F;script&gt;') }
before do
sign_in(user)
@@ -99,6 +100,14 @@ feature 'Issue Sidebar' do
restore_window_size
open_issue_sidebar
end
+
+ it 'escapes XSS when viewing issue labels' do
+ page.within('.block.labels') do
+ find('.edit-link').click
+
+ expect(page).to have_content '<script>alert("xss");</script>'
+ end
+ end
end
context 'editing issue labels', :js do
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index 53706ef84bc..c7cfd01f588 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -34,6 +34,9 @@ describe 'New issue', :js do
click_button 'Submit issue'
+ # reCAPTCHA alerts when it can't contact the server, so just accept it and move on
+ page.driver.browser.switch_to.alert.accept
+
# it is impossible to test recaptcha automatically and there is no possibility to fill in recaptcha
# recaptcha verification is skipped in test environment and it always returns true
expect(page).not_to have_content('issue title')
diff --git a/spec/features/merge_request/user_assigns_themselves_spec.rb b/spec/features/merge_request/user_assigns_themselves_spec.rb
new file mode 100644
index 00000000000..b6b38186a22
--- /dev/null
+++ b/spec/features/merge_request/user_assigns_themselves_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+describe 'Merge request > User assigns themselves' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:issue1) { create(:issue, project: project) }
+ let(:issue2) { create(:issue, project: project) }
+ let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue1.to_reference} and #{issue2.to_reference}") }
+
+ context 'logged in as a member of the project' do
+ before do
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'updates related issues', :js do
+ click_link 'Assign yourself to these issues'
+
+ expect(page).to have_content '2 issues have been assigned to you'
+ end
+
+ it 'returns user to the merge request', :js do
+ click_link 'Assign yourself to these issues'
+
+ expect(page).to have_content merge_request.description
+ end
+
+ context 'when related issues are already assigned' do
+ before do
+ [issue1, issue2].each { |issue| issue.update!(assignees: [user]) }
+ end
+
+ it 'does not display if related issues are already assigned' do
+ expect(page).not_to have_content 'Assign yourself'
+ end
+ end
+ end
+
+ context 'logged in as a non-member of the project' do
+ before do
+ sign_in(create(:user))
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'does not not show assignment link' do
+ expect(page).not_to have_content 'Assign yourself'
+ end
+ end
+end
diff --git a/spec/features/merge_requests/award_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb
index a24464f2556..2f24cfbd9e3 100644
--- a/spec/features/merge_requests/award_spec.rb
+++ b/spec/features/merge_request/user_awards_emoji_spec.rb
@@ -1,9 +1,9 @@
require 'rails_helper'
-feature 'Merge request awards', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User awards emoji', :js do
let(:project) { create(:project, :public, :repository) }
- let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:user) { project.creator }
+ let(:merge_request) { create(:merge_request, source_project: project, author: create(:user)) }
describe 'logged in' do
before do
diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_request/user_cherry_picks_spec.rb
index 205e38337d1..494096b21c0 100644
--- a/spec/features/merge_requests/cherry_pick_spec.rb
+++ b/spec/features/merge_request/user_cherry_picks_spec.rb
@@ -1,17 +1,17 @@
-require 'spec_helper'
+require 'rails_helper'
-describe 'Cherry-pick Merge Requests', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User cherry-picks', :js do
let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: group) }
+ let(:user) { project.creator }
let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user) }
before do
- sign_in user
project.add_master(user)
+ sign_in(user)
end
- context "Viewing a merged merge request" do
+ context 'Viewing a merged merge request' do
before do
service = MergeRequests::MergeService.new(project, user)
@@ -21,24 +21,24 @@ describe 'Cherry-pick Merge Requests', :js do
end
# Fast-forward merge, or merged before GitLab 8.5.
- context "Without a merge commit" do
+ context 'Without a merge commit' do
before do
merge_request.merge_commit_sha = nil
merge_request.save
end
- it "doesn't show a Cherry-pick button" do
+ it 'does not show a Cherry-pick button' do
visit project_merge_request_path(project, merge_request)
- expect(page).not_to have_link "Cherry-pick"
+ expect(page).not_to have_link 'Cherry-pick'
end
end
- context "With a merge commit" do
- it "shows a Cherry-pick button" do
+ context 'With a merge commit' do
+ it 'shows a Cherry-pick button' do
visit project_merge_request_path(project, merge_request)
- expect(page).to have_link "Cherry-pick"
+ expect(page).to have_link 'Cherry-pick'
end
end
end
diff --git a/spec/features/merge_requests/image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
index d0f8da4e6cd..7c4fd25bb39 100644
--- a/spec/features/merge_requests/image_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
@@ -1,14 +1,13 @@
require 'spec_helper'
-feature 'image diff notes', :js do
+feature 'Merge request > User creates image diff notes', :js do
include NoteInteractionHelpers
- let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
before do
- project.add_master(user)
- sign_in user
+ sign_in(user)
# Stub helper to return any blob file as image from public app folder.
# This is necessary to run this specs since we don't display repo images in capybara.
@@ -25,20 +24,14 @@ feature 'image diff notes', :js do
create_image_diff_note
end
- it 'shows indicator badge on image diff' do
+ it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes' do
indicator = find('.js-image-badge')
-
- expect(indicator).to have_content('1')
- end
-
- it 'shows the avatar badge on the new note' do
badge = find('.image-diff-avatar-link .badge')
+ expect(indicator).to have_content('1')
expect(badge).to have_content('1')
- end
- it 'allows collapsing/expanding the discussion notes' do
- find('.js-diff-notes-toggle', :first).click
+ find('.js-diff-notes-toggle').click
expect(page).not_to have_content('image diff test comment')
@@ -86,15 +79,9 @@ feature 'image diff notes', :js do
wait_for_requests
end
- it 'render diff indicators within the image diff frame' do
+ it 'render diff indicators within the image diff frame, diff notes, and avatar badge numbers' do
expect(page).to have_css('.js-image-badge', count: 2)
- end
-
- it 'shows the diff notes' do
expect(page).to have_css('.diff-content .note', count: 2)
- end
-
- it 'shows the diff notes with correct avatar badge numbers' do
expect(page).to have_css('.image-diff-avatar-link', text: 1)
expect(page).to have_css('.image-diff-avatar-link', text: 2)
end
@@ -127,19 +114,13 @@ feature 'image diff notes', :js do
create_image_diff_note
end
- it 'shows indicator badge on image diff' do
+ it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes' do
indicator = find('.js-image-badge', match: :first)
-
- expect(indicator).to have_content('1')
- end
-
- it 'shows the avatar badge on the new note' do
badge = find('.image-diff-avatar-link .badge', match: :first)
+ expect(indicator).to have_content('1')
expect(badge).to have_content('1')
- end
- it 'allows expanding/collapsing the discussion notes' do
page.all('.js-diff-notes-toggle')[0].click
page.all('.js-diff-notes-toggle')[1].click
@@ -154,7 +135,7 @@ feature 'image diff notes', :js do
end
end
- describe 'discussion tab polling', :js do
+ describe 'discussion tab polling' do
let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user) }
let(:path) { "files/images/ee_repo_logo.png" }
diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb
new file mode 100644
index 00000000000..1ac31de62cb
--- /dev/null
+++ b/spec/features/merge_request/user_creates_mr_spec.rb
@@ -0,0 +1,31 @@
+require 'rails_helper'
+
+describe 'Merge request > User creates MR' do
+ it_behaves_like 'a creatable merge request'
+
+ context 'from a forked project' do
+ include ProjectForksHelper
+
+ let(:canonical_project) { create(:project, :public, :repository) }
+
+ let(:source_project) do
+ fork_project(canonical_project, user,
+ repository: true,
+ namespace: user.namespace)
+ end
+
+ context 'to canonical project' do
+ it_behaves_like 'a creatable merge request'
+ end
+
+ context 'to another forked project' do
+ let(:target_project) do
+ fork_project(canonical_project, user,
+ repository: true,
+ namespace: user.namespace)
+ end
+
+ it_behaves_like 'a creatable merge request'
+ end
+ end
+end
diff --git a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
index ddd034e1376..e1e70b6d260 100644
--- a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb
+++ b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
@@ -1,8 +1,8 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Clicking toggle commit message link', :js do
- let(:user) { create(:user) }
+describe 'Merge request < User customizes merge commit message', :js do
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
let(:issue_1) { create(:issue, project: project)}
let(:issue_2) { create(:issue, project: project)}
let(:merge_request) do
@@ -33,17 +33,14 @@ feature 'Clicking toggle commit message link', :js do
before do
project.add_master(user)
-
- sign_in user
-
+ sign_in(user)
visit project_merge_request_path(project, merge_request)
+ end
+ it 'toggles commit message between message with description and without description' do
expect(page).not_to have_selector('.js-commit-message')
click_button "Modify commit message"
expect(textbox).to be_visible
- end
-
- it "toggles commit message between message with description and without description " do
expect(textbox.value).to eq(default_message)
click_link "Include description in commit message"
diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb
new file mode 100644
index 00000000000..8c9e782aa76
--- /dev/null
+++ b/spec/features/merge_request/user_edits_mr_spec.rb
@@ -0,0 +1,11 @@
+require 'rails_helper'
+
+describe 'Merge request > User edits MR' do
+ it_behaves_like 'an editable merge request'
+
+ context 'for a forked project' do
+ it_behaves_like 'an editable merge request' do
+ let(:source_project) { create(:project, :repository, forked_from_project: target_project) }
+ end
+ end
+end
diff --git a/spec/features/merge_requests/discussion_lock_spec.rb b/spec/features/merge_request/user_locks_discussion_spec.rb
index 7bbd3b1e69e..a68df872334 100644
--- a/spec/features/merge_requests/discussion_lock_spec.rb
+++ b/spec/features/merge_request/user_locks_discussion_spec.rb
@@ -1,9 +1,9 @@
-require 'spec_helper'
+require 'rails_helper'
-describe 'Discussion Lock', :js do
+describe 'Merge request > User locks discussion', :js do
let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request, source_project: project, author: user) }
let(:project) { create(:project, :public, :repository) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
before do
sign_in(user)
diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_request/user_merges_immediately_spec.rb
index e1317b33ad1..b16fc9bfc89 100644
--- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
+++ b/spec/features/merge_request/user_merges_immediately_spec.rb
@@ -1,9 +1,8 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge immediately', :js do
- let(:user) { create(:user) }
+describe 'Merge requests > User merges immediately', :js do
let(:project) { create(:project, :public, :repository) }
-
+ let(:user) { project.creator }
let!(:merge_request) do
create(:merge_request_with_diffs, source_project: project,
author: user,
@@ -11,25 +10,18 @@ feature 'Merge immediately', :js do
head_pipeline: pipeline,
source_branch: pipeline.ref)
end
-
let(:pipeline) do
create(:ci_pipeline, project: project,
ref: 'master',
sha: project.repository.commit('master').id)
end
- before do
- project.add_master(user)
- end
-
context 'when there is active pipeline for merge request' do
- background do
- create(:ci_build, pipeline: pipeline)
- end
-
before do
- sign_in user
- visit project_merge_request_path(merge_request.project, merge_request)
+ create(:ci_build, pipeline: pipeline)
+ project.add_master(user)
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
it 'enables merge immediately' do
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
index 7d9282b932b..a045791f6b4 100644
--- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
@@ -1,18 +1,17 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Only allow merge requests to be merged if the pipeline succeeds', :js do
+describe 'Merge request > User merges only if pipeline succeeds', :js do
let(:merge_request) { create(:merge_request_with_diffs) }
let(:project) { merge_request.target_project }
before do
- sign_in merge_request.author
-
project.add_master(merge_request.author)
+ sign_in(merge_request.author)
end
- context 'project does not have CI enabled', :js do
+ context 'project does not have CI enabled' do
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -20,8 +19,8 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
end
- context 'when project has CI enabled', :js do
- given!(:pipeline) do
+ context 'when project has CI enabled' do
+ let!(:pipeline) do
create(:ci_empty_pipeline,
project: project,
sha: merge_request.diff_head_sha,
@@ -35,10 +34,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI is running' do
- given(:status) { :running }
+ let(:status) { :running }
it 'does not allow to merge immediately' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -48,10 +47,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI failed' do
- given(:status) { :failed }
+ let(:status) { :failed }
it 'does not allow MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -61,10 +60,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI canceled' do
- given(:status) { :canceled }
+ let(:status) { :canceled }
it 'does not allow MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -74,10 +73,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI succeeded' do
- given(:status) { :success }
+ let(:status) { :success }
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -86,10 +85,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI skipped' do
- given(:status) { :skipped }
+ let(:status) { :skipped }
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -104,10 +103,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI is running' do
- given(:status) { :running }
+ let(:status) { :running }
it 'allows MR to be merged immediately' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -119,10 +118,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI failed' do
- given(:status) { :failed }
+ let(:status) { :failed }
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -131,10 +130,10 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
context 'when CI succeeded' do
- given(:status) { :success }
+ let(:status) { :success }
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -143,8 +142,4 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', :js d
end
end
end
-
- def visit_merge_request(merge_request)
- visit project_merge_request_path(merge_request.project, merge_request)
- end
end
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
index ac46cc1f0e4..890774922aa 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
@@ -1,16 +1,14 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge When Pipeline Succeeds', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User merges when pipeline succeeds', :js do
let(:project) { create(:project, :public, :repository) }
-
+ let(:user) { project.creator }
let(:merge_request) do
create(:merge_request_with_diffs, source_project: project,
author: user,
title: 'Bug NS-04',
merge_params: { force_remove_source_branch: '1' })
end
-
let(:pipeline) do
create(:ci_pipeline, project: project,
sha: merge_request.diff_head_sha,
@@ -23,17 +21,10 @@ feature 'Merge When Pipeline Succeeds', :js do
end
context 'when there is active pipeline for merge request' do
- background do
- create(:ci_build, pipeline: pipeline)
- end
-
before do
- sign_in user
- visit_merge_request(merge_request)
- end
-
- it 'displays the Merge when pipeline succeeds button' do
- expect(page).to have_button "Merge when pipeline succeeds"
+ create(:ci_build, pipeline: pipeline)
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
describe 'enabling Merge when pipeline succeeds' do
@@ -44,7 +35,7 @@ feature 'Merge When Pipeline Succeeds', :js do
expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds"
expect(page).to have_content "The source branch will not be removed"
expect(page).to have_selector ".js-cancel-auto-merge"
- visit_merge_request(merge_request) # Needed to refresh the page
+ visit project_merge_request_path(project, merge_request) # Needed to refresh the page
expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i
end
end
@@ -115,14 +106,13 @@ feature 'Merge When Pipeline Succeeds', :js do
title: 'MepMep',
merge_when_pipeline_succeeds: true)
end
-
let!(:build) do
create(:ci_build, pipeline: pipeline)
end
before do
sign_in user
- visit_merge_request(merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'allows to cancel the automatic merge' do
@@ -130,31 +120,67 @@ feature 'Merge When Pipeline Succeeds', :js do
expect(page).to have_button "Merge when pipeline succeeds"
- visit_merge_request(merge_request) # refresh the page
+ refresh
+
expect(page).to have_content "canceled the automatic merge"
end
context 'when pipeline succeeds' do
- background { build.success }
+ before do
+ build.success
+ refresh
+ end
it 'merges merge request' do
- visit_merge_request(merge_request) # refresh the page
-
expect(page).to have_content 'The changes were merged'
expect(merge_request.reload).to be_merged
end
end
+
+ context 'view merge request with MWPS enabled but automatically merge fails' do
+ before do
+ merge_request.update(
+ merge_user: merge_request.author,
+ merge_error: 'Something went wrong'
+ )
+ refresh
+ end
+
+ it 'shows information about the merge error' do
+ # Wait for the `ci_status` and `merge_check` requests
+ wait_for_requests
+
+ page.within('.mr-widget-body') do
+ expect(page).to have_content('Something went wrong')
+ end
+ end
+ end
+
+ context 'view merge request with MWPS enabled but automatically merge fails' do
+ before do
+ merge_request.update(
+ merge_user: merge_request.author,
+ merge_error: 'Something went wrong'
+ )
+ refresh
+ end
+
+ it 'shows information about the merge error' do
+ # Wait for the `ci_status` and `merge_check` requests
+ wait_for_requests
+
+ page.within('.mr-widget-body') do
+ expect(page).to have_content('Something went wrong')
+ end
+ end
+ end
end
context 'when pipeline is not active' do
- it "does not allow to enable merge when pipeline succeeds" do
- visit_merge_request(merge_request)
+ it 'does not allow to enable merge when pipeline succeeds' do
+ visit project_merge_request_path(project, merge_request)
expect(page).not_to have_link 'Merge when pipeline succeeds'
end
end
-
- def visit_merge_request(merge_request)
- visit project_merge_request_path(merge_request.project, merge_request)
- end
end
diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb
index d44eb23d7f4..2b4623d6dc9 100644
--- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -1,11 +1,15 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge requests > User posts diff notes', :js do
+describe 'Merge request > User posts diff notes', :js do
include MergeRequestDiffHelpers
- let(:user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
+ let(:user) { project.creator }
+ let(:comment_button_class) { '.add-diff-note' }
+ let(:notes_holder_input_class) { 'js-temp-notes-holder' }
+ let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' }
+ let(:test_note_comment) { 'this is a test note!' }
before do
set_cookie('sidebar_collapsed', 'true')
@@ -14,11 +18,6 @@ feature 'Merge requests > User posts diff notes', :js do
sign_in(user)
end
- let(:comment_button_class) { '.add-diff-note' }
- let(:notes_holder_input_class) { 'js-temp-notes-holder' }
- let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' }
- let(:test_note_comment) { 'this is a test note!' }
-
context 'when hovering over a parallel view diff file' do
before do
visit diffs_project_merge_request_path(project, merge_request, view: 'parallel')
diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb
index e17e9c2ccf5..50d06565fc0 100644
--- a/spec/features/merge_requests/user_posts_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_notes_spec.rb
@@ -1,9 +1,10 @@
-require 'spec_helper'
+require 'rails_helper'
-describe 'Merge requests > User posts notes', :js do
+describe 'Merge request > User posts notes', :js do
include NoteInteractionHelpers
let(:project) { create(:project, :repository) }
+ let(:user) { project.creator }
let(:merge_request) do
create(:merge_request, source_project: project, target_project: project)
end
@@ -13,7 +14,8 @@ describe 'Merge requests > User posts notes', :js do
end
before do
- sign_in(create(:admin))
+ project.add_master(user)
+ sign_in(user)
visit project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb
index 05d99a2dff2..19995559fae 100644
--- a/spec/features/merge_requests/conflicts_spec.rb
+++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb
@@ -1,8 +1,8 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge request conflict resolution', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User resolves conflicts', :js do
let(:project) { create(:project, :repository) }
+ let(:user) { project.creator }
before do
# In order to have the diffs collapsed, we need to disable the increase feature
@@ -100,12 +100,12 @@ feature 'Merge request conflict resolution', :js do
end
it 'shows a link to the conflict resolution page' do
- expect(page).to have_link('conflicts', href: /\/conflicts\Z/)
+ expect(page).to have_link('conflicts', href: %r{/conflicts\Z})
end
context 'in Inline view mode' do
before do
- click_link('conflicts', href: /\/conflicts\Z/)
+ click_link('conflicts', href: %r{/conflicts\Z})
end
include_examples "conflicts are resolved in Interactive mode"
@@ -114,7 +114,7 @@ feature 'Merge request conflict resolution', :js do
context 'in Parallel view mode' do
before do
- click_link('conflicts', href: /\/conflicts\Z/)
+ click_link('conflicts', href: %r{/conflicts\Z})
click_button 'Side-by-side'
end
@@ -128,7 +128,7 @@ feature 'Merge request conflict resolution', :js do
before do
visit project_merge_request_path(project, merge_request)
- click_link('conflicts', href: /\/conflicts\Z/)
+ click_link('conflicts', href: %r{/conflicts\Z})
end
it 'conflicts can not be resolved in Interactive mode' do
@@ -177,12 +177,11 @@ feature 'Merge request conflict resolution', :js do
before do
project.add_developer(user)
sign_in(user)
-
visit project_merge_request_path(project, merge_request)
end
it 'does not show a link to the conflict resolution page' do
- expect(page).not_to have_link('conflicts', href: /\/conflicts\Z/)
+ expect(page).not_to have_link('conflicts', href: %r{/conflicts\Z})
end
it 'shows an error if the conflicts page is visited directly' do
diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 9d4194d8ca0..3e83a549682 100644
--- a/spec/features/merge_requests/diff_notes_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -1,10 +1,11 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Diff notes resolve', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User resolves diff notes and discussions', :js do
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:guest) { create(:user) }
let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") }
- let!(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) }
+ let!(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) }
let(:path) { "files/ruby/popen.rb" }
let(:position) do
Gitlab::Diff::Position.new(
@@ -19,7 +20,7 @@ feature 'Diff notes resolve', :js do
context 'no discussions' do
before do
project.add_master(user)
- sign_in user
+ sign_in(user)
note.destroy
visit_merge_request
end
@@ -33,7 +34,7 @@ feature 'Diff notes resolve', :js do
context 'as authorized user' do
before do
project.add_master(user)
- sign_in user
+ sign_in(user)
visit_merge_request
end
@@ -67,6 +68,8 @@ feature 'Diff notes resolve', :js do
click_button 'Resolve discussion'
end
+ expect(page).to have_selector('.discussion-body', visible: false)
+
page.within '.diff-content .note' do
expect(page).to have_selector('.line-resolve-btn.is-active')
end
@@ -105,7 +108,7 @@ feature 'Diff notes resolve', :js do
it 'shows resolved discussion when toggled' do
find(".timeline-content .discussion[data-discussion-id='#{note.discussion_id}'] .discussion-toggle-button").click
- expect(page.find(".timeline-content #note_#{note.noteable_id}")).to be_visible
+ expect(page.find(".timeline-content #note_#{note.id}")).to be_visible
end
end
@@ -318,9 +321,7 @@ feature 'Diff notes resolve', :js do
end
it 'shows jump to next discussion button' do
- page.all('.discussion-reply-holder').each do |holder|
- expect(holder).to have_selector('.discussion-next-btn')
- end
+ expect(page.all('.discussion-reply-holder')).to all(have_selector('.discussion-next-btn'))
end
it 'displays next discussion even if hidden' do
@@ -426,11 +427,9 @@ feature 'Diff notes resolve', :js do
end
context 'as a guest' do
- let(:guest) { create(:user) }
-
before do
project.add_guest(guest)
- sign_in guest
+ sign_in(guest)
end
context 'someone elses merge request' do
@@ -456,10 +455,10 @@ feature 'Diff notes resolve', :js do
end
context 'guest users merge request' do
+ let(:user) { guest }
+
before do
- mr = create(:merge_request_with_diffs, source_project: project, source_branch: 'markdown', author: guest, title: "Bug")
- create(:diff_note_on_merge_request, project: project, noteable: mr)
- visit_merge_request(mr)
+ visit_merge_request
end
it 'allows user to mark a note as resolved' do
@@ -521,7 +520,7 @@ feature 'Diff notes resolve', :js do
end
def visit_merge_request(mr = nil)
- mr = mr || merge_request
+ mr ||= merge_request
visit project_merge_request_path(mr.project, mr)
end
end
diff --git a/spec/features/merge_requests/resolve_outdated_diff_discussions.rb b/spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb
index 25abbb469ab..9ba9e8b9585 100644
--- a/spec/features/merge_requests/resolve_outdated_diff_discussions.rb
+++ b/spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Resolve outdated diff discussions', :js do
+feature 'Merge request > User resolves outdated diff discussions', :js do
let(:project) { create(:project, :repository, :public) }
let(:merge_request) 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
new file mode 100644
index 00000000000..8a834adbf17
--- /dev/null
+++ b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
@@ -0,0 +1,26 @@
+require 'rails_helper'
+
+describe 'Merge request > User scrolls to note on load', :js do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:merge_request) { create(:merge_request, source_project: project, author: user) }
+ let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
+ let(:fragment_id) { "#note_#{note.id}" }
+
+ before do
+ sign_in(user)
+ page.current_window.resize_to(1000, 300)
+ visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
+ end
+
+ it 'scrolls down to fragment' do
+ page_height = page.current_window.size[1]
+ page_scroll_y = page.evaluate_script("window.scrollY")
+ fragment_position_top = page.evaluate_script("Math.round($('#{fragment_id}').offset().top)")
+
+ expect(find('.js-toggle-content').visible?).to eq true
+ expect(find(fragment_id).visible?).to eq true
+ expect(fragment_position_top).to be >= page_scroll_y
+ expect(fragment_position_top).to be < (page_scroll_y + page_height)
+ end
+end
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
index ef8f314cc03..9c0a04405a6 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
@@ -1,10 +1,10 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Diff note avatars', :js do
+describe 'Merge request > User sees avatars on diff notes', :js do
include NoteInteractionHelpers
- let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") }
let(:path) { "files/ruby/popen.rb" }
let(:position) do
@@ -151,7 +151,6 @@ feature 'Diff note avatars', :js do
page.within '.js-discussion-note-form' do
find('.js-note-text').native.send_keys('Test')
-
find('.js-comment-button').click
wait_for_requests
@@ -169,7 +168,6 @@ feature 'Diff note avatars', :js do
context 'multiple comments' do
before do
create_list(:diff_note_on_merge_request, 3, project: project, noteable: merge_request, in_reply_to: note)
-
visit diffs_project_merge_request_path(project, merge_request, view: view)
wait_for_requests
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
index 55de9a01ed5..726f35557a7 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
@@ -1,8 +1,8 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge Request closing issues message', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User sees closing issues message', :js do
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
let(:issue_1) { create(:issue, project: project)}
let(:issue_2) { create(:issue, project: project)}
let(:merge_request) do
@@ -19,9 +19,7 @@ feature 'Merge Request closing issues message', :js do
before do
project.add_master(user)
-
- sign_in user
-
+ sign_in(user)
visit project_merge_request_path(project, merge_request)
wait_for_requests
end
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
new file mode 100644
index 00000000000..01115318370
--- /dev/null
+++ b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb
@@ -0,0 +1,22 @@
+require 'rails_helper'
+
+describe 'Merge request > User sees deleted target branch', :js do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:user) { project.creator }
+
+ before do
+ project.add_master(user)
+ DeleteBranchService.new(project, user).execute('feature')
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows a message about missing target branch' do
+ expect(page).to have_content('Target branch does not exist')
+ end
+
+ it 'does not show link to target branch' do
+ expect(page).not_to have_selector('.mr-widget-body .js-branch-text a')
+ end
+end
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
new file mode 100644
index 00000000000..3abe363d523
--- /dev/null
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -0,0 +1,56 @@
+require 'rails_helper'
+
+describe 'Merge request > User sees deployment widget', :js do
+ describe 'when deployed to an environment' do
+ let(:user) { create(:user) }
+ let(:project) { merge_request.target_project }
+ let(:merge_request) { create(:merge_request, :merged) }
+ let(:environment) { create(:environment, project: project) }
+ let(:role) { :developer }
+ let(:sha) { project.commit('master').id }
+ let!(:deployment) { create(:deployment, environment: environment, sha: sha) }
+ let!(:manual) { }
+
+ before do
+ project.add_user(user, role)
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ wait_for_requests
+ end
+
+ it 'displays that the environment is deployed' do
+ wait_for_requests
+
+ expect(page).to have_content("Deployed to #{environment.name}")
+ expect(find('.js-deploy-time')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
+ end
+
+ context 'with stop action' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ let(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
+ let(:deployment) do
+ create(:deployment, environment: environment, ref: merge_request.target_branch,
+ sha: sha, deployable: build, on_stop: 'close_app')
+ end
+
+ before do
+ wait_for_requests
+ end
+
+ it 'does start build when stop button clicked' do
+ accept_confirm { click_button('Stop environment') }
+
+ expect(page).to have_content('close_app')
+ end
+
+ context 'for reporter' do
+ let(:role) { :reporter }
+
+ it 'does not show stop button' do
+ expect(page).not_to have_button('Stop environment')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb
index 1bf77296ae6..a9063f2bcb3 100644
--- a/spec/features/merge_requests/diffs_spec.rb
+++ b/spec/features/merge_request/user_sees_diff_spec.rb
@@ -1,6 +1,6 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Diffs URL', :js do
+describe 'Merge request > User sees diff', :js do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_requests/discussion_spec.rb b/spec/features/merge_request/user_sees_discussions_spec.rb
index 05789bbd31d..d6e8c8e86ba 100644
--- a/spec/features/merge_requests/discussion_spec.rb
+++ b/spec/features/merge_request/user_sees_discussions_spec.rb
@@ -1,19 +1,20 @@
-require 'spec_helper'
+require 'rails_helper'
+
+describe 'Merge request > User sees discussions' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:merge_request) { create(:merge_request, source_project: project) }
-feature 'Merge Request Discussions' do
before do
- sign_in(create(:admin))
+ project.add_master(user)
+ sign_in(user)
end
describe "Diff discussions" do
- let(:merge_request) { create(:merge_request, importing: true) }
- let(:project) { merge_request.source_project }
let!(:old_merge_request_diff) { merge_request.merge_request_diffs.create(diff_refs: outdated_diff_refs) }
let!(:new_merge_request_diff) { merge_request.merge_request_diffs.create }
-
let!(:outdated_discussion) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: outdated_position).to_discussion }
let!(:active_discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project).to_discussion }
-
let(:outdated_position) do
Gitlab::Diff::Position.new(
old_path: "files/ruby/popen.rb",
@@ -23,7 +24,6 @@ feature 'Merge Request Discussions' do
diff_refs: outdated_diff_refs
)
end
-
let(:outdated_diff_refs) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e").diff_refs }
before do
@@ -50,9 +50,6 @@ feature 'Merge Request Discussions' do
end
describe 'Commit comments displayed in MR context', :js do
- let(:merge_request) { create(:merge_request) }
- let(:project) { merge_request.project }
-
shared_examples 'a functional discussion' do
let(:discussion_id) { note.discussion_id(merge_request) }
diff --git a/spec/features/merge_request/user_sees_empty_state_spec.rb b/spec/features/merge_request/user_sees_empty_state_spec.rb
new file mode 100644
index 00000000000..a939c7e9001
--- /dev/null
+++ b/spec/features/merge_request/user_sees_empty_state_spec.rb
@@ -0,0 +1,30 @@
+require 'rails_helper'
+
+describe 'Merge request > User sees empty state' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ it 'shows an empty state and a "New merge request" button' do
+ visit project_merge_requests_path(project)
+
+ expect(page).to have_selector('.empty-state')
+ expect(page).to have_link 'New merge request', href: project_new_merge_request_path(project)
+ end
+
+ context 'if there are merge requests' do
+ before do
+ create(:merge_request, source_project: project)
+
+ visit project_merge_requests_path(project)
+ end
+
+ it 'does not show an empty state' do
+ expect(page).not_to have_selector('.empty-state')
+ end
+ end
+end
diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
index 892c32c8806..85df43df38e 100644
--- a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
@@ -1,24 +1,23 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Check if mergeable with unresolved discussions', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User sees merge button depending on unresolved discussions', :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) }
before do
- sign_in user
project.add_master(user)
+ sign_in(user)
end
context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do
before do
project.update_column(:only_allow_merge_if_all_discussions_are_resolved, true)
+ visit project_merge_request_path(project, merge_request)
end
context 'with unresolved discussions' do
it 'does not allow to merge' do
- visit_merge_request(merge_request)
-
expect(page).not_to have_button 'Merge'
expect(page).to have_content('There are unresolved discussions.')
end
@@ -27,11 +26,10 @@ feature 'Check if mergeable with unresolved discussions', :js do
context 'with all discussions resolved' do
before do
merge_request.discussions.each { |d| d.resolve!(user) }
+ visit project_merge_request_path(project, merge_request)
end
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
-
expect(page).to have_button 'Merge'
end
end
@@ -40,12 +38,11 @@ feature 'Check if mergeable with unresolved discussions', :js do
context 'when project.only_allow_merge_if_all_discussions_are_resolved == false' do
before do
project.update_column(:only_allow_merge_if_all_discussions_are_resolved, false)
+ visit project_merge_request_path(project, merge_request)
end
context 'with unresolved discussions' do
it 'does not allow to merge' do
- visit_merge_request(merge_request)
-
expect(page).to have_button 'Merge'
end
end
@@ -53,17 +50,12 @@ feature 'Check if mergeable with unresolved discussions', :js do
context 'with all discussions resolved' do
before do
merge_request.discussions.each { |d| d.resolve!(user) }
+ visit project_merge_request_path(project, merge_request)
end
it 'allows MR to be merged' do
- visit_merge_request(merge_request)
-
expect(page).to have_button 'Merge'
end
end
end
-
- def visit_merge_request(merge_request)
- visit project_merge_request_path(merge_request.project, merge_request)
- end
end
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index 8970586a160..56224e505d9 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -1,9 +1,9 @@
require 'rails_helper'
-describe 'Merge request', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User sees merge widget', :js do
let(:project) { create(:project, :repository) }
let(:project_only_mwps) { create(:project, :repository, only_allow_merge_if_pipeline_succeeds: true) }
+ let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project) }
let(:merge_request_in_only_mwps_project) { create(:merge_request, source_project: project_only_mwps) }
diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
index a7e7c0eeff6..a43ba05c64c 100644
--- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
@@ -1,16 +1,14 @@
require 'rails_helper'
-feature 'Mini Pipeline Graph', :js do
- let(:user) { create(:user) }
+describe 'Merge request < User sees mini pipeline graph', :js do
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project, head_pipeline: pipeline) }
-
let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: 'master', status: 'running', sha: project.commit.id) }
let(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') }
before do
build.run
-
sign_in(user)
visit_merge_request
end
@@ -19,13 +17,13 @@ feature 'Mini Pipeline Graph', :js do
visit project_merge_request_path(project, merge_request, format: format, serializer: serializer)
end
- it 'should display a mini pipeline graph' do
+ it 'displays a mini pipeline graph' do
expect(page).to have_selector('.mr-widget-pipeline-graph')
end
context 'as json' do
- let(:artifacts_file1) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
- let(:artifacts_file2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png') }
+ let(:artifacts_file1) { fixture_file_upload(Rails.root.join('spec/fixtures/banana_sample.gif'), 'image/gif') }
+ let(:artifacts_file2) { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'), 'image/png') }
before do
create(:ci_build, pipeline: pipeline, legacy_artifacts_file: artifacts_file1)
@@ -51,7 +49,7 @@ feature 'Mini Pipeline Graph', :js do
first('.mini-pipeline-graph-dropdown-toggle')
end
- it 'should expand when hovered' do
+ it 'expands when hovered' do
find('.mini-pipeline-graph-dropdown-toggle')
before_width = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').outerWidth();")
@@ -63,13 +61,13 @@ feature 'Mini Pipeline Graph', :js do
expect(before_width).to be < after_width
end
- it 'should show dropdown caret when hovered' do
+ it 'shows dropdown caret when hovered' do
toggle.hover
expect(toggle).to have_selector('.fa-caret-down')
end
- it 'should show tooltip when hovered' do
+ it 'shows tooltip when hovered' do
toggle.hover
expect(page).to have_selector('.tooltip')
@@ -87,17 +85,17 @@ feature 'Mini Pipeline Graph', :js do
wait_for_requests
end
- it 'should open when toggle is clicked' do
+ it 'pens when toggle is clicked' do
expect(toggle.find(:xpath, '..')).to have_selector('.mini-pipeline-graph-dropdown-menu')
end
- it 'should close when toggle is clicked again' do
+ it 'closes when toggle is clicked again' do
toggle.click
expect(toggle.find(:xpath, '..')).not_to have_selector('.mini-pipeline-graph-dropdown-menu')
end
- it 'should close when clicking somewhere else' do
+ it 'closes when clicking somewhere else' do
find('body').click
expect(toggle.find(:xpath, '..')).not_to have_selector('.mini-pipeline-graph-dropdown-menu')
@@ -109,14 +107,14 @@ feature 'Mini Pipeline Graph', :js do
first('.mini-pipeline-graph-dropdown-item')
end
- it 'should visit the build page when clicked' do
+ it 'visits the build page when clicked' do
build_item.click
find('.build-page')
expect(current_path).to eql(project_job_path(project, build))
end
- it 'should show tooltip when hovered' do
+ it 'shows tooltip when hovered' do
build_item.hover
expect(page).to have_selector('.tooltip')
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
new file mode 100644
index 00000000000..029b66b5e8e
--- /dev/null
+++ b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+describe 'Merge request > User sees MR from deleted forked project', :js do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:fork_project) { create(:project, :public, :repository, forked_from_project: project) }
+ let!(:merge_request) do
+ create(:merge_request_with_diffs, source_project: fork_project,
+ target_project: project,
+ description: 'Test merge request')
+ end
+
+ before do
+ MergeRequests::MergeService.new(project, user).execute(merge_request)
+ fork_project.destroy!
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'user can access merge request' do
+ expect(page).to have_content 'Test merge request'
+ expect(page).to have_content "(removed):#{merge_request.source_branch}"
+ end
+end
diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
index 56aa0b2ede2..c1608be402a 100644
--- a/spec/features/merge_requests/deleted_source_branch_spec.rb
+++ b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
@@ -1,23 +1,21 @@
-require 'spec_helper'
+require 'rails_helper'
# This test serves as a regression test for a bug that caused an error
# message to be shown by JavaScript when the source branch was deleted.
-# Please do not remove "js: true".
-describe 'Deleted source branch', :js do
- let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request) }
+# Please do not remove ":js".
+describe 'Merge request > User sees MR with deleted source branch', :js do
+ let(:project) { create(:project, :public, :repository) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:user) { project.creator }
before do
- sign_in user
- merge_request.project.add_master(user)
merge_request.update!(source_branch: 'this-branch-does-not-exist')
- visit project_merge_request_path(merge_request.project, merge_request)
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows a message about missing source branch' do
- expect(page).to have_content(
- 'Source branch does not exist.'
- )
+ expect(page).to have_content('Source branch does not exist.')
end
it 'still contains Discussion, Commits and Changes tabs' do
diff --git a/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb
new file mode 100644
index 00000000000..b4cda269852
--- /dev/null
+++ b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb
@@ -0,0 +1,35 @@
+require 'rails_helper'
+
+describe 'Merge request > User sees notes from forked project', :js do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:fork_project) { create(:project, :public, :repository, forked_from_project: project) }
+ let!(:merge_request) do
+ create(:merge_request_with_diffs, source_project: fork_project,
+ target_project: project,
+ description: 'Test merge request')
+ end
+
+ before do
+ create(:note_on_commit, note: 'A commit comment',
+ project: fork_project,
+ commit_id: merge_request.commit_shas.first)
+ sign_in(user)
+ end
+
+ it 'user can reply to the comment' do
+ visit project_merge_request_path(project, merge_request)
+
+ expect(page).to have_content('A commit comment')
+
+ page.within('.discussion-notes') do
+ find('.btn-text-field').click
+ find('#note_note').send_keys('A reply comment')
+ find('.comment-btn').click
+ end
+
+ wait_for_requests
+
+ expect(page).to have_content('A reply comment')
+ end
+end
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
new file mode 100644
index 00000000000..d30dcefc6aa
--- /dev/null
+++ b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb
@@ -0,0 +1,34 @@
+require 'rails_helper'
+
+describe 'Merge request > User sees pipelines from forked project', :js do
+ let(:target_project) { create(:project, :public, :repository) }
+ let(:user) { target_project.creator }
+ let(:fork_project) { create(:project, :repository, forked_from_project: target_project) }
+ let!(:merge_request) do
+ create(:merge_request_with_diffs, source_project: fork_project,
+ target_project: target_project,
+ description: 'Test merge request')
+ end
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: fork_project,
+ sha: merge_request.diff_head_sha,
+ ref: merge_request.source_branch)
+ end
+
+ before do
+ create(:ci_build, pipeline: pipeline, name: 'rspec')
+ create(:ci_build, pipeline: pipeline, name: 'spinach')
+
+ sign_in(user)
+ visit project_merge_request_path(target_project, merge_request)
+ end
+
+ it 'user visits a pipelines page' do
+ page.within('.merge-request-tabs') { click_link 'Pipelines' }
+
+ page.within('.ci-table') do
+ expect(page).to have_content(pipeline.id)
+ end
+ end
+end
diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb
index 04e3f4bdcf1..a42c016392b 100644
--- a/spec/features/merge_requests/pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_pipelines_spec.rb
@@ -1,14 +1,14 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Pipelines for Merge Requests', :js do
+describe 'Merge request > User sees pipelines', :js do
describe 'pipeline tab' do
- given(:user) { create(:user) }
- given(:merge_request) { create(:merge_request) }
- given(:project) { merge_request.target_project }
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.target_project }
+ let(:user) { project.creator }
before do
project.add_master(user)
- sign_in user
+ sign_in(user)
end
context 'with pipelines' do
@@ -23,7 +23,7 @@ feature 'Pipelines for Merge Requests', :js do
merge_request.update_attribute(:head_pipeline_id, pipeline.id)
end
- scenario 'user visits merge request pipelines tab' do
+ it 'user visits merge request pipelines tab' do
visit project_merge_request_path(project, merge_request)
expect(page.find('.ci-widget')).to have_content('pending')
@@ -36,7 +36,7 @@ feature 'Pipelines for Merge Requests', :js do
expect(page).to have_selector('.stage-cell')
end
- scenario 'pipeline sha does not equal last commit sha' do
+ it 'pipeline sha does not equal last commit sha' do
pipeline.update_attribute(:sha, '19e2e9b4ef76b422ce1154af39a91323ccc57434')
visit project_merge_request_path(project, merge_request)
wait_for_requests
@@ -51,7 +51,7 @@ feature 'Pipelines for Merge Requests', :js do
visit project_merge_request_path(project, merge_request)
end
- scenario 'user visits merge request page' do
+ it 'user visits merge request page' do
page.within('.merge-request-tabs') do
expect(page).to have_no_link('Pipelines')
end
@@ -60,22 +60,22 @@ feature 'Pipelines for Merge Requests', :js do
end
describe 'race condition' do
- given(:project) { create(:project, :repository) }
- given(:user) { create(:user) }
- given(:build_push_data) { { ref: 'feature', checkout_sha: TestEnv::BRANCH_SHA['feature'] } }
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+ let(:build_push_data) { { ref: 'feature', checkout_sha: TestEnv::BRANCH_SHA['feature'] } }
- given(:merge_request_params) do
+ let(:merge_request_params) do
{ "source_branch" => "feature", "source_project_id" => project.id,
"target_branch" => "master", "target_project_id" => project.id, "title" => "A" }
end
- background do
+ before do
project.add_master(user)
sign_in user
end
context 'when pipeline and merge request were created simultaneously' do
- background do
+ before do
stub_ci_pipeline_to_return_yaml_file
threads = []
@@ -91,7 +91,7 @@ feature 'Pipelines for Merge Requests', :js do
threads.each { |thr| thr.join }
end
- scenario 'user sees pipeline in merge request widget' do
+ it 'user sees pipeline in merge request widget' do
visit project_merge_request_path(project, @merge_request)
expect(page.find(".ci-widget")).to have_content(TestEnv::BRANCH_SHA['feature'])
diff --git a/spec/features/merge_requests/user_sees_system_notes_spec.rb b/spec/features/merge_request/user_sees_system_notes_spec.rb
index 03dc61c2efa..a00a682757d 100644
--- a/spec/features/merge_requests/user_sees_system_notes_spec.rb
+++ b/spec/features/merge_request/user_sees_system_notes_spec.rb
@@ -1,15 +1,15 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge requests > User sees system notes' do
+describe 'Merge request > User sees system notes' do
let(:public_project) { create(:project, :public, :repository) }
let(:private_project) { create(:project, :private, :repository) }
+ let(:user) { private_project.creator }
let(:issue) { create(:issue, project: private_project) }
let(:merge_request) { create(:merge_request, source_project: public_project, source_branch: 'markdown') }
let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: public_project, note: "mentioned in #{issue.to_reference(public_project)}") }
context 'when logged-in as a member of the private project' do
before do
- user = create(:user)
private_project.add_developer(user)
sign_in(user)
end
diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb
index 482f2e51c8b..3a15d70979a 100644
--- a/spec/features/merge_requests/versions_spec.rb
+++ b/spec/features/merge_request/user_sees_versions_spec.rb
@@ -1,15 +1,17 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Merge Request versions', :js do
+describe 'Merge request > User sees versions', :js do
let(:merge_request) { create(:merge_request, importing: true) }
let(:project) { merge_request.source_project }
+ let(:user) { project.creator }
let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
let!(:merge_request_diff2) { merge_request.merge_request_diffs.create(head_commit_sha: nil) }
let!(:merge_request_diff3) { merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
- let!(:params) { Hash.new }
+ let!(:params) { {} }
before do
- sign_in(create(:admin))
+ project.add_master(user)
+ sign_in(user)
visit diffs_project_merge_request_path(project, merge_request, params)
end
@@ -62,19 +64,10 @@ feature 'Merge Request versions', :js do
end
end
- it 'should show older version' do
- page.within '.mr-version-dropdown' do
- expect(page).to have_content 'version 1'
- end
-
+ it 'shows comments that were last relevant at that version' do
expect(page).to have_content '5 changed files'
- end
-
- it 'show the message about comments' do
expect(page).to have_content 'Not all comments are displayed'
- end
- it 'shows comments that were last relevant at that version' do
position = Gitlab::Diff::Position.new(
old_path: ".gitmodules",
new_path: ".gitmodules",
@@ -86,7 +79,7 @@ feature 'Merge Request versions', :js do
outdated_diff_note.position = outdated_diff_note.original_position
outdated_diff_note.save!
- visit current_url
+ refresh
expect(page).to have_css(".diffs .notes[data-discussion-id='#{outdated_diff_note.discussion_id}']")
end
@@ -110,26 +103,16 @@ feature 'Merge Request versions', :js do
end
end
- it 'has a path with comparison context' do
+ it 'has a path with comparison context and shows comments that were last relevant at that version' do
expect(page).to have_current_path diffs_project_merge_request_path(
project,
merge_request.iid,
diff_id: merge_request_diff3.id,
start_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
)
- end
-
- it 'should have correct value in the compare dropdown' do
- page.within '.mr-version-compare-dropdown' do
- expect(page).to have_content 'version 1'
- end
- end
-
- it 'show the message about comments' do
+ expect(page).to have_content '4 changed files with 15 additions and 6 deletions'
expect(page).to have_content 'Not all comments are displayed'
- end
- it 'shows comments that were last relevant at that version' do
position = Gitlab::Diff::Position.new(
old_path: ".gitmodules",
new_path: ".gitmodules",
@@ -141,7 +124,7 @@ feature 'Merge Request versions', :js do
outdated_diff_note.position = outdated_diff_note.original_position
outdated_diff_note.save!
- visit current_url
+ refresh
wait_for_requests
expect(page).to have_css(".diffs .notes[data-discussion-id='#{outdated_diff_note.discussion_id}']")
@@ -151,7 +134,7 @@ feature 'Merge Request versions', :js do
expect(page).to have_content '4 changed files with 15 additions and 6 deletions'
end
- it 'should return to latest version when "Show latest version" button is clicked' do
+ it 'returns to latest version when "Show latest version" button is clicked' do
click_link 'Show latest version'
page.within '.mr-version-dropdown' do
expect(page).to have_content 'latest version'
@@ -173,7 +156,7 @@ feature 'Merge Request versions', :js do
end
end
- it 'should have 0 chages between versions' do
+ it 'has 0 chages between versions' do
page.within '.mr-version-compare-dropdown' do
expect(find('.dropdown-toggle')).to have_content 'version 1'
end
@@ -194,7 +177,7 @@ feature 'Merge Request versions', :js do
end
end
- it 'should set the compared versions to be the same' do
+ it 'sets the compared versions to be the same' do
page.within '.mr-version-compare-dropdown' do
expect(find('.dropdown-toggle')).to have_content 'version 2'
end
diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_request/user_sees_wip_help_message_spec.rb
index 2617e735c25..bc25243244e 100644
--- a/spec/features/merge_requests/wip_message_spec.rb
+++ b/spec/features/merge_request/user_sees_wip_help_message_spec.rb
@@ -1,8 +1,8 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Work In Progress help message' do
- let!(:project) { create(:project, :public, :repository) }
- let!(:user) { create(:user) }
+describe 'Merge request > User sees WIP help message' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
before do
project.add_master(user)
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
index 486555ed5cd..dbca279569a 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
@@ -1,13 +1,12 @@
-require 'spec_helper'
+require 'rails_helper'
-feature 'Create New Merge Request', :js do
- let(:user) { create(:user) }
+describe 'Merge request > User selects branches for new MR', :js do
let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
before do
project.add_master(user)
-
- sign_in user
+ sign_in(user)
end
it 'selects the source branch sha when a tag with the same name exists' do
@@ -62,7 +61,7 @@ feature 'Create New Merge Request', :js do
fill_in "merge_request_title", with: "Orphaned MR test"
click_button "Submit merge request"
- click_link "Check out branch"
+ click_button "Check out branch"
expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
end
diff --git a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
index fa3d988b27a..2e95a628013 100644
--- a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
+++ b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
@@ -1,10 +1,13 @@
-require 'spec_helper'
+require 'rails_helper'
+
+describe 'Merge request > User toggles whitespace changes', :js do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:user) { project.creator }
-feature 'Toggle Whitespace Changes', :js do
before do
- sign_in(create(:admin))
- merge_request = create(:merge_request)
- project = merge_request.source_project
+ project.add_master(user)
+ sign_in(user)
visit diffs_project_merge_request_path(project, merge_request)
end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_request/user_uses_slash_commands_spec.rb
index 5874bf5e187..bd739e69d6c 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_request/user_uses_slash_commands_spec.rb
@@ -1,8 +1,14 @@
require 'rails_helper'
-feature 'Merge Requests > User uses quick actions', :js do
+describe 'Merge request > User uses quick actions', :js do
include QuickActionsHelpers
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:guest) { create(:user) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
+
it_behaves_like 'issuable record that supports quick actions in its description and notes', :merge_request do
let(:issuable) { create(:merge_request, source_project: project) }
let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } }
@@ -20,15 +26,7 @@ feature 'Merge Requests > User uses quick actions', :js do
visit project_merge_request_path(project, merge_request)
end
- after do
- wait_for_requests
- end
-
describe 'time tracking' do
- before do
- visit project_merge_request_path(project, merge_request)
- end
-
it_behaves_like 'issuable time tracker'
end
@@ -56,7 +54,6 @@ feature 'Merge Requests > User uses quick actions', :js do
end
context 'when the current user cannot toggle the WIP prefix' do
- let(:guest) { create(:user) }
before do
project.add_guest(guest)
sign_out(:user)
@@ -102,7 +99,6 @@ feature 'Merge Requests > User uses quick actions', :js do
end
context 'when the current user cannot merge the MR' do
- let(:guest) { create(:user) }
before do
project.add_guest(guest)
sign_out(:user)
@@ -186,7 +182,6 @@ feature 'Merge Requests > User uses quick actions', :js do
end
context 'when current user can not change target branch' do
- let(:guest) { create(:user) }
before do
project.add_guest(guest)
sign_out(:user)
diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb
deleted file mode 100644
index b2d64a62b4f..00000000000
--- a/spec/features/merge_requests/assign_issues_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'rails_helper'
-
-feature 'Merge request issue assignment', :js do
- let(:user) { create(:user) }
- let(:project) { create(:project, :public, :repository) }
- let(:issue1) { create(:issue, project: project) }
- let(:issue2) { create(:issue, project: project) }
- let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue1.to_reference} and #{issue2.to_reference}") }
- let(:service) { MergeRequests::AssignIssuesService.new(merge_request, user, user, project) }
-
- before do
- project.add_developer(user)
- end
-
- def visit_merge_request(current_user = nil)
- sign_in(current_user || user)
- visit project_merge_request_path(project, merge_request)
- end
-
- context 'logged in as author' do
- it 'updates related issues' do
- visit_merge_request
- click_link "Assign yourself to these issues"
-
- expect(page).to have_content "2 issues have been assigned to you"
- end
-
- it 'returns user to the merge request' do
- visit_merge_request
- click_link "Assign yourself to these issues"
-
- expect(page).to have_content merge_request.description
- end
-
- it "doesn't display if related issues are already assigned" do
- [issue1, issue2].each { |issue| issue.update!(assignees: [user]) }
-
- visit_merge_request
-
- expect(page).not_to have_content "Assign yourself"
- end
- end
-
- context 'not MR author' do
- it "doesn't not show assignment link" do
- visit_merge_request(create(:user))
-
- expect(page).not_to have_content "Assign yourself"
- end
- end
-end
diff --git a/spec/features/merge_requests/create_new_mr_from_fork_spec.rb b/spec/features/merge_requests/create_new_mr_from_fork_spec.rb
deleted file mode 100644
index 93c40ff6443..00000000000
--- a/spec/features/merge_requests/create_new_mr_from_fork_spec.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require 'spec_helper'
-
-feature 'Creating a merge request from a fork', :js do
- include ProjectForksHelper
-
- let(:user) { create(:user) }
- let(:project) { create(:project, :public, :repository) }
- let!(:source_project) do
- fork_project(project, user,
- repository: true,
- namespace: user.namespace)
- end
-
- before do
- source_project.add_master(user)
-
- sign_in(user)
- end
-
- shared_examples 'create merge request to other project' do
- it 'has all possible target projects' do
- visit project_new_merge_request_path(source_project)
-
- first('.js-target-project').click
-
- within('.dropdown-target-project .dropdown-content') do
- expect(page).to have_content(project.full_path)
- expect(page).to have_content(target_project.full_path)
- expect(page).to have_content(source_project.full_path)
- end
- end
-
- it 'allows creating the merge request to another target project' do
- visit project_merge_requests_path(source_project)
-
- page.within '.content' do
- click_link 'New merge request'
- end
-
- find('.js-source-branch', match: :first).click
- find('.dropdown-source-branch .dropdown-content a', match: :first).click
-
- first('.js-target-project').click
- find('.dropdown-target-project .dropdown-content a', text: target_project.full_path).click
-
- click_button 'Compare branches and continue'
-
- wait_for_requests
-
- expect { click_button 'Submit merge request' }
- .to change { target_project.merge_requests.reload.size }.by(1)
- end
-
- it 'updates the branches when selecting a new target project' do
- target_project_member = target_project.owner
- CreateBranchService.new(target_project, target_project_member)
- .execute('a-brand-new-branch-to-test', 'master')
- visit project_new_merge_request_path(source_project)
-
- first('.js-target-project').click
- find('.dropdown-target-project .dropdown-content a', text: target_project.full_path).click
-
- wait_for_requests
-
- first('.js-target-branch').click
-
- within('.dropdown-target-branch .dropdown-content') do
- expect(page).to have_content('a-brand-new-branch-to-test')
- end
- end
- end
-
- context 'creating to the source of a fork' do
- let!(:target_project) { project }
-
- it_behaves_like('create merge request to other project')
- end
-
- context 'creating to a sibling of a fork' do
- let!(:target_project) do
- other_user = create(:user)
- fork_project(project, other_user,
- repository: true,
- namespace: other_user.namespace)
- end
-
- it_behaves_like('create merge request to other project')
- end
-end
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
deleted file mode 100644
index 53b62caf743..00000000000
--- a/spec/features/merge_requests/created_from_fork_spec.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-require 'spec_helper'
-
-feature 'Merge request created from fork' do
- include ProjectForksHelper
-
- given(:user) { create(:user) }
- given(:project) { create(:project, :public, :repository) }
- given(:forked_project) { fork_project(project, user, repository: true) }
-
- given!(:merge_request) do
- create(:merge_request_with_diffs, source_project: forked_project,
- target_project: project,
- description: 'Test merge request')
- end
-
- background do
- forked_project.add_master(user)
- sign_in user
- end
-
- scenario 'user can access merge request' do
- visit_merge_request(merge_request)
-
- expect(page).to have_content 'Test merge request'
- end
-
- context 'when a commit comment exists on the merge request' do
- given(:comment) { 'A commit comment' }
- given(:reply) { 'A reply comment' }
-
- background do
- create(:note_on_commit, note: comment,
- project: forked_project,
- commit_id: merge_request.commit_shas.first)
- end
-
- scenario 'user can reply to the comment', :js do
- visit_merge_request(merge_request)
-
- expect(page).to have_content(comment)
-
- page.within('.discussion-notes') do
- find('.btn-text-field').click
- find('#note_note').send_keys(reply)
- find('.comment-btn').click
- end
-
- wait_for_requests
-
- expect(page).to have_content(reply)
- end
- end
-
- context 'source project is deleted' do
- background do
- MergeRequests::MergeService.new(project, user).execute(merge_request)
- forked_project.destroy!
- end
-
- scenario 'user can access merge request', :js do
- visit_merge_request(merge_request)
-
- expect(page).to have_content 'Test merge request'
- expect(page).to have_content "(removed):#{merge_request.source_branch}"
- end
- end
-
- context 'pipeline present in source project' do
- given(:pipeline) do
- create(:ci_pipeline,
- project: forked_project,
- sha: merge_request.diff_head_sha,
- ref: merge_request.source_branch)
- end
-
- background do
- create(:ci_build, pipeline: pipeline, name: 'rspec')
- create(:ci_build, pipeline: pipeline, name: 'spinach')
- end
-
- scenario 'user visits a pipelines page', :js do
- visit_merge_request(merge_request)
- page.within('.merge-request-tabs') { click_link 'Pipelines' }
-
- page.within('.ci-table') do
- expect(page).to have_content pipeline.id
- end
- end
- end
-
- def visit_merge_request(mr)
- visit project_merge_request_path(project, mr)
- end
-end
diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb
deleted file mode 100644
index 79be2fbf945..00000000000
--- a/spec/features/merge_requests/edit_mr_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-require 'spec_helper'
-
-feature 'Edit Merge Request' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :public, :repository) }
- let(:merge_request) { create(:merge_request, :simple, source_project: project) }
-
- before do
- project.add_master(user)
-
- sign_in user
-
- visit edit_project_merge_request_path(project, merge_request)
- end
-
- context 'editing a MR' do
- it 'has class js-quick-submit in form' do
- expect(page).to have_selector('.js-quick-submit')
- end
-
- it 'warns about version conflict' do
- merge_request.update(title: "New title")
-
- fill_in 'merge_request_title', with: 'bug 345'
- fill_in 'merge_request_description', with: 'bug description'
-
- click_button 'Save changes'
-
- expect(page).to have_content 'Someone edited the merge request the same time you did'
- end
-
- it 'allows to unselect "Remove source branch"', :js do
- merge_request.update(merge_params: { 'force_remove_source_branch' => '1' })
- expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
-
- visit edit_project_merge_request_path(project, merge_request)
- uncheck 'Remove source branch when merge request is accepted'
-
- click_button 'Save changes'
-
- expect(page).to have_unchecked_field 'remove-source-branch-input'
- expect(page).to have_content 'Remove source branch'
- end
-
- it 'should preserve description textarea height', :js do
- long_description = %q(
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ac ornare ligula, ut tempus arcu. Etiam ultricies accumsan dolor vitae faucibus. Donec at elit lacus. Mauris orci ante, aliquam quis lorem eget, convallis faucibus arcu. Aenean at pulvinar lacus. Ut viverra quam massa, molestie ornare tortor dignissim a. Suspendisse tristique pellentesque tellus, id lacinia metus elementum id. Nam tristique, arcu rhoncus faucibus viverra, lacus ipsum sagittis ligula, vitae convallis odio lacus a nibh. Ut tincidunt est purus, ac vestibulum augue maximus in. Suspendisse vel erat et mi ultricies semper. Pellentesque volutpat pellentesque consequat.
-
- Cras congue nec ligula tristique viverra. Curabitur fringilla fringilla fringilla. Donec rhoncus dignissim orci ut accumsan. Ut rutrum urna a rhoncus varius. Maecenas blandit, mauris nec accumsan gravida, augue nibh finibus magna, sed maximus turpis libero nec neque. Suspendisse at semper est. Nunc imperdiet dapibus dui, varius sollicitudin erat luctus non. Sed pellentesque ligula eget posuere facilisis. Donec dictum commodo volutpat. Donec egestas dui ac magna sollicitudin bibendum. Vivamus purus neque, ullamcorper ac feugiat et, tempus sit amet metus. Praesent quis viverra neque. Sed bibendum viverra est, eu aliquam mi ornare vitae. Proin et dapibus ipsum. Nunc tortor diam, malesuada nec interdum vel, placerat quis justo. Ut viverra at erat eu laoreet.
-
- Pellentesque commodo, diam sit amet dignissim condimentum, tortor justo pretium est, non venenatis metus eros ut nunc. Etiam ut neque eget sem dapibus aliquam. Curabitur vel elit lorem. Nulla nec enim elit. Sed ut ex id justo facilisis convallis at ac augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nullam cursus egestas turpis non tristique. Suspendisse in erat sem. Fusce libero elit, fermentum gravida mauris id, auctor iaculis felis. Nullam vulputate tempor laoreet.
-
- Nam tempor et magna sed convallis. Fusce sit amet sollicitudin risus, a ullamcorper lacus. Morbi gravida quis sem eget porttitor. Donec eu egestas mauris, in elementum tortor. Sed eget ex mi. Mauris iaculis tortor ut est auctor, nec dignissim quam sagittis. Suspendisse vel metus non quam suscipit tincidunt. Cras molestie lacus non justo finibus sodales quis vitae erat. In a porttitor nisi, id sollicitudin urna. Ut at felis tellus. Suspendisse potenti.
-
- Maecenas leo ligula, varius at neque vitae, ornare maximus justo. Nullam convallis luctus risus et vulputate. Duis suscipit faucibus iaculis. Etiam quis tortor faucibus, tristique tellus sit amet, sodales neque. Nulla dapibus nisi vel aliquet consequat. Etiam faucibus, metus eget condimentum iaculis, enim urna lobortis sem, id efficitur eros sapien nec nisi. Aenean ut finibus ex.
- )
-
- fill_in 'merge_request_description', with: long_description
-
- height = get_textarea_height
- find('.js-md-preview-button').click
- find('.js-md-write-button').click
- new_height = get_textarea_height
-
- expect(height).to eq(new_height)
- end
-
- def get_textarea_height
- find('#merge_request_description')
- page.evaluate_script('document.getElementById("merge_request_description").offsetHeight')
- end
- end
-end
diff --git a/spec/features/merge_requests/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb
deleted file mode 100644
index 7adae08e499..00000000000
--- a/spec/features/merge_requests/filter_by_labels_spec.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-require 'rails_helper'
-
-feature 'Merge Request filtering by Labels', :js do
- include FilteredSearchHelpers
- include MergeRequestHelpers
-
- let(:project) { create(:project, :public, :repository) }
- let!(:user) { create(:user) }
- let!(:label) { create(:label, project: project) }
-
- let!(:bug) { create(:label, project: project, title: 'bug') }
- let!(:feature) { create(:label, project: project, title: 'feature') }
- let!(:enhancement) { create(:label, project: project, title: 'enhancement') }
-
- let!(:mr1) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "fix") }
- let!(:mr2) { create(:merge_request, title: "Bugfix2", source_project: project, target_project: project, source_branch: "wip") }
- let!(:mr3) { create(:merge_request, title: "Feature1", source_project: project, target_project: project, source_branch: "improve/awesome") }
-
- before do
- mr1.labels << bug
-
- mr2.labels << bug
- mr2.labels << enhancement
-
- mr3.title = "Feature1"
- mr3.labels << feature
-
- project.add_master(user)
- sign_in(user)
-
- visit project_merge_requests_path(project)
- end
-
- context 'filter by label bug' do
- before do
- input_filtered_search('label:~bug')
- end
-
- it 'apply the filter' do
- expect(page).to have_content "Bugfix1"
- expect(page).to have_content "Bugfix2"
- expect(page).not_to have_content "Feature1"
- end
- end
-
- context 'filter by label feature' do
- before do
- input_filtered_search('label:~feature')
- end
-
- it 'applies the filter' do
- expect(page).to have_content "Feature1"
- expect(page).not_to have_content "Bugfix2"
- expect(page).not_to have_content "Bugfix1"
- end
- end
-
- context 'filter by label enhancement' do
- before do
- input_filtered_search('label:~enhancement')
- end
-
- it 'applies the filter' do
- expect(page).to have_content "Bugfix2"
- expect(page).not_to have_content "Feature1"
- expect(page).not_to have_content "Bugfix1"
- end
- end
-
- context 'filter by label enhancement and bug in issues list' do
- before do
- input_filtered_search('label:~bug label:~enhancement')
- end
-
- it 'applies the filters' do
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- expect(page).to have_content "Bugfix2"
- expect(page).not_to have_content "Feature1"
- end
- end
-
- context 'filter dropdown' do
- it 'filters by label name' do
- init_label_search
- filtered_search.send_keys('~bug')
-
- page.within '.filter-dropdown' do
- expect(page).not_to have_content 'enhancement'
- expect(page).to have_content 'bug'
- end
- end
- end
-end
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
deleted file mode 100644
index 8db94352f73..00000000000
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-require 'rails_helper'
-
-feature 'Merge Request filtering by Milestone' do
- include FilteredSearchHelpers
- include MergeRequestHelpers
-
- let(:project) { create(:project, :public, :repository) }
- let!(:user) { create(:user)}
- let(:milestone) { create(:milestone, project: project) }
-
- def filter_by_milestone(title)
- find(".js-milestone-select").click
- find(".milestone-filter a", text: title).click
- end
-
- before do
- project.add_master(user)
- sign_in(user)
- end
-
- scenario 'filters by no Milestone', :js do
- create(:merge_request, :with_diffs, source_project: project)
- create(:merge_request, :simple, source_project: project, milestone: milestone)
-
- visit_merge_requests(project)
- input_filtered_search('milestone:none')
-
- expect_tokens([milestone_token('none', false)])
- expect_filtered_search_input_empty
-
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- expect(page).to have_css('.merge-request', count: 1)
- end
-
- context 'filters by upcoming milestone', :js do
- it 'does not show merge requests with no expiry' do
- create(:merge_request, :with_diffs, source_project: project)
- create(:merge_request, :simple, source_project: project, milestone: milestone)
-
- visit_merge_requests(project)
- input_filtered_search('milestone:upcoming')
-
- expect(page).to have_css('.merge-request', count: 0)
- end
-
- it 'shows merge requests in future' do
- milestone = create(:milestone, project: project, due_date: Date.tomorrow)
- create(:merge_request, :with_diffs, source_project: project)
- create(:merge_request, :simple, source_project: project, milestone: milestone)
-
- visit_merge_requests(project)
- input_filtered_search('milestone:upcoming')
-
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- expect(page).to have_css('.merge-request', count: 1)
- end
-
- it 'does not show merge requests in past' do
- milestone = create(:milestone, project: project, due_date: Date.yesterday)
- create(:merge_request, :with_diffs, source_project: project)
- create(:merge_request, :simple, source_project: project, milestone: milestone)
-
- visit_merge_requests(project)
- input_filtered_search('milestone:upcoming')
-
- expect(page).to have_css('.merge-request', count: 0)
- end
- end
-
- scenario 'filters by a specific Milestone', :js do
- create(:merge_request, :with_diffs, source_project: project, milestone: milestone)
- create(:merge_request, :simple, source_project: project)
-
- visit_merge_requests(project)
- input_filtered_search("milestone:%'#{milestone.title}'")
-
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- expect(page).to have_css('.merge-request', count: 1)
- end
-
- context 'when milestone has single quotes in title' do
- background do
- milestone.update(name: "rock 'n' roll")
- end
-
- scenario 'filters by a specific Milestone', :js do
- create(:merge_request, :with_diffs, source_project: project, milestone: milestone)
- create(:merge_request, :simple, source_project: project)
-
- visit_merge_requests(project)
- input_filtered_search("milestone:%\"#{milestone.title}\"")
-
- expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- expect(page).to have_css('.merge-request', count: 1)
- end
- end
-end
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
deleted file mode 100644
index aac295ab940..00000000000
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ /dev/null
@@ -1,337 +0,0 @@
-require 'rails_helper'
-
-describe 'Filter merge requests' do
- include FilteredSearchHelpers
- include MergeRequestHelpers
-
- let!(:project) { create(:project, :repository) }
- let!(:group) { create(:group) }
- let!(:user) { create(:user) }
- let!(:milestone) { create(:milestone, project: project) }
- let!(:label) { create(:label, project: project) }
- let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
-
- before do
- project.add_master(user)
- group.add_developer(user)
- sign_in(user)
- create(:merge_request, source_project: project, target_project: project)
-
- visit project_merge_requests_path(project)
- end
-
- describe 'for assignee from mr#index' do
- let(:search_query) { "assignee:@#{user.username}" }
-
- def expect_assignee_visual_tokens
- wait_for_requests
-
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- before do
- input_filtered_search(search_query)
-
- expect_mr_list_count(0)
- end
-
- context 'assignee', :js do
- it 'updates to current user' do
- expect_assignee_visual_tokens()
- end
-
- it 'does not change when closed link is clicked' do
- find('.issues-state-filters [data-state="closed"]').click
-
- expect_assignee_visual_tokens()
- end
-
- it 'does not change when all link is clicked' do
- find('.issues-state-filters [data-state="all"]').click
-
- expect_assignee_visual_tokens()
- end
- end
- end
-
- describe 'for milestone from mr#index' do
- let(:search_query) { "milestone:%\"#{milestone.title}\"" }
-
- def expect_milestone_visual_tokens
- expect_tokens([milestone_token("\"#{milestone.title}\"")])
- expect_filtered_search_input_empty
- end
-
- before do
- input_filtered_search(search_query)
-
- expect_mr_list_count(0)
- end
-
- context 'milestone', :js do
- it 'updates to current milestone' do
- expect_milestone_visual_tokens()
- end
-
- it 'does not change when closed link is clicked' do
- find('.issues-state-filters [data-state="closed"]').click
-
- expect_milestone_visual_tokens()
- end
-
- it 'does not change when all link is clicked' do
- find('.issues-state-filters [data-state="all"]').click
-
- expect_milestone_visual_tokens()
- end
- end
- end
-
- describe 'for label from mr#index', :js do
- it 'filters by no label' do
- input_filtered_search('label:none')
-
- expect_mr_list_count(1)
- expect_tokens([label_token('none', false)])
- expect_filtered_search_input_empty
- end
-
- it 'filters by a label' do
- input_filtered_search("label:~#{label.title}")
-
- expect_mr_list_count(0)
- expect_tokens([label_token(label.title)])
- expect_filtered_search_input_empty
- end
-
- it "filters by `won't fix` and another label" do
- input_filtered_search("label:~\"#{wontfix.title}\" label:~#{label.title}")
-
- expect_mr_list_count(0)
- expect_tokens([label_token("\"#{wontfix.title}\""), label_token(label.title)])
- expect_filtered_search_input_empty
- end
-
- it "filters by `won't fix` label followed by another label after page load" do
- input_filtered_search("label:~\"#{wontfix.title}\"")
-
- expect_mr_list_count(0)
- expect_tokens([label_token("\"#{wontfix.title}\"")])
- expect_filtered_search_input_empty
-
- input_filtered_search_keys("label:~#{label.title}")
-
- expect_mr_list_count(0)
- expect_tokens([label_token("\"#{wontfix.title}\""), label_token(label.title)])
- expect_filtered_search_input_empty
- end
- end
-
- describe 'for assignee and label from mr#index' do
- let(:search_query) { "assignee:@#{user.username} label:~#{label.title}" }
-
- before do
- input_filtered_search(search_query)
-
- expect_mr_list_count(0)
- end
-
- context 'assignee and label', :js do
- def expect_assignee_label_visual_tokens
- wait_for_requests
-
- expect_tokens([assignee_token(user.name), label_token(label.title)])
- expect_filtered_search_input_empty
- end
-
- it 'updates to current assignee and label' do
- expect_assignee_label_visual_tokens()
- end
-
- it 'does not change when closed link is clicked' do
- find('.issues-state-filters [data-state="closed"]').click
-
- expect_assignee_label_visual_tokens()
- end
-
- it 'does not change when all link is clicked' do
- find('.issues-state-filters [data-state="all"]').click
-
- expect_assignee_label_visual_tokens()
- end
- end
- end
-
- describe 'filter merge requests by text' do
- before do
- create(:merge_request, title: "Bug", source_project: project, target_project: project, source_branch: "wip")
-
- bug_label = create(:label, project: project, title: 'bug')
- milestone = create(:milestone, title: "8", project: project)
-
- mr = create(:merge_request,
- title: "Bug 2",
- source_project: project,
- target_project: project,
- source_branch: "fix",
- milestone: milestone,
- author: user,
- assignee: user)
- mr.labels << bug_label
-
- visit project_merge_requests_path(project)
- end
-
- context 'only text', :js do
- it 'filters merge requests by searched text' do
- input_filtered_search('bug')
-
- expect_mr_list_count(2)
- end
-
- it 'does not show any merge requests' do
- input_filtered_search('testing')
-
- page.within '.mr-list' do
- expect(page).not_to have_selector('.merge-request')
- end
- end
- end
-
- context 'filters and searches', :js do
- it 'filters by text and label' do
- input_filtered_search('Bug')
-
- expect_mr_list_count(2)
- expect_filtered_search_input('Bug')
-
- input_filtered_search_keys(' label:~bug')
-
- expect_mr_list_count(1)
- expect_tokens([label_token('bug')])
- expect_filtered_search_input('Bug')
- end
-
- it 'filters by text and milestone' do
- input_filtered_search('Bug')
-
- expect_mr_list_count(2)
- expect_filtered_search_input('Bug')
-
- input_filtered_search_keys(' milestone:%8')
-
- expect_mr_list_count(1)
- expect_tokens([milestone_token('8')])
- expect_filtered_search_input('Bug')
- end
-
- it 'filters by text and assignee' do
- input_filtered_search('Bug')
-
- expect_mr_list_count(2)
- expect_filtered_search_input('Bug')
-
- input_filtered_search_keys(" assignee:@#{user.username}")
-
- expect_mr_list_count(1)
-
- wait_for_requests
-
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input('Bug')
- end
-
- it 'filters by text and author' do
- input_filtered_search('Bug')
-
- expect_mr_list_count(2)
- expect_filtered_search_input('Bug')
-
- input_filtered_search_keys(" author:@#{user.username}")
-
- wait_for_requests
-
- expect_mr_list_count(1)
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input('Bug')
- end
- end
- end
-
- describe 'filter merge requests and sort', :js do
- before do
- bug_label = create(:label, project: project, title: 'bug')
-
- mr1 = create(:merge_request, title: "Frontend", source_project: project, target_project: project, source_branch: "wip")
- mr2 = create(:merge_request, title: "Bug 2", source_project: project, target_project: project, source_branch: "fix")
-
- mr1.labels << bug_label
- mr2.labels << bug_label
-
- visit project_merge_requests_path(project)
- end
-
- it 'is able to filter and sort merge requests' do
- input_filtered_search('label:~bug')
-
- expect_mr_list_count(2)
-
- click_button 'Created date'
- page.within '.dropdown-menu-sort' do
- click_link 'Priority'
- end
- wait_for_requests
-
- page.within '.mr-list' do
- expect(page).to have_content('Frontend')
- end
- end
- end
-
- describe 'filter by assignee id', :js do
- it 'filter by current user' do
- visit project_merge_requests_path(project, assignee_id: user.id)
-
- wait_for_requests
-
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'filter by new user' do
- new_user = create(:user)
- project.add_developer(new_user)
-
- visit project_merge_requests_path(project, assignee_id: new_user.id)
-
- wait_for_requests
-
- expect_tokens([assignee_token(new_user.name)])
- expect_filtered_search_input_empty
- end
- end
-
- describe 'filter by author id', :js do
- it 'filter by current user' do
- visit project_merge_requests_path(project, author_id: user.id)
-
- wait_for_requests
-
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'filter by new user' do
- new_user = create(:user)
- project.add_developer(new_user)
-
- visit project_merge_requests_path(project, author_id: new_user.id)
-
- wait_for_requests
-
- expect_tokens([author_token(new_user.name)])
- expect_filtered_search_input_empty
- end
- end
-end
diff --git a/spec/features/merge_requests/filters_generic_behavior_spec.rb b/spec/features/merge_requests/filters_generic_behavior_spec.rb
new file mode 100644
index 00000000000..0e7fac6b409
--- /dev/null
+++ b/spec/features/merge_requests/filters_generic_behavior_spec.rb
@@ -0,0 +1,81 @@
+require 'rails_helper'
+
+describe 'Merge Requests > Filters generic behavior', :js do
+ include FilteredSearchHelpers
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:bug) { create(:label, project: project, title: 'bug') }
+ let(:open_mr) { create(:merge_request, title: 'Bugfix1', source_project: project, target_project: project, source_branch: 'bugfix1') }
+ let(:merged_mr) { create(:merge_request, :merged, title: 'Bugfix2', source_project: project, target_project: project, source_branch: 'bugfix2') }
+ let(:closed_mr) { create(:merge_request, :closed, title: 'Feature', source_project: project, target_project: project, source_branch: 'improve/awesome') }
+
+ before do
+ open_mr.labels << bug
+ merged_mr.labels << bug
+ closed_mr.labels << bug
+
+ sign_in(user)
+ visit project_merge_requests_path(project)
+ end
+
+ context 'when filtered by a label' do
+ before do
+ input_filtered_search('label:~bug')
+ end
+
+ describe 'state tabs' do
+ it 'does not change when state tabs are clicked' do
+ expect(page).to have_issuable_counts(open: 1, merged: 1, closed: 1, all: 3)
+ expect(page).to have_content 'Bugfix1'
+ expect(page).not_to have_content 'Bugfix2'
+ expect(page).not_to have_content 'Feature'
+
+ find('.issues-state-filters [data-state="merged"]').click
+
+ expect(page).to have_issuable_counts(open: 1, merged: 1, closed: 1, all: 3)
+ expect(page).not_to have_content 'Bugfix1'
+ expect(page).to have_content 'Bugfix2'
+ expect(page).not_to have_content 'Feature'
+
+ find('.issues-state-filters [data-state="closed"]').click
+
+ expect(page).to have_issuable_counts(open: 1, merged: 1, closed: 1, all: 3)
+ expect(page).not_to have_content 'Bugfix1'
+ expect(page).not_to have_content 'Bugfix2'
+ expect(page).to have_content 'Feature'
+
+ find('.issues-state-filters [data-state="all"]').click
+
+ expect(page).to have_issuable_counts(open: 1, merged: 1, closed: 1, all: 3)
+ expect(page).to have_content 'Bugfix1'
+ expect(page).to have_content 'Bugfix2'
+ expect(page).to have_content 'Feature'
+ end
+ end
+
+ describe 'clear button' do
+ it 'allows user to remove filtered labels' do
+ first('.clear-search').click
+ filtered_search.send_keys(:enter)
+
+ expect(page).to have_issuable_counts(open: 1, merged: 1, closed: 1, all: 3)
+ expect(page).to have_content 'Bugfix1'
+ expect(page).not_to have_content 'Bugfix2'
+ expect(page).not_to have_content 'Feature'
+ end
+ end
+ end
+
+ context 'filter dropdown' do
+ it 'filters by label name' do
+ init_label_search
+ filtered_search.send_keys('~bug')
+
+ page.within '.filter-dropdown' do
+ expect(page).not_to have_content 'enhancement'
+ expect(page).to have_content 'bug'
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
deleted file mode 100644
index 1ebf762a006..00000000000
--- a/spec/features/merge_requests/form_spec.rb
+++ /dev/null
@@ -1,301 +0,0 @@
-require 'rails_helper'
-
-describe 'New/edit merge request', :js do
- include ProjectForksHelper
-
- let!(:project) { create(:project, :public, :repository) }
- let(:forked_project) { fork_project(project, nil, repository: true) }
- let!(:user) { create(:user) }
- let!(:user2) { create(:user) }
- let!(:milestone) { create(:milestone, project: project) }
- let!(:label) { create(:label, project: project) }
- let!(:label2) { create(:label, project: project) }
-
- before do
- project.add_master(user)
- project.add_master(user2)
- end
-
- context 'owned projects' do
- before do
- sign_in(user)
- end
-
- context 'new merge request' do
- before do
- visit project_new_merge_request_path(
- project,
- merge_request: {
- source_project_id: project.id,
- target_project_id: project.id,
- source_branch: 'fix',
- target_branch: 'master'
- })
- end
-
- it 'creates new merge request' do
- click_button 'Assignee'
- page.within '.dropdown-menu-user' do
- click_link user2.name
- end
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user2.id.to_s)
- page.within '.js-assignee-search' do
- expect(page).to have_content user2.name
- end
-
- find('a', text: 'Assign to me').click
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
- page.within '.js-assignee-search' do
- expect(page).to have_content user.name
- end
-
- click_button 'Milestone'
- page.within '.issue-milestone' do
- click_link milestone.title
- end
- expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
- page.within '.js-milestone-select' do
- expect(page).to have_content milestone.title
- end
-
- click_button 'Labels'
- page.within '.dropdown-menu-labels' do
- click_link label.title
- click_link label2.title
- end
- page.within '.js-label-select' do
- expect(page).to have_content label.title
- end
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
-
- click_button 'Submit merge request'
-
- page.within '.issuable-sidebar' do
- page.within '.assignee' do
- expect(page).to have_content user.name
- end
-
- page.within '.milestone' do
- expect(page).to have_content milestone.title
- end
-
- page.within '.labels' do
- expect(page).to have_content label.title
- expect(page).to have_content label2.title
- end
- end
-
- page.within '.breadcrumbs' do
- merge_request = MergeRequest.find_by(source_branch: 'fix')
-
- expect(page).to have_text("Merge Requests #{merge_request.to_reference}")
- end
- end
-
- it 'description has autocomplete' do
- find('#merge_request_description').native.send_keys('')
- fill_in 'merge_request_description', with: '@'
-
- expect(page).to have_selector('.atwho-view')
- end
- end
-
- context 'edit merge request' do
- before do
- merge_request = create(:merge_request,
- source_project: project,
- target_project: project,
- source_branch: 'fix',
- target_branch: 'master'
- )
-
- visit edit_project_merge_request_path(project, merge_request)
- end
-
- it 'updates merge request' do
- click_button 'Assignee'
- page.within '.dropdown-menu-user' do
- click_link user.name
- end
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
- page.within '.js-assignee-search' do
- expect(page).to have_content user.name
- end
-
- click_button 'Milestone'
- page.within '.issue-milestone' do
- click_link milestone.title
- end
- expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
- page.within '.js-milestone-select' do
- expect(page).to have_content milestone.title
- end
-
- click_button 'Labels'
- page.within '.dropdown-menu-labels' do
- click_link label.title
- click_link label2.title
- end
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
- page.within '.js-label-select' do
- expect(page).to have_content label.title
- end
-
- click_button 'Save changes'
-
- page.within '.issuable-sidebar' do
- page.within '.assignee' do
- expect(page).to have_content user.name
- end
-
- page.within '.milestone' do
- expect(page).to have_content milestone.title
- end
-
- page.within '.labels' do
- expect(page).to have_content label.title
- expect(page).to have_content label2.title
- end
- end
- end
-
- it 'description has autocomplete' do
- find('#merge_request_description').native.send_keys('')
- fill_in 'merge_request_description', with: '@'
-
- expect(page).to have_selector('.atwho-view')
- end
- end
- end
-
- context 'forked project' do
- before do
- forked_project.add_master(user)
- sign_in(user)
- end
-
- context 'new merge request' do
- before do
- visit project_new_merge_request_path(
- forked_project,
- merge_request: {
- source_project_id: forked_project.id,
- target_project_id: project.id,
- source_branch: 'fix',
- target_branch: 'master'
- })
- end
-
- it 'creates new merge request' do
- click_button 'Assignee'
- page.within '.dropdown-menu-user' do
- click_link user.name
- end
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
- page.within '.js-assignee-search' do
- expect(page).to have_content user.name
- end
-
- click_button 'Milestone'
- page.within '.issue-milestone' do
- click_link milestone.title
- end
- expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
- page.within '.js-milestone-select' do
- expect(page).to have_content milestone.title
- end
-
- click_button 'Labels'
- page.within '.dropdown-menu-labels' do
- click_link label.title
- click_link label2.title
- end
- page.within '.js-label-select' do
- expect(page).to have_content label.title
- end
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
-
- click_button 'Submit merge request'
-
- page.within '.issuable-sidebar' do
- page.within '.assignee' do
- expect(page).to have_content user.name
- end
-
- page.within '.milestone' do
- expect(page).to have_content milestone.title
- end
-
- page.within '.labels' do
- expect(page).to have_content label.title
- expect(page).to have_content label2.title
- end
- end
- end
- end
-
- context 'edit merge request' do
- before do
- merge_request = create(:merge_request,
- source_project: forked_project,
- target_project: project,
- source_branch: 'fix',
- target_branch: 'master'
- )
-
- visit edit_project_merge_request_path(project, merge_request)
- end
-
- it 'should update merge request' do
- click_button 'Assignee'
- page.within '.dropdown-menu-user' do
- click_link user.name
- end
- expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
- page.within '.js-assignee-search' do
- expect(page).to have_content user.name
- end
-
- click_button 'Milestone'
- page.within '.issue-milestone' do
- click_link milestone.title
- end
- expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
- page.within '.js-milestone-select' do
- expect(page).to have_content milestone.title
- end
-
- click_button 'Labels'
- page.within '.dropdown-menu-labels' do
- click_link label.title
- click_link label2.title
- end
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
- expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
- page.within '.js-label-select' do
- expect(page).to have_content label.title
- end
-
- click_button 'Save changes'
-
- page.within '.issuable-sidebar' do
- page.within '.assignee' do
- expect(page).to have_content user.name
- end
-
- page.within '.milestone' do
- expect(page).to have_content milestone.title
- end
-
- page.within '.labels' do
- expect(page).to have_content label.title
- expect(page).to have_content label2.title
- end
- end
- end
- end
- end
-end
diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb
deleted file mode 100644
index daca4422bf1..00000000000
--- a/spec/features/merge_requests/reset_filters_spec.rb
+++ /dev/null
@@ -1,136 +0,0 @@
-require 'rails_helper'
-
-feature 'Merge requests filter clear button', :js do
- include FilteredSearchHelpers
- include MergeRequestHelpers
- include IssueHelpers
-
- let!(:project) { create(:project, :public, :repository) }
- let!(:user) { create(:user) }
- let!(:milestone) { create(:milestone, project: project) }
- let!(:bug) { create(:label, project: project, name: 'bug')}
- let!(:mr1) { create(:merge_request, title: "Feature", source_project: project, target_project: project, source_branch: "improve/awesome", milestone: milestone, author: user, assignee: user) }
- let!(:mr2) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "fix") }
-
- let(:merge_request_css) { '.merge-request' }
- let(:clear_search_css) { '.filtered-search-box .clear-search' }
-
- before do
- mr2.labels << bug
- project.add_developer(user)
- end
-
- context 'when a milestone filter has been applied' do
- it 'resets the milestone filter' do
- visit_merge_requests(project, milestone_title: milestone.title)
-
- expect(page).to have_css(merge_request_css, count: 1)
- expect(get_filtered_search_placeholder).to eq('')
-
- reset_filters
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- end
- end
-
- context 'when a label filter has been applied' do
- it 'resets the label filter' do
- visit_merge_requests(project, label_name: bug.name)
-
- expect(page).to have_css(merge_request_css, count: 1)
- expect(get_filtered_search_placeholder).to eq('')
-
- reset_filters
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- end
- end
-
- context 'when multiple label filters have been applied' do
- let!(:label) { create(:label, project: project, name: 'Frontend') }
- let(:filter_dropdown) { find("#js-dropdown-label .filter-dropdown") }
-
- before do
- visit_merge_requests(project)
- init_label_search
- end
-
- it 'filters bug label' do
- filtered_search.set('~bug')
-
- filter_dropdown.find('.filter-dropdown-item', text: bug.title).click
- init_label_search
-
- expect(filter_dropdown.find('.filter-dropdown-item', text: bug.title)).to be_visible
- expect(filter_dropdown.find('.filter-dropdown-item', text: label.title)).to be_visible
- end
- end
-
- context 'when a text search has been conducted' do
- it 'resets the text search filter' do
- visit_merge_requests(project, search: 'Bug')
-
- expect(page).to have_css(merge_request_css, count: 1)
- expect(get_filtered_search_placeholder).to eq('')
-
- reset_filters
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- end
- end
-
- context 'when author filter has been applied' do
- it 'resets the author filter' do
- visit_merge_requests(project, author_username: user.username)
-
- expect(page).to have_css(merge_request_css, count: 1)
- expect(get_filtered_search_placeholder).to eq('')
-
- reset_filters
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- end
- end
-
- context 'when assignee filter has been applied' do
- it 'resets the assignee filter' do
- visit_merge_requests(project, assignee_username: user.username)
-
- expect(page).to have_css(merge_request_css, count: 1)
- expect(get_filtered_search_placeholder).to eq('')
-
- reset_filters
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- end
- end
-
- context 'when all filters have been applied' do
- it 'clears all filters' do
- visit_merge_requests(project, assignee_username: user.username, author_username: user.username, milestone_title: milestone.title, label_name: bug.name, search: 'Bug')
-
- expect(page).to have_css(merge_request_css, count: 0)
- expect(get_filtered_search_placeholder).to eq('')
-
- reset_filters
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- end
- end
-
- context 'when no filters have been applied' do
- it 'the clear button should not be visible' do
- visit_merge_requests(project)
-
- expect(page).to have_css(merge_request_css, count: 2)
- expect(get_filtered_search_placeholder).to eq(default_placeholder)
- expect(page).not_to have_css(clear_search_css)
- end
- end
-end
diff --git a/spec/features/merge_requests/target_branch_spec.rb b/spec/features/merge_requests/target_branch_spec.rb
deleted file mode 100644
index d9f7a056dea..00000000000
--- a/spec/features/merge_requests/target_branch_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-
-describe 'Target branch', :js do
- let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request) }
- let(:project) { merge_request.project }
-
- def path_to_merge_request
- project_merge_request_path(project, merge_request)
- end
-
- before do
- sign_in user
- project.add_master(user)
- end
-
- context 'when branch was deleted' do
- before do
- DeleteBranchService.new(project, user).execute('feature')
- visit path_to_merge_request
- end
-
- it 'shows a message about missing target branch' do
- expect(page).to have_content(
- 'Target branch does not exist'
- )
- end
-
- it 'does not show link to target branch' do
- expect(page).not_to have_selector('.mr-widget-body .js-branch-text a')
- end
- end
-end
diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb
deleted file mode 100644
index cd92ad22267..00000000000
--- a/spec/features/merge_requests/toggler_behavior_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-feature 'toggler_behavior', :js do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:merge_request) { create(:merge_request, source_project: project, author: user) }
- let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
- let(:fragment_id) { "#note_#{note.id}" }
-
- before do
- sign_in(create(:admin))
- project = merge_request.source_project
- page.current_window.resize_to(1000, 300)
- visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
- end
-
- describe 'scroll position' do
- it 'should be scrolled down to fragment' do
- page_height = page.current_window.size[1]
- page_scroll_y = page.evaluate_script("window.scrollY")
- fragment_position_top = page.evaluate_script("Math.round($('#{fragment_id}').offset().top)")
- expect(find('.js-toggle-content').visible?).to eq true
- expect(find(fragment_id).visible?).to eq true
- expect(fragment_position_top).to be >= page_scroll_y
- expect(fragment_position_top).to be < (page_scroll_y + page_height)
- end
- end
-end
diff --git a/spec/features/merge_requests/user_filters_by_assignees_spec.rb b/spec/features/merge_requests/user_filters_by_assignees_spec.rb
new file mode 100644
index 00000000000..d6c770c93f1
--- /dev/null
+++ b/spec/features/merge_requests/user_filters_by_assignees_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+describe 'Merge Requests > User filters by assignees', :js do
+ include FilteredSearchHelpers
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+
+ before do
+ create(:merge_request, assignee: user, title: 'Bugfix1', source_project: project, target_project: project, source_branch: 'bugfix1')
+ create(:merge_request, title: 'Bugfix2', source_project: project, target_project: project, source_branch: 'bugfix2')
+
+ sign_in(user)
+ visit project_merge_requests_path(project)
+ end
+
+ context 'filtering by assignee:none' do
+ it 'applies the filter' do
+ input_filtered_search('assignee:none')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).not_to have_content 'Bugfix1'
+ expect(page).to have_content 'Bugfix2'
+ end
+ end
+
+ context 'filtering by assignee:@username' do
+ it 'applies the filter' do
+ input_filtered_search("assignee:@#{user.username}")
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_content 'Bugfix1'
+ expect(page).not_to have_content 'Bugfix2'
+ end
+ end
+end
diff --git a/spec/features/merge_requests/user_filters_by_labels_spec.rb b/spec/features/merge_requests/user_filters_by_labels_spec.rb
new file mode 100644
index 00000000000..08d741af93d
--- /dev/null
+++ b/spec/features/merge_requests/user_filters_by_labels_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+describe 'Merge Requests > User filters by labels', :js do
+ include FilteredSearchHelpers
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:mr1) { create(:merge_request, title: 'Bugfix1', source_project: project, target_project: project, source_branch: 'bugfix1') }
+ let(:mr2) { create(:merge_request, title: 'Bugfix2', source_project: project, target_project: project, source_branch: 'bugfix2') }
+
+ before do
+ bug_label = create(:label, project: project, title: 'bug')
+ enhancement_label = create(:label, project: project, title: 'enhancement')
+ mr1.labels << bug_label
+ mr2.labels << bug_label << enhancement_label
+
+ sign_in(user)
+ visit project_merge_requests_path(project)
+ end
+
+ context 'filtering by label:none' do
+ it 'applies the filter' do
+ input_filtered_search('label:none')
+
+ expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0)
+ expect(page).not_to have_content 'Bugfix1'
+ expect(page).not_to have_content 'Bugfix2'
+ end
+ end
+
+ context 'filtering by label:~enhancement' do
+ it 'applies the filter' do
+ input_filtered_search('label:~enhancement')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_content 'Bugfix2'
+ expect(page).not_to have_content 'Bugfix1'
+ end
+ end
+
+ context 'filtering by label:~enhancement and label:~bug' do
+ it 'applies the filters' do
+ input_filtered_search('label:~bug label:~enhancement')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_content 'Bugfix2'
+ end
+ end
+end
diff --git a/spec/features/merge_requests/user_filters_by_milestones_spec.rb b/spec/features/merge_requests/user_filters_by_milestones_spec.rb
new file mode 100644
index 00000000000..727a236d980
--- /dev/null
+++ b/spec/features/merge_requests/user_filters_by_milestones_spec.rb
@@ -0,0 +1,62 @@
+require 'rails_helper'
+
+describe 'Merge Requests > User filters by milestones', :js do
+ include FilteredSearchHelpers
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let(:milestone) { create(:milestone, project: project) }
+
+ before do
+ create(:merge_request, :with_diffs, source_project: project)
+ create(:merge_request, :simple, source_project: project, milestone: milestone)
+
+ sign_in(user)
+ visit project_merge_requests_path(project)
+ end
+
+ it 'filters by no milestone' do
+ input_filtered_search('milestone:none')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_css('.merge-request', count: 1)
+ end
+
+ it 'filters by a specific milestone' do
+ input_filtered_search("milestone:%'#{milestone.title}'")
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_css('.merge-request', count: 1)
+ end
+
+ describe 'filters by upcoming milestone' do
+ it 'does not show merge requests with no expiry' do
+ input_filtered_search('milestone:upcoming')
+
+ expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0)
+ expect(page).to have_css('.merge-request', count: 0)
+ end
+
+ context 'with an upcoming milestone' do
+ let(:milestone) { create(:milestone, project: project, due_date: Date.tomorrow) }
+
+ it 'shows merge requests' do
+ input_filtered_search('milestone:upcoming')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_css('.merge-request', count: 1)
+ end
+ end
+
+ context 'with a due milestone' do
+ let(:milestone) { create(:milestone, project: project, due_date: Date.yesterday) }
+
+ it 'does not show any merge requests' do
+ input_filtered_search('milestone:upcoming')
+
+ expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0)
+ expect(page).to have_css('.merge-request', count: 0)
+ end
+ end
+ end
+end
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
new file mode 100644
index 00000000000..1615899a047
--- /dev/null
+++ b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+describe 'Merge requests > User filters by multiple criteria', :js do
+ include FilteredSearchHelpers
+
+ let!(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+ let!(:milestone) { create(:milestone, title: 'v1.1', project: project) }
+ let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
+
+ before do
+ sign_in(user)
+ mr = create(:merge_request, title: 'Bugfix2', author: user, assignee: user, source_project: project, target_project: project, milestone: milestone)
+ mr.labels << wontfix
+
+ visit project_merge_requests_path(project)
+ end
+
+ describe 'filtering by label:~"Won\'t fix" and assignee:~bug' do
+ it 'applies the filters' do
+ input_filtered_search("label:~\"Won't fix\" assignee:@#{user.username}")
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_content 'Bugfix2'
+ expect_filtered_search_input_empty
+ end
+ end
+
+ describe 'filtering by text, author, assignee, milestone, and label' do
+ it 'filters by text, author, assignee, milestone, and label' do
+ input_filtered_search_keys("author:@#{user.username} assignee:@#{user.username} milestone:%\"v1.1\" label:~\"Won't fix\" Bug")
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_content 'Bugfix2'
+ expect_filtered_search_input('Bug')
+ end
+ end
+end
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 416a0f78a45..ef7ae490b0f 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -1,6 +1,6 @@
-require 'spec_helper'
+require 'rails_helper'
-describe 'Projects > Merge requests > User lists merge requests' do
+describe 'Merge requests > User lists merge requests' do
include MergeRequestHelpers
include SortingHelper
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb
index a96404b86ed..199ba7e87ad 100644
--- a/spec/features/merge_requests/update_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_mass_updates_spec.rb
@@ -1,8 +1,8 @@
require 'rails_helper'
-feature 'Multiple merge requests updating from merge_requests#index' do
- let!(:user) { create(:user)}
- let!(:project) { create(:project, :repository) }
+describe 'Merge requests > User mass updates', :js do
+ let(:project) { create(:project, :repository) }
+ let(:user) { project.creator }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
before do
@@ -10,7 +10,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
sign_in(user)
end
- context 'status', :js do
+ context 'status' do
describe 'close merge request' do
before do
visit project_merge_requests_path(project)
@@ -37,13 +37,13 @@ feature 'Multiple merge requests updating from merge_requests#index' do
end
end
- context 'assignee', :js do
+ context 'assignee' do
describe 'set assignee' do
before do
visit project_merge_requests_path(project)
end
- it "updates merge request with assignee" do
+ it 'updates merge request with assignee' do
change_assignee(user.name)
page.within('.merge-request .controls') do
@@ -59,7 +59,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
visit project_merge_requests_path(project)
end
- it "removes assignee from the merge request" do
+ it 'removes assignee from the merge request' do
change_assignee('Unassigned')
expect(find('.merge-request .controls')).not_to have_css('.author_link')
@@ -67,7 +67,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
end
end
- context 'milestone', :js do
+ context 'milestone' do
let(:milestone) { create(:milestone, project: project) }
describe 'set milestone' do
@@ -75,7 +75,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
visit project_merge_requests_path(project)
end
- it "updates merge request with milestone" do
+ it 'updates merge request with milestone' do
change_milestone(milestone.title)
expect(find('.merge-request')).to have_content milestone.title
@@ -89,7 +89,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
visit project_merge_requests_path(project)
end
- it "removes milestone from the merge request" do
+ it 'removes milestone from the merge request' do
change_milestone("No Milestone")
expect(find('.merge-request')).not_to have_content milestone.title
diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb
deleted file mode 100644
index ec2da72ddff..00000000000
--- a/spec/features/merge_requests/widget_deployments_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'spec_helper'
-
-feature 'Widget Deployments Header', :js do
- describe 'when deployed to an environment' do
- given(:user) { create(:user) }
- given(:project) { merge_request.target_project }
- given(:merge_request) { create(:merge_request, :merged) }
- given(:environment) { create(:environment, project: project) }
- given(:role) { :developer }
- given(:sha) { project.commit('master').id }
- given!(:deployment) { create(:deployment, environment: environment, sha: sha) }
- given!(:manual) { }
-
- background do
- sign_in(user)
- project.add_role(user, role)
- visit project_merge_request_path(project, merge_request)
- end
-
- scenario 'displays that the environment is deployed' do
- wait_for_requests
-
- expect(page).to have_content("Deployed to #{environment.name}")
- expect(find('.js-deploy-time')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
- end
-
- context 'with stop action' do
- given(:pipeline) { create(:ci_pipeline, project: project) }
- given(:build) { create(:ci_build, pipeline: pipeline) }
- given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
- given(:deployment) do
- create(:deployment, environment: environment, ref: merge_request.target_branch,
- sha: sha, deployable: build, on_stop: 'close_app')
- end
-
- background do
- wait_for_requests
- end
-
- scenario 'does show stop button' do
- expect(page).to have_button('Stop environment')
- end
-
- scenario 'does start build when stop button clicked' do
- accept_confirm { click_button('Stop environment') }
-
- expect(page).to have_content('close_app')
- end
-
- context 'for reporter' do
- given(:role) { :reporter }
-
- scenario 'does not show stop button' do
- expect(page).not_to have_button('Stop environment')
- end
- end
- end
- end
-end
diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb
index 49d8e52f861..a5e325ee2e3 100644
--- a/spec/features/oauth_login_spec.rb
+++ b/spec/features/oauth_login_spec.rb
@@ -10,8 +10,7 @@ feature 'OAuth Login', :js, :allow_forgery_protection do
def stub_omniauth_config(provider)
OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345"))
- set_devise_mapping(context: Rails.application)
- Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
+ stub_omniauth_provider(provider)
end
providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2,
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 266af8f4e3d..90d6841af0e 100644
--- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -32,18 +32,6 @@ describe 'User visits the profile preferences page' do
end
end
- describe 'User changes their multi file editor preferences', :js do
- it 'set the new_repo cookie when the option is ON' do
- choose 'user_multi_file_on'
- expect(get_cookie('new_repo')).not_to be_nil
- end
-
- it 'deletes the new_repo cookie when the option is OFF' do
- choose 'user_multi_file_off'
- expect(get_cookie('new_repo')).to be_nil
- end
- end
-
describe 'User changes their default dashboard', :js do
it 'creates a flash message' do
select 'Starred Projects', from: 'user_dashboard'
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index 69e4c9f04a1..89d3bd24b89 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -17,12 +17,15 @@ feature 'Editing file blob', :js do
sign_in(user)
end
- def edit_and_commit
+ def edit_and_commit(commit_changes: true)
wait_for_requests
find('.js-edit-blob').click
find('#editor')
execute_script('ace.edit("editor").setValue("class NextFeature\nend\n")')
- click_button 'Commit changes'
+
+ if commit_changes
+ click_button 'Commit changes'
+ end
end
context 'from MR diff' do
@@ -39,13 +42,26 @@ feature 'Editing file blob', :js do
context 'from blob file path' do
before do
visit project_blob_path(project, tree_join(branch, file_path))
- edit_and_commit
end
it 'updates content' do
+ edit_and_commit
+
expect(page).to have_content 'successfully committed'
expect(page).to have_content 'NextFeature'
end
+
+ it 'previews content' do
+ edit_and_commit(commit_changes: false)
+ click_link 'Preview changes'
+ wait_for_requests
+
+ old_line_count = page.all('.line_holder.old').size
+ new_line_count = page.all('.line_holder.new').size
+
+ expect(old_line_count).to be > 0
+ expect(new_line_count).to be > 0
+ end
end
end
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 523cc08496b..94bde723e2f 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -13,6 +13,8 @@ feature 'Gcp Cluster', :js do
end
context 'when user has signed with Google' do
+ let(:project_id) { 'test-project-1234' }
+
before do
allow_any_instance_of(Projects::Clusters::GcpController)
.to receive(:token_in_session).and_return('token')
@@ -23,7 +25,7 @@ feature 'Gcp Cluster', :js do
context 'when user has a GCP project with billing enabled' do
before do
allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing)
- stub_google_project_billing_status
+ allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return('true')
end
context 'when user does not have a cluster and visits cluster index page' do
@@ -93,7 +95,7 @@ feature 'Gcp Cluster', :js do
context 'when user disables the cluster' do
before do
- page.find(:css, '.js-toggle-cluster').click
+ page.find(:css, '.js-cluster-enable-toggle-area .js-project-feature-toggle').click
page.within('#cluster-integration') { click_button 'Save changes' }
end
@@ -131,15 +133,41 @@ feature 'Gcp Cluster', :js do
context 'when user does not have a GCP project with billing enabled' do
before do
+ allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing)
+ allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return('false')
+
visit project_clusters_path(project)
click_link 'Add cluster'
click_link 'Create on GKE'
+
+ fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
+ fill_in 'cluster_name', with: 'dev-cluster'
+ click_button 'Create cluster'
+ end
+
+ it 'user sees form with error' do
+ expect(page).to have_content('Please enable billing for one of your projects to be able to create a cluster, then try again.')
+ end
+ end
+
+ context 'when gcp billing status is not in redis' do
+ before do
+ allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing)
+ allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return(nil)
+
+ visit project_clusters_path(project)
+
+ click_link 'Add cluster'
+ click_link 'Create on GKE'
+
+ fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
+ fill_in 'cluster_name', with: 'dev-cluster'
+ click_button 'Create cluster'
end
- it 'user sees a check page' do
- pending 'the frontend still has not been implemented'
- expect(page).to have_link('Continue')
+ it 'user sees form with error' do
+ expect(page).to have_content('We could not verify that one of your projects on GCP has billing enabled. Please try again.')
end
end
end
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index a519b9f9c7e..b9ab434c259 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -62,7 +62,7 @@ feature 'User Cluster', :js do
context 'when user disables the cluster' do
before do
- page.find(:css, '.js-toggle-cluster').click
+ page.find(:css, '.js-cluster-enable-toggle-area .js-project-feature-toggle').click
fill_in 'cluster_name', with: 'dev-cluster'
page.within('#cluster-integration') { click_button 'Save changes' }
end
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index eae2910a8f6..497a50bebe4 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -37,13 +37,13 @@ feature 'Clusters', :js do
context 'inline update of cluster' do
it 'user can update cluster' do
- expect(page).to have_selector('.js-toggle-cluster-list')
+ expect(page).to have_selector('.js-project-feature-toggle')
end
context 'with sucessfull request' do
it 'user sees updated cluster' do
expect do
- page.find('.js-toggle-cluster-list').click
+ page.find('.js-project-feature-toggle').click
wait_for_requests
end.to change { cluster.reload.enabled }
@@ -57,7 +57,7 @@ feature 'Clusters', :js do
expect_any_instance_of(Clusters::UpdateService).to receive(:execute).and_call_original
allow_any_instance_of(Clusters::Cluster).to receive(:valid?) { false }
- page.find('.js-toggle-cluster-list').click
+ page.find('.js-project-feature-toggle').click
expect(page).to have_content('Something went wrong on our end.')
expect(page).to have_selector('.is-checked')
diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb
index 41f3c15a94c..b650c1f4197 100644
--- a/spec/features/projects/commits/user_browses_commits_spec.rb
+++ b/spec/features/projects/commits/user_browses_commits_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'User broweses commits' do
+describe 'User browses commits' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
@@ -31,6 +31,19 @@ describe 'User broweses commits' do
check_author_link(RepoHelpers.sample_commit.author_email, user)
end
end
+
+ context 'when the blob does not exist' do
+ let(:commit) { create(:commit, project: project) }
+
+ it 'shows a blank label' do
+ allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(nil)
+ allow_any_instance_of(Gitlab::Diff::File).to receive(:raw_binary?).and_return(true)
+
+ visit(project_commit_path(project, commit))
+
+ expect(find('.diff-file-changes', visible: false)).to have_content('No file name available')
+ end
+ end
end
private
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index af125e1b9d3..e8bb9c6a86c 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -32,7 +32,7 @@ feature 'Import/Export - project import integration test', :js do
expect(page).to have_content('Import an exported GitLab project')
expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=#{project_path}")
- expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}_test-project-path\h*\z/).and_call_original
+ expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}\z/).and_call_original
attach_file('file', file)
click_on 'Import project'
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 6bbbafaabfd..5d311f2dde3 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -380,9 +380,18 @@ feature 'Jobs' do
it 'shows manual action empty state' do
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_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')
end
+
+ it 'plays manual action and shows pending status', :js do
+ click_link 'Trigger this manual action'
+
+ wait_for_requests
+ expect(page).to have_content('This job has not started yet')
+ expect(page).to have_content('This job is in pending state and is waiting to be picked by a runner')
+ expect(page).to have_content('pending')
+ end
end
context 'Non triggered job' do
@@ -392,9 +401,22 @@ feature 'Jobs' do
visit project_job_path(project, job)
end
- it 'shows manual action empty state' do
+ it 'shows empty state' do
expect(page).to have_content('This job has not been triggered yet')
- expect(page).to have_content('This job depends on upstream jobs that need to succeed in order for this job to be triggered.')
+ expect(page).to have_content('This job depends on upstream jobs that need to succeed in order for this job to be triggered')
+ end
+ end
+
+ context 'Pending job' do
+ let(:job) { create(:ci_build, :pending, pipeline: pipeline) }
+
+ before do
+ visit project_job_path(project, job)
+ end
+
+ it 'shows pending empty state' do
+ expect(page).to have_content('This job has not started yet')
+ expect(page).to have_content('This job is in pending state and is waiting to be picked by a runner')
end
end
end
diff --git a/spec/features/projects/members/share_with_group_spec.rb b/spec/features/projects/members/share_with_group_spec.rb
index 3198798306c..4cf48098401 100644
--- a/spec/features/projects/members/share_with_group_spec.rb
+++ b/spec/features/projects/members/share_with_group_spec.rb
@@ -122,7 +122,7 @@ feature 'Project > Members > Share with Group', :js do
select2 group.id, from: '#link_group_id'
fill_in 'expires_at_groups', with: (Time.now + 4.5.days).strftime('%Y-%m-%d')
- page.find('body').click
+ click_on 'share-with-group-tab'
find('.btn-create').click
end
diff --git a/spec/features/projects/merge_requests/list_spec.rb b/spec/features/projects/merge_requests/list_spec.rb
deleted file mode 100644
index b34b13db381..00000000000
--- a/spec/features/projects/merge_requests/list_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require 'spec_helper'
-
-feature 'Merge Requests List' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
-
- background do
- project.add_developer(user)
-
- sign_in(user)
- end
-
- scenario 'user does not see create new list button' do
- create(:merge_request, source_project: project)
-
- visit project_merge_requests_path(project)
-
- expect(page).not_to have_selector('.js-new-board-list')
- end
-
- it 'should show an empty state' do
- visit project_merge_requests_path(project)
-
- expect(page).to have_selector('.empty-state')
- end
-
- it 'empty state should have a create merge request button' do
- visit project_merge_requests_path(project)
-
- expect(page).to have_link 'New merge request', href: project_new_merge_request_path(project)
- end
-
- context 'if there are merge requests' do
- before do
- create(:merge_request, assignee: user, source_project: project)
-
- visit project_merge_requests_path(project)
- end
-
- it 'should not show an empty state' do
- expect(page).not_to have_selector('.empty-state')
- end
- end
-end
diff --git a/spec/features/projects/merge_requests/user_manages_subscription_spec.rb b/spec/features/projects/merge_requests/user_manages_subscription_spec.rb
index 4ca435491cb..f55eb5c6664 100644
--- a/spec/features/projects/merge_requests/user_manages_subscription_spec.rb
+++ b/spec/features/projects/merge_requests/user_manages_subscription_spec.rb
@@ -13,20 +13,18 @@ describe 'User manages subscription', :js do
end
it 'toggles subscription' do
- subscribe_button = find('.js-issuable-subscribe-button')
+ page.within('.js-issuable-subscribe-button') do
+ expect(page).to have_css 'button:not(.is-checked)'
+ find('button:not(.is-checked)').click
- expect(subscribe_button).to have_content('Subscribe')
+ wait_for_requests
- click_on('Subscribe')
+ expect(page).to have_css 'button.is-checked'
+ find('button.is-checked').click
- wait_for_requests
+ wait_for_requests
- expect(subscribe_button).to have_content('Unsubscribe')
-
- click_on('Unsubscribe')
-
- wait_for_requests
-
- expect(subscribe_button).to have_content('Subscribe')
+ expect(page).to have_css 'button:not(.is-checked)'
+ end
end
end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 81b282502fc..14670e91006 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -43,7 +43,7 @@ feature 'Repository settings' do
fill_in 'deploy_key_title', with: 'new_deploy_key'
fill_in 'deploy_key_key', with: new_ssh_key
- check 'deploy_key_can_push'
+ check 'deploy_key_deploy_keys_projects_attributes_0_can_push'
click_button 'Add key'
expect(page).to have_content('new_deploy_key')
@@ -57,7 +57,7 @@ feature 'Repository settings' do
find('li', text: private_deploy_key.title).click_link('Edit')
fill_in 'deploy_key_title', with: 'updated_deploy_key'
- check 'deploy_key_can_push'
+ check 'deploy_key_deploy_keys_projects_attributes_0_can_push'
click_button 'Save changes'
expect(page).to have_content('updated_deploy_key')
@@ -74,11 +74,9 @@ feature 'Repository settings' do
find('li', text: private_deploy_key.title).click_link('Edit')
fill_in 'deploy_key_title', with: 'updated_deploy_key'
- check 'deploy_key_can_push'
click_button 'Save changes'
expect(page).to have_content('updated_deploy_key')
- expect(page).to have_content('Write access allowed')
end
scenario 'remove an existing deploy key' do
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
index 3f6d16c8acf..0c67196f53e 100644
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ b/spec/features/projects/tree/create_directory_spec.rb
@@ -14,7 +14,7 @@ feature 'Multi-file editor new directory', :js do
wait_for_requests
- click_link('Multi Edit')
+ click_link('Web IDE')
wait_for_requests
end
diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb
index ba71eef07f4..85f7318c05d 100644
--- a/spec/features/projects/tree/create_file_spec.rb
+++ b/spec/features/projects/tree/create_file_spec.rb
@@ -14,7 +14,7 @@ feature 'Multi-file editor new file', :js do
wait_for_requests
- click_link('Multi Edit')
+ click_link('Web IDE')
wait_for_requests
end
diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb
index 9fbb1dbd0e8..f81e8677e92 100644
--- a/spec/features/projects/tree/upload_file_spec.rb
+++ b/spec/features/projects/tree/upload_file_spec.rb
@@ -16,7 +16,7 @@ feature 'Multi-file editor upload file', :js do
wait_for_requests
- click_link('Multi Edit')
+ click_link('Web IDE')
wait_for_requests
end
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
index 670e8dda916..975c157bcf5 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
describe 'User can display performance bar', :js do
- shared_examples 'performance bar is disabled' do
+ shared_examples 'performance bar cannot be displayed' do
it 'does not show the performance bar by default' do
expect(page).not_to have_css('#peek')
end
@@ -17,7 +17,7 @@ describe 'User can display performance bar', :js do
end
end
- shared_examples 'performance bar is enabled' do
+ shared_examples 'performance bar can be displayed' do
it 'does not show the performance bar by default' do
expect(page).not_to have_css('#peek')
end
@@ -33,6 +33,18 @@ describe 'User can display performance bar', :js do
end
end
+ shared_examples 'performance bar is enabled by default in development' do
+ before do
+ allow(Rails.env).to receive(:development?).and_return(true)
+ end
+
+ it 'shows the performance bar by default' do
+ refresh # Because we're stubbing Rails.env after the 1st visit to root_path
+
+ expect(page).to have_css('#peek')
+ end
+ end
+
let(:group) { create(:group) }
context 'when user is logged-out' do
@@ -45,7 +57,7 @@ describe 'User can display performance bar', :js do
stub_application_setting(performance_bar_allowed_group_id: nil)
end
- it_behaves_like 'performance bar is disabled'
+ it_behaves_like 'performance bar cannot be displayed'
end
context 'when the performance_bar feature is enabled' do
@@ -53,7 +65,7 @@ describe 'User can display performance bar', :js do
stub_application_setting(performance_bar_allowed_group_id: group.id)
end
- it_behaves_like 'performance bar is disabled'
+ it_behaves_like 'performance bar cannot be displayed'
end
end
@@ -72,7 +84,8 @@ describe 'User can display performance bar', :js do
stub_application_setting(performance_bar_allowed_group_id: nil)
end
- it_behaves_like 'performance bar is disabled'
+ it_behaves_like 'performance bar cannot be displayed'
+ it_behaves_like 'performance bar is enabled by default in development'
end
context 'when the performance_bar feature is enabled' do
@@ -80,7 +93,8 @@ describe 'User can display performance bar', :js do
stub_application_setting(performance_bar_allowed_group_id: group.id)
end
- it_behaves_like 'performance bar is enabled'
+ it_behaves_like 'performance bar is enabled by default in development'
+ it_behaves_like 'performance bar can be displayed'
end
end
end
diff --git a/spec/features/user_page_spec.rb b/spec/features/user_page_spec.rb
new file mode 100644
index 00000000000..19c587e53c8
--- /dev/null
+++ b/spec/features/user_page_spec.rb
@@ -0,0 +1,107 @@
+require 'spec_helper'
+
+describe 'User page', :js do
+ let!(:user) { create :user }
+ let!(:private_project) do
+ create :project, :private, name: 'private', namespace: user.namespace do |project|
+ project.add_master(user)
+ end
+ end
+
+ let!(:internal_project) do
+ create :project, :internal, name: 'internal', namespace: user.namespace do |project|
+ project.add_master(user)
+ end
+ end
+
+ let!(:public_project) do
+ create :project, :public, name: 'public', namespace: user.namespace do |project|
+ project.add_master(user)
+ end
+ end
+
+ def click_nav_link(name)
+ page.within '.nav-links' do
+ click_link name
+ end
+ end
+
+ context 'when not signed in' do
+ it 'renders user public project' do
+ visit user_path(user)
+ click_nav_link('Personal projects')
+
+ expect(page).to have_css('.tab-content #projects.active')
+ expect(title).to start_with(user.name)
+
+ expect(page).to have_content(public_project.name)
+ expect(page).not_to have_content(private_project.name)
+ expect(page).not_to have_content(internal_project.name)
+ end
+ end
+
+ context 'when signed in as another user' do
+ let(:another_user) { create :user }
+
+ before do
+ sign_in(another_user)
+ end
+
+ it 'renders user public and internal projects' do
+ visit user_path(user)
+ click_nav_link('Personal projects')
+
+ expect(title).to start_with(user.name)
+
+ expect(page).not_to have_content(private_project.name)
+ expect(page).to have_content(public_project.name)
+ expect(page).to have_content(internal_project.name)
+ end
+ end
+
+ context 'when signed in as user' do
+ before do
+ sign_in(user)
+ end
+
+ describe 'personal projects' do
+ it 'renders all user projects' do
+ visit user_path(user)
+ click_nav_link('Personal projects')
+
+ expect(title).to start_with(user.name)
+
+ expect(page).to have_content(private_project.name)
+ expect(page).to have_content(public_project.name)
+ expect(page).to have_content(internal_project.name)
+ end
+ end
+
+ describe 'contributed projects' do
+ context 'when user has contributions' do
+ let(:contributed_project) do
+ create :project, :public, :empty_repo
+ end
+
+ before do
+ Issues::CreateService.new(contributed_project, user, { title: 'Bug in old browser' }).execute
+ event = create(:push_event, project: contributed_project, author: user)
+ create(:push_event_payload, event: event, commit_count: 3)
+ end
+
+ it 'renders contributed project' do
+ visit user_path(user)
+
+ expect(title).to start_with(user.name)
+ expect(page).to have_css('.js-contrib-calendar')
+
+ click_nav_link('Contributed projects')
+
+ page.within '#contributed' do
+ expect(page).to have_content(contributed_project.name)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb
index ae050f36b4a..375bcc9087e 100644
--- a/spec/finders/group_descendants_finder_spec.rb
+++ b/spec/finders/group_descendants_finder_spec.rb
@@ -35,6 +35,15 @@ describe GroupDescendantsFinder do
expect(finder.execute).to contain_exactly(project)
end
+ it 'does not include projects shared with the group' do
+ project = create(:project, namespace: group)
+ other_project = create(:project)
+ other_project.project_group_links.create(group: group,
+ group_access: ProjectGroupLink::MASTER)
+
+ expect(finder.execute).to contain_exactly(project)
+ end
+
context 'when archived is `true`' do
let(:params) { { archived: 'true' } }
@@ -189,6 +198,17 @@ describe GroupDescendantsFinder do
expect(finder.execute).to contain_exactly(subgroup, matching_project)
end
+ context 'with a small page size' do
+ let(:params) { { filter: 'test', per_page: 1 } }
+
+ it 'contains all the ancestors of a matching subgroup regardless the page size' do
+ subgroup = create(:group, :private, parent: group)
+ matching = create(:group, :private, name: 'testgroup', parent: subgroup)
+
+ expect(finder.execute).to contain_exactly(subgroup, matching)
+ end
+ end
+
it 'does not include the parent itself' do
group.update!(name: 'test')
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index 27a09d7c6f5..be80ee7d767 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe GroupProjectsFinder do
let(:group) { create(:group) }
+ let(:subgroup) { create(:group, parent: group) }
let(:current_user) { create(:user) }
let(:options) { {} }
@@ -12,6 +13,8 @@ describe GroupProjectsFinder do
let!(:shared_project_1) { create(:project, :public, path: '3') }
let!(:shared_project_2) { create(:project, :private, path: '4') }
let!(:shared_project_3) { create(:project, :internal, path: '5') }
+ let!(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) }
+ let!(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) }
before do
shared_project_1.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group)
@@ -35,11 +38,31 @@ describe GroupProjectsFinder do
context "only owned" do
let(:options) { { only_owned: true } }
- it { is_expected.to match_array([private_project, public_project]) }
+ context 'with subgroups projects', :nested_groups do
+ before do
+ options[:include_subgroups] = true
+ end
+
+ it { is_expected.to match_array([private_project, public_project, subgroup_project, subgroup_private_project]) }
+ end
+
+ context 'without subgroups projects' do
+ it { is_expected.to match_array([private_project, public_project]) }
+ end
end
context "all" do
- it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
+ context 'with subgroups projects', :nested_groups do
+ before do
+ options[:include_subgroups] = true
+ end
+
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project, subgroup_project, subgroup_private_project]) }
+ end
+
+ context 'without subgroups projects' do
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
+ end
end
end
@@ -71,9 +94,20 @@ describe GroupProjectsFinder do
context "without external user" do
before do
private_project.add_master(current_user)
+ subgroup_private_project.add_master(current_user)
end
- it { is_expected.to match_array([private_project, public_project]) }
+ context 'with subgroups projects', :nested_groups do
+ before do
+ options[:include_subgroups] = true
+ end
+
+ it { is_expected.to match_array([private_project, public_project, subgroup_project, subgroup_private_project]) }
+ end
+
+ context 'without subgroups projects' do
+ it { is_expected.to match_array([private_project, public_project]) }
+ end
end
context "with external user" do
@@ -81,12 +115,32 @@ describe GroupProjectsFinder do
current_user.update_attributes(external: true)
end
- it { is_expected.to eq([public_project]) }
+ context 'with subgroups projects', :nested_groups do
+ before do
+ options[:include_subgroups] = true
+ end
+
+ it { is_expected.to match_array([public_project, subgroup_project]) }
+ end
+
+ context 'without subgroups projects' do
+ it { is_expected.to eq([public_project]) }
+ end
end
end
context "all" do
- it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project]) }
+ context 'with subgroups projects', :nested_groups do
+ before do
+ options[:include_subgroups] = true
+ end
+
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project, subgroup_project]) }
+ end
+
+ context 'without subgroups projects' do
+ it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project]) }
+ end
end
end
@@ -100,7 +154,17 @@ describe GroupProjectsFinder do
context "only owned" do
let(:options) { { only_owned: true } }
- it { is_expected.to eq([public_project]) }
+ context 'with subgroups projects', :nested_groups do
+ before do
+ options[:include_subgroups] = true
+ end
+
+ it { is_expected.to match_array([public_project, subgroup_project]) }
+ end
+
+ context 'without subgroups projects' do
+ it { is_expected.to eq([public_project]) }
+ end
end
end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 47fd98234f9..abb7631d7d7 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -3,13 +3,17 @@ require 'spec_helper'
describe IssuesFinder do
set(:user) { create(:user) }
set(:user2) { create(:user) }
- set(:project1) { create(:project) }
+ set(:group) { create(:group) }
+ set(:subgroup) { create(:group, parent: group) }
+ set(:project1) { create(:project, group: group) }
set(:project2) { create(:project) }
+ set(:project3) { create(:project, group: subgroup) }
set(:milestone) { create(:milestone, project: project1) }
set(:label) { create(:label, project: project2) }
set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) }
set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab') }
set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 1.week.from_now) }
+ set(:issue4) { create(:issue, project: project3) }
set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) }
set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) }
set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) }
@@ -25,10 +29,12 @@ describe IssuesFinder do
project1.add_master(user)
project2.add_developer(user)
project2.add_developer(user2)
+ project3.add_developer(user)
issue1
issue2
issue3
+ issue4
award_emoji1
award_emoji2
@@ -39,7 +45,7 @@ describe IssuesFinder do
let(:scope) { 'all' }
it 'returns all issues' do
- expect(issues).to contain_exactly(issue1, issue2, issue3)
+ expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
end
context 'filtering by assignee ID' do
@@ -50,6 +56,26 @@ describe IssuesFinder do
end
end
+ context 'filtering by group_id' do
+ let(:params) { { group_id: group.id } }
+
+ context 'when include_subgroup param not set' do
+ it 'returns all group issues' do
+ expect(issues).to contain_exactly(issue1)
+ end
+ end
+
+ context 'when include_subgroup param is true', :nested_groups do
+ before do
+ params[:include_subgroups] = true
+ end
+
+ it 'returns all group and subgroup issues' do
+ expect(issues).to contain_exactly(issue1, issue4)
+ end
+ end
+ end
+
context 'filtering by author ID' do
let(:params) { { author_id: user2.id } }
@@ -87,7 +113,7 @@ describe IssuesFinder do
let(:params) { { milestone_title: Milestone::None.title } }
it 'returns issues with no milestone' do
- expect(issues).to contain_exactly(issue2, issue3)
+ expect(issues).to contain_exactly(issue2, issue3, issue4)
end
end
@@ -185,7 +211,7 @@ describe IssuesFinder do
let(:params) { { label_name: Label::None.title } }
it 'returns issues with no labels' do
- expect(issues).to contain_exactly(issue1, issue3)
+ expect(issues).to contain_exactly(issue1, issue3, issue4)
end
end
@@ -210,7 +236,7 @@ describe IssuesFinder do
let(:params) { { state: 'opened' } }
it 'returns only opened issues' do
- expect(issues).to contain_exactly(issue1, issue2, issue3)
+ expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
end
end
@@ -226,7 +252,7 @@ describe IssuesFinder do
let(:params) { { state: 'all' } }
it 'returns all issues' do
- expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue)
+ expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4)
end
end
@@ -234,7 +260,7 @@ describe IssuesFinder do
let(:params) { { state: 'invalid_state' } }
it 'returns all issues' do
- expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue)
+ expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4)
end
end
end
@@ -338,7 +364,7 @@ describe IssuesFinder do
end
it "doesn't return issues if feature disabled" do
- [project1, project2].each do |project|
+ [project1, project2, project3].each do |project|
project.project_feature.update!(issues_access_level: ProjectFeature::DISABLED)
end
@@ -351,7 +377,7 @@ describe IssuesFinder do
it 'returns the number of rows for the default state' do
finder = described_class.new(user)
- expect(finder.row_count).to eq(3)
+ expect(finder.row_count).to eq(4)
end
it 'returns the number of rows for a given state' do
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 687ffaec7cc..9385c892c9e 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -6,31 +6,36 @@ describe MergeRequestsFinder do
let(:user) { create :user }
let(:user2) { create :user }
- let(:project1) { create(:project, :public) }
+ let(:group) { create(:group) }
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project1) { create(:project, :public, group: group) }
let(:project2) { fork_project(project1, user) }
let(:project3) do
p = fork_project(project1, user)
p.update!(archived: true)
p
end
+ let(:project4) { create(:project, :public, group: subgroup) }
let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') }
let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2) }
let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3) }
+ let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4) }
before do
project1.add_master(user)
project2.add_developer(user)
project3.add_developer(user)
project2.add_developer(user2)
+ project4.add_developer(user)
end
describe "#execute" do
it 'filters by scope' do
params = { scope: 'authored', state: 'opened' }
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(3)
+ expect(merge_requests.size).to eq(4)
end
it 'filters by project' do
@@ -39,10 +44,26 @@ describe MergeRequestsFinder do
expect(merge_requests.size).to eq(1)
end
+ it 'filters by group' do
+ params = { group_id: group.id }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests.size).to eq(2)
+ end
+
+ it 'filters by group including subgroups', :nested_groups do
+ params = { group_id: group.id, include_subgroups: true }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests.size).to eq(3)
+ end
+
it 'filters by non_archived' do
params = { non_archived: true }
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(3)
+ expect(merge_requests.size).to eq(4)
end
it 'filters by iid' do
@@ -73,14 +94,14 @@ describe MergeRequestsFinder do
end
context 'with created_after and created_before params' do
- let(:project4) { create(:project, forked_from_project: project1) }
+ let(:new_project) { create(:project, forked_from_project: project1) }
let!(:new_merge_request) do
create(:merge_request,
:simple,
author: user,
created_at: 1.week.from_now,
- source_project: project4,
+ source_project: new_project,
target_project: project1)
end
@@ -89,12 +110,12 @@ describe MergeRequestsFinder do
:simple,
author: user,
created_at: 1.week.ago,
- source_project: project4,
- target_project: project4)
+ source_project: new_project,
+ target_project: new_project)
end
before do
- project4.add_master(user)
+ new_project.add_master(user)
end
it 'filters by created_after' do
@@ -106,7 +127,7 @@ describe MergeRequestsFinder do
end
it 'filters by created_before' do
- params = { project_id: project4.id, created_before: old_merge_request.created_at + 1.second }
+ params = { project_id: new_project.id, created_before: old_merge_request.created_at + 1.second }
merge_requests = described_class.new(user, params).execute
@@ -119,7 +140,7 @@ describe MergeRequestsFinder do
it 'returns the number of rows for the default state' do
finder = described_class.new(user)
- expect(finder.row_count).to eq(3)
+ expect(finder.row_count).to eq(4)
end
it 'returns the number of rows for a given state' do
diff --git a/spec/finders/milestones_finder_spec.rb b/spec/finders/milestones_finder_spec.rb
index 8ae08656e01..0b3cf7ece5f 100644
--- a/spec/finders/milestones_finder_spec.rb
+++ b/spec/finders/milestones_finder_spec.rb
@@ -21,10 +21,19 @@ describe MilestonesFinder do
expect(result).to contain_exactly(milestone_1, milestone_2)
end
- it 'returns milestones for groups and projects' do
- result = described_class.new(project_ids: [project_1.id, project_2.id], group_ids: group.id, state: 'all').execute
+ context 'milestones for groups and project' do
+ let(:result) do
+ described_class.new(project_ids: [project_1.id, project_2.id], group_ids: group.id, state: 'all').execute
+ end
+
+ it 'returns milestones for groups and projects' do
+ expect(result).to contain_exactly(milestone_1, milestone_2, milestone_3, milestone_4)
+ end
- expect(result).to contain_exactly(milestone_1, milestone_2, milestone_3, milestone_4)
+ it 'orders milestones by due date' do
+ expect(result.first).to eq(milestone_1)
+ expect(result.second).to eq(milestone_3)
+ end
end
context 'with filters' do
@@ -61,30 +70,4 @@ describe MilestonesFinder do
expect(result.to_a).to contain_exactly(milestone_1)
end
end
-
- context 'with order' do
- let(:params) do
- {
- project_ids: [project_1.id, project_2.id],
- group_ids: group.id,
- state: 'all'
- }
- end
-
- it "default orders by due date" do
- result = described_class.new(params).execute
-
- expect(result.first).to eq(milestone_1)
- expect(result.second).to eq(milestone_3)
- end
-
- it "orders by parameter" do
- result = described_class.new(params.merge(order: 'id DESC')).execute
-
- expect(result.first).to eq(milestone_4)
- expect(result.second).to eq(milestone_3)
- expect(result.third).to eq(milestone_2)
- expect(result.fourth).to eq(milestone_1)
- end
- end
end
diff --git a/spec/fixtures/api/schemas/entities/issue.json b/spec/fixtures/api/schemas/entities/issue.json
index 3d3329a3406..38467b4ca20 100644
--- a/spec/fixtures/api/schemas/entities/issue.json
+++ b/spec/fixtures/api/schemas/entities/issue.json
@@ -28,7 +28,6 @@
"confidential": { "type": "boolean" },
"discussion_locked": { "type": ["boolean", "null"] },
"updated_by_id": { "type": ["string", "null"] },
- "deleted_at": { "type": ["string", "null"] },
"time_estimate": { "type": "integer" },
"total_time_spent": { "type": "integer" },
"human_time_estimate": { "type": ["integer", "null"] },
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index 7f662098216..05461787f06 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -13,7 +13,6 @@
"updated_by_id": { "type": ["string", "null"] },
"created_at": { "type": "string" },
"updated_at": { "type": "string" },
- "deleted_at": { "type": ["string", "null"] },
"time_estimate": { "type": "integer" },
"total_time_spent": { "type": "integer" },
"human_time_estimate": { "type": ["integer", "null"] },
diff --git a/spec/fixtures/api/schemas/public_api/v4/pipelines.json b/spec/fixtures/api/schemas/public_api/v4/pipelines.json
new file mode 100644
index 00000000000..8b08a00f708
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/pipelines.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "pipeline/basic.json" }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/user/basic.json b/spec/fixtures/api/schemas/public_api/v4/user/basic.json
index bf330d8278c..2d815be32a6 100644
--- a/spec/fixtures/api/schemas/public_api/v4/user/basic.json
+++ b/spec/fixtures/api/schemas/public_api/v4/user/basic.json
@@ -2,12 +2,16 @@
"type": ["object", "null"],
"required": [
"id",
+ "name",
+ "username",
"state",
"avatar_url",
"web_url"
],
"properties": {
"id": { "type": "integer" },
+ "name": { "type": "string" },
+ "username": { "type": "string" },
"state": { "type": "string" },
"avatar_url": { "type": "string" },
"web_url": { "type": "string" }
diff --git a/spec/fixtures/emails/attachment.eml b/spec/fixtures/emails/attachment.eml
index f25c3d1a449..b3a30b3221b 100644
--- a/spec/fixtures/emails/attachment.eml
+++ b/spec/fixtures/emails/attachment.eml
@@ -91,7 +91,7 @@ x #ccc solid;padding-left:1ex"><div>
adding=3D"0" border=3D"0"><tbody>
<tr>
<td style=3D"vertical-align:top;width:55px">
- <img src=3D"http://www.gravatar.com/avatar/42776c4982dff1fa45ee8248=
+ <img src=3D"https://www.gravatar.com/avatar/42776c4982dff1fa45ee8248=
532f8ad0.png?s=3D45&amp;r=3Dpg&amp;d=3Didenticon" title=3D"Neil" style=3D"m=
ax-width:694px" width=3D"45" height=3D"45">
</td>
@@ -121,7 +121,7 @@ nk">@eviltrout</a> Any idea why it showed up in suggested topics? </p>
<div style=3D"color:#666">
<p>To respond, reply to this email or visit <a href=3D"http://meta.disc=
ourse.org/t/spam-post-pops-back-up-in-suggested-topics/11005/5" style=3D"co=
-lor:#666" target=3D"_blank">http://meta.discourse.org/t/spam-post-pops-back=
+lor:#666" target=3D"_blank">https://meta.discourse.org/t/spam-post-pops-back=
-up-in-suggested-topics/11005/5</a> in your browser.</p>
</div>
@@ -132,12 +132,12 @@ lor:#666" target=3D"_blank">http://meta.discourse.org/t/spam-post-pops-back=
lpadding=3D"0" border=3D"0"><tbody>
<tr>
<td style=3D"vertical-align:top;width:55px">
- <img src=3D"http://www.gravatar.com/avatar/42776c4982dff1fa45ee8248=
+ <img src=3D"https://www.gravatar.com/avatar/42776c4982dff1fa45ee8248=
532f8ad0.png?s=3D45&amp;r=3Dpg&amp;d=3Didenticon" title=3D"Neil" style=3D"m=
ax-width:694px" width=3D"45" height=3D"45">
</td>
<td>
- <a href=3D"http://meta.discourse.org/users/neil" style=3D"font-size=
+ <a href=3D"https://meta.discourse.org/users/neil" style=3D"font-size=
:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;c=
olor:#3b5998;text-decoration:none;font-weight:bold" target=3D"_blank">Neil<=
/a><br>
@@ -155,12 +155,12 @@ vember 19</span>
adding=3D"0" border=3D"0"><tbody>
<tr>
<td style=3D"vertical-align:top;width:55px">
- <img src=3D"http://www.gravatar.com/avatar/5120fc4e345db0d1a9648882=
+ <img src=3D"https://www.gravatar.com/avatar/5120fc4e345db0d1a9648882=
72073819.png?s=3D45&amp;r=3Dpg&amp;d=3Didenticon" title=3D"riking" style=3D=
"max-width:694px" width=3D"45" height=3D"45">
</td>
<td>
- <a href=3D"http://meta.discourse.org/users/riking" style=3D"font-si=
+ <a href=3D"https://meta.discourse.org/users/riking" style=3D"font-si=
ze:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif=
;color:#3b5998;text-decoration:none;font-weight:bold" target=3D"_blank">rik=
ing</a><br>
@@ -173,7 +173,7 @@ vember 19</span>
<td style=3D"padding-top:5px" colspan=3D"2">
<p style=3D"margin-top:0"><u></u></p><div>
<div></div>
-<img width=3D"20" height=3D"20" src=3D"http://www.gravatar.com/avatar/51d62=
+<img width=3D"20" height=3D"20" src=3D"https://www.gravatar.com/avatar/51d62=
3f33f8b83095db84ff35e15dbe8.png?s=3D40&amp;r=3Dpg&amp;d=3Didenticon" style=
=3D"max-width:694px">codinghorror:</div>
<blockquote><p style=3D"margin-top:0">I can&#39;t even find that topic by n=
@@ -193,12 +193,12 @@ uld be invisible to me, and not showing up in Suggested Topics.</p>
adding=3D"0" border=3D"0"><tbody>
<tr>
<td style=3D"vertical-align:top;width:55px">
- <img src=3D"http://www.gravatar.com/avatar/51d623f33f8b83095db84ff3=
+ <img src=3D"https://www.gravatar.com/avatar/51d623f33f8b83095db84ff3=
5e15dbe8.png?s=3D45&amp;r=3Dpg&amp;d=3Didenticon" title=3D"codinghorror" st=
yle=3D"max-width:694px" width=3D"45" height=3D"45">
</td>
<td>
- <a href=3D"http://meta.discourse.org/users/codinghorror" style=3D"f=
+ <a href=3D"https://meta.discourse.org/users/codinghorror" style=3D"f=
ont-size:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans=
-serif;color:#3b5998;text-decoration:none;font-weight:bold" target=3D"_blan=
k">codinghorror</a><br>
@@ -219,12 +219,12 @@ rout" target=3D"_blank">@eviltrout</a>? I can&#39;t even find that topic by=
adding=3D"0" border=3D"0"><tbody>
<tr>
<td style=3D"vertical-align:top;width:55px">
- <img src=3D"http://www.gravatar.com/avatar/5120fc4e345db0d1a9648882=
+ <img src=3D"https://www.gravatar.com/avatar/5120fc4e345db0d1a9648882=
72073819.png?s=3D45&amp;r=3Dpg&amp;d=3Didenticon" title=3D"riking" style=3D=
"max-width:694px" width=3D"45" height=3D"45">
</td>
<td>
- <a href=3D"http://meta.discourse.org/users/riking" style=3D"font-si=
+ <a href=3D"https://meta.discourse.org/users/riking" style=3D"font-si=
ze:13px;font-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif=
;color:#3b5998;text-decoration:none;font-weight:bold" target=3D"_blank">rik=
ing</a><br>
@@ -241,7 +241,7 @@ lar spam post, and it was promptly deleted/hidden, but it just popped up in=
<p style=3D"margin-top:0"></p>
<div><a href=3D"//cdn.discourse.org/uploads/meta_discourse/2158/50b8b49557c=
-b249e.png" target=3D"_blank"><img src=3D"http://cdn.discourse.org/uploads/m=
+b249e.png" target=3D"_blank"><img src=3D"https://cdn.discourse.org/uploads/m=
eta_discourse/_optimized/ab1/c92/acd2c33402_584x134.png" width=3D"584" heig=
ht=3D"134" style=3D"max-width:694px"><div>
@@ -257,12 +257,12 @@ ht=3D"134" style=3D"max-width:694px"><div>
<div style=3D"color:#666">
<p>To respond, reply to this email or visit <a href=3D"http://meta.discours=
e.org/t/spam-post-pops-back-up-in-suggested-topics/11005/5" style=3D"color:=
-#666" target=3D"_blank">http://meta.discourse.org/t/spam-post-pops-back-up-=
+#666" target=3D"_blank">https://meta.discourse.org/t/spam-post-pops-back-up-=
in-suggested-topics/11005/5</a> in your browser.</p>
</div>
<div style=3D"color:#666">
-<p>To unsubscribe from these emails, visit your <a href=3D"http://meta.disc=
+<p>To unsubscribe from these emails, visit your <a href=3D"https://meta.disc=
ourse.org/user_preferences" style=3D"color:#666" target=3D"_blank">user pre=
ferences</a>.</p>
</div>
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 5c5d53877a6..f7a4a7afced 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -100,7 +100,7 @@ describe ApplicationHelper do
end
it 'returns a generic avatar' do
- expect(helper.gravatar_icon(user_email)).to match('no_avatar.png')
+ expect(helper.gravatar_icon(user_email)).to match_asset_path('no_avatar.png')
end
end
@@ -110,14 +110,14 @@ describe ApplicationHelper do
end
it 'returns a generic avatar when email is blank' do
- expect(helper.gravatar_icon('')).to match('no_avatar.png')
+ expect(helper.gravatar_icon('')).to match_asset_path('no_avatar.png')
end
it 'returns a valid Gravatar URL' do
stub_config_setting(https: false)
expect(helper.gravatar_icon(user_email))
- .to match('http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118')
+ .to match('https://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118')
end
it 'uses HTTPs when configured' do
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 04620f6d88c..a030796c54e 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -22,6 +22,13 @@ describe BlobHelper do
expect(result).to eq(%[<pre class="code highlight"><code><span id="LC1" class="line" lang="">:type "assem"))</span></code></pre>])
end
+ it 'returns plaintext for long blobs' do
+ stub_const('Blob::MAXIMUM_TEXT_HIGHLIGHT_SIZE', 1)
+ result = helper.highlight(blob_name, blob_content)
+
+ expect(result).to eq(%[<pre class="code highlight"><code><span id="LC1" class="line" lang="">(make-pathname :defaults name</span>\n<span id="LC2" class="line" lang="">:type "assem"))</span></code></pre>])
+ end
+
it 'highlights single block' do
expected = %Q[<pre class="code highlight"><code><span id="LC1" class="line" lang="common_lisp"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>
<span id="LC2" class="line" lang="common_lisp"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span></code></pre>]
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index f9c31ac61d8..15cbe36ae76 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -266,4 +266,14 @@ describe DiffHelper do
end
end
end
+
+ context '#diff_file_path_text' do
+ it 'returns full path by default' do
+ expect(diff_file_path_text(diff_file)).to eq(diff_file.new_path)
+ end
+
+ it 'returns truncated path' do
+ expect(diff_file_path_text(diff_file, max: 10)).to eq("...open.rb")
+ end
+ end
end
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 32432ee1e81..5f608fe18d9 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -105,7 +105,7 @@ describe GroupsHelper do
it 'outputs the groups in the correct order' do
expect(helper.group_title(very_deep_nested_group))
- .to match(/<li style="text-indent: 16px;"><a.*>#{deep_nested_group.name}.*<\/li>.*<a.*>#{very_deep_nested_group.name}<\/a>/m)
+ .to match(%r{<li style="text-indent: 16px;"><a.*>#{deep_nested_group.name}.*</li>.*<a.*>#{very_deep_nested_group.name}</a>}m)
end
end
@@ -120,7 +120,7 @@ describe GroupsHelper do
let(:possible_help_texts) do
{
default_help: "This setting will be applied to all subgroups unless overridden by a group owner",
- ancestor_locked_but_you_can_override: /This setting is applied on <a .+>.+<\/a>\. You can override the setting or .+/,
+ ancestor_locked_but_you_can_override: %r{This setting is applied on <a .+>.+</a>\. You can override the setting or .+},
ancestor_locked_so_ask_the_owner: /This setting is applied on .+\. To share projects in this group with another group, ask the owner to override the setting or remove the share with group lock from .+/,
ancestor_locked_and_has_been_overridden: /This setting is applied on .+ and has been overridden on this subgroup/
}
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index d601cbdb39b..7fa665aecdc 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -125,10 +125,10 @@ describe IssuablesHelper do
describe '#updated_at_by' do
let(:user) { create(:user) }
let(:unedited_issuable) { create(:issue) }
- let(:edited_issuable) { create(:issue, last_edited_by: user, created_at: 3.days.ago, updated_at: 2.days.ago, last_edited_at: 2.days.ago) }
+ let(:edited_issuable) { create(:issue, last_edited_by: user, created_at: 3.days.ago, updated_at: 1.day.ago, last_edited_at: 2.days.ago) }
let(:edited_updated_at_by) do
{
- updatedAt: edited_issuable.updated_at.to_time.iso8601,
+ updatedAt: edited_issuable.last_edited_at.to_time.iso8601,
updatedBy: {
name: user.name,
path: user_path(user)
@@ -142,7 +142,7 @@ describe IssuablesHelper do
context 'when updated by a deleted user' do
let(:edited_updated_at_by) do
{
- updatedAt: edited_issuable.updated_at.to_time.iso8601,
+ updatedAt: edited_issuable.last_edited_at.to_time.iso8601,
updatedBy: {
name: User.ghost.name,
path: user_path(User.ghost)
@@ -192,4 +192,33 @@ describe IssuablesHelper do
expect(JSON.parse(helper.issuable_initial_data(issue))).to eq(expected_data)
end
end
+
+ describe '#selected_labels' do
+ context 'if label_name param is a string' do
+ it 'returns a new label with title' do
+ allow(helper).to receive(:params)
+ .and_return(ActionController::Parameters.new(label_name: 'test label'))
+
+ labels = helper.selected_labels
+
+ expect(labels).to be_an(Array)
+ expect(labels.size).to eq(1)
+ expect(labels.first.title).to eq('test label')
+ end
+ end
+
+ context 'if label_name param is an array' do
+ it 'returns a new label with title for each element' do
+ allow(helper).to receive(:params)
+ .and_return(ActionController::Parameters.new(label_name: ['test label 1', 'test label 2']))
+
+ labels = helper.selected_labels
+
+ expect(labels).to be_an(Array)
+ expect(labels.size).to eq(2)
+ expect(labels.first.title).to eq('test label 1')
+ expect(labels.second.title).to eq('test label 2')
+ end
+ end
+ end
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 0286d36952c..619baa78bfa 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -104,7 +104,7 @@ describe LabelsHelper do
context 'with a tooltip argument' do
context 'set to false' do
it 'does not include the has-tooltip class' do
- expect(link_to_label(label, tooltip: false)).not_to match %r{has-tooltip}
+ expect(link_to_label(label, tooltip: false)).not_to match /has-tooltip/
end
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index ede9d232efd..c0251bf7dc0 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe ProjectsHelper do
+ include ProjectForksHelper
+
describe "#project_status_css_class" do
it "returns appropriate class" do
expect(project_status_css_class("started")).to eq("active")
@@ -10,9 +12,9 @@ describe ProjectsHelper do
end
describe "can_change_visibility_level?" do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project) }
let(:user) { create(:project_member, :reporter, user: create(:user), project: project).user }
- let(:fork_project) { Projects::ForkService.new(project, user).execute }
+ let(:forked_project) { fork_project(project, user) }
it "returns false if there are no appropriate permissions" do
allow(helper).to receive(:can?) { false }
@@ -26,21 +28,29 @@ describe ProjectsHelper do
expect(helper.can_change_visibility_level?(project, user)).to be_truthy
end
+ it 'allows visibility level to be changed if the project is forked' do
+ allow(helper).to receive(:can?).with(user, :change_visibility_level, project) { true }
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ fork_project(project)
+
+ expect(helper.can_change_visibility_level?(project, user)).to be_truthy
+ end
+
context "forks" do
it "returns false if there are permissions and origin project is PRIVATE" do
allow(helper).to receive(:can?) { true }
- project.update visibility_level: Gitlab::VisibilityLevel::PRIVATE
+ project.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
- expect(helper.can_change_visibility_level?(fork_project, user)).to be_falsey
+ expect(helper.can_change_visibility_level?(forked_project, user)).to be_falsey
end
it "returns true if there are permissions and origin project is INTERNAL" do
allow(helper).to receive(:can?) { true }
- project.update visibility_level: Gitlab::VisibilityLevel::INTERNAL
+ project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
- expect(helper.can_change_visibility_level?(fork_project, user)).to be_truthy
+ expect(helper.can_change_visibility_level?(forked_project, user)).to be_truthy
end
end
end
diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb
index fa8cfda3b86..b5b15726816 100644
--- a/spec/helpers/version_check_helper_spec.rb
+++ b/spec/helpers/version_check_helper_spec.rb
@@ -27,7 +27,7 @@ describe VersionCheckHelper do
end
it 'should have a VersionCheck url as the src' do
- expect(@image_tag).to match(/src="https:\/\/version\.host\.com\/check\.svg\?gitlab_info=xxx"/)
+ expect(@image_tag).to match(%r{src="https://version\.host\.com/check\.svg\?gitlab_info=xxx"})
end
end
end
diff --git a/spec/initializers/gollum_spec.rb b/spec/initializers/gollum_spec.rb
new file mode 100644
index 00000000000..adf824a8947
--- /dev/null
+++ b/spec/initializers/gollum_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+describe 'gollum' do
+ let(:project) { create(:project) }
+ let(:user) { project.owner }
+ let(:wiki) { ProjectWiki.new(project, user) }
+ let(:gollum_wiki) { Gollum::Wiki.new(wiki.repository.path) }
+
+ before do
+ create_page(page_name, 'content1')
+ end
+
+ after do
+ destroy_page(page_name)
+ end
+
+ context 'with simple paths' do
+ let(:page_name) { 'page1' }
+
+ it 'returns the entry hash if it matches the file name' do
+ expect(tree_entry(page_name)).not_to be_nil
+ end
+
+ it 'returns nil if the path does not fit completely' do
+ expect(tree_entry("foo/#{page_name}")).to be_nil
+ end
+ end
+
+ context 'with complex paths' do
+ let(:page_name) { '/foo/bar/page2' }
+
+ it 'returns the entry hash if it matches the file name' do
+ expect(tree_entry(page_name)).not_to be_nil
+ end
+
+ it 'returns nil if the path does not fit completely' do
+ expect(tree_entry("foo1/bar/page2")).to be_nil
+ expect(tree_entry("foo/bar1/page2")).to be_nil
+ end
+ end
+
+ def tree_entry(name)
+ gollum_wiki.repo.git.tree_entry(wiki_commits[0].commit, name + '.md')
+ end
+
+ def wiki_commits
+ gollum_wiki.repo.commits
+ end
+
+ def commit_details
+ Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
+ end
+
+ def create_page(name, content)
+ wiki.wiki.write_page(name, :markdown, content, commit_details)
+ end
+
+ def destroy_page(name)
+ page = wiki.find_page(name).page
+ wiki.delete_page(page, "test commit")
+ end
+end
diff --git a/spec/initializers/grape_route_helpers_fix_spec.rb b/spec/initializers/grape_route_helpers_fix_spec.rb
new file mode 100644
index 00000000000..2cf5924128f
--- /dev/null
+++ b/spec/initializers/grape_route_helpers_fix_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+require_relative '../../config/initializers/grape_route_helpers_fix'
+
+describe 'route shadowing' do
+ include GrapeRouteHelpers::NamedRouteMatcher
+
+ it 'does not occur' do
+ path = api_v4_projects_merge_requests_path(id: 1)
+ expect(path).to eq('/api/v4/projects/1/merge_requests')
+
+ path = api_v4_projects_merge_requests_path(id: 1, merge_request_iid: 3)
+ expect(path).to eq('/api/v4/projects/1/merge_requests/3')
+ end
+end
diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb
index a11824d0ac5..838ca9fabef 100644
--- a/spec/initializers/settings_spec.rb
+++ b/spec/initializers/settings_spec.rb
@@ -24,7 +24,7 @@ describe Settings do
expect(described_class.host_without_www('http://foo.com')).to eq 'foo.com'
expect(described_class.host_without_www('http://www.foo.com')).to eq 'foo.com'
expect(described_class.host_without_www('http://secure.foo.com')).to eq 'secure.foo.com'
- expect(described_class.host_without_www('http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
+ expect(described_class.host_without_www('https://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
expect(described_class.host_without_www('https://foo.com')).to eq 'foo.com'
expect(described_class.host_without_www('https://www.foo.com')).to eq 'foo.com'
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index 2aa4fb1f6c6..cf3a76d0d2e 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -1,3 +1,5 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import Api from '~/api';
describe('Api', () => {
@@ -7,20 +9,17 @@ describe('Api', () => {
api_version: dummyApiVersion,
relative_url_root: dummyUrlRoot,
};
- const dummyResponse = 'hello from outer space!';
- const sendDummyResponse = () => {
- const deferred = $.Deferred();
- deferred.resolve(dummyResponse);
- return deferred.promise();
- };
let originalGon;
+ let mock;
beforeEach(() => {
+ mock = new MockAdapter(axios);
originalGon = window.gon;
window.gon = Object.assign({}, dummyGon);
});
afterEach(() => {
+ mock.restore();
window.gon = originalGon;
});
@@ -38,15 +37,13 @@ describe('Api', () => {
describe('group', () => {
it('fetches a group', (done) => {
const groupId = '123456';
- const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}.json`;
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- return sendDummyResponse();
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}`;
+ mock.onGet(expectedUrl).reply(200, {
+ name: 'test',
});
Api.group(groupId, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.name).toBe('test');
done();
});
});
@@ -57,19 +54,13 @@ describe('Api', () => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups.json`;
- const expectedData = Object.assign({
- search: query,
- per_page: 20,
- }, options);
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, [{
+ name: 'test',
+ }]);
Api.groups(query, options, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
done();
});
});
@@ -79,19 +70,13 @@ describe('Api', () => {
it('fetches namespaces', (done) => {
const query = 'dummy query';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/namespaces.json`;
- const expectedData = {
- search: query,
- per_page: 20,
- };
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, [{
+ name: 'test',
+ }]);
Api.namespaces(query, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
done();
});
});
@@ -103,21 +88,13 @@ describe('Api', () => {
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
window.gon.current_user_id = 1;
- const expectedData = Object.assign({
- search: query,
- per_page: 20,
- membership: true,
- simple: true,
- }, options);
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, [{
+ name: 'test',
+ }]);
Api.projects(query, options, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
done();
});
});
@@ -126,20 +103,13 @@ describe('Api', () => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json`;
- const expectedData = Object.assign({
- search: query,
- per_page: 20,
- simple: true,
- }, options);
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, [{
+ name: 'test',
+ }]);
Api.projects(query, options, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
done();
});
});
@@ -154,16 +124,16 @@ describe('Api', () => {
const expectedData = {
label: labelData,
};
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.type).toEqual('POST');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
+ mock.onPost(expectedUrl).reply((config) => {
+ expect(config.data).toBe(JSON.stringify(expectedData));
+
+ return [200, {
+ name: 'test',
+ }];
});
Api.newLabel(namespace, project, labelData, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.name).toBe('test');
done();
});
});
@@ -174,19 +144,13 @@ describe('Api', () => {
const groupId = '123456';
const query = 'dummy query';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/projects.json`;
- const expectedData = {
- search: query,
- per_page: 20,
- };
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, [{
+ name: 'test',
+ }]);
Api.groupProjects(groupId, query, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
done();
});
});
@@ -197,14 +161,10 @@ describe('Api', () => {
const licenseKey = "driver's license";
const data = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/licenses/${licenseKey}`;
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.data).toEqual(data);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, 'test');
Api.licenseText(licenseKey, data, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response).toBe('test');
done();
});
});
@@ -214,13 +174,10 @@ describe('Api', () => {
it('fetches a gitignore text', (done) => {
const gitignoreKey = 'ignore git';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/gitignores/${gitignoreKey}`;
- spyOn(jQuery, 'get').and.callFake((url, callback) => {
- expect(url).toEqual(expectedUrl);
- callback(dummyResponse);
- });
+ mock.onGet(expectedUrl).reply(200, 'test');
Api.gitignoreText(gitignoreKey, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response).toBe('test');
done();
});
});
@@ -230,13 +187,10 @@ describe('Api', () => {
it('fetches a .gitlab-ci.yml', (done) => {
const gitlabCiYmlKey = 'Y CI ML';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/gitlab_ci_ymls/${gitlabCiYmlKey}`;
- spyOn(jQuery, 'get').and.callFake((url, callback) => {
- expect(url).toEqual(expectedUrl);
- callback(dummyResponse);
- });
+ mock.onGet(expectedUrl).reply(200, 'test');
Api.gitlabCiYml(gitlabCiYmlKey, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response).toBe('test');
done();
});
});
@@ -246,13 +200,10 @@ describe('Api', () => {
it('fetches a Dockerfile', (done) => {
const dockerfileYmlKey = 'a giant whale';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/dockerfiles/${dockerfileYmlKey}`;
- spyOn(jQuery, 'get').and.callFake((url, callback) => {
- expect(url).toEqual(expectedUrl);
- callback(dummyResponse);
- });
+ mock.onGet(expectedUrl).reply(200, 'test');
Api.dockerfileYml(dockerfileYmlKey, (response) => {
- expect(response).toBe(dummyResponse);
+ expect(response).toBe('test');
done();
});
});
@@ -262,17 +213,13 @@ describe('Api', () => {
it('fetches an issue template', (done) => {
const namespace = 'some namespace';
const project = 'some project';
- const templateKey = 'template key';
+ const templateKey = ' template #%?.key ';
const templateType = 'template type';
- const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${templateKey}`;
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- return sendDummyResponse();
- });
+ const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent(templateKey)}`;
+ mock.onGet(expectedUrl).reply(200, 'test');
Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => {
- expect(error).toBe(null);
- expect(response).toBe(dummyResponse);
+ expect(response).toBe('test');
done();
});
});
@@ -283,20 +230,14 @@ describe('Api', () => {
const query = 'dummy query';
const options = { unused: 'option' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users.json`;
- const expectedData = Object.assign({
- search: query,
- per_page: 20,
- }, options);
- spyOn(jQuery, 'ajax').and.callFake((request) => {
- expect(request.url).toEqual(expectedUrl);
- expect(request.dataType).toEqual('json');
- expect(request.data).toEqual(expectedData);
- return sendDummyResponse();
- });
+ mock.onGet(expectedUrl).reply(200, [{
+ name: 'test',
+ }]);
Api.users(query, options)
- .then((response) => {
- expect(response).toBe(dummyResponse);
+ .then(({ data }) => {
+ expect(data.length).toBe(1);
+ expect(data[0].name).toBe('test');
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/behaviors/secret_values_spec.js b/spec/javascripts/behaviors/secret_values_spec.js
index 9eeae474e7d..38d9bba6868 100644
--- a/spec/javascripts/behaviors/secret_values_spec.js
+++ b/spec/javascripts/behaviors/secret_values_spec.js
@@ -1,16 +1,24 @@
import SecretValues from '~/behaviors/secret_values';
-function generateFixtureMarkup(secrets, isRevealed) {
+function generateValueMarkup(
+ secret,
+ valueClass = 'js-secret-value',
+ placeholderClass = 'js-secret-value-placeholder',
+) {
+ return `
+ <div class="${placeholderClass}">
+ ***
+ </div>
+ <div class="hide ${valueClass}">
+ ${secret}
+ </div>
+ `;
+}
+
+function generateFixtureMarkup(secrets, isRevealed, valueClass, placeholderClass) {
return `
<div class="js-secret-container">
- ${secrets.map(secret => `
- <div class="js-secret-value-placeholder">
- ***
- </div>
- <div class="hide js-secret-value">
- ${secret}
- </div>
- `).join('')}
+ ${secrets.map(secret => generateValueMarkup(secret, valueClass, placeholderClass)).join('')}
<button
class="js-secret-value-reveal-button"
data-secret-reveal-status="${isRevealed}"
@@ -21,11 +29,25 @@ function generateFixtureMarkup(secrets, isRevealed) {
`;
}
-function setupSecretFixture(secrets, isRevealed) {
+function setupSecretFixture(
+ secrets,
+ isRevealed,
+ valueClass = 'js-secret-value',
+ placeholderClass = 'js-secret-value-placeholder',
+) {
const wrapper = document.createElement('div');
- wrapper.innerHTML = generateFixtureMarkup(secrets, isRevealed);
-
- const secretValues = new SecretValues(wrapper.querySelector('.js-secret-container'));
+ wrapper.innerHTML = generateFixtureMarkup(
+ secrets,
+ isRevealed,
+ valueClass,
+ placeholderClass,
+ );
+
+ const secretValues = new SecretValues({
+ container: wrapper.querySelector('.js-secret-container'),
+ valueSelector: `.${valueClass}`,
+ placeholderSelector: `.${placeholderClass}`,
+ });
secretValues.init();
return wrapper;
@@ -49,7 +71,7 @@ describe('setupSecretValues', () => {
expect(revealButton.textContent).toEqual('Hide value');
});
- it('should value hidden initially', () => {
+ it('should have value hidden initially', () => {
const wrapper = setupSecretFixture(secrets, false);
const values = wrapper.querySelectorAll('.js-secret-value');
const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder');
@@ -143,4 +165,64 @@ describe('setupSecretValues', () => {
});
});
});
+
+ describe('with dynamic secrets', () => {
+ const secrets = ['mysecret123', 'happygoat456', 'tanuki789'];
+
+ it('should toggle values and placeholders', () => {
+ const wrapper = setupSecretFixture(secrets, false);
+ // Insert the new dynamic row
+ wrapper.querySelector('.js-secret-container').insertAdjacentHTML('afterbegin', generateValueMarkup('foobarbazdynamic'));
+
+ const revealButton = wrapper.querySelector('.js-secret-value-reveal-button');
+ const values = wrapper.querySelectorAll('.js-secret-value');
+ const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder');
+
+ revealButton.click();
+
+ expect(values.length).toEqual(4);
+ values.forEach((value) => {
+ expect(value.classList.contains('hide')).toEqual(false);
+ });
+ expect(placeholders.length).toEqual(4);
+ placeholders.forEach((placeholder) => {
+ expect(placeholder.classList.contains('hide')).toEqual(true);
+ });
+
+ revealButton.click();
+
+ expect(values.length).toEqual(4);
+ values.forEach((value) => {
+ expect(value.classList.contains('hide')).toEqual(true);
+ });
+ expect(placeholders.length).toEqual(4);
+ placeholders.forEach((placeholder) => {
+ expect(placeholder.classList.contains('hide')).toEqual(false);
+ });
+ });
+ });
+
+ describe('selector options', () => {
+ const secrets = ['mysecret123'];
+
+ it('should respect `valueSelector` and `placeholderSelector` options', () => {
+ const valueClass = 'js-some-custom-placeholder-selector';
+ const placeholderClass = 'js-some-custom-value-selector';
+
+ const wrapper = setupSecretFixture(secrets, false, valueClass, placeholderClass);
+ const values = wrapper.querySelectorAll(`.${valueClass}`);
+ const placeholders = wrapper.querySelectorAll(`.${placeholderClass}`);
+ const revealButton = wrapper.querySelector('.js-secret-value-reveal-button');
+
+ expect(values.length).toEqual(1);
+ expect(placeholders.length).toEqual(1);
+
+ revealButton.click();
+
+ expect(values.length).toEqual(1);
+ expect(values[0].classList.contains('hide')).toEqual(false);
+ expect(placeholders.length).toEqual(1);
+ expect(placeholders[0].classList.contains('hide')).toEqual(true);
+ });
+ });
});
diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js
index df1b2c9960b..a143fc827d5 100644
--- a/spec/javascripts/blob/notebook/index_spec.js
+++ b/spec/javascripts/blob/notebook/index_spec.js
@@ -45,7 +45,7 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('does not show loading icon', () => {
@@ -96,7 +96,7 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('does not show loading icon', () => {
@@ -127,7 +127,7 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('does not show loading icon', () => {
diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js
index cfa6650d85f..892411a6a40 100644
--- a/spec/javascripts/blob/viewer/index_spec.js
+++ b/spec/javascripts/blob/viewer/index_spec.js
@@ -1,28 +1,35 @@
/* eslint-disable no-new */
+import MockAdapter from 'axios-mock-adapter';
import BlobViewer from '~/blob/viewer/index';
+import axios from '~/lib/utils/axios_utils';
describe('Blob viewer', () => {
let blob;
+ let mock;
+
preloadFixtures('snippets/show.html.raw');
beforeEach(() => {
+ mock = new MockAdapter(axios);
+
loadFixtures('snippets/show.html.raw');
$('#modal-upload-blob').remove();
blob = new BlobViewer();
- spyOn($, 'ajax').and.callFake(() => {
- const d = $.Deferred();
-
- d.resolve({
- html: '<div>testing</div>',
- });
+ mock.onGet('http://test.host/snippets/1.json?viewer=rich').reply(200, {
+ html: '<div>testing</div>',
+ });
- return d.promise();
+ mock.onGet('http://test.host/snippets/1.json?viewer=simple').reply(200, {
+ html: '<div>testing</div>',
});
+
+ spyOn(axios, 'get').and.callThrough();
});
afterEach(() => {
+ mock.restore();
location.hash = '';
});
@@ -30,7 +37,6 @@ describe('Blob viewer', () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => {
- expect($.ajax).toHaveBeenCalled();
expect(
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]')
.classList.contains('hidden'),
@@ -46,7 +52,6 @@ describe('Blob viewer', () => {
new BlobViewer();
setTimeout(() => {
- expect($.ajax).toHaveBeenCalled();
expect(
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]')
.classList.contains('hidden'),
@@ -64,12 +69,8 @@ describe('Blob viewer', () => {
});
asyncClick()
+ .then(() => asyncClick())
.then(() => {
- expect($.ajax).toHaveBeenCalled();
- return asyncClick();
- })
- .then(() => {
- expect($.ajax.calls.count()).toBe(1);
expect(
document.querySelector('.blob-viewer[data-type="simple"]').getAttribute('data-loaded'),
).toBe('true');
@@ -122,7 +123,6 @@ describe('Blob viewer', () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => {
- expect($.ajax).toHaveBeenCalled();
expect(
copyButton.classList.contains('disabled'),
).toBeFalsy();
@@ -135,8 +135,6 @@ describe('Blob viewer', () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => {
- expect($.ajax).toHaveBeenCalled();
-
expect(
copyButton.getAttribute('data-original-title'),
).toBe('Copy source to clipboard');
@@ -171,14 +169,14 @@ describe('Blob viewer', () => {
it('sends AJAX request when switching to simple view', () => {
blob.switchToViewer('simple');
- expect($.ajax).toHaveBeenCalled();
+ expect(axios.get).toHaveBeenCalled();
});
it('does not send AJAX request when switching to rich view', () => {
blob.switchToViewer('simple');
blob.switchToViewer('rich');
- expect($.ajax.calls.count()).toBe(1);
+ expect(axios.get.calls.count()).toBe(1);
});
});
});
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index 4e73fa1fe87..80a598e63bd 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -55,7 +55,7 @@ describe('Board card', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('returns false when detailIssue is empty', () => {
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 7c5888b6d82..a5fcb10b9dd 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -60,7 +60,7 @@ describe('Board list component', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('renders component', () => {
@@ -154,6 +154,18 @@ describe('Board list component', () => {
});
});
+ it('sets data attribute with invalid id', (done) => {
+ component.showCount = true;
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.board-list-count').getAttribute('data-issue-id'),
+ ).toBe('-1');
+
+ done();
+ });
+ });
+
it('shows how many more issues to load', (done) => {
component.showCount = true;
component.list.issuesSize = 20;
@@ -172,9 +184,9 @@ describe('Board list component', () => {
component.$refs.list.style.height = '100px';
component.$refs.list.style.overflow = 'scroll';
- for (let i = 0; i < 19; i += 1) {
- const issue = component.list.issues[0];
- issue.id += 1;
+ for (let i = 1; i < 20; i += 1) {
+ const issue = Object.assign({}, component.list.issues[0]);
+ issue.id += i;
component.list.issues.push(issue);
}
diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js
index c62c537841c..e204985f039 100644
--- a/spec/javascripts/boards/board_new_issue_spec.js
+++ b/spec/javascripts/boards/board_new_issue_spec.js
@@ -58,7 +58,7 @@ describe('Issue boards new issue form', () => {
afterEach(() => {
vm.$destroy();
- mock.reset();
+ mock.restore();
});
it('calls submit if submit button is clicked', (done) => {
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 49fb20f4c84..8411f4dd8a6 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -35,7 +35,7 @@ describe('Store', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('starts with a blank state', () => {
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index 8ef221257be..278155c585e 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -45,6 +45,9 @@ describe('Issue card component', () => {
component = new Vue({
el: document.querySelector('.test-container'),
+ components: {
+ 'issue-card': gl.issueBoards.IssueCardInner,
+ },
data() {
return {
list,
@@ -53,9 +56,6 @@ describe('Issue card component', () => {
rootPath: '/',
};
},
- components: {
- 'issue-card': gl.issueBoards.IssueCardInner,
- },
template: `
<issue-card
:issue="issue"
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index 645ce831b53..34964b20b05 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -5,7 +5,7 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-
+import _ from 'underscore';
import '~/boards/models/issue';
import '~/boards/models/label';
import '~/boards/models/list';
@@ -30,7 +30,7 @@ describe('List model', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('gets issues when created', (done) => {
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index 9ae2d535398..0671facb285 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -1,5 +1,6 @@
/* global BoardService */
/* eslint-disable comma-dangle, no-unused-vars, quote-props */
+import _ from 'underscore';
export const listObj = {
id: 300,
diff --git a/spec/javascripts/boards/utils/query_data_spec.js b/spec/javascripts/boards/utils/query_data_spec.js
new file mode 100644
index 00000000000..922215ffc1d
--- /dev/null
+++ b/spec/javascripts/boards/utils/query_data_spec.js
@@ -0,0 +1,27 @@
+import queryData from '~/boards/utils/query_data';
+
+describe('queryData', () => {
+ it('parses path for label with trailing +', () => {
+ expect(
+ queryData('label_name[]=label%2B', {}),
+ ).toEqual({
+ label_name: ['label+'],
+ });
+ });
+
+ it('parses path for milestone with trailing +', () => {
+ expect(
+ queryData('milestone_title=A%2B', {}),
+ ).toEqual({
+ milestone_title: 'A+',
+ });
+ });
+
+ it('parses path for search terms with spaces', () => {
+ expect(
+ queryData('search=two+words', {}),
+ ).toEqual({
+ search: 'two words',
+ });
+ });
+});
diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js
index f5be9ea0fb2..7b38f6b7855 100644
--- a/spec/javascripts/clusters/clusters_bundle_spec.js
+++ b/spec/javascripts/clusters/clusters_bundle_spec.js
@@ -23,16 +23,24 @@ describe('Clusters', () => {
});
describe('toggle', () => {
- it('should update the button and the input field on click', () => {
- cluster.toggleButton.click();
+ it('should update the button and the input field on click', (done) => {
+ const toggleButton = document.querySelector('.js-cluster-enable-toggle-area .js-project-feature-toggle');
+ const toggleInput = document.querySelector('.js-cluster-enable-toggle-area .js-project-feature-toggle-input');
- expect(
- cluster.toggleButton.classList,
- ).not.toContain('is-checked');
+ toggleButton.click();
- expect(
- cluster.toggleInput.getAttribute('value'),
- ).toEqual('false');
+ getSetTimeoutPromise()
+ .then(() => {
+ expect(
+ toggleButton.classList,
+ ).not.toContain('is-checked');
+
+ expect(
+ toggleInput.getAttribute('value'),
+ ).toEqual('false');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
diff --git a/spec/javascripts/clusters/clusters_index_spec.js b/spec/javascripts/clusters/clusters_index_spec.js
deleted file mode 100644
index 0a8b63ed5b4..00000000000
--- a/spec/javascripts/clusters/clusters_index_spec.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import setClusterTableToggles from '~/clusters/clusters_index';
-import { setTimeout } from 'core-js/library/web/timers';
-
-describe('Clusters table', () => {
- preloadFixtures('clusters/index_cluster.html.raw');
- let mock;
-
- beforeEach(() => {
- loadFixtures('clusters/index_cluster.html.raw');
- mock = new MockAdapter(axios);
- setClusterTableToggles();
- });
-
- describe('update cluster', () => {
- it('renders loading state while request is made', () => {
- const button = document.querySelector('.js-toggle-cluster-list');
-
- button.click();
-
- expect(button.classList).toContain('is-loading');
- expect(button.getAttribute('disabled')).toEqual('true');
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('shows updated state after sucessfull request', (done) => {
- mock.onPut().reply(200, {}, {});
- const button = document.querySelector('.js-toggle-cluster-list');
- button.click();
-
- expect(button.classList).toContain('is-loading');
-
- setTimeout(() => {
- expect(button.classList).not.toContain('is-loading');
- expect(button.classList).not.toContain('is-checked');
- done();
- }, 0);
- });
-
- it('shows inital state after failed request', (done) => {
- mock.onPut().reply(500, {}, {});
- const button = document.querySelector('.js-toggle-cluster-list');
-
- button.click();
- expect(button.classList).toContain('is-loading');
-
- setTimeout(() => {
- expect(button.classList).not.toContain('is-loading');
- expect(button.classList).toContain('is-checked');
- done();
- }, 0);
- });
- });
-});
diff --git a/spec/javascripts/commit/commit_pipeline_status_component_spec.js b/spec/javascripts/commit/commit_pipeline_status_component_spec.js
new file mode 100644
index 00000000000..90f290e845e
--- /dev/null
+++ b/spec/javascripts/commit/commit_pipeline_status_component_spec.js
@@ -0,0 +1,104 @@
+import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Commit pipeline status component', () => {
+ let vm;
+ let Component;
+ let mock;
+ const mockCiStatus = {
+ details_path: '/root/hello-world/pipelines/1',
+ favicon: 'canceled.ico',
+ group: 'canceled',
+ has_details: true,
+ icon: 'status_canceled',
+ label: 'canceled',
+ text: 'canceled',
+ };
+
+ beforeEach(() => {
+ Component = Vue.extend(commitPipelineStatus);
+ });
+
+ describe('While polling pipeline data succesfully', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet('/dummy/endpoint').reply(() => {
+ const res = Promise.resolve([200, {
+ pipelines: [
+ {
+ details: {
+ status: mockCiStatus,
+ },
+ },
+ ],
+ }]);
+ return res;
+ });
+ vm = mountComponent(Component, {
+ endpoint: '/dummy/endpoint',
+ });
+ });
+
+ afterEach(() => {
+ vm.poll.stop();
+ vm.$destroy();
+ mock.restore();
+ });
+
+ it('shows the loading icon when polling is starting', (done) => {
+ expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.loading-container')).toBe(null);
+ done();
+ });
+ });
+
+ it('contains a ciStatus when the polling is succesful ', (done) => {
+ setTimeout(() => {
+ expect(vm.ciStatus).toEqual(mockCiStatus);
+ done();
+ });
+ });
+
+ it('contains a ci-status icon when polling is succesful', (done) => {
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.ci-status-icon')).not.toBe(null);
+ expect(vm.$el.querySelector('.ci-status-icon').classList).toContain(`ci-status-icon-${mockCiStatus.group}`);
+ done();
+ });
+ });
+ });
+
+ describe('When polling data was not succesful', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet('/dummy/endpoint').reply(() => {
+ const res = Promise.reject([502, { }]);
+ return res;
+ });
+ vm = new Component({
+ props: {
+ endpoint: '/dummy/endpoint',
+ },
+ });
+ });
+
+ afterEach(() => {
+ vm.poll.stop();
+ vm.$destroy();
+ mock.restore();
+ });
+
+ it('calls an errorCallback', (done) => {
+ spyOn(vm, 'errorCallback').and.callThrough();
+ vm.$mount();
+ setTimeout(() => {
+ expect(vm.errorCallback.calls.count()).toEqual(1);
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index 9fc047b1f5e..0afe09d87bc 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import pipelinesTable from '~/commit/pipelines/pipelines_table.vue';
@@ -9,9 +10,10 @@ describe('Pipelines table in Commits and Merge requests', () => {
preloadFixtures(jsonFixtureName);
beforeEach(() => {
- PipelinesTable = Vue.extend(pipelinesTable);
const pipelines = getJSONFixture(jsonFixtureName).pipelines;
- pipeline = pipelines.find(p => p.id === 1);
+
+ PipelinesTable = Vue.extend(pipelinesTable);
+ pipeline = pipelines.find(p => p.user !== null && p.commit !== null);
});
describe('successful request', () => {
diff --git a/spec/javascripts/commit_merge_requests_spec.js b/spec/javascripts/commit_merge_requests_spec.js
new file mode 100644
index 00000000000..3466ef51ea8
--- /dev/null
+++ b/spec/javascripts/commit_merge_requests_spec.js
@@ -0,0 +1,60 @@
+import * as CommitMergeRequests from '~/commit_merge_requests';
+
+describe('CommitMergeRequests', () => {
+ describe('createContent', () => {
+ it('should return created content', () => {
+ const content1 = CommitMergeRequests.createContent([{ iid: 1, path: '/path1', title: 'foo' }, { iid: 2, path: '/path2', title: 'baz' }])[0];
+ expect(content1.tagName).toEqual('SPAN');
+ expect(content1.childElementCount).toEqual(4);
+
+ const content2 = CommitMergeRequests.createContent([])[0];
+ expect(content2.tagName).toEqual('SPAN');
+ expect(content2.childElementCount).toEqual(0);
+ expect(content2.innerText).toEqual('No related merge requests found');
+ });
+ });
+
+ describe('getHeaderText', () => {
+ it('should return header text', () => {
+ expect(CommitMergeRequests.getHeaderText(0, 1)).toEqual('1 merge request');
+ expect(CommitMergeRequests.getHeaderText(0, 2)).toEqual('2 merge requests');
+ expect(CommitMergeRequests.getHeaderText(1, 1)).toEqual(',');
+ expect(CommitMergeRequests.getHeaderText(1, 2)).toEqual(',');
+ });
+ });
+
+ describe('createHeader', () => {
+ it('should return created header', () => {
+ const header = CommitMergeRequests.createHeader(0, 1)[0];
+ expect(header.tagName).toEqual('SPAN');
+ expect(header.innerText).toEqual('1 merge request');
+ });
+ });
+
+ describe('createItem', () => {
+ it('should return created item', () => {
+ const item = CommitMergeRequests.createItem({ iid: 1, path: '/path', title: 'foo' })[0];
+ expect(item.tagName).toEqual('SPAN');
+ expect(item.childElementCount).toEqual(2);
+ expect(item.children[0].tagName).toEqual('A');
+ expect(item.children[1].tagName).toEqual('SPAN');
+ });
+ });
+
+ describe('createLink', () => {
+ it('should return created link', () => {
+ const link = CommitMergeRequests.createLink({ iid: 1, path: '/path', title: 'foo' })[0];
+ expect(link.tagName).toEqual('A');
+ expect(link.href).toMatch(/\/path$/);
+ expect(link.innerText).toEqual('!1');
+ });
+ });
+
+ describe('createTitle', () => {
+ it('should return created title', () => {
+ const title = CommitMergeRequests.createTitle({ iid: 1, path: '/path', title: 'foo' })[0];
+ expect(title.tagName).toEqual('SPAN');
+ expect(title.innerText).toEqual('foo');
+ });
+ });
+});
diff --git a/spec/javascripts/commits_spec.js b/spec/javascripts/commits_spec.js
index d0176520440..44ec9e4eabf 100644
--- a/spec/javascripts/commits_spec.js
+++ b/spec/javascripts/commits_spec.js
@@ -1,4 +1,6 @@
import 'vendor/jquery.endless-scroll';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import CommitsList from '~/commits';
describe('Commits List', () => {
@@ -43,30 +45,47 @@ describe('Commits List', () => {
describe('on entering input', () => {
let ajaxSpy;
+ let mock;
beforeEach(() => {
CommitsList.init(25);
CommitsList.searchField.val('');
spyOn(history, 'replaceState').and.stub();
- ajaxSpy = spyOn(jQuery, 'ajax').and.callFake((req) => {
- req.success({
- data: '<li>Result</li>',
- });
+ mock = new MockAdapter(axios);
+
+ mock.onGet('/h5bp/html5-boilerplate/commits/master').reply(200, {
+ html: '<li>Result</li>',
});
+
+ ajaxSpy = spyOn(axios, 'get').and.callThrough();
+ });
+
+ afterEach(() => {
+ mock.restore();
});
- it('should save the last search string', () => {
+ it('should save the last search string', (done) => {
CommitsList.searchField.val('GitLab');
- CommitsList.filterResults();
- expect(ajaxSpy).toHaveBeenCalled();
- expect(CommitsList.lastSearch).toEqual('GitLab');
+ CommitsList.filterResults()
+ .then(() => {
+ expect(ajaxSpy).toHaveBeenCalled();
+ expect(CommitsList.lastSearch).toEqual('GitLab');
+
+ done();
+ })
+ .catch(done.fail);
});
- it('should not make ajax call if the input does not change', () => {
- CommitsList.filterResults();
- expect(ajaxSpy).not.toHaveBeenCalled();
- expect(CommitsList.lastSearch).toEqual('');
+ it('should not make ajax call if the input does not change', (done) => {
+ CommitsList.filterResults()
+ .then(() => {
+ expect(ajaxSpy).not.toHaveBeenCalled();
+ expect(CommitsList.lastSearch).toEqual('');
+
+ done();
+ })
+ .catch(done.fail);
});
});
});
diff --git a/spec/javascripts/create_item_dropdown_spec.js b/spec/javascripts/create_item_dropdown_spec.js
new file mode 100644
index 00000000000..143137c23ec
--- /dev/null
+++ b/spec/javascripts/create_item_dropdown_spec.js
@@ -0,0 +1,183 @@
+import CreateItemDropdown from '~/create_item_dropdown';
+
+const DROPDOWN_ITEM_DATA = [{
+ title: 'one',
+ id: 'one',
+ text: 'one',
+}, {
+ title: 'two',
+ id: 'two',
+ text: 'two',
+}, {
+ title: 'three',
+ id: 'three',
+ text: 'three',
+}];
+
+describe('CreateItemDropdown', () => {
+ preloadFixtures('static/create_item_dropdown.html.raw');
+
+ let $wrapperEl;
+ let createItemDropdown;
+
+ function createItemAndClearInput(text) {
+ // Filter for the new item
+ $wrapperEl.find('.dropdown-input-field')
+ .val(text)
+ .trigger('input');
+
+ // Create the new item
+ const $createButton = $wrapperEl.find('.js-dropdown-create-new-item');
+ $createButton.click();
+
+ // Clear out the filter
+ $wrapperEl.find('.dropdown-input-field')
+ .val('')
+ .trigger('input');
+ }
+
+ beforeEach(() => {
+ loadFixtures('static/create_item_dropdown.html.raw');
+ $wrapperEl = $('.js-create-item-dropdown-fixture-root');
+ });
+
+ afterEach(() => {
+ $wrapperEl.remove();
+ });
+
+ describe('items', () => {
+ beforeEach(() => {
+ createItemDropdown = new CreateItemDropdown({
+ $dropdown: $wrapperEl.find('.js-dropdown-menu-toggle'),
+ defaultToggleLabel: 'All variables',
+ fieldName: 'variable[environment]',
+ getData: (term, callback) => {
+ callback(DROPDOWN_ITEM_DATA);
+ },
+ });
+ });
+
+ it('should have a dropdown item for each piece of data', () => {
+ // Get the data in the dropdown
+ $('.js-dropdown-menu-toggle').click();
+
+ const $itemEls = $wrapperEl.find('.js-dropdown-content a');
+ expect($itemEls.length).toEqual(DROPDOWN_ITEM_DATA.length);
+ });
+ });
+
+ describe('created items', () => {
+ const NEW_ITEM_TEXT = 'foobarbaz';
+
+ beforeEach(() => {
+ createItemDropdown = new CreateItemDropdown({
+ $dropdown: $wrapperEl.find('.js-dropdown-menu-toggle'),
+ defaultToggleLabel: 'All variables',
+ fieldName: 'variable[environment]',
+ getData: (term, callback) => {
+ callback(DROPDOWN_ITEM_DATA);
+ },
+ });
+
+ // Open the dropdown
+ $('.js-dropdown-menu-toggle').click();
+
+ // Filter for the new item
+ $wrapperEl.find('.dropdown-input-field')
+ .val(NEW_ITEM_TEXT)
+ .trigger('input');
+ });
+
+ it('create new item button should include the filter text', () => {
+ expect($wrapperEl.find('.js-dropdown-create-new-item code').text()).toEqual(NEW_ITEM_TEXT);
+ });
+
+ it('should update the dropdown with the newly created item', () => {
+ // Create the new item
+ const $createButton = $wrapperEl.find('.js-dropdown-create-new-item');
+ $createButton.click();
+
+ expect($wrapperEl.find('.dropdown-toggle-text').text()).toEqual(NEW_ITEM_TEXT);
+ expect($wrapperEl.find('input[name="variable[environment]"]').val()).toEqual(NEW_ITEM_TEXT);
+ });
+
+ it('should include newly created item in dropdown list', () => {
+ createItemAndClearInput(NEW_ITEM_TEXT);
+
+ const $itemEls = $wrapperEl.find('.js-dropdown-content a');
+ expect($itemEls.length).toEqual(1 + DROPDOWN_ITEM_DATA.length);
+ expect($($itemEls.get(DROPDOWN_ITEM_DATA.length)).text()).toEqual(NEW_ITEM_TEXT);
+ });
+
+ it('should not duplicate an item when trying to create an existing item', () => {
+ createItemAndClearInput(DROPDOWN_ITEM_DATA[0].text);
+
+ const $itemEls = $wrapperEl.find('.js-dropdown-content a');
+ expect($itemEls.length).toEqual(DROPDOWN_ITEM_DATA.length);
+ });
+ });
+
+ describe('clearDropdown()', () => {
+ beforeEach(() => {
+ createItemDropdown = new CreateItemDropdown({
+ $dropdown: $wrapperEl.find('.js-dropdown-menu-toggle'),
+ defaultToggleLabel: 'All variables',
+ fieldName: 'variable[environment]',
+ getData: (term, callback) => {
+ callback(DROPDOWN_ITEM_DATA);
+ },
+ });
+ });
+
+ it('should clear all data and filter input', () => {
+ const filterInput = $wrapperEl.find('.dropdown-input-field');
+
+ // Get the data in the dropdown
+ $('.js-dropdown-menu-toggle').click();
+
+ // Filter for an item
+ filterInput
+ .val('one')
+ .trigger('input');
+
+ const $itemElsAfterFilter = $wrapperEl.find('.js-dropdown-content a');
+ expect($itemElsAfterFilter.length).toEqual(1);
+
+ createItemDropdown.clearDropdown();
+
+ const $itemElsAfterClear = $wrapperEl.find('.js-dropdown-content a');
+ expect($itemElsAfterClear.length).toEqual(0);
+ expect(filterInput.val()).toEqual('');
+ });
+ });
+
+ describe('createNewItemFromValue option', () => {
+ beforeEach(() => {
+ createItemDropdown = new CreateItemDropdown({
+ $dropdown: $wrapperEl.find('.js-dropdown-menu-toggle'),
+ defaultToggleLabel: 'All variables',
+ fieldName: 'variable[environment]',
+ getData: (term, callback) => {
+ callback(DROPDOWN_ITEM_DATA);
+ },
+ createNewItemFromValue: newValue => ({
+ title: `${newValue}-title`,
+ id: `${newValue}-id`,
+ text: `${newValue}-text`,
+ }),
+ });
+ });
+
+ it('all items go through createNewItemFromValue', () => {
+ // Get the data in the dropdown
+ $('.js-dropdown-menu-toggle').click();
+
+ createItemAndClearInput('new-item');
+
+ const $itemEls = $wrapperEl.find('.js-dropdown-content a');
+ expect($itemEls.length).toEqual(1 + DROPDOWN_ITEM_DATA.length);
+ expect($($itemEls[3]).text()).toEqual('new-item-text');
+ expect($wrapperEl.find('.dropdown-toggle-text').text()).toEqual('new-item-title');
+ });
+ });
+});
diff --git a/spec/javascripts/cycle_analytics/banner_spec.js b/spec/javascripts/cycle_analytics/banner_spec.js
index fb6b7fee168..64a76a6ee5f 100644
--- a/spec/javascripts/cycle_analytics/banner_spec.js
+++ b/spec/javascripts/cycle_analytics/banner_spec.js
@@ -20,8 +20,9 @@ describe('Cycle analytics banner', () => {
expect(
vm.$el.querySelector('h4').textContent.trim(),
).toEqual('Introducing Cycle Analytics');
+
expect(
- vm.$el.querySelector('p').textContent.trim(),
+ vm.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toContain('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.');
expect(
vm.$el.querySelector('a').textContent.trim(),
diff --git a/spec/javascripts/cycle_analytics/total_time_component_spec.js b/spec/javascripts/cycle_analytics/total_time_component_spec.js
index 31b65fd1cde..ad0fc38a856 100644
--- a/spec/javascripts/cycle_analytics/total_time_component_spec.js
+++ b/spec/javascripts/cycle_analytics/total_time_component_spec.js
@@ -23,7 +23,7 @@ describe('Total time component', () => {
},
});
- expect(vm.$el.textContent.trim()).toEqual('3 days 4 hrs');
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toEqual('3 days 4 hrs');
});
it('should render information for hours and minutes', () => {
@@ -34,7 +34,7 @@ describe('Total time component', () => {
},
});
- expect(vm.$el.textContent.trim()).toEqual('4 hrs 35 mins');
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toEqual('4 hrs 35 mins');
});
it('should render information for seconds', () => {
@@ -44,7 +44,7 @@ describe('Total time component', () => {
},
});
- expect(vm.$el.textContent.trim()).toEqual('45 s');
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toEqual('45 s');
});
});
diff --git a/spec/javascripts/deploy_keys/components/app_spec.js b/spec/javascripts/deploy_keys/components/app_spec.js
index 0ca9290d3d2..b870f87eab9 100644
--- a/spec/javascripts/deploy_keys/components/app_spec.js
+++ b/spec/javascripts/deploy_keys/components/app_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import eventHub from '~/deploy_keys/eventhub';
import deployKeysApp from '~/deploy_keys/components/app.vue';
diff --git a/spec/javascripts/deploy_keys/components/key_spec.js b/spec/javascripts/deploy_keys/components/key_spec.js
index 2f28c5bbf01..b7aadf604a4 100644
--- a/spec/javascripts/deploy_keys/components/key_spec.js
+++ b/spec/javascripts/deploy_keys/components/key_spec.js
@@ -53,18 +53,24 @@ describe('Deploy keys key', () => {
).toBe('Remove');
});
- it('shows write access text when key has write access', (done) => {
- vm.deployKey.can_push = true;
+ it('shows write access title when key has write access', (done) => {
+ vm.deployKey.deploy_keys_projects[0].can_push = true;
Vue.nextTick(() => {
expect(
- vm.$el.querySelector('.write-access-allowed'),
- ).not.toBeNull();
-
- expect(
- vm.$el.querySelector('.write-access-allowed').textContent.trim(),
+ vm.$el.querySelector('.deploy-project-label').getAttribute('data-original-title'),
).toBe('Write access allowed');
+ done();
+ });
+ });
+
+ it('does not show write access title when key has write access', (done) => {
+ vm.deployKey.deploy_keys_projects[0].can_push = false;
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.deploy-project-label').getAttribute('data-original-title'),
+ ).toBe('Read access only');
done();
});
});
diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js
index 0e141adb628..7a34126eef7 100644
--- a/spec/javascripts/environments/environment_item_spec.js
+++ b/spec/javascripts/environments/environment_item_spec.js
@@ -68,7 +68,7 @@ describe('Environment item', () => {
username: 'root',
id: 1,
state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
commit: {
@@ -84,7 +84,7 @@ describe('Environment item', () => {
username: 'root',
id: 1,
state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index d02adb25b4e..a41a4e5a3f7 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import environmentsComponent from '~/environments/components/environments_app.vue';
import { environment, folder } from './mock_data';
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index 4ea4d9d7499..a085074d312 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue';
import { environmentsList } from '../mock_data';
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
index 2ecb64d84b5..0684c3498a2 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import AjaxCache from '~/lib/utils/ajax_cache';
import UsersCache from '~/lib/utils/users_cache';
diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb
index d26ea3febe8..8e74c4f859c 100644
--- a/spec/javascripts/fixtures/clusters.rb
+++ b/spec/javascripts/fixtures/clusters.rb
@@ -31,19 +31,4 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
-
- context 'rendering non-empty state' do
- before do
- cluster
- end
-
- it 'clusters/index_cluster.html.raw' do |example|
- get :index,
- namespace_id: namespace,
- project_id: project
-
- expect(response).to be_success
- store_frontend_fixture(response, example.description)
- end
- end
end
diff --git a/spec/javascripts/fixtures/create_item_dropdown.html.haml b/spec/javascripts/fixtures/create_item_dropdown.html.haml
new file mode 100644
index 00000000000..d4d91b93caf
--- /dev/null
+++ b/spec/javascripts/fixtures/create_item_dropdown.html.haml
@@ -0,0 +1,13 @@
+.js-create-item-dropdown-fixture-root
+ %input{ name: 'variable[environment]', type: 'hidden' }
+ = dropdown_tag('some label',
+ options: { toggle_class: 'js-dropdown-menu-toggle',
+ content_class: 'js-dropdown-content',
+ filter: true,
+ dropdown_class: "dropdown-menu-selectable",
+ footer_content: true }) do
+ %ul.dropdown-footer-list
+ %li
+ %button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item" }
+ Create wildcard
+ %code
diff --git a/spec/javascripts/fixtures/projects.json b/spec/javascripts/fixtures/projects.json
index 1339ee00870..68a150f602a 100644
--- a/spec/javascripts/fixtures/projects.json
+++ b/spec/javascripts/fixtures/projects.json
@@ -14,7 +14,7 @@
"username": "root",
"id": 1,
"state": "active",
- "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
"web_url": "http://localhost:3000/u/root"
},
"name": "test",
diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb
new file mode 100644
index 00000000000..703cd3d49fa
--- /dev/null
+++ b/spec/javascripts/fixtures/search.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe SearchController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('search/')
+ end
+
+ it 'search/show.html.raw' do |example|
+ get :show
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js
index 97e3ab682c5..7198dbd4cf2 100644
--- a/spec/javascripts/flash_spec.js
+++ b/spec/javascripts/flash_spec.js
@@ -183,11 +183,15 @@ describe('Flash', () => {
});
it('adds flash element into container', () => {
- flash('test');
+ flash('test', 'alert', document, null, false, true);
expect(
document.querySelector('.flash-alert'),
).not.toBeNull();
+
+ expect(
+ document.body.className,
+ ).toContain('flash-shown');
});
it('adds flash into specified parent', () => {
@@ -220,13 +224,17 @@ describe('Flash', () => {
});
it('removes element after clicking', () => {
- flash('test', 'alert', document, null, false);
+ flash('test', 'alert', document, null, false, true);
document.querySelector('.flash-alert').click();
expect(
document.querySelector('.flash-alert'),
).toBeNull();
+
+ expect(
+ document.body.className,
+ ).not.toContain('flash-shown');
});
describe('with actionConfig', () => {
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
index a3fa07d5bc2..eb9330f5e5b 100644
--- a/spec/javascripts/fly_out_nav_spec.js
+++ b/spec/javascripts/fly_out_nav_spec.js
@@ -167,30 +167,26 @@ describe('Fly out sidebar navigation', () => {
describe('mouseEnterTopItems', () => {
beforeEach(() => {
- jasmine.clock().install();
-
el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute; top: 0; left: 100px; height: 200px;"></div>';
});
- afterEach(() => {
- jasmine.clock().uninstall();
- });
-
- it('shows sub-items after 0ms if no menu is open', () => {
+ it('shows sub-items after 0ms if no menu is open', (done) => {
mouseEnterTopItems(el);
expect(
getHideSubItemsInterval(),
).toBe(0);
- jasmine.clock().tick(0);
+ setTimeout(() => {
+ expect(
+ el.querySelector('.sidebar-sub-level-items').style.display,
+ ).toBe('block');
- expect(
- el.querySelector('.sidebar-sub-level-items').style.display,
- ).toBe('block');
+ done();
+ });
});
- it('shows sub-items after 300ms if a menu is currently open', () => {
+ it('shows sub-items after 300ms if a menu is currently open', (done) => {
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top,
@@ -203,17 +199,19 @@ describe('Fly out sidebar navigation', () => {
clientY: el.getBoundingClientRect().top + 10,
});
- mouseEnterTopItems(el);
+ mouseEnterTopItems(el, 0);
expect(
getHideSubItemsInterval(),
).toBe(300);
- jasmine.clock().tick(300);
+ setTimeout(() => {
+ expect(
+ el.querySelector('.sidebar-sub-level-items').style.display,
+ ).toBe('block');
- expect(
- el.querySelector('.sidebar-sub-level-items').style.display,
- ).toBe('block');
+ done();
+ });
});
});
diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js
index 97e39f6411b..8338efe915b 100644
--- a/spec/javascripts/groups/components/app_spec.js
+++ b/spec/javascripts/groups/components/app_spec.js
@@ -256,6 +256,36 @@ describe('AppComponent', () => {
});
});
+ describe('showLeaveGroupModal', () => {
+ it('caches candidate group (as props) which is to be left', () => {
+ const group = Object.assign({}, mockParentGroupItem);
+ expect(vm.targetGroup).toBe(null);
+ expect(vm.targetParentGroup).toBe(null);
+ vm.showLeaveGroupModal(group, mockParentGroupItem);
+ expect(vm.targetGroup).not.toBe(null);
+ expect(vm.targetParentGroup).not.toBe(null);
+ });
+
+ it('updates props which show modal confirmation dialog', () => {
+ const group = Object.assign({}, mockParentGroupItem);
+ expect(vm.showModal).toBeFalsy();
+ expect(vm.groupLeaveConfirmationMessage).toBe('');
+ vm.showLeaveGroupModal(group, mockParentGroupItem);
+ expect(vm.showModal).toBeTruthy();
+ expect(vm.groupLeaveConfirmationMessage).toBe(`Are you sure you want to leave the "${group.fullName}" group?`);
+ });
+ });
+
+ describe('hideLeaveGroupModal', () => {
+ it('hides modal confirmation which is shown before leaving the group', () => {
+ const group = Object.assign({}, mockParentGroupItem);
+ vm.showLeaveGroupModal(group, mockParentGroupItem);
+ expect(vm.showModal).toBeTruthy();
+ vm.hideLeaveGroupModal();
+ expect(vm.showModal).toBeFalsy();
+ });
+ });
+
describe('leaveGroup', () => {
let groupItem;
let childGroupItem;
@@ -265,21 +295,24 @@ describe('AppComponent', () => {
groupItem.children = mockChildren;
childGroupItem = groupItem.children[0];
groupItem.isChildrenLoading = false;
+ vm.targetGroup = childGroupItem;
+ vm.targetParentGroup = groupItem;
});
- it('should leave group and remove group item from tree', (done) => {
+ it('hides modal confirmation leave group and remove group item from tree', (done) => {
const notice = `You left the "${childGroupItem.fullName}" group.`;
spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ notice }));
spyOn(vm.store, 'removeGroup').and.callThrough();
spyOn(window, 'Flash');
spyOn($, 'scrollTo');
- vm.leaveGroup(childGroupItem, groupItem);
- expect(childGroupItem.isBeingRemoved).toBeTruthy();
- expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
+ vm.leaveGroup();
+ expect(vm.showModal).toBeFalsy();
+ expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
+ expect(vm.service.leaveGroup).toHaveBeenCalledWith(vm.targetGroup.leavePath);
setTimeout(() => {
expect($.scrollTo).toHaveBeenCalledWith(0);
- expect(vm.store.removeGroup).toHaveBeenCalledWith(childGroupItem, groupItem);
+ expect(vm.store.removeGroup).toHaveBeenCalledWith(vm.targetGroup, vm.targetParentGroup);
expect(window.Flash).toHaveBeenCalledWith(notice, 'notice');
done();
}, 0);
@@ -291,13 +324,13 @@ describe('AppComponent', () => {
spyOn(vm.store, 'removeGroup').and.callThrough();
spyOn(window, 'Flash');
- vm.leaveGroup(childGroupItem, groupItem);
- expect(childGroupItem.isBeingRemoved).toBeTruthy();
+ vm.leaveGroup();
+ expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
setTimeout(() => {
expect(vm.store.removeGroup).not.toHaveBeenCalled();
expect(window.Flash).toHaveBeenCalledWith(message);
- expect(childGroupItem.isBeingRemoved).toBeFalsy();
+ expect(vm.targetGroup.isBeingRemoved).toBeFalsy();
done();
}, 0);
});
@@ -309,12 +342,12 @@ describe('AppComponent', () => {
spyOn(window, 'Flash');
vm.leaveGroup(childGroupItem, groupItem);
- expect(childGroupItem.isBeingRemoved).toBeTruthy();
+ expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
setTimeout(() => {
expect(vm.store.removeGroup).not.toHaveBeenCalled();
expect(window.Flash).toHaveBeenCalledWith(message);
- expect(childGroupItem.isBeingRemoved).toBeFalsy();
+ expect(vm.targetGroup.isBeingRemoved).toBeFalsy();
done();
}, 0);
});
@@ -364,7 +397,7 @@ describe('AppComponent', () => {
Vue.nextTick(() => {
expect(eventHub.$on).toHaveBeenCalledWith('fetchPage', jasmine.any(Function));
expect(eventHub.$on).toHaveBeenCalledWith('toggleChildren', jasmine.any(Function));
- expect(eventHub.$on).toHaveBeenCalledWith('leaveGroup', jasmine.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('showLeaveGroupModal', jasmine.any(Function));
expect(eventHub.$on).toHaveBeenCalledWith('updatePagination', jasmine.any(Function));
expect(eventHub.$on).toHaveBeenCalledWith('updateGroups', jasmine.any(Function));
newVm.$destroy();
@@ -404,7 +437,7 @@ describe('AppComponent', () => {
Vue.nextTick(() => {
expect(eventHub.$off).toHaveBeenCalledWith('fetchPage', jasmine.any(Function));
expect(eventHub.$off).toHaveBeenCalledWith('toggleChildren', jasmine.any(Function));
- expect(eventHub.$off).toHaveBeenCalledWith('leaveGroup', jasmine.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('showLeaveGroupModal', jasmine.any(Function));
expect(eventHub.$off).toHaveBeenCalledWith('updatePagination', jasmine.any(Function));
expect(eventHub.$off).toHaveBeenCalledWith('updateGroups', jasmine.any(Function));
done();
@@ -439,5 +472,14 @@ describe('AppComponent', () => {
done();
});
});
+
+ it('renders modal confirmation dialog', () => {
+ vm.groupLeaveConfirmationMessage = 'Are you sure you want to leave the "foo" group?';
+ vm.showModal = true;
+ const modalDialogEl = vm.$el.querySelector('.modal');
+ expect(modalDialogEl).not.toBe(null);
+ expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?');
+ expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave');
+ });
});
});
diff --git a/spec/javascripts/groups/components/item_actions_spec.js b/spec/javascripts/groups/components/item_actions_spec.js
index 6d6fb410859..acccbe639c4 100644
--- a/spec/javascripts/groups/components/item_actions_spec.js
+++ b/spec/javascripts/groups/components/item_actions_spec.js
@@ -26,32 +26,12 @@ describe('ItemActionsComponent', () => {
vm.$destroy();
});
- describe('computed', () => {
- describe('leaveConfirmationMessage', () => {
- it('should return appropriate string for leave group confirmation', () => {
- expect(vm.leaveConfirmationMessage).toBe('Are you sure you want to leave the "platform / hardware" group?');
- });
- });
- });
-
describe('methods', () => {
describe('onLeaveGroup', () => {
- it('should change `modalStatus` prop to `true` which shows confirmation dialog', () => {
- expect(vm.modalStatus).toBeFalsy();
- vm.onLeaveGroup();
- expect(vm.modalStatus).toBeTruthy();
- });
- });
-
- describe('leaveGroup', () => {
- it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => {
+ it('emits `showLeaveGroupModal` event with `group` and `parentGroup` props', () => {
spyOn(eventHub, '$emit');
- vm.modalStatus = true;
-
- vm.leaveGroup();
-
- expect(vm.modalStatus).toBeFalsy();
- expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup);
+ vm.onLeaveGroup();
+ expect(eventHub.$emit).toHaveBeenCalledWith('showLeaveGroupModal', vm.group, vm.parentGroup);
});
});
});
@@ -72,7 +52,8 @@ describe('ItemActionsComponent', () => {
expect(editBtn.getAttribute('href')).toBe(group.editPath);
expect(editBtn.getAttribute('aria-label')).toBe('Edit group');
expect(editBtn.dataset.originalTitle).toBe('Edit group');
- expect(editBtn.querySelector('i.fa.fa-cogs')).toBeDefined();
+ expect(editBtn.querySelectorAll('svg use').length).not.toBe(0);
+ expect(editBtn.querySelector('svg use').getAttribute('xlink:href')).toContain('#settings');
newVm.$destroy();
});
@@ -88,17 +69,10 @@ describe('ItemActionsComponent', () => {
expect(leaveBtn.getAttribute('href')).toBe(group.leavePath);
expect(leaveBtn.getAttribute('aria-label')).toBe('Leave this group');
expect(leaveBtn.dataset.originalTitle).toBe('Leave this group');
- expect(leaveBtn.querySelector('i.fa.fa-sign-out')).toBeDefined();
+ expect(leaveBtn.querySelectorAll('svg use').length).not.toBe(0);
+ expect(leaveBtn.querySelector('svg use').getAttribute('xlink:href')).toContain('#leave');
newVm.$destroy();
});
-
- it('should show modal dialog when `modalStatus` is set to `true`', () => {
- vm.modalStatus = true;
- const modalDialogEl = vm.$el.querySelector('.modal');
- expect(modalDialogEl).toBeDefined();
- expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?');
- expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave');
- });
});
});
diff --git a/spec/javascripts/helpers/class_spec_helper_spec.js b/spec/javascripts/helpers/class_spec_helper_spec.js
index 686b8eaed31..1415ffb7eb3 100644
--- a/spec/javascripts/helpers/class_spec_helper_spec.js
+++ b/spec/javascripts/helpers/class_spec_helper_spec.js
@@ -3,7 +3,7 @@
import './class_spec_helper';
describe('ClassSpecHelper', () => {
- describe('itShouldBeAStaticMethod', function () {
+ describe('itShouldBeAStaticMethod', () => {
beforeEach(() => {
class TestClass {
instanceMethod() { this.prop = 'val'; }
@@ -14,23 +14,5 @@ describe('ClassSpecHelper', () => {
});
ClassSpecHelper.itShouldBeAStaticMethod(ClassSpecHelper, 'itShouldBeAStaticMethod');
-
- it('should have a defined spec', () => {
- expect(ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'staticMethod').description).toBe('should be a static method');
- });
-
- it('should pass for a static method', () => {
- const spec = ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'staticMethod');
- expect(spec.status()).toBe('passed');
- });
-
- it('should fail for an instance method', (done) => {
- const spec = ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'instanceMethod');
- spec.resultCallback = (result) => {
- expect(result.status).toBe('failed');
- done();
- };
- spec.execute();
- });
});
});
diff --git a/spec/javascripts/helpers/user_mock_data_helper.js b/spec/javascripts/helpers/user_mock_data_helper.js
index a9783ea065c..323fee3767e 100644
--- a/spec/javascripts/helpers/user_mock_data_helper.js
+++ b/spec/javascripts/helpers/user_mock_data_helper.js
@@ -4,7 +4,7 @@ export default {
for (let i = 0; i < numberUsers; i = i += 1) {
users.push(
{
- avatar: 'http://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ avatar: 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
id: (i + 1),
name: `GitLab User ${i}`,
username: `gitlab${i}`,
diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js
index 9033eb9ce02..d0fba908e34 100644
--- a/spec/javascripts/integrations/integration_settings_form_spec.js
+++ b/spec/javascripts/integrations/integration_settings_form_spec.js
@@ -1,3 +1,5 @@
+import MockAdaptor from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import IntegrationSettingsForm from '~/integrations/integration_settings_form';
describe('IntegrationSettingsForm', () => {
@@ -109,91 +111,117 @@ describe('IntegrationSettingsForm', () => {
describe('testSettings', () => {
let integrationSettingsForm;
let formData;
+ let mock;
beforeEach(() => {
+ mock = new MockAdaptor(axios);
+
+ spyOn(axios, 'put').and.callThrough();
+
integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
formData = integrationSettingsForm.$form.serialize();
});
- it('should make an ajax request with provided `formData`', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ afterEach(() => {
+ mock.restore();
+ });
- integrationSettingsForm.testSettings(formData);
+ it('should make an ajax request with provided `formData`', (done) => {
+ integrationSettingsForm.testSettings(formData)
+ .then(() => {
+ expect(axios.put).toHaveBeenCalledWith(integrationSettingsForm.testEndPoint, formData);
- expect($.ajax).toHaveBeenCalledWith({
- type: 'PUT',
- url: integrationSettingsForm.testEndPoint,
- data: formData,
- });
+ done();
+ })
+ .catch(done.fail);
});
- it('should show error Flash with `Save anyway` action if ajax request responds with error in test', () => {
+ it('should show error Flash with `Save anyway` action if ajax request responds with error in test', (done) => {
const errorMessage = 'Test failed.';
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
-
- integrationSettingsForm.testSettings(formData);
+ mock.onPut(integrationSettingsForm.testEndPoint).reply(200, {
+ error: true,
+ message: errorMessage,
+ service_response: 'some error',
+ });
- deferred.resolve({ error: true, message: errorMessage, service_response: 'some error' });
+ integrationSettingsForm.testSettings(formData)
+ .then(() => {
+ const $flashContainer = $('.flash-container');
+ expect($flashContainer.find('.flash-text').text().trim()).toEqual('Test failed. some error');
+ expect($flashContainer.find('.flash-action')).toBeDefined();
+ expect($flashContainer.find('.flash-action').text().trim()).toEqual('Save anyway');
- const $flashContainer = $('.flash-container');
- expect($flashContainer.find('.flash-text').text().trim()).toEqual('Test failed. some error');
- expect($flashContainer.find('.flash-action')).toBeDefined();
- expect($flashContainer.find('.flash-action').text().trim()).toEqual('Save anyway');
+ done();
+ })
+ .catch(done.fail);
});
- it('should submit form if ajax request responds without any error in test', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('should submit form if ajax request responds without any error in test', (done) => {
+ spyOn(integrationSettingsForm.$form, 'submit');
- integrationSettingsForm.testSettings(formData);
+ mock.onPut(integrationSettingsForm.testEndPoint).reply(200, {
+ error: false,
+ });
- spyOn(integrationSettingsForm.$form, 'submit');
- deferred.resolve({ error: false });
+ integrationSettingsForm.testSettings(formData)
+ .then(() => {
+ expect(integrationSettingsForm.$form.submit).toHaveBeenCalled();
- expect(integrationSettingsForm.$form.submit).toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
});
- it('should submit form when clicked on `Save anyway` action of error Flash', () => {
- const errorMessage = 'Test failed.';
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('should submit form when clicked on `Save anyway` action of error Flash', (done) => {
+ spyOn(integrationSettingsForm.$form, 'submit');
- integrationSettingsForm.testSettings(formData);
+ const errorMessage = 'Test failed.';
+ mock.onPut(integrationSettingsForm.testEndPoint).reply(200, {
+ error: true,
+ message: errorMessage,
+ });
- deferred.resolve({ error: true, message: errorMessage });
+ integrationSettingsForm.testSettings(formData)
+ .then(() => {
+ const $flashAction = $('.flash-container .flash-action');
+ expect($flashAction).toBeDefined();
- const $flashAction = $('.flash-container .flash-action');
- expect($flashAction).toBeDefined();
+ $flashAction.get(0).click();
+ })
+ .then(() => {
+ expect(integrationSettingsForm.$form.submit).toHaveBeenCalled();
- spyOn(integrationSettingsForm.$form, 'submit');
- $flashAction.get(0).click();
- expect(integrationSettingsForm.$form.submit).toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
});
- it('should show error Flash if ajax request failed', () => {
+ it('should show error Flash if ajax request failed', (done) => {
const errorMessage = 'Something went wrong on our end.';
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
- integrationSettingsForm.testSettings(formData);
+ mock.onPut(integrationSettingsForm.testEndPoint).networkError();
- deferred.reject();
+ integrationSettingsForm.testSettings(formData)
+ .then(() => {
+ expect($('.flash-container .flash-text').text().trim()).toEqual(errorMessage);
- expect($('.flash-container .flash-text').text().trim()).toEqual(errorMessage);
+ done();
+ })
+ .catch(done.fail);
});
- it('should always call `toggleSubmitBtnState` with `false` once request is completed', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
-
- integrationSettingsForm.testSettings(formData);
+ it('should always call `toggleSubmitBtnState` with `false` once request is completed', (done) => {
+ mock.onPut(integrationSettingsForm.testEndPoint).networkError();
spyOn(integrationSettingsForm, 'toggleSubmitBtnState');
- deferred.reject();
- expect(integrationSettingsForm.toggleSubmitBtnState).toHaveBeenCalledWith(false);
+ integrationSettingsForm.testSettings(formData)
+ .then(() => {
+ expect(integrationSettingsForm.toggleSubmitBtnState).toHaveBeenCalledWith(false);
+
+ done();
+ })
+ .catch(done.fail);
});
});
});
diff --git a/spec/javascripts/issuable_spec.js b/spec/javascripts/issuable_spec.js
index 5a9112716f4..d53ffecbd35 100644
--- a/spec/javascripts/issuable_spec.js
+++ b/spec/javascripts/issuable_spec.js
@@ -1,3 +1,5 @@
+import MockAdaptor from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import IssuableIndex from '~/issuable_index';
describe('Issuable', () => {
@@ -19,6 +21,8 @@ describe('Issuable', () => {
});
describe('resetIncomingEmailToken', () => {
+ let mock;
+
beforeEach(() => {
const element = document.createElement('a');
element.classList.add('incoming-email-token-reset');
@@ -30,14 +34,28 @@ describe('Issuable', () => {
document.body.appendChild(input);
Issuable = new IssuableIndex('issue_');
+
+ mock = new MockAdaptor(axios);
+
+ mock.onPut('foo').reply(200, {
+ new_address: 'testing123',
+ });
});
- it('should send request to reset email token', () => {
- spyOn(jQuery, 'ajax').and.callThrough();
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('should send request to reset email token', (done) => {
+ spyOn(axios, 'put').and.callThrough();
document.querySelector('.incoming-email-token-reset').click();
- expect(jQuery.ajax).toHaveBeenCalled();
- expect(jQuery.ajax.calls.argsFor(0)[0].url).toEqual('foo');
+ setTimeout(() => {
+ expect(axios.put).toHaveBeenCalledWith('foo');
+ expect($('#issuable_email').val()).toBe('testing123');
+
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 1454ca52018..1c9f48028f2 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -59,7 +59,7 @@ describe('Issuable output', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
realtimeRequestCount = 0;
vm.poll.stop();
@@ -218,6 +218,39 @@ describe('Issuable output', () => {
});
});
+ describe('shows dialog when issue has unsaved changed', () => {
+ it('confirms on title change', (done) => {
+ vm.showForm = true;
+ vm.state.titleText = 'title has changed';
+ const e = { returnValue: null };
+ vm.handleBeforeUnloadEvent(e);
+ Vue.nextTick(() => {
+ expect(e.returnValue).not.toBeNull();
+ done();
+ });
+ });
+
+ it('confirms on description change', (done) => {
+ vm.showForm = true;
+ vm.state.descriptionText = 'description has changed';
+ const e = { returnValue: null };
+ vm.handleBeforeUnloadEvent(e);
+ Vue.nextTick(() => {
+ expect(e.returnValue).not.toBeNull();
+ done();
+ });
+ });
+
+ it('does nothing when nothing has changed', (done) => {
+ const e = { returnValue: null };
+ vm.handleBeforeUnloadEvent(e);
+ Vue.nextTick(() => {
+ expect(e.returnValue).toBeNull();
+ done();
+ });
+ });
+ });
+
describe('error when updating', () => {
beforeEach(() => {
spyOn(window, 'Flash').and.callThrough();
diff --git a/spec/javascripts/issue_show/components/fields/description_template_spec.js b/spec/javascripts/issue_show/components/fields/description_template_spec.js
index 2b7ee65094b..30441faf844 100644
--- a/spec/javascripts/issue_show/components/fields/description_template_spec.js
+++ b/spec/javascripts/issue_show/components/fields/description_template_spec.js
@@ -1,7 +1,5 @@
import Vue from 'vue';
import descriptionTemplate from '~/issue_show/components/fields/description_template.vue';
-import '~/templates/issuable_template_selector';
-import '~/templates/issuable_template_selectors';
describe('Issue description template component', () => {
let vm;
diff --git a/spec/javascripts/issue_show/components/form_spec.js b/spec/javascripts/issue_show/components/form_spec.js
index 000b53af016..50ce019c32a 100644
--- a/spec/javascripts/issue_show/components/form_spec.js
+++ b/spec/javascripts/issue_show/components/form_spec.js
@@ -1,7 +1,5 @@
import Vue from 'vue';
import formComponent from '~/issue_show/components/form.vue';
-import '~/templates/issuable_template_selector';
-import '~/templates/issuable_template_selectors';
describe('Inline edit form component', () => {
let vm;
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
index b740c9ed893..0452934ea9e 100644
--- a/spec/javascripts/job_spec.js
+++ b/spec/javascripts/job_spec.js
@@ -52,19 +52,13 @@ describe('Job', () => {
expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false);
expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
});
-
- it('displays the remove date correctly', () => {
- const removeDateElement = document.querySelector('.js-artifacts-remove');
- expect(removeDateElement.innerText.trim()).toBe('1 year remaining');
- });
});
describe('running build', () => {
it('updates the build trace on an interval', function () {
const deferred1 = $.Deferred();
const deferred2 = $.Deferred();
- const deferred3 = $.Deferred();
- spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
+ spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise());
spyOn(urlUtils, 'visitUrl');
deferred1.resolve({
@@ -75,9 +69,7 @@ describe('Job', () => {
complete: false,
});
- deferred2.resolve();
-
- deferred3.resolve({
+ deferred2.resolve({
html: '<span>More</span>',
status: 'running',
state: 'finalstate',
@@ -99,9 +91,8 @@ describe('Job', () => {
it('replaces the entire build trace', () => {
const deferred1 = $.Deferred();
const deferred2 = $.Deferred();
- const deferred3 = $.Deferred();
- spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
+ spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise());
spyOn(urlUtils, 'visitUrl');
@@ -112,9 +103,7 @@ describe('Job', () => {
complete: false,
});
- deferred2.resolve();
-
- deferred3.resolve({
+ deferred2.resolve({
html: '<span>Different</span>',
status: 'running',
append: false,
@@ -175,9 +164,8 @@ describe('Job', () => {
it('shows incremented size', () => {
const deferred1 = $.Deferred();
const deferred2 = $.Deferred();
- const deferred3 = $.Deferred();
- spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
+ spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise());
spyOn(urlUtils, 'visitUrl');
@@ -189,8 +177,6 @@ describe('Job', () => {
total: 100,
});
- deferred2.resolve();
-
this.job = new Job();
expect(
@@ -199,7 +185,7 @@ describe('Job', () => {
jasmine.clock().tick(4001);
- deferred3.resolve({
+ deferred2.resolve({
html: '<span>Update</span>',
status: 'success',
append: true,
diff --git a/spec/javascripts/jobs/header_spec.js b/spec/javascripts/jobs/header_spec.js
index 83395ea451e..a9df0418d5d 100644
--- a/spec/javascripts/jobs/header_spec.js
+++ b/spec/javascripts/jobs/header_spec.js
@@ -31,6 +31,7 @@ describe('Job details header', () => {
email: 'foo@bar.com',
avatar_url: 'link',
},
+ started: '2018-01-08T09:48:27.319Z',
new_issue_path: 'path',
},
isLoading: false,
@@ -43,15 +44,32 @@ describe('Job details header', () => {
vm.$destroy();
});
- it('should render provided job information', () => {
- expect(
- vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
- ).toEqual('failed Job #123 triggered 3 weeks ago by Foo');
+ describe('triggered job', () => {
+ beforeEach(() => {
+ vm = mountComponent(HeaderComponent, props);
+ });
+
+ it('should render provided job information', () => {
+ expect(
+ vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
+ ).toEqual('failed Job #123 triggered 3 weeks ago by Foo');
+ });
+
+ it('should render new issue link', () => {
+ expect(
+ vm.$el.querySelector('.js-new-issue').getAttribute('href'),
+ ).toEqual(props.job.new_issue_path);
+ });
});
- it('should render new issue link', () => {
- expect(
- vm.$el.querySelector('.js-new-issue').getAttribute('href'),
- ).toEqual(props.job.new_issue_path);
+ describe('created job', () => {
+ it('should render created key', () => {
+ props.job.started = false;
+ vm = mountComponent(HeaderComponent, props);
+
+ expect(
+ vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
+ ).toEqual('failed Job #123 created 3 weeks ago by Foo');
+ });
});
});
diff --git a/spec/javascripts/jobs/job_details_mediator_spec.js b/spec/javascripts/jobs/job_details_mediator_spec.js
index 3069a0cd60e..ca5c9cf87e4 100644
--- a/spec/javascripts/jobs/job_details_mediator_spec.js
+++ b/spec/javascripts/jobs/job_details_mediator_spec.js
@@ -12,6 +12,10 @@ describe('JobMediator', () => {
mock = new MockAdapter(axios);
});
+ afterEach(() => {
+ mock.restore();
+ });
+
it('should set defaults', () => {
expect(mediator.store).toBeDefined();
expect(mediator.service).toBeDefined();
@@ -24,10 +28,6 @@ describe('JobMediator', () => {
mock.onGet().reply(200, job, {});
});
- afterEach(() => {
- mock.restore();
- });
-
it('should store received data', (done) => {
mediator.fetchJob();
setTimeout(() => {
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index 43532275121..43589d54be4 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -37,7 +37,7 @@ export default {
username: 'root',
id: 1,
state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
erase_path: '/root/ci-mock/-/jobs/4757/erase',
@@ -54,7 +54,7 @@ export default {
username: 'root',
id: 1,
state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
active: false,
@@ -107,10 +107,10 @@ export default {
username: 'root',
id: 1,
state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
- author_gravatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ author_gravatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
commit_url: 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
},
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 0a9d815f469..1052b4e7c20 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -1,6 +1,8 @@
/* eslint-disable promise/catch-or-return */
import * as commonUtils from '~/lib/utils/common_utils';
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
describe('common_utils', () => {
describe('parseUrl', () => {
@@ -413,37 +415,48 @@ describe('common_utils', () => {
describe('setCiStatusFavicon', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
+ let mock;
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
document.body.appendChild(favicon);
+ mock = new MockAdapter(axios);
});
afterEach(() => {
+ mock.restore();
document.body.removeChild(document.getElementById('favicon'));
});
- it('should reset favicon in case of error', () => {
- const favicon = document.getElementById('favicon');
- spyOn($, 'ajax').and.callFake(function (options) {
- options.error();
- expect(favicon.getAttribute('href')).toEqual('null');
- });
+ it('should reset favicon in case of error', (done) => {
+ mock.onGet(BUILD_URL).networkError();
- commonUtils.setCiStatusFavicon(BUILD_URL);
+ commonUtils.setCiStatusFavicon(BUILD_URL)
+ .then(() => {
+ const favicon = document.getElementById('favicon');
+ expect(favicon.getAttribute('href')).toEqual('null');
+ done();
+ })
+ // Error is already caught in catch() block of setCiStatusFavicon,
+ // It won't throw another error for us to catch
+ .catch(done.fail);
});
- it('should set page favicon to CI status favicon based on provided status', () => {
+ it('should set page favicon to CI status favicon based on provided status', (done) => {
const FAVICON_PATH = '//icon_status_success';
- const favicon = document.getElementById('favicon');
- spyOn($, 'ajax').and.callFake(function (options) {
- options.success({ favicon: FAVICON_PATH });
- expect(favicon.getAttribute('href')).toEqual(FAVICON_PATH);
+ mock.onGet(BUILD_URL).reply(200, {
+ favicon: FAVICON_PATH,
});
- commonUtils.setCiStatusFavicon(BUILD_URL);
+ commonUtils.setCiStatusFavicon(BUILD_URL)
+ .then(() => {
+ const favicon = document.getElementById('favicon');
+ expect(favicon.getAttribute('href')).toEqual(FAVICON_PATH);
+ done();
+ })
+ .catch(done.fail);
});
});
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index 6f8dad6b835..69a23d7b2f3 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -63,13 +63,13 @@ describe('text_utility', () => {
});
});
- describe('stripeHtml', () => {
+ describe('stripHtml', () => {
it('replaces html tag with the default replacement', () => {
- expect(textUtils.stripeHtml('This is a text with <p>html</p>.')).toEqual('This is a text with html.');
+ expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual('This is a text with html.');
});
it('replaces html tags with the provided replacement', () => {
- expect(textUtils.stripeHtml('This is a text with <p>html</p>.', ' ')).toEqual('This is a text with html .');
+ expect(textUtils.stripHtml('This is a text with <p>html</p>.', ' ')).toEqual('This is a text with html .');
});
});
});
diff --git a/spec/javascripts/lib/utils/users_cache_spec.js b/spec/javascripts/lib/utils/users_cache_spec.js
index ec6ea35952b..50371c8c5f6 100644
--- a/spec/javascripts/lib/utils/users_cache_spec.js
+++ b/spec/javascripts/lib/utils/users_cache_spec.js
@@ -92,7 +92,9 @@ describe('UsersCache', () => {
apiSpy = (query, options) => {
expect(query).toBe('');
expect(options).toEqual({ username: dummyUsername });
- return Promise.resolve([dummyUser]);
+ return Promise.resolve({
+ data: [dummyUser],
+ });
};
UsersCache.retrieve(dummyUsername)
diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js
index e983e4de3fc..5d0ee91d977 100644
--- a/spec/javascripts/merge_request_notes_spec.js
+++ b/spec/javascripts/merge_request_notes_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import 'autosize';
import '~/gl_form';
import '~/lib/utils/text_utility';
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 2f02c11482f..bae3219b043 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -19,17 +19,24 @@ import IssuablesHelper from '~/helpers/issuables_helper';
$('input[type=checkbox]').attr('checked', true)[0].dispatchEvent(changeEvent);
return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
});
- return it('submits an ajax request on tasklist:changed', function() {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
+
+ it('submits an ajax request on tasklist:changed', (done) => {
+ spyOn(jQuery, 'ajax').and.callFake((req) => {
expect(req.type).toBe('PATCH');
expect(req.url).toBe(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`);
- return expect(req.data.merge_request.description).not.toBe(null);
+ expect(req.data.merge_request.description).not.toBe(null);
+ done();
});
- return $('.js-task-list-field').trigger('tasklist:changed');
+
+ $('.js-task-list-field').trigger('tasklist:changed');
});
});
describe('class constructor', () => {
+ beforeEach(() => {
+ spyOn(jQuery, 'ajax').and.stub();
+ });
+
it('calls .initCloseReopenReport', () => {
spyOn(IssuablesHelper, 'initCloseReopenReport');
@@ -63,8 +70,8 @@ import IssuablesHelper from '~/helpers/issuables_helper';
beforeEach(() => {
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
this.el = document.querySelector('.js-issuable-actions');
- const merge = new MergeRequest();
- merge.hideCloseButton();
+ new MergeRequest(); // eslint-disable-line no-new
+ MergeRequest.hideCloseButton();
});
it('hides the dropdown close item and selects the next item', () => {
@@ -83,8 +90,7 @@ import IssuablesHelper from '~/helpers/issuables_helper';
beforeEach(() => {
loadFixtures('merge_requests/merge_request_of_current_user.html.raw');
this.el = document.querySelector('.js-issuable-actions');
- const merge = new MergeRequest();
- merge.hideCloseButton();
+ MergeRequest.hideCloseButton();
});
it('hides the close button', () => {
diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
index 481b46c3ac6..6fa6f44f953 100644
--- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
+++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
@@ -1,7 +1,9 @@
/* eslint-disable no-new */
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
-import '~/flash';
+import timeoutPromise from './helpers/set_timeout_promise_helper';
describe('Mini Pipeline Graph Dropdown', () => {
preloadFixtures('static/mini_dropdown_graph.html.raw');
@@ -27,6 +29,16 @@ describe('Mini Pipeline Graph Dropdown', () => {
});
describe('When dropdown is clicked', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
it('should call getBuildsList', () => {
const getBuildsListSpy = spyOn(
MiniPipelineGraph.prototype,
@@ -41,46 +53,55 @@ describe('Mini Pipeline Graph Dropdown', () => {
});
it('should make a request to the endpoint provided in the html', () => {
- const ajaxSpy = spyOn($, 'ajax').and.callFake(function () {});
+ const ajaxSpy = spyOn(axios, 'get').and.callThrough();
+
+ mock.onGet('foobar').reply(200, {
+ html: '',
+ });
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click();
- expect(ajaxSpy.calls.allArgs()[0][0].url).toEqual('foobar');
+ expect(ajaxSpy.calls.allArgs()[0][0]).toEqual('foobar');
});
- it('should not close when user uses cmd/ctrl + click', () => {
- spyOn($, 'ajax').and.callFake(function (params) {
- params.success({
- html: `<li>
- <a class="mini-pipeline-graph-dropdown-item" href="#">
- <span class="ci-status-icon ci-status-icon-failed"></span>
- <span class="ci-build-text">build</span>
- </a>
- <a class="ci-action-icon-wrapper js-ci-action-icon" href="#"></a>
- </li>`,
- });
+ it('should not close when user uses cmd/ctrl + click', (done) => {
+ mock.onGet('foobar').reply(200, {
+ html: `<li>
+ <a class="mini-pipeline-graph-dropdown-item" href="#">
+ <span class="ci-status-icon ci-status-icon-failed"></span>
+ <span class="ci-build-text">build</span>
+ </a>
+ <a class="ci-action-icon-wrapper js-ci-action-icon" href="#"></a>
+ </li>`,
});
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click();
- document.querySelector('a.mini-pipeline-graph-dropdown-item').click();
-
- expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true);
+ timeoutPromise()
+ .then(() => {
+ document.querySelector('a.mini-pipeline-graph-dropdown-item').click();
+ })
+ .then(timeoutPromise)
+ .then(() => {
+ expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true);
+ })
+ .then(done)
+ .catch(done.fail);
});
- });
- it('should close the dropdown when request returns an error', (done) => {
- spyOn($, 'ajax').and.callFake(options => options.error());
+ it('should close the dropdown when request returns an error', (done) => {
+ mock.onGet('foobar').networkError();
- new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
+ new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
- document.querySelector('.js-builds-dropdown-button').click();
+ document.querySelector('.js-builds-dropdown-button').click();
- setTimeout(() => {
- expect($('.js-builds-dropdown-tests .dropdown').hasClass('open')).toEqual(false);
- done();
- }, 0);
+ setTimeout(() => {
+ expect($('.js-builds-dropdown-tests .dropdown').hasClass('open')).toEqual(false);
+ done();
+ });
+ });
});
});
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index 9885b8a790f..eb8f6bbe50d 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -38,7 +38,7 @@ describe('Dashboard', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('shows up a loading state', (done) => {
diff --git a/spec/javascripts/notebook/cells/markdown_spec.js b/spec/javascripts/notebook/cells/markdown_spec.js
index a88e9ed3d99..02304bf5d7d 100644
--- a/spec/javascripts/notebook/cells/markdown_spec.js
+++ b/spec/javascripts/notebook/cells/markdown_spec.js
@@ -42,6 +42,18 @@ describe('Markdown component', () => {
expect(vm.$el.querySelector('.markdown h1')).not.toBeNull();
});
+ it('sanitizes output', (done) => {
+ Object.assign(cell, {
+ source: ['[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+Cg==)\n'],
+ });
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('a')).toBeNull();
+
+ done();
+ });
+ });
+
describe('katex', () => {
beforeEach(() => {
json = getJSONFixture('blob/notebook/math.json');
diff --git a/spec/javascripts/notebook/cells/output/html_sanitize_tests.js b/spec/javascripts/notebook/cells/output/html_sanitize_tests.js
new file mode 100644
index 00000000000..d587573fc9e
--- /dev/null
+++ b/spec/javascripts/notebook/cells/output/html_sanitize_tests.js
@@ -0,0 +1,66 @@
+export default {
+ 'protocol-based JS injection: simple, no spaces': {
+ input: '<a href="javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: simple, spaces before': {
+ input: '<a href="javascript :alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: simple, spaces after': {
+ input: '<a href="javascript: alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: simple, spaces before and after': {
+ input: '<a href="javascript : alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: preceding colon': {
+ input: '<a href=":javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: UTF-8 encoding': {
+ input: '<a href="javascript&#58;">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: long UTF-8 encoding': {
+ input: '<a href="javascript&#0058;">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: long UTF-8 encoding without semicolons': {
+ input: '<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: hex encoding': {
+ input: '<a href="javascript&#x3A;">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: long hex encoding': {
+ input: '<a href="javascript&#x003A;">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: hex encoding without semicolons': {
+ input: '<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: null char': {
+ input: '<a href=java\0script:alert("XSS")>foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: invalid URL char': {
+ input: '<img src=java\script:alert("XSS")>', // eslint-disable-line no-useless-escape
+ output: '<img>',
+ },
+ 'protocol-based JS injection: Unicode': {
+ input: '<a href="\u0001java\u0003script:alert(\'XSS\')">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: spaces and entities': {
+ input: '<a href=" &#14; javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'img on error': {
+ input: '<img src="x" onerror="alert(document.domain)" />',
+ output: '<img src="x">',
+ },
+};
diff --git a/spec/javascripts/notebook/cells/output/html_spec.js b/spec/javascripts/notebook/cells/output/html_spec.js
new file mode 100644
index 00000000000..9c5385f2922
--- /dev/null
+++ b/spec/javascripts/notebook/cells/output/html_spec.js
@@ -0,0 +1,29 @@
+import Vue from 'vue';
+import htmlOutput from '~/notebook/cells/output/html.vue';
+import sanitizeTests from './html_sanitize_tests';
+
+describe('html output cell', () => {
+ function createComponent(rawCode) {
+ const Component = Vue.extend(htmlOutput);
+
+ return new Component({
+ propsData: {
+ rawCode,
+ },
+ }).$mount();
+ }
+
+ describe('sanitizes output', () => {
+ Object.keys(sanitizeTests).forEach((key) => {
+ it(key, () => {
+ const test = sanitizeTests[key];
+ const vm = createComponent(test.input);
+ const outputEl = [...vm.$el.querySelectorAll('div')].pop();
+
+ expect(outputEl.innerHTML).toEqual(test.output);
+
+ vm.$destroy();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js
index 20e352dd8bd..104d03377b6 100644
--- a/spec/javascripts/notes/components/comment_form_spec.js
+++ b/spec/javascripts/notes/components/comment_form_spec.js
@@ -139,13 +139,21 @@ describe('issue_comment_form component', () => {
});
describe('event enter', () => {
- it('should save note when cmd/ctrl+enter is pressed', () => {
+ it('should save note when cmd+enter is pressed', () => {
spyOn(vm, 'handleSave').and.callThrough();
vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo';
vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleSave).toHaveBeenCalled();
});
+
+ it('should save note when ctrl+enter is pressed', () => {
+ spyOn(vm, 'handleSave').and.callThrough();
+ vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo';
+ vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(13, false, true));
+
+ expect(vm.handleSave).toHaveBeenCalled();
+ });
});
});
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index 7c8d6685ee1..36c56cd3862 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import notesApp from '~/notes/components/notes_app.vue';
import service from '~/notes/services/notes_service';
diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js
index 86e9e2a32a9..f841a408d09 100644
--- a/spec/javascripts/notes/components/note_form_spec.js
+++ b/spec/javascripts/notes/components/note_form_spec.js
@@ -69,13 +69,20 @@ describe('issue_note_form component', () => {
});
describe('enter', () => {
- it('should submit note', () => {
+ it('should save note when cmd+enter is pressed', () => {
spyOn(vm, 'handleUpdate').and.callThrough();
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleUpdate).toHaveBeenCalled();
});
+ it('should save note when ctrl+enter is pressed', () => {
+ spyOn(vm, 'handleUpdate').and.callThrough();
+ vm.$el.querySelector('textarea').value = 'Foo';
+ vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, false, true));
+
+ expect(vm.handleUpdate).toHaveBeenCalled();
+ });
});
});
diff --git a/spec/javascripts/notes/components/noteable_note_spec.js b/spec/javascripts/notes/components/noteable_note_spec.js
index c8a6cb7e612..cb63b64724d 100644
--- a/spec/javascripts/notes/components/noteable_note_spec.js
+++ b/spec/javascripts/notes/components/noteable_note_spec.js
@@ -1,4 +1,4 @@
-
+import _ from 'underscore';
import Vue from 'vue';
import store from '~/notes/stores';
import issueNote from '~/notes/components/noteable_note.vue';
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 6b608adff15..f0c800c759d 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -29,7 +29,6 @@ export const noteableDataMock = {
can_create_note: true,
can_update: true,
},
- deleted_at: null,
description: '',
due_date: null,
human_time_estimate: null,
@@ -108,7 +107,7 @@ export const note = {
"name": "Administrator",
"username": "root",
"state": "active",
- "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"path": "/root"
},
"created_at": "2017-08-10T15:24:03.087Z",
@@ -283,7 +282,6 @@ export const loggedOutnoteableData = {
"updated_by_id": 1,
"created_at": "2017-02-07T10:11:18.395Z",
"updated_at": "2017-08-08T10:22:51.564Z",
- "deleted_at": null,
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 167f074fb9b..a40821a5693 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -1,4 +1,5 @@
/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */
+import _ from 'underscore';
import * as urlUtils from '~/lib/utils/url_utility';
import 'autosize';
import '~/gl_form';
diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js
index f90e0093d25..b24563f738b 100644
--- a/spec/javascripts/oauth_remember_me_spec.js
+++ b/spec/javascripts/oauth_remember_me_spec.js
@@ -1,4 +1,4 @@
-import OAuthRememberMe from '~/oauth_remember_me';
+import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me';
describe('OAuthRememberMe', () => {
preloadFixtures('static/oauth_remember_me.html.raw');
diff --git a/spec/javascripts/pager_spec.js b/spec/javascripts/pager_spec.js
index 2fd87754238..b09494f0b77 100644
--- a/spec/javascripts/pager_spec.js
+++ b/spec/javascripts/pager_spec.js
@@ -1,5 +1,6 @@
/* global fixture */
-
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import * as utils from '~/lib/utils/url_utility';
import Pager from '~/pager';
@@ -9,7 +10,6 @@ describe('pager', () => {
beforeEach(() => {
setFixtures('<div class="content_list"></div><div class="loading"></div>');
- spyOn($, 'ajax');
});
afterEach(() => {
@@ -47,39 +47,90 @@ describe('pager', () => {
});
describe('getOld', () => {
+ const urlRegex = /(.*)some_list(.*)$/;
+ let mock;
+
+ function mockSuccess() {
+ mock.onGet(urlRegex).reply(200, {
+ count: 0,
+ html: '',
+ });
+ }
+
+ function mockError() {
+ mock.onGet(urlRegex).networkError();
+ }
+
beforeEach(() => {
setFixtures('<div class="content_list" data-href="/some_list"></div><div class="loading"></div>');
+ spyOn(axios, 'get').and.callThrough();
+
+ mock = new MockAdapter(axios);
+
Pager.init();
});
- it('shows loader while loading next page', () => {
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('shows loader while loading next page', (done) => {
+ mockSuccess();
+
spyOn(Pager.loading, 'show');
Pager.getOld();
- expect(Pager.loading.show).toHaveBeenCalled();
+
+ setTimeout(() => {
+ expect(Pager.loading.show).toHaveBeenCalled();
+
+ done();
+ });
});
- it('hides loader on success', () => {
- spyOn($, 'ajax').and.callFake(options => options.success({}));
+ it('hides loader on success', (done) => {
+ mockSuccess();
+
spyOn(Pager.loading, 'hide');
Pager.getOld();
- expect(Pager.loading.hide).toHaveBeenCalled();
+
+ setTimeout(() => {
+ expect(Pager.loading.hide).toHaveBeenCalled();
+
+ done();
+ });
});
- it('hides loader on error', () => {
- spyOn($, 'ajax').and.callFake(options => options.error());
+ it('hides loader on error', (done) => {
+ mockError();
+
spyOn(Pager.loading, 'hide');
Pager.getOld();
- expect(Pager.loading.hide).toHaveBeenCalled();
+
+ setTimeout(() => {
+ expect(Pager.loading.hide).toHaveBeenCalled();
+
+ done();
+ });
});
- it('sends request to url with offset and limit params', () => {
- spyOn($, 'ajax');
+ it('sends request to url with offset and limit params', (done) => {
Pager.offset = 100;
Pager.limit = 20;
Pager.getOld();
- const [{ data, url }] = $.ajax.calls.argsFor(0);
- expect(data).toBe('limit=20&offset=100');
- expect(url).toBe('/some_list');
+
+ setTimeout(() => {
+ const [url, params] = axios.get.calls.argsFor(0);
+
+ expect(params).toEqual({
+ params: {
+ limit: 20,
+ offset: 100,
+ },
+ });
+ expect(url).toBe('/some_list');
+
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/abuse_reports_spec.js b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
index 7f6b5873011..d2386077aa6 100644
--- a/spec/javascripts/abuse_reports_spec.js
+++ b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
@@ -1,5 +1,5 @@
import '~/lib/utils/text_utility';
-import AbuseReports from '~/abuse_reports';
+import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports';
describe('Abuse Reports', () => {
const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw';
diff --git a/spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js b/spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
new file mode 100644
index 00000000000..440a6585d57
--- /dev/null
+++ b/spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
@@ -0,0 +1,63 @@
+import Vue from 'vue';
+
+import axios from '~/lib/utils/axios_utils';
+import stopJobsModal from '~/pages/admin/jobs/index/components/stop_jobs_modal.vue';
+import * as urlUtility from '~/lib/utils/url_utility';
+
+import mountComponent from '../../../../../helpers/vue_mount_component_helper';
+
+describe('stop_jobs_modal.vue', () => {
+ const props = {
+ url: `${gl.TEST_HOST}/stop_jobs_modal.vue/stopAll`,
+ };
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ beforeEach(() => {
+ const Component = Vue.extend(stopJobsModal);
+ vm = mountComponent(Component, props);
+ });
+
+ describe('onSubmit', () => {
+ it('stops jobs and redirects to overview page', (done) => {
+ const responseURL = `${gl.TEST_HOST}/stop_jobs_modal.vue/jobs`;
+ const redirectSpy = spyOn(urlUtility, 'redirectTo');
+ spyOn(axios, 'post').and.callFake((url) => {
+ expect(url).toBe(props.url);
+ return Promise.resolve({
+ request: {
+ responseURL,
+ },
+ });
+ });
+
+ vm.onSubmit()
+ .then(() => {
+ expect(redirectSpy).toHaveBeenCalledWith(responseURL);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays error if stopping jobs failed', (done) => {
+ const dummyError = new Error('stopping jobs failed');
+ const redirectSpy = spyOn(urlUtility, 'redirectTo');
+ spyOn(axios, 'post').and.callFake((url) => {
+ expect(url).toBe(props.url);
+ return Promise.reject(dummyError);
+ });
+
+ vm.onSubmit()
+ .then(done.fail)
+ .catch((error) => {
+ expect(error).toBe(dummyError);
+ expect(redirectSpy).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/pages/milestones/shared/components/delete_milestone_modal_spec.js b/spec/javascripts/pages/milestones/shared/components/delete_milestone_modal_spec.js
new file mode 100644
index 00000000000..3cd33a3e900
--- /dev/null
+++ b/spec/javascripts/pages/milestones/shared/components/delete_milestone_modal_spec.js
@@ -0,0 +1,95 @@
+import Vue from 'vue';
+
+import axios from '~/lib/utils/axios_utils';
+import deleteMilestoneModal from '~/pages/milestones/shared/components/delete_milestone_modal.vue';
+import eventHub from '~/pages/milestones/shared/event_hub';
+import * as urlUtility from '~/lib/utils/url_utility';
+
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+
+describe('delete_milestone_modal.vue', () => {
+ const Component = Vue.extend(deleteMilestoneModal);
+ const props = {
+ issueCount: 1,
+ mergeRequestCount: 2,
+ milestoneId: 3,
+ milestoneTitle: 'my milestone title',
+ milestoneUrl: `${gl.TEST_HOST}/delete_milestone_modal.vue/milestone`,
+ };
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('onSubmit', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, props);
+ spyOn(eventHub, '$emit');
+ });
+
+ it('deletes milestone and redirects to overview page', (done) => {
+ const responseURL = `${gl.TEST_HOST}/delete_milestone_modal.vue/milestoneOverview`;
+ spyOn(axios, 'delete').and.callFake((url) => {
+ expect(url).toBe(props.milestoneUrl);
+ expect(eventHub.$emit).toHaveBeenCalledWith('deleteMilestoneModal.requestStarted', props.milestoneUrl);
+ eventHub.$emit.calls.reset();
+ return Promise.resolve({
+ request: {
+ responseURL,
+ },
+ });
+ });
+ const redirectSpy = spyOn(urlUtility, 'redirectTo');
+
+ vm.onSubmit()
+ .then(() => {
+ expect(redirectSpy).toHaveBeenCalledWith(responseURL);
+ expect(eventHub.$emit).toHaveBeenCalledWith('deleteMilestoneModal.requestFinished', { milestoneUrl: props.milestoneUrl, successful: true });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays error if deleting milestone failed', (done) => {
+ const dummyError = new Error('deleting milestone failed');
+ dummyError.response = { status: 418 };
+ spyOn(axios, 'delete').and.callFake((url) => {
+ expect(url).toBe(props.milestoneUrl);
+ expect(eventHub.$emit).toHaveBeenCalledWith('deleteMilestoneModal.requestStarted', props.milestoneUrl);
+ eventHub.$emit.calls.reset();
+ return Promise.reject(dummyError);
+ });
+ const redirectSpy = spyOn(urlUtility, 'redirectTo');
+
+ vm.onSubmit()
+ .catch((error) => {
+ expect(error).toBe(dummyError);
+ expect(redirectSpy).not.toHaveBeenCalled();
+ expect(eventHub.$emit).toHaveBeenCalledWith('deleteMilestoneModal.requestFinished', { milestoneUrl: props.milestoneUrl, successful: false });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('text', () => {
+ it('contains the issue and milestone count', () => {
+ vm = mountComponent(Component, props);
+ const value = vm.text;
+
+ expect(value).toContain('remove it from 1 issue and 2 merge requests');
+ });
+
+ it('contains neither issue nor milestone count', () => {
+ vm = mountComponent(Component, { ...props,
+ issueCount: 0,
+ mergeRequestCount: 0,
+ });
+
+ const value = vm.text;
+
+ expect(value).toContain('is not currently used');
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/async_button_spec.js b/spec/javascripts/pipelines/async_button_spec.js
index 48620898357..d010d897642 100644
--- a/spec/javascripts/pipelines/async_button_spec.js
+++ b/spec/javascripts/pipelines/async_button_spec.js
@@ -13,7 +13,7 @@ describe('Pipelines Async Button', () => {
propsData: {
endpoint: '/foo',
title: 'Foo',
- icon: 'fa fa-foo',
+ icon: 'repeat',
cssClass: 'bar',
},
}).$mount();
@@ -23,8 +23,8 @@ describe('Pipelines Async Button', () => {
expect(component.$el.tagName).toEqual('BUTTON');
});
- it('should render the provided icon', () => {
- expect(component.$el.querySelector('i').getAttribute('class')).toContain('fa fa-foo');
+ it('should render svg icon', () => {
+ expect(component.$el.querySelector('svg')).not.toBeNull();
});
it('should render the provided title', () => {
diff --git a/spec/javascripts/pipelines/empty_state_spec.js b/spec/javascripts/pipelines/empty_state_spec.js
index 6611b74594f..97f04844b3a 100644
--- a/spec/javascripts/pipelines/empty_state_spec.js
+++ b/spec/javascripts/pipelines/empty_state_spec.js
@@ -24,11 +24,11 @@ describe('Pipelines Empty State', () => {
expect(component.$el.querySelector('h4').textContent).toContain('Build with confidence');
expect(
- component.$el.querySelector('p').textContent,
+ component.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toContain('Continous Integration can help catch bugs by running your tests automatically');
expect(
- component.$el.querySelector('p').textContent,
+ component.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toContain('Continuous Deployment can help you deliver code to your product environment');
});
diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js
index 35e36e9c353..c3dc7b53d0f 100644
--- a/spec/javascripts/pipelines/graph/job_component_spec.js
+++ b/spec/javascripts/pipelines/graph/job_component_spec.js
@@ -61,14 +61,14 @@ describe('pipeline graph job component', () => {
it('it should render status and name', () => {
component = mountComponent(JobComponent, {
job: {
- id: 4256,
+ id: 4257,
name: 'test',
status: {
icon: 'icon_status_success',
text: 'passed',
label: 'passed',
group: 'success',
- details_path: '/root/ci-mock/builds/4256',
+ details_path: '/root/ci-mock/builds/4257',
has_details: false,
},
},
@@ -118,7 +118,7 @@ describe('pipeline graph job component', () => {
it('should not render status label when it is not provided', () => {
component = mountComponent(JobComponent, {
job: {
- id: 4256,
+ id: 4258,
name: 'test',
status: {
icon: 'icon_status_success',
@@ -132,7 +132,7 @@ describe('pipeline graph job component', () => {
it('should not render status label when it is provided', () => {
component = mountComponent(JobComponent, {
job: {
- id: 4256,
+ id: 4259,
name: 'test',
status: {
icon: 'icon_status_success',
diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
index 063ab53681b..f744f1af5e6 100644
--- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js
+++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
@@ -4,18 +4,18 @@ import stageColumnComponent from '~/pipelines/components/graph/stage_column_comp
describe('stage column component', () => {
let component;
const mockJob = {
- id: 4256,
+ id: 4250,
name: 'test',
status: {
icon: 'icon_status_success',
text: 'passed',
label: 'passed',
group: 'success',
- details_path: '/root/ci-mock/builds/4256',
+ details_path: '/root/ci-mock/builds/4250',
action: {
icon: 'retry',
title: 'Retry',
- path: '/root/ci-mock/builds/4256/retry',
+ path: '/root/ci-mock/builds/4250/retry',
method: 'post',
},
},
@@ -24,10 +24,17 @@ describe('stage column component', () => {
beforeEach(() => {
const StageColumnComponent = Vue.extend(stageColumnComponent);
+ const mockJobs = [];
+ for (let i = 0; i < 3; i += 1) {
+ const mockedJob = Object.assign({}, mockJob);
+ mockedJob.id += i;
+ mockJobs.push(mockedJob);
+ }
+
component = new StageColumnComponent({
propsData: {
title: 'foo',
- jobs: [mockJob, mockJob, mockJob],
+ jobs: mockJobs,
},
}).$mount();
});
diff --git a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
index 9fec2f61f78..bc6413a159f 100644
--- a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
+++ b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import PipelineMediator from '~/pipelines/pipeline_details_mediatior';
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index 367b42cefb0..a99ebc4e51a 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import pipelinesComp from '~/pipelines/components/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store';
diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js
index a9126d2f4e9..de744739e42 100644
--- a/spec/javascripts/pipelines/pipelines_table_row_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js
@@ -24,9 +24,10 @@ describe('Pipelines Table Row', () => {
beforeEach(() => {
const pipelines = getJSONFixture(jsonFixtureName).pipelines;
- pipeline = pipelines.find(p => p.id === 1);
- pipelineWithoutAuthor = pipelines.find(p => p.id === 2);
- pipelineWithoutCommit = pipelines.find(p => p.id === 3);
+
+ pipeline = pipelines.find(p => p.user !== null && p.commit !== null);
+ pipelineWithoutAuthor = pipelines.find(p => p.user === null && p.commit !== null);
+ pipelineWithoutCommit = pipelines.find(p => p.user === null && p.commit === null);
});
afterEach(() => {
diff --git a/spec/javascripts/pipelines/pipelines_table_spec.js b/spec/javascripts/pipelines/pipelines_table_spec.js
index ca2f9163313..4fc3c08145e 100644
--- a/spec/javascripts/pipelines/pipelines_table_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_spec.js
@@ -11,9 +11,10 @@ describe('Pipelines Table', () => {
preloadFixtures(jsonFixtureName);
beforeEach(() => {
- PipelinesTableComponent = Vue.extend(pipelinesTableComp);
const pipelines = getJSONFixture(jsonFixtureName).pipelines;
- pipeline = pipelines.find(p => p.id === 1);
+
+ PipelinesTableComponent = Vue.extend(pipelinesTableComp);
+ pipeline = pipelines.find(p => p.user !== null && p.commit !== null);
});
describe('table', () => {
diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js
index 1b96b2e3d51..61c2f783acc 100644
--- a/spec/javascripts/pipelines/stage_spec.js
+++ b/spec/javascripts/pipelines/stage_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import stage from '~/pipelines/components/stage.vue';
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index 43e7d9e1224..6a8a85e3dfb 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import registry from '~/registry/components/app.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
@@ -89,7 +90,7 @@ describe('Registry List', () => {
it('should render empty message', (done) => {
setTimeout(() => {
expect(
- vm.$el.querySelector('p').textContent.trim(),
+ 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.');
done();
}, 0);
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
index c4d3866c922..debde1bb357 100644
--- a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
+++ b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
@@ -12,7 +12,7 @@ describe('Multi-file editor commit sidebar list collapsed', () => {
vm = createComponentWithStore(Component, store);
- vm.$store.state.openFiles.push(file(), file());
+ vm.$store.state.openFiles.push(file('file1'), file('file2'));
vm.$store.state.openFiles[0].tempFile = true;
vm.$store.state.openFiles.forEach((f) => {
Object.assign(f, {
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
index fc7c9ae9dd7..4b20fdf70d6 100644
--- a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
+++ b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
@@ -10,7 +10,7 @@ describe('Multi-file editor commit sidebar list item', () => {
beforeEach(() => {
const Component = Vue.extend(listItem);
- f = file();
+ f = file('test-file');
vm = mountComponent(Component, {
file: f,
diff --git a/spec/javascripts/repo/components/new_dropdown/modal_spec.js b/spec/javascripts/repo/components/new_dropdown/modal_spec.js
index 233cca06ed0..8bbc3100357 100644
--- a/spec/javascripts/repo/components/new_dropdown/modal_spec.js
+++ b/spec/javascripts/repo/components/new_dropdown/modal_spec.js
@@ -18,8 +18,10 @@ describe('new file modal component', () => {
}));
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- commit: {
- id: '123branch',
+ data: {
+ commit: {
+ id: '123branch',
+ },
},
}));
diff --git a/spec/javascripts/repo/components/new_dropdown/upload_spec.js b/spec/javascripts/repo/components/new_dropdown/upload_spec.js
index 788c08e5279..667112ab21a 100644
--- a/spec/javascripts/repo/components/new_dropdown/upload_spec.js
+++ b/spec/javascripts/repo/components/new_dropdown/upload_spec.js
@@ -17,8 +17,10 @@ describe('new dropdown upload', () => {
}));
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- commit: {
- id: '123branch',
+ data: {
+ commit: {
+ id: '123branch',
+ },
},
}));
diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js
index cd93fb3ccbf..93e94b4f24c 100644
--- a/spec/javascripts/repo/components/repo_commit_section_spec.js
+++ b/spec/javascripts/repo/components/repo_commit_section_spec.js
@@ -29,7 +29,7 @@ describe('RepoCommitSection', () => {
comp.$store.state.rightPanelCollapsed = false;
comp.$store.state.currentBranch = 'master';
- comp.$store.state.openFiles = [file(), file()];
+ comp.$store.state.openFiles = [file('file1'), file('file2')];
comp.$store.state.openFiles.forEach(f => Object.assign(f, {
changed: true,
content: 'testing',
@@ -87,8 +87,10 @@ describe('RepoCommitSection', () => {
changedFiles = JSON.parse(JSON.stringify(vm.$store.getters.changedFiles));
spyOn(service, 'commit').and.returnValue(Promise.resolve({
- short_id: '1',
- stats: {},
+ data: {
+ short_id: '1',
+ stats: {},
+ },
}));
});
diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js
index 0810da87e80..27b55ed1f87 100644
--- a/spec/javascripts/repo/components/repo_file_spec.js
+++ b/spec/javascripts/repo/components/repo_file_spec.js
@@ -25,7 +25,7 @@ describe('RepoFile', () => {
vm = new RepoFile({
store,
propsData: {
- file: file(),
+ file: file('t4'),
},
});
spyOn(vm, 'timeFormated').and.returnValue(updated);
@@ -39,7 +39,7 @@ describe('RepoFile', () => {
it('does render if hasFiles is true and is loading tree', () => {
vm = createComponent({
- file: file(),
+ file: file('t1'),
});
expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy();
@@ -47,7 +47,7 @@ describe('RepoFile', () => {
it('does not render commit message and datetime if mini', (done) => {
vm = createComponent({
- file: file(),
+ file: file('t2'),
});
vm.$store.state.openFiles.push(vm.file);
@@ -61,7 +61,7 @@ describe('RepoFile', () => {
it('fires clickFile when the link is clicked', () => {
vm = createComponent({
- file: file(),
+ file: file('t3'),
});
spyOn(vm, 'clickFile');
diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js
index 507bca983df..933e8d3a06a 100644
--- a/spec/javascripts/repo/components/repo_tab_spec.js
+++ b/spec/javascripts/repo/components/repo_tab_spec.js
@@ -56,7 +56,7 @@ describe('RepoTab', () => {
});
it('renders an fa-circle icon if tab is changed', () => {
- const tab = file();
+ const tab = file('changedFile');
tab.changed = true;
vm = createComponent({
tab,
@@ -68,7 +68,7 @@ describe('RepoTab', () => {
describe('methods', () => {
describe('closeTab', () => {
it('does not close tab if is changed', (done) => {
- const tab = file();
+ const tab = file('closeFile');
tab.changed = true;
tab.opened = true;
vm = createComponent({
diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js
index 0beaf643793..2c363364d70 100644
--- a/spec/javascripts/repo/components/repo_tabs_spec.js
+++ b/spec/javascripts/repo/components/repo_tabs_spec.js
@@ -4,7 +4,7 @@ import repoTabs from '~/ide/components/repo_tabs.vue';
import { file, resetStore } from '../helpers';
describe('RepoTabs', () => {
- const openedFiles = [file(), file()];
+ const openedFiles = [file('open1'), file('open2')];
let vm;
function createComponent() {
diff --git a/spec/javascripts/repo/stores/actions/file_spec.js b/spec/javascripts/repo/stores/actions/file_spec.js
index 8ce01d3bf12..e2d8f002e27 100644
--- a/spec/javascripts/repo/stores/actions/file_spec.js
+++ b/spec/javascripts/repo/stores/actions/file_spec.js
@@ -18,7 +18,7 @@ describe('Multi-file store file actions', () => {
oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line
store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line
- localFile = file();
+ localFile = file('testFile');
localFile.active = true;
localFile.opened = true;
localFile.parentTreeUrl = 'parentTreeUrl';
@@ -81,7 +81,7 @@ describe('Multi-file store file actions', () => {
});
it('sets next file as active', (done) => {
- const f = file();
+ const f = file('otherfile');
store.state.openFiles.push(f);
expect(f.active).toBeFalsy();
@@ -119,7 +119,7 @@ describe('Multi-file store file actions', () => {
});
it('calls scrollToTab', (done) => {
- store.dispatch('setFileActive', file())
+ store.dispatch('setFileActive', file('setThisActive'))
.then(() => {
expect(scrollToTabSpy).toHaveBeenCalled();
@@ -128,7 +128,7 @@ describe('Multi-file store file actions', () => {
});
it('sets the file active', (done) => {
- const localFile = file();
+ const localFile = file('activeFile');
store.dispatch('setFileActive', localFile)
.then(() => {
@@ -139,7 +139,7 @@ describe('Multi-file store file actions', () => {
});
it('returns early if file is already active', (done) => {
- const localFile = file();
+ const localFile = file('earlyActive');
localFile.active = true;
store.dispatch('setFileActive', localFile)
@@ -151,11 +151,11 @@ describe('Multi-file store file actions', () => {
});
it('sets current active file to not active', (done) => {
- const localFile = file();
+ const localFile = file('currentActive');
localFile.active = true;
store.state.openFiles.push(localFile);
- store.dispatch('setFileActive', file())
+ store.dispatch('setFileActive', file('newActive'))
.then(() => {
expect(localFile.active).toBeFalsy();
@@ -166,7 +166,7 @@ describe('Multi-file store file actions', () => {
it('resets location.hash for line highlighting', (done) => {
location.hash = 'test';
- store.dispatch('setFileActive', file())
+ store.dispatch('setFileActive', file('otherActive'))
.then(() => {
expect(location.hash).not.toBe('test');
@@ -176,7 +176,7 @@ describe('Multi-file store file actions', () => {
});
describe('getFileData', () => {
- let localFile = file();
+ let localFile;
beforeEach(() => {
spyOn(service, 'getFileData').and.returnValue(Promise.resolve({
@@ -194,10 +194,17 @@ describe('Multi-file store file actions', () => {
}),
}));
- localFile = file();
+ localFile = file('newCreate');
localFile.url = 'getFileDataURL';
});
+ afterEach(() => {
+ store.dispatch('closeFile', {
+ file: localFile,
+ force: true,
+ });
+ });
+
it('calls the service', (done) => {
store.dispatch('getFileData', localFile)
.then(() => {
@@ -268,7 +275,7 @@ describe('Multi-file store file actions', () => {
beforeEach(() => {
spyOn(service, 'getRawFileData').and.returnValue(Promise.resolve('raw'));
- tmpFile = file();
+ tmpFile = file('tmpFile');
});
it('calls getRawFileData service method', (done) => {
@@ -294,7 +301,7 @@ describe('Multi-file store file actions', () => {
let tmpFile;
beforeEach(() => {
- tmpFile = file();
+ tmpFile = file('tmpFile');
});
it('updates file content', (done) => {
diff --git a/spec/javascripts/repo/stores/actions_spec.js b/spec/javascripts/repo/stores/actions_spec.js
index 0b0d34f072a..f678967b092 100644
--- a/spec/javascripts/repo/stores/actions_spec.js
+++ b/spec/javascripts/repo/stores/actions_spec.js
@@ -48,14 +48,14 @@ describe('Multi-file store actions', () => {
describe('discardAllChanges', () => {
beforeEach(() => {
- store.state.openFiles.push(file());
+ store.state.openFiles.push(file('discardAll'));
store.state.openFiles[0].changed = true;
});
});
describe('closeAllFiles', () => {
beforeEach(() => {
- store.state.openFiles.push(file());
+ store.state.openFiles.push(file('closeAll'));
store.state.openFiles[0].opened = true;
});
@@ -97,7 +97,7 @@ describe('Multi-file store actions', () => {
it('opens discard popup if there are changed files', (done) => {
store.state.editMode = true;
- store.state.openFiles.push(file());
+ store.state.openFiles.push(file('discardChanges'));
store.state.openFiles[0].changed = true;
store.dispatch('toggleEditMode')
@@ -111,7 +111,7 @@ describe('Multi-file store actions', () => {
it('can force closed if there are changed files', (done) => {
store.state.editMode = true;
- store.state.openFiles.push(file());
+ store.state.openFiles.push(file('forceClose'));
store.state.openFiles[0].changed = true;
store.dispatch('toggleEditMode', true)
@@ -124,7 +124,7 @@ describe('Multi-file store actions', () => {
});
it('discards file changes', (done) => {
- const f = file();
+ const f = file('discard');
store.state.editMode = true;
store.state.openFiles.push(f);
f.changed = true;
@@ -178,7 +178,9 @@ describe('Multi-file store actions', () => {
it('calls service', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- commit: { id: '123' },
+ data: {
+ commit: { id: '123' },
+ },
}));
store.dispatch('checkCommitStatus')
@@ -192,7 +194,9 @@ describe('Multi-file store actions', () => {
it('returns true if current ref does not equal returned ID', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- commit: { id: '123' },
+ data: {
+ commit: { id: '123' },
+ },
}));
store.dispatch('checkCommitStatus')
@@ -206,7 +210,9 @@ describe('Multi-file store actions', () => {
it('returns false if current ref equals returned ID', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- commit: { id: '1' },
+ data: {
+ commit: { id: '1' },
+ },
}));
store.dispatch('checkCommitStatus')
@@ -250,13 +256,15 @@ describe('Multi-file store actions', () => {
describe('success', () => {
beforeEach(() => {
spyOn(service, 'commit').and.returnValue(Promise.resolve({
- id: '123456',
- short_id: '123',
- message: 'test message',
- committed_date: 'date',
- stats: {
- additions: '1',
- deletions: '2',
+ data: {
+ id: '123456',
+ short_id: '123',
+ message: 'test message',
+ committed_date: 'date',
+ stats: {
+ additions: '1',
+ deletions: '2',
+ },
},
}));
});
@@ -285,8 +293,8 @@ describe('Multi-file store actions', () => {
});
it('adds commit data to changed files', (done) => {
- const changedFile = file();
- const f = file();
+ const changedFile = file('changed');
+ const f = file('newfile');
changedFile.changed = true;
store.state.openFiles.push(changedFile, f);
@@ -300,19 +308,6 @@ describe('Multi-file store actions', () => {
}).catch(done.fail);
});
- it('closes all files', (done) => {
- store.state.openFiles.push(file());
- store.state.openFiles[0].opened = true;
-
- store.dispatch('commitChanges', { payload, newMr: false })
- .then(Vue.nextTick)
- .then(() => {
- expect(store.state.openFiles.length).toBe(0);
-
- done();
- }).catch(done.fail);
- });
-
it('scrolls to top of page', (done) => {
store.dispatch('commitChanges', { payload, newMr: false })
.then(() => {
@@ -337,7 +332,9 @@ describe('Multi-file store actions', () => {
describe('failed', () => {
beforeEach(() => {
spyOn(service, 'commit').and.returnValue(Promise.resolve({
- message: 'failed message',
+ data: {
+ message: 'failed message',
+ },
}));
});
diff --git a/spec/javascripts/repo/stores/mutations/file_spec.js b/spec/javascripts/repo/stores/mutations/file_spec.js
index 947a60587df..6e204ef0404 100644
--- a/spec/javascripts/repo/stores/mutations/file_spec.js
+++ b/spec/javascripts/repo/stores/mutations/file_spec.js
@@ -117,7 +117,7 @@ describe('Multi-file store file mutations', () => {
describe('CREATE_TMP_FILE', () => {
it('adds file into parent tree', () => {
- const f = file();
+ const f = file('tmpFile');
mutations.CREATE_TMP_FILE(localState, {
file: f,
diff --git a/spec/javascripts/repo/stores/mutations/tree_spec.js b/spec/javascripts/repo/stores/mutations/tree_spec.js
index cf1248ba28b..e6ca8ea139e 100644
--- a/spec/javascripts/repo/stores/mutations/tree_spec.js
+++ b/spec/javascripts/repo/stores/mutations/tree_spec.js
@@ -57,7 +57,7 @@ describe('Multi-file store tree mutations', () => {
describe('CREATE_TMP_TREE', () => {
it('adds tree into parent tree', () => {
- const tmpEntry = file();
+ const tmpEntry = file('tmpTree');
mutations.CREATE_TMP_TREE(localState, {
tmpEntry,
diff --git a/spec/javascripts/search_spec.js b/spec/javascripts/search_spec.js
new file mode 100644
index 00000000000..38e94d45e55
--- /dev/null
+++ b/spec/javascripts/search_spec.js
@@ -0,0 +1,40 @@
+import Api from '~/api';
+import Search from '~/pages/search/show/search';
+
+describe('Search', () => {
+ const fixturePath = 'search/show.html.raw';
+ const searchTerm = 'some search';
+ const fillDropdownInput = (dropdownSelector) => {
+ const dropdownElement = document.querySelector(dropdownSelector).parentNode;
+ const inputElement = dropdownElement.querySelector('.dropdown-input-field');
+ inputElement.value = searchTerm;
+ return inputElement;
+ };
+
+ preloadFixtures(fixturePath);
+
+ beforeEach(() => {
+ loadFixtures(fixturePath);
+ new Search(); // eslint-disable-line no-new
+ });
+
+ it('requests groups from backend when filtering', (done) => {
+ spyOn(Api, 'groups').and.callFake((term) => {
+ expect(term).toBe(searchTerm);
+ done();
+ });
+ const inputElement = fillDropdownInput('.js-search-group-dropdown');
+
+ $(inputElement).trigger('input');
+ });
+
+ it('requests projects from backend when filtering', (done) => {
+ spyOn(Api, 'projects').and.callFake((term) => {
+ expect(term).toBe(searchTerm);
+ done();
+ });
+ const inputElement = fillDropdownInput('.js-search-project-dropdown');
+
+ $(inputElement).trigger('input');
+ });
+});
diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js
index 3b094d20838..d9e84e35f69 100644
--- a/spec/javascripts/sidebar/mock_data.js
+++ b/spec/javascripts/sidebar/mock_data.js
@@ -15,7 +15,6 @@ const RESPONSE_MAP = {
updated_by_id: 1,
created_at: '2017-02-02T21: 49: 49.664Z',
updated_at: '2017-05-03T22: 26: 03.760Z',
- deleted_at: null,
time_estimate: 0,
total_time_spent: 0,
human_time_estimate: null,
@@ -28,7 +27,7 @@ const RESPONSE_MAP = {
username: 'user0',
id: 22,
state: 'active',
- avatar_url: 'http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
+ avatar_url: 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
web_url: 'http: //localhost:3001/user0',
},
{
@@ -36,7 +35,7 @@ const RESPONSE_MAP = {
username: 'tajuana',
id: 18,
state: 'active',
- avatar_url: 'http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
+ avatar_url: 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
web_url: 'http: //localhost:3001/tajuana',
},
{
@@ -44,7 +43,7 @@ const RESPONSE_MAP = {
username: 'michaele.will',
id: 16,
state: 'active',
- avatar_url: 'http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
+ avatar_url: 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
web_url: 'http: //localhost:3001/michaele.will',
},
],
@@ -73,24 +72,24 @@ const RESPONSE_MAP = {
username: 'user0',
id: 22,
state: 'active',
- avatar_url: 'http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
- web_url: 'http: //localhost:3001/user0',
+ avatar_url: 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3001/user0',
},
{
name: 'Marguerite Bartell',
username: 'tajuana',
id: 18,
state: 'active',
- avatar_url: 'http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
- web_url: 'http: //localhost:3001/tajuana',
+ avatar_url: 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3001/tajuana',
},
{
name: 'Laureen Ritchie',
username: 'michaele.will',
id: 16,
state: 'active',
- avatar_url: 'http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
- web_url: 'http: //localhost:3001/michaele.will',
+ avatar_url: 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3001/michaele.will',
},
],
human_time_estimate: null,
@@ -101,24 +100,24 @@ const RESPONSE_MAP = {
username: 'user0',
id: 22,
state: 'active',
- avatar_url: 'http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
- web_url: 'http: //localhost:3001/user0',
+ avatar_url: 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3001/user0',
},
{
name: 'Marguerite Bartell',
username: 'tajuana',
id: 18,
state: 'active',
- avatar_url: 'http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
- web_url: 'http: //localhost:3001/tajuana',
+ avatar_url: 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3001/tajuana',
},
{
name: 'Laureen Ritchie',
username: 'michaele.will',
id: 16,
state: 'active',
- avatar_url: 'http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
- web_url: 'http: //localhost:3001/michaele.will',
+ avatar_url: 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3001/michaele.will',
},
],
subscribed: true,
@@ -153,7 +152,6 @@ const RESPONSE_MAP = {
updated_by_id: 1,
created_at: '2017-06-27T19:54:42.437Z',
updated_at: '2017-08-18T03:39:49.222Z',
- deleted_at: null,
time_estimate: 0,
total_time_spent: 0,
human_time_estimate: null,
@@ -184,7 +182,7 @@ const mockData = {
id: 1,
name: 'Administrator',
username: 'root',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
},
rootPath: '/',
fullPath: '/gitlab-org/gitlab-shell',
@@ -196,7 +194,7 @@ const mockData = {
human_total_time_spent: null,
},
user: {
- avatar: 'http://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ avatar: 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
id: 1,
name: 'Administrator',
username: 'root',
diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js
index b97e24d9dcf..6bb6d639f24 100644
--- a/spec/javascripts/sidebar/sidebar_assignees_spec.js
+++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import SidebarAssignees from '~/sidebar/components/assignees/sidebar_assignees';
import SidebarMediator from '~/sidebar/sidebar_mediator';
diff --git a/spec/javascripts/sidebar/sidebar_mediator_spec.js b/spec/javascripts/sidebar/sidebar_mediator_spec.js
index 9efd109b996..afa18cc127e 100644
--- a/spec/javascripts/sidebar/sidebar_mediator_spec.js
+++ b/spec/javascripts/sidebar/sidebar_mediator_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
import SidebarMediator from '~/sidebar/sidebar_mediator';
diff --git a/spec/javascripts/sidebar/sidebar_move_issue_spec.js b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
index 8b0d51bbcc8..97f762d07a7 100644
--- a/spec/javascripts/sidebar/sidebar_move_issue_spec.js
+++ b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarStore from '~/sidebar/stores/sidebar_store';
diff --git a/spec/javascripts/sidebar/sidebar_store_spec.js b/spec/javascripts/sidebar/sidebar_store_spec.js
index ea4eae1e23f..3591f96ff87 100644
--- a/spec/javascripts/sidebar/sidebar_store_spec.js
+++ b/spec/javascripts/sidebar/sidebar_store_spec.js
@@ -6,14 +6,14 @@ const ASSIGNEE = {
id: 2,
name: 'gitlab user 2',
username: 'gitlab2',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
};
const ANOTHER_ASSINEE = {
id: 3,
name: 'gitlab user 3',
username: 'gitlab3',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
};
const PARTICIPANT = {
@@ -38,7 +38,7 @@ describe('Sidebar store', () => {
id: 1,
name: 'Administrator',
username: 'root',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
},
editable: true,
rootPath: '/',
diff --git a/spec/javascripts/sidebar/subscriptions_spec.js b/spec/javascripts/sidebar/subscriptions_spec.js
index 9b33dd02fb9..79db05f04ed 100644
--- a/spec/javascripts/sidebar/subscriptions_spec.js
+++ b/spec/javascripts/sidebar/subscriptions_spec.js
@@ -20,23 +20,23 @@ describe('Subscriptions', function () {
subscribed: undefined,
});
- expect(vm.$refs.loadingButton.loading).toBe(true);
- expect(vm.$refs.loadingButton.label).toBeUndefined();
+ expect(vm.$refs.toggleButton.isLoading).toBe(true);
+ expect(vm.$refs.toggleButton.$el.querySelector('.project-feature-toggle')).toHaveClass('is-loading');
});
- it('has "Subscribe" text when currently not subscribed', () => {
+ it('is toggled "off" when currently not subscribed', () => {
vm = mountComponent(Subscriptions, {
subscribed: false,
});
- expect(vm.$refs.loadingButton.label).toBe('Subscribe');
+ expect(vm.$refs.toggleButton.$el.querySelector('.project-feature-toggle')).not.toHaveClass('is-checked');
});
- it('has "Unsubscribe" text when currently not subscribed', () => {
+ it('is toggled "on" when currently subscribed', () => {
vm = mountComponent(Subscriptions, {
subscribed: true,
});
- expect(vm.$refs.loadingButton.label).toBe('Unsubscribe');
+ expect(vm.$refs.toggleButton.$el.querySelector('.project-feature-toggle')).toHaveClass('is-checked');
});
});
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js
index a53e8a94d89..b1b03ef1e09 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
@@ -1,5 +1,5 @@
import AccessorUtilities from '~/lib/utils/accessor';
-import SigninTabsMemoizer from '~/signin_tabs_memoizer';
+import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
(() => {
describe('SigninTabsMemoizer', () => {
@@ -53,6 +53,13 @@ import SigninTabsMemoizer from '~/signin_tabs_memoizer';
expect(memo.readData()).toEqual('#standard');
});
+ it('overrides last selected tab with hash tag when given', () => {
+ window.location.hash = '#ldap';
+ createMemoizer();
+
+ expect(memo.readData()).toEqual('#ldap');
+ });
+
describe('class constructor', () => {
beforeEach(() => {
memo = createMemoizer();
diff --git a/spec/javascripts/smart_interval_spec.js b/spec/javascripts/smart_interval_spec.js
index 1c87fcec245..7265e1b6cb5 100644
--- a/spec/javascripts/smart_interval_spec.js
+++ b/spec/javascripts/smart_interval_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import SmartInterval from '~/smart_interval';
describe('SmartInterval', function () {
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 6897c991066..9b2a5379855 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -1,12 +1,13 @@
/* eslint-disable jasmine/no-global-setup */
import $ from 'jquery';
-import _ from 'underscore';
import 'jasmine-jquery';
import '~/commons';
import Vue from 'vue';
import VueResource from 'vue-resource';
+import { getDefaultAdapter } from '~/lib/utils/axios_utils';
+
const isHeadlessChrome = /\bHeadlessChrome\//.test(navigator.userAgent);
Vue.config.devtools = !isHeadlessChrome;
Vue.config.productionTip = false;
@@ -31,7 +32,6 @@ jasmine.getJSONFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
// globalize common libraries
window.$ = window.jQuery = $;
-window._ = _;
// stub expected globals
window.gl = window.gl || {};
@@ -61,6 +61,8 @@ beforeEach(() => {
Vue.http.interceptors = builtinVueHttpInterceptors.slice();
});
+const axiosDefaultAdapter = getDefaultAdapter();
+
// render all of our tests
const testsContext = require.context('.', true, /_spec$/);
testsContext.keys().forEach(function (path) {
@@ -96,6 +98,12 @@ describe('test errors', () => {
it('has no Vue error', () => {
expect(hasVueErrors).toBe(false);
});
+
+ it('restores axios adapter after mocking', () => {
+ if (getDefaultAdapter() !== axiosDefaultAdapter) {
+ fail('axios adapter is not restored! Did you forget a restore() on MockAdapter?');
+ }
+ });
});
// if we're generating coverage reports, make sure to include all files so
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
index 59e16f0786e..35871dddf89 100644
--- a/spec/javascripts/todos_spec.js
+++ b/spec/javascripts/todos_spec.js
@@ -1,5 +1,5 @@
import * as urlUtils from '~/lib/utils/url_utility';
-import Todos from '~/todos';
+import Todos from '~/pages/dashboard/todos/index/todos';
import '~/lib/utils/common_utils';
describe('Todos', () => {
diff --git a/spec/javascripts/toggle_buttons_spec.js b/spec/javascripts/toggle_buttons_spec.js
new file mode 100644
index 00000000000..205e396d682
--- /dev/null
+++ b/spec/javascripts/toggle_buttons_spec.js
@@ -0,0 +1,120 @@
+import setupToggleButtons from '~/toggle_buttons';
+import getSetTimeoutPromise from './helpers/set_timeout_promise_helper';
+
+function generateMarkup(isChecked = true) {
+ return `
+ <button type="button" class="${isChecked ? 'is-checked' : ''} js-project-feature-toggle">
+ <input type="hidden" class="js-project-feature-toggle-input" value="${isChecked}" />
+ </button>
+ `;
+}
+
+function setupFixture(isChecked, clickCallback) {
+ const wrapper = document.createElement('div');
+ wrapper.innerHTML = generateMarkup(isChecked);
+
+ setupToggleButtons(wrapper, clickCallback);
+
+ return wrapper;
+}
+
+describe('ToggleButtons', () => {
+ describe('when input value is true', () => {
+ it('should initialize as checked', () => {
+ const wrapper = setupFixture(true);
+
+ expect(wrapper.querySelector('.js-project-feature-toggle').classList.contains('is-checked')).toEqual(true);
+ expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('true');
+ });
+
+ it('should toggle to unchecked when clicked', (done) => {
+ const wrapper = setupFixture(true);
+ const toggleButton = wrapper.querySelector('.js-project-feature-toggle');
+
+ toggleButton.click();
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect(toggleButton.classList.contains('is-checked')).toEqual(false);
+ expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('false');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('when input value is false', () => {
+ it('should initialize as unchecked', () => {
+ const wrapper = setupFixture(false);
+
+ expect(wrapper.querySelector('.js-project-feature-toggle').classList.contains('is-checked')).toEqual(false);
+ expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('false');
+ });
+
+ it('should toggle to checked when clicked', (done) => {
+ const wrapper = setupFixture(false);
+ const toggleButton = wrapper.querySelector('.js-project-feature-toggle');
+
+ toggleButton.click();
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect(toggleButton.classList.contains('is-checked')).toEqual(true);
+ expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('true');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ it('should emit `trigger-change` event', (done) => {
+ const changeSpy = jasmine.createSpy('changeEventHandler');
+ const wrapper = setupFixture(false);
+ const toggleButton = wrapper.querySelector('.js-project-feature-toggle');
+ const input = wrapper.querySelector('.js-project-feature-toggle-input');
+
+ $(input).on('trigger-change', changeSpy);
+
+ toggleButton.click();
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect(changeSpy).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ describe('clickCallback', () => {
+ it('should show loading indicator while waiting', (done) => {
+ const isChecked = true;
+ const clickCallback = (newValue, toggleButton) => {
+ const input = toggleButton.querySelector('.js-project-feature-toggle-input');
+
+ expect(newValue).toEqual(false);
+
+ // Check for the loading state
+ expect(toggleButton.classList.contains('is-checked')).toEqual(false);
+ expect(toggleButton.classList.contains('is-loading')).toEqual(true);
+ expect(toggleButton.disabled).toEqual(true);
+ expect(input.value).toEqual('true');
+
+ // After the callback finishes, check that the loading state is gone
+ getSetTimeoutPromise()
+ .then(() => {
+ expect(toggleButton.classList.contains('is-checked')).toEqual(false);
+ expect(toggleButton.classList.contains('is-loading')).toEqual(false);
+ expect(toggleButton.disabled).toEqual(false);
+ expect(input.value).toEqual('false');
+ })
+ .then(done)
+ .catch(done.fail);
+ };
+
+ const wrapper = setupFixture(isChecked, clickCallback);
+ const toggleButton = wrapper.querySelector('.js-project-feature-toggle');
+
+ toggleButton.click();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js
index a750bc78f36..f14d5f6f76c 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js
@@ -1,39 +1,39 @@
import Vue from 'vue';
-import authorComponent from '~/vue_merge_request_widget/components/mr_widget_author';
-
-const author = {
- webUrl: 'http://foo.bar',
- avatarUrl: 'http://gravatar.com/foo',
- name: 'fatihacet',
-};
-const createComponent = () => {
- const Component = Vue.extend(authorComponent);
-
- return new Component({
- el: document.createElement('div'),
- propsData: { author },
- });
-};
+import authorComponent from '~/vue_merge_request_widget/components/mr_widget_author.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
describe('MRWidgetAuthor', () => {
- describe('props', () => {
- it('should have props', () => {
- const authorProp = authorComponent.props.author;
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(authorComponent);
+
+ vm = mountComponent(Component, {
+ author: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://localhost:3000/root',
+ avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ },
- expect(authorProp).toBeDefined();
- expect(authorProp.type instanceof Object).toBeTruthy();
- expect(authorProp.required).toBeTruthy();
});
});
- describe('template', () => {
- it('should have correct elements', () => {
- const el = createComponent().$el;
+ afterEach(() => {
+ vm.$destroy();
+ });
- expect(el.tagName).toEqual('A');
- expect(el.getAttribute('href')).toEqual(author.webUrl);
- expect(el.querySelector('img').getAttribute('src')).toEqual(author.avatarUrl);
- expect(el.querySelector('.author').innerText.trim()).toEqual(author.name);
- });
+ it('renders link with the author web url', () => {
+ expect(vm.$el.getAttribute('href')).toEqual('http://localhost:3000/root');
+ });
+
+ it('renders image with avatar url', () => {
+ expect(
+ vm.$el.querySelector('img').getAttribute('src'),
+ ).toEqual('http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon');
+ });
+
+ it('renders author name', () => {
+ expect(vm.$el.textContent.trim()).toEqual('Administrator');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js
index 515ddcbb875..8c55622b15e 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js
@@ -1,61 +1,40 @@
import Vue from 'vue';
-import authorTimeComponent from '~/vue_merge_request_widget/components/mr_widget_author_time';
-
-const props = {
- actionText: 'Merged by',
- author: {
- webUrl: 'http://foo.bar',
- avatarUrl: 'http://gravatar.com/foo',
- name: 'fatihacet',
- },
- dateTitle: '2017-03-23T23:02:00.807Z',
- dateReadable: '12 hours ago',
-};
-const createComponent = () => {
- const Component = Vue.extend(authorTimeComponent);
-
- return new Component({
- el: document.createElement('div'),
- propsData: props,
- });
-};
+import authorTimeComponent from '~/vue_merge_request_widget/components/mr_widget_author_time.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
describe('MRWidgetAuthorTime', () => {
- describe('props', () => {
- it('should have props', () => {
- const { actionText, author, dateTitle, dateReadable } = authorTimeComponent.props;
- const ActionTextClass = actionText.type;
- const DateTitleClass = dateTitle.type;
- const DateReadableClass = dateReadable.type;
-
- expect(new ActionTextClass() instanceof String).toBeTruthy();
- expect(actionText.required).toBeTruthy();
-
- expect(author.type instanceof Object).toBeTruthy();
- expect(author.required).toBeTruthy();
-
- expect(new DateTitleClass() instanceof String).toBeTruthy();
- expect(dateTitle.required).toBeTruthy();
-
- expect(new DateReadableClass() instanceof String).toBeTruthy();
- expect(dateReadable.required).toBeTruthy();
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(authorTimeComponent);
+
+ vm = mountComponent(Component, {
+ actionText: 'Merged by',
+ author: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://localhost:3000/root',
+ avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ },
+ dateTitle: '2017-03-23T23:02:00.807Z',
+ dateReadable: '12 hours ago',
});
});
- describe('components', () => {
- it('should have components', () => {
- expect(authorTimeComponent.components['mr-widget-author']).toBeDefined();
- });
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders provided action text', () => {
+ expect(vm.$el.textContent).toContain('Merged by');
});
- describe('template', () => {
- it('should have correct elements', () => {
- const el = createComponent().$el;
+ it('renders author', () => {
+ expect(vm.$el.textContent).toContain('Administrator');
+ });
- expect(el.tagName).toEqual('H4');
- expect(el.querySelector('a').getAttribute('href')).toEqual(props.author.webUrl);
- expect(el.querySelector('time').innerText).toContain(props.dateReadable);
- expect(el.querySelector('time').getAttribute('title')).toEqual(props.dateTitle);
- });
+ it('renders provided time', () => {
+ expect(vm.$el.querySelector('time').getAttribute('title')).toEqual('2017-03-23T23:02:00.807Z');
+ expect(vm.$el.querySelector('time').textContent.trim()).toEqual('12 hours ago');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
index 06f89fabf42..13e5595bbfc 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
@@ -1,104 +1,219 @@
import Vue from 'vue';
-import headerComponent from '~/vue_merge_request_widget/components/mr_widget_header';
-
-const createComponent = (mr) => {
- const Component = Vue.extend(headerComponent);
- return new Component({
- el: document.createElement('div'),
- propsData: { mr },
- });
-};
+import headerComponent from '~/vue_merge_request_widget/components/mr_widget_header.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
describe('MRWidgetHeader', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr } = headerComponent.props;
+ let vm;
+ let Component;
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
- });
+ beforeEach(() => {
+ Component = Vue.extend(headerComponent);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
});
describe('computed', () => {
- let vm;
- beforeEach(() => {
- vm = createComponent({
- divergedCommitsCount: 12,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '/foo/bar/mr-widget-refactor',
- targetBranch: 'master',
+ describe('shouldShowCommitsBehindText', () => {
+ it('return true when there are divergedCommitsCount', () => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
+ targetBranch: 'master',
+ } });
+
+ expect(vm.shouldShowCommitsBehindText).toEqual(true);
+ });
+
+ it('returns false where there are no divergedComits count', () => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 0,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
+ targetBranch: 'master',
+ } });
+ expect(vm.shouldShowCommitsBehindText).toEqual(false);
});
});
- it('shouldShowCommitsBehindText', () => {
- expect(vm.shouldShowCommitsBehindText).toBeTruthy();
+ describe('commitsText', () => {
+ it('returns singular when there is one commit', () => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 1,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
+ targetBranch: 'master',
+ } });
- vm.mr.divergedCommitsCount = 0;
- expect(vm.shouldShowCommitsBehindText).toBeFalsy();
- });
+ expect(vm.commitsText).toEqual('1 commit behind');
+ });
- it('commitsText', () => {
- expect(vm.commitsText).toEqual('commits');
+ it('returns plural when there is more than one commit', () => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 2,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>',
+ targetBranch: 'master',
+ } });
- vm.mr.divergedCommitsCount = 1;
- expect(vm.commitsText).toEqual('commit');
+ expect(vm.commitsText).toEqual('2 commits behind');
+ });
});
});
describe('template', () => {
- let vm;
- let el;
- const sourceBranchPath = '/foo/bar/mr-widget-refactor';
- const mr = {
- divergedCommitsCount: 12,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: `<a href="${sourceBranchPath}">mr-widget-refactor</a>`,
- targetBranchPath: 'foo/bar/commits-path',
- targetBranchTreePath: 'foo/bar/tree/path',
- targetBranch: 'master',
- isOpen: true,
- emailPatchesPath: '/mr/email-patches',
- plainDiffPath: '/mr/plainDiffPath',
- };
-
- beforeEach(() => {
- vm = createComponent(mr);
- el = vm.$el;
+ describe('common elements', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ } });
+ });
+
+ it('renders source branch link', () => {
+ expect(
+ vm.$el.querySelector('.js-source-branch').innerHTML,
+ ).toEqual('<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>');
+ });
+
+ it('renders clipboard button', () => {
+ expect(vm.$el.querySelector('.btn-clipboard')).not.toEqual(null);
+ });
+
+ it('renders target branch', () => {
+ expect(vm.$el.querySelector('.js-target-branch').textContent.trim()).toEqual('master');
+ });
+ });
+
+ describe('with an open merge request', () => {
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ beforeEach(() => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ } });
+ });
+
+ it('renders checkout branch button with modal trigger', () => {
+ const button = vm.$el.querySelector('.js-check-out-branch');
+
+ expect(button.textContent.trim()).toEqual('Check out branch');
+ expect(button.getAttribute('data-target')).toEqual('#modal_merge_info');
+ expect(button.getAttribute('data-toggle')).toEqual('modal');
+ });
+
+ it('renders download dropdown with links', () => {
+ expect(
+ vm.$el.querySelector('.js-download-email-patches').textContent.trim(),
+ ).toEqual('Email patches');
+
+ expect(
+ vm.$el.querySelector('.js-download-email-patches').getAttribute('href'),
+ ).toEqual('/mr/email-patches');
+
+ expect(
+ vm.$el.querySelector('.js-download-plain-diff').textContent.trim(),
+ ).toEqual('Plain diff');
+
+ expect(
+ vm.$el.querySelector('.js-download-plain-diff').getAttribute('href'),
+ ).toEqual('/mr/plainDiffPath');
+ });
});
- it('should render template elements correctly', () => {
- expect(el.classList.contains('mr-source-target')).toBeTruthy();
- const sourceBranchLink = el.querySelectorAll('.label-branch')[0];
- const targetBranchLink = el.querySelectorAll('.label-branch')[1];
- const commitsCount = el.querySelector('.diverged-commits-count');
-
- expect(sourceBranchLink.textContent).toContain(mr.sourceBranch);
- expect(targetBranchLink.textContent).toContain(mr.targetBranch);
- expect(sourceBranchLink.querySelector('a').getAttribute('href')).toEqual(sourceBranchPath);
- expect(targetBranchLink.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchTreePath);
- expect(commitsCount.textContent).toContain('12 commits behind');
- expect(commitsCount.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchPath);
-
- expect(el.textContent).toContain('Check out branch');
- expect(el.querySelectorAll('.dropdown li a')[0].getAttribute('href')).toEqual(mr.emailPatchesPath);
- expect(el.querySelectorAll('.dropdown li a')[1].getAttribute('href')).toEqual(mr.plainDiffPath);
+ describe('with a closed merge request', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: false,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ } });
+ });
+
+ it('does not render checkout branch button with modal trigger', () => {
+ const button = vm.$el.querySelector('.js-check-out-branch');
+
+ expect(button).toEqual(null);
+ });
+
+ it('does not render download dropdown with links', () => {
+ expect(
+ vm.$el.querySelector('.js-download-email-patches'),
+ ).toEqual(null);
+
+ expect(
+ vm.$el.querySelector('.js-download-plain-diff'),
+ ).toEqual(null);
+ });
});
- it('should not have right action links if the MR state is not open', (done) => {
- vm.mr.isOpen = false;
- Vue.nextTick(() => {
- expect(el.textContent).not.toContain('Check out branch');
- expect(el.querySelectorAll('.dropdown li a').length).toEqual(0);
- done();
+ describe('without diverged commits', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 0,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ } });
+ });
+
+ it('does not render diverged commits info', () => {
+ expect(vm.$el.querySelector('.diverged-commits-count')).toEqual(null);
});
});
- it('should not render diverged commits count if the MR has no diverged commits', (done) => {
- vm.mr.divergedCommitsCount = null;
- Vue.nextTick(() => {
- expect(el.textContent).not.toContain('commits behind');
- expect(el.querySelectorAll('.diverged-commits-count').length).toEqual(0);
- done();
+ describe('with diverged commits', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, { mr: {
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ } });
+ });
+
+ it('renders diverged commits info', () => {
+ expect(vm.$el.querySelector('.diverged-commits-count').textContent.trim()).toEqual('(12 commits behind)');
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js
index 4da4fc82c26..cc43639f576 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js
@@ -1,51 +1,56 @@
import Vue from 'vue';
-import mergeHelpComponent from '~/vue_merge_request_widget/components/mr_widget_merge_help';
-
-const props = {
- missingBranch: 'this-is-not-the-branch-you-are-looking-for',
-};
-const text = `If the ${props.missingBranch} branch exists in your local repository`;
-
-const createComponent = () => {
- const Component = Vue.extend(mergeHelpComponent);
- return new Component({
- el: document.createElement('div'),
- propsData: props,
- });
-};
+import mergeHelpComponent from '~/vue_merge_request_widget/components/mr_widget_merge_help.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
describe('MRWidgetMergeHelp', () => {
- describe('props', () => {
- it('should have props', () => {
- const { missingBranch } = mergeHelpComponent.props;
- const MissingBranchTypeClass = missingBranch.type;
-
- expect(new MissingBranchTypeClass() instanceof String).toBeTruthy();
- expect(missingBranch.required).toBeFalsy();
- expect(missingBranch.default).toEqual('');
- });
+ let vm;
+ let Component;
+
+ beforeEach(() => {
+ Component = Vue.extend(mergeHelpComponent);
});
- describe('template', () => {
- let vm;
- let el;
+ afterEach(() => {
+ vm.$destroy();
+ });
+ describe('with missing branch', () => {
beforeEach(() => {
- vm = createComponent();
- el = vm.$el;
+ vm = mountComponent(Component, {
+ missingBranch: 'this-is-not-the-branch-you-are-looking-for',
+ });
});
- it('should have the correct elements', () => {
- expect(el.classList.contains('mr-widget-help')).toBeTruthy();
- expect(el.textContent).toContain(text);
+ it('renders missing branch information', () => {
+ expect(
+ vm.$el.textContent.trim().replace(/[\r\n]+/g, ' ').replace(/\s\s+/g, ' '),
+ ).toEqual(
+ 'If the this-is-not-the-branch-you-are-looking-for branch exists in your local repository, you can merge this merge request manually using the command line',
+ );
});
- it('should not show missing branch name if missingBranch props is not provided', (done) => {
- vm.missingBranch = null;
- Vue.nextTick(() => {
- expect(el.textContent).not.toContain(text);
- done();
- });
+ it('renders button to open help modal', () => {
+ expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-target')).toEqual('#modal_merge_info');
+ expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-toggle')).toEqual('modal');
+ });
+ });
+
+ describe('without missing branch', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component);
+ });
+
+ it('renders information about how to merge manually', () => {
+ expect(
+ vm.$el.textContent.trim().replace(/[\r\n]+/g, ' ').replace(/\s\s+/g, ' '),
+ ).toEqual(
+ 'You can merge this merge request manually using the command line',
+ );
+ });
+
+ it('renders element to open a modal', () => {
+ expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-target')).toEqual('#modal_merge_info');
+ expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-toggle')).toEqual('modal');
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js
index f86fb6a0b4b..637bf483deb 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js
@@ -1,117 +1,82 @@
import Vue from 'vue';
-import relatedLinksComponent from '~/vue_merge_request_widget/components/mr_widget_related_links';
+import relatedLinksComponent from '~/vue_merge_request_widget/components/mr_widget_related_links.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
-const createComponent = (data) => {
- const Component = Vue.extend(relatedLinksComponent);
+describe('MRWidgetRelatedLinks', () => {
+ let vm;
- return new Component({
- el: document.createElement('div'),
- propsData: data,
- });
-};
+ const createComponent = (data) => {
+ const Component = Vue.extend(relatedLinksComponent);
-describe('MRWidgetRelatedLinks', () => {
- describe('props', () => {
- it('should have props', () => {
- const { relatedLinks } = relatedLinksComponent.props;
+ return mountComponent(Component, data);
+ };
- expect(relatedLinks).toBeDefined();
- expect(relatedLinks.type instanceof Object).toBeTruthy();
- expect(relatedLinks.required).toBeTruthy();
- });
+ afterEach(() => {
+ vm.$destroy();
});
describe('computed', () => {
- const data = {
- relatedLinks: {
- closing: '/foo',
- mentioned: '/foo',
- assignToMe: '/foo',
- },
- };
-
- describe('hasLinks', () => {
- it('should return correct value when we have links reference', () => {
- const vm = createComponent(data);
- expect(vm.hasLinks).toBeTruthy();
-
- vm.relatedLinks.closing = null;
- expect(vm.hasLinks).toBeTruthy();
-
- vm.relatedLinks.mentioned = null;
- expect(vm.hasLinks).toBeTruthy();
-
- vm.relatedLinks.assignToMe = null;
- expect(vm.hasLinks).toBeFalsy();
- });
- });
-
describe('closesText', () => {
- it('returns correct text for open merge request', () => {
- data.state = 'open';
- const vm = createComponent(data);
+ it('returns Closes text for open merge request', () => {
+ vm = createComponent({ state: 'open', relatedLinks: {} });
expect(vm.closesText).toEqual('Closes');
});
it('returns correct text for closed merge request', () => {
- data.state = 'closed';
- const vm = createComponent(data);
+ vm = createComponent({ state: 'closed', relatedLinks: {} });
expect(vm.closesText).toEqual('Did not close');
});
it('returns correct tense for merged request', () => {
- data.state = 'merged';
- const vm = createComponent(data);
+ vm = createComponent({ state: 'merged', relatedLinks: {} });
expect(vm.closesText).toEqual('Closed');
});
});
});
- describe('template', () => {
- it('should have only have closing issues text', () => {
- const vm = createComponent({
- relatedLinks: {
- closing: '<a href="#">#23</a> and <a>#42</a>',
- },
- });
- const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim();
-
- expect(content).toContain('Closes #23 and #42');
- expect(content).not.toContain('Mentions');
+ it('should have only have closing issues text', () => {
+ vm = createComponent({
+ relatedLinks: {
+ closing: '<a href="#">#23</a> and <a>#42</a>',
+ },
});
+ const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim();
- it('should have only have mentioned issues text', () => {
- const vm = createComponent({
- relatedLinks: {
- mentioned: '<a href="#">#7</a>',
- },
- });
+ expect(content).toContain('Closes #23 and #42');
+ expect(content).not.toContain('Mentions');
+ });
- expect(vm.$el.innerText).toContain('Mentions #7');
- expect(vm.$el.innerText).not.toContain('Closes');
+ it('should have only have mentioned issues text', () => {
+ vm = createComponent({
+ relatedLinks: {
+ mentioned: '<a href="#">#7</a>',
+ },
});
- it('should have closing and mentioned issues at the same time', () => {
- const vm = createComponent({
- relatedLinks: {
- closing: '<a href="#">#7</a>',
- mentioned: '<a href="#">#23</a> and <a>#42</a>',
- },
- });
- const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim();
+ expect(vm.$el.innerText).toContain('Mentions #7');
+ expect(vm.$el.innerText).not.toContain('Closes');
+ });
- expect(content).toContain('Closes #7');
- expect(content).toContain('Mentions #23 and #42');
+ it('should have closing and mentioned issues at the same time', () => {
+ vm = createComponent({
+ relatedLinks: {
+ closing: '<a href="#">#7</a>',
+ mentioned: '<a href="#">#23</a> and <a>#42</a>',
+ },
});
+ const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim();
- it('should have assing issues link', () => {
- const vm = createComponent({
- relatedLinks: {
- assignToMe: '<a href="#">Assign yourself to these issues</a>',
- },
- });
+ expect(content).toContain('Closes #7');
+ expect(content).toContain('Mentions #23 and #42');
+ });
- expect(vm.$el.innerText).toContain('Assign yourself to these issues');
+ it('should have assing issues link', () => {
+ vm = createComponent({
+ relatedLinks: {
+ assignToMe: '<a href="#">Assign yourself to these issues</a>',
+ },
});
+
+ expect(vm.$el.innerText).toContain('Assign yourself to these issues');
});
});
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
new file mode 100644
index 00000000000..c39fcda0071
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
@@ -0,0 +1,44 @@
+import Vue from 'vue';
+import mrStatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('MR widget status icon component', () => {
+ let vm;
+ let Component;
+
+ beforeEach(() => {
+ Component = Vue.extend(mrStatusIcon);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('while loading', () => {
+ it('renders loading icon', () => {
+ vm = mountComponent(Component, { status: 'loading' });
+ expect(vm.$el.querySelector('.mr-widget-icon i').classList).toContain('fa-spinner');
+ });
+ });
+
+ describe('with status icon', () => {
+ it('renders ci status icon', () => {
+ vm = mountComponent(Component, { status: 'failed' });
+ expect(vm.$el.querySelector('.js-ci-status-icon-failed')).not.toBeNull();
+ });
+ });
+
+ describe('with disabled button', () => {
+ it('renders a disabled button', () => {
+ vm = mountComponent(Component, { status: 'failed', showDisabledButton: true });
+ expect(vm.$el.querySelector('.js-disabled-merge-button').textContent.trim()).toEqual('Merge');
+ });
+ });
+
+ describe('without disabled button', () => {
+ it('does not render a disabled button', () => {
+ vm = mountComponent(Component, { status: 'failed' });
+ expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js
index 4869fb17d96..f98ebdb38e6 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js
@@ -1,18 +1,31 @@
import Vue from 'vue';
-import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived';
+import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetArchived', () => {
- describe('template', () => {
- it('should have correct elements', () => {
- const Component = Vue.extend(archivedComponent);
- const el = new Component({
- el: document.createElement('div'),
- }).$el;
+ let vm;
- expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.querySelector('button').classList.contains('btn-success')).toBeTruthy();
- expect(el.querySelector('button').disabled).toBeTruthy();
- expect(el.innerText).toContain('This project is archived, write access has been disabled');
- });
+ beforeEach(() => {
+ const Component = Vue.extend(archivedComponent);
+ vm = mountComponent(Component);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders a ci status failed icon', () => {
+ expect(vm.$el.querySelector('.ci-status-icon')).not.toBeNull();
+ });
+
+ it('renders a disabled button', () => {
+ expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
+ expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Merge');
+ });
+
+ it('renders information', () => {
+ expect(
+ vm.$el.querySelector('.bold').textContent.trim(),
+ ).toEqual('This project is archived, write access has been disabled');
});
});
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 6042d7384d5..95c94e95e3a 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
@@ -1,32 +1,47 @@
import Vue from 'vue';
-import autoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed';
-
-const mergeError = 'This is the merge error';
+import autoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue';
+import eventHub from '~/vue_merge_request_widget/event_hub';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetAutoMergeFailed', () => {
- describe('props', () => {
- it('should have props', () => {
- const mrProp = autoMergeFailedComponent.props.mr;
+ let vm;
+ const mergeError = 'This is the merge error';
- expect(mrProp.type instanceof Object).toBeTruthy();
- expect(mrProp.required).toBeTruthy();
+ beforeEach(() => {
+ const Component = Vue.extend(autoMergeFailedComponent);
+ vm = mountComponent(Component, {
+ mr: { mergeError },
});
});
- describe('template', () => {
- const Component = Vue.extend(autoMergeFailedComponent);
- const vm = new Component({
- el: document.createElement('div'),
- propsData: {
- mr: { mergeError },
- },
- });
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders failed message', () => {
+ expect(vm.$el.textContent).toContain('This merge request failed to be merged automatically');
+ });
+
+ it('renders merge error provided', () => {
+ expect(vm.$el.innerText).toContain(mergeError);
+ });
+
+ it('render refresh button', () => {
+ expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Refresh');
+ });
+
+ it('emits event and shows loading icon when button is clicked', (done) => {
+ spyOn(eventHub, '$emit');
+ vm.$el.querySelector('button').click();
+
+ expect(eventHub.$emit.calls.argsFor(0)[0]).toEqual('MRWidgetUpdateRequested');
- it('should have correct elements', () => {
- expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeFalsy();
- expect(vm.$el.innerText).toContain('This merge request failed to be merged automatically');
- expect(vm.$el.innerText).toContain(mergeError);
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
+ expect(
+ vm.$el.querySelector('button i').classList,
+ ).toContain('fa-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 6b7aa935ad3..658cadddb81 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
@@ -1,19 +1,29 @@
import Vue from 'vue';
-import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking';
+import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetChecking', () => {
- describe('template', () => {
- it('should have correct elements', () => {
- const Component = Vue.extend(checkingComponent);
- const el = new Component({
- el: document.createElement('div'),
- }).$el;
+ let Component;
+ let vm;
- expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.querySelector('button').classList.contains('btn-success')).toBeTruthy();
- expect(el.querySelector('button').disabled).toBeTruthy();
- expect(el.innerText).toContain('Checking ability to merge automatically');
- expect(el.querySelector('i')).toBeDefined();
- });
+ beforeEach(() => {
+ Component = Vue.extend(checkingComponent);
+ vm = mountComponent(Component);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders disabled button', () => {
+ expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
+ });
+
+ it('renders loading icon', () => {
+ expect(vm.$el.querySelector('.mr-widget-icon i').classList).toContain('fa-spinner');
+ });
+
+ it('renders information about merging', () => {
+ expect(vm.$el.querySelector('.media-body').textContent.trim()).toEqual('Checking ability to merge automatically');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
index 1bf97bbf093..51a34739ee9 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
@@ -1,74 +1,58 @@
import Vue from 'vue';
-import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed';
-
-const mr = {
- targetBranch: 'good-branch',
- targetBranchPath: '/good-branch',
- metrics: {
- mergedBy: {},
- mergedAt: 'mergedUpdatedAt',
- closedBy: {
- name: 'Fatih Acet',
- username: 'fatihacet',
- },
- closedAt: 'closedEventUpdatedAt',
- readableMergedAt: '',
- readableClosedAt: '',
- },
- updatedAt: 'mrUpdatedAt',
- closedAt: '1 day ago',
-};
-
-const createComponent = () => {
- const Component = Vue.extend(closedComponent);
-
- return new Component({
- el: document.createElement('div'),
- propsData: { mr },
- });
-};
+import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetClosed', () => {
- describe('props', () => {
- it('should have props', () => {
- const mrProp = closedComponent.props.mr;
-
- expect(mrProp.type instanceof Object).toBeTruthy();
- expect(mrProp.required).toBeTruthy();
- });
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(closedComponent);
+ vm = mountComponent(Component, { mr: {
+ metrics: {
+ mergedBy: {},
+ closedBy: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://localhost:3000/root',
+ avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ },
+ mergedAt: 'Jan 24, 2018 1:02pm GMT+0000',
+ closedAt: 'Jan 24, 2018 1:02pm GMT+0000',
+ readableMergedAt: '',
+ readableClosedAt: 'less than a minute ago',
+ },
+ targetBranchPath: '/twitter/flight/commits/so_long_jquery',
+ targetBranch: 'so_long_jquery',
+ } });
});
- describe('components', () => {
- it('should have components added', () => {
- expect(closedComponent.components['mr-widget-author-and-time']).toBeDefined();
- });
+ afterEach(() => {
+ vm.$destroy();
});
- describe('template', () => {
- let vm;
- let el;
+ it('renders warning icon', () => {
+ expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull();
+ });
- beforeEach(() => {
- vm = createComponent();
- el = vm.$el;
- });
+ it('renders closed by information with author and time', () => {
+ expect(
+ vm.$el.querySelector('.js-mr-widget-author').textContent.trim().replace(/\s\s+/g, ' '),
+ ).toContain(
+ 'Closed by Administrator less than a minute ago',
+ );
+ });
- afterEach(() => {
- vm.$destroy();
- });
+ it('links to the user that closed the MR', () => {
+ expect(vm.$el.querySelector('.author-link').getAttribute('href')).toEqual('http://localhost:3000/root');
+ });
- it('should have correct elements', () => {
- expect(el.querySelector('h4').textContent).toContain('Closed by');
- expect(el.querySelector('h4').textContent).toContain(mr.metrics.closedBy.name);
- expect(el.textContent).toContain('The changes were not merged into');
- expect(el.querySelector('.label-branch').getAttribute('href')).toEqual(mr.targetBranchPath);
- expect(el.querySelector('.label-branch').textContent).toContain(mr.targetBranch);
- });
+ it('renders information about the changes not being merged', () => {
+ expect(
+ vm.$el.querySelector('.mr-info-list').textContent.trim().replace(/\s\s+/g, ' '),
+ ).toContain('The changes were not merged into so_long_jquery');
+ });
- it('should use closedEvent updatedAt as tooltip title', () => {
- expect(
- el.querySelector('time').getAttribute('title'),
- ).toBe('closedEventUpdatedAt');
- });
+ it('renders link for target branch', () => {
+ expect(vm.$el.querySelector('.label-branch').getAttribute('href')).toEqual('/twitter/flight/commits/so_long_jquery');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
index 5d4c7ec09dc..a7d69fdcdb9 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
@@ -1,105 +1,85 @@
import Vue from 'vue';
-import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts';
+import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
-const ConflictsComponent = Vue.extend(conflictsComponent);
-const path = '/conflicts';
-
describe('MRWidgetConflicts', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr } = conflictsComponent.props;
+ let Component;
+ let vm;
+ const path = '/conflicts';
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
- });
+ beforeEach(() => {
+ Component = Vue.extend(conflictsComponent);
});
- describe('template', () => {
- describe('when allowed to merge', () => {
- let vm;
-
- beforeEach(() => {
- vm = mountComponent(ConflictsComponent, {
- mr: {
- canMerge: true,
- conflictResolutionPath: path,
- },
- });
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('should tell you about conflicts without bothering other people', () => {
- expect(vm.$el.textContent).toContain('There are merge conflicts');
- expect(vm.$el.textContent).not.toContain('ask someone with write access');
- });
-
- it('should allow you to resolve the conflicts', () => {
- const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button');
+ afterEach(() => {
+ vm.$destroy();
+ });
- expect(resolveButton.textContent).toContain('Resolve conflicts');
- expect(resolveButton.getAttribute('href')).toEqual(path);
+ describe('when allowed to merge', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ mr: {
+ canMerge: true,
+ conflictResolutionPath: path,
+ },
});
+ });
- it('should have merge buttons', () => {
- const mergeButton = vm.$el.querySelector('.js-disabled-merge-button');
- const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button');
-
- expect(mergeButton.textContent).toContain('Merge');
- expect(mergeButton.disabled).toBeTruthy();
- expect(mergeButton.classList.contains('btn-success')).toEqual(true);
- expect(mergeLocallyButton.textContent).toContain('Merge locally');
- });
+ it('should tell you about conflicts without bothering other people', () => {
+ expect(vm.$el.textContent).toContain('There are merge conflicts');
+ expect(vm.$el.textContent).not.toContain('ask someone with write access');
});
- describe('when user does not have permission to merge', () => {
- let vm;
+ it('should allow you to resolve the conflicts', () => {
+ const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button');
- beforeEach(() => {
- vm = mountComponent(ConflictsComponent, {
- mr: {
- canMerge: false,
- },
- });
- });
+ expect(resolveButton.textContent).toContain('Resolve conflicts');
+ expect(resolveButton.getAttribute('href')).toEqual(path);
+ });
- afterEach(() => {
- vm.$destroy();
- });
+ it('should have merge buttons', () => {
+ const mergeButton = vm.$el.querySelector('.js-disabled-merge-button');
+ const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button');
- it('should show proper message', () => {
- expect(vm.$el.textContent).toContain('ask someone with write access');
- });
+ expect(mergeButton.textContent).toContain('Merge');
+ expect(mergeButton.disabled).toBeTruthy();
+ expect(mergeButton.classList.contains('btn-success')).toEqual(true);
+ expect(mergeLocallyButton.textContent).toContain('Merge locally');
+ });
+ });
- it('should not have action buttons', () => {
- expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined();
- expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull();
- expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull();
+ describe('when user does not have permission to merge', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ mr: {
+ canMerge: false,
+ },
});
});
- describe('when fast-forward or semi-linear merge enabled', () => {
- let vm;
+ it('should show proper message', () => {
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('ask someone with write access');
+ });
- beforeEach(() => {
- vm = mountComponent(ConflictsComponent, {
- mr: {
- shouldBeRebased: true,
- },
- });
- });
+ it('should not have action buttons', () => {
+ expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined();
+ expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull();
+ expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull();
+ });
+ });
- afterEach(() => {
- vm.$destroy();
+ describe('when fast-forward or semi-linear merge enabled', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ mr: {
+ shouldBeRebased: true,
+ },
});
+ });
- it('should tell you to rebase locally', () => {
- expect(vm.$el.textContent).toContain('Fast-forward merge is not possible.');
- expect(vm.$el.textContent).toContain('To merge this request, first rebase locally');
- });
+ it('should tell you to rebase locally', () => {
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('Fast-forward merge is not possible.');
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('To merge this request, first rebase locally');
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
index cef365eec8a..a57b9811e08 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
@@ -1,45 +1,37 @@
import Vue from 'vue';
-import failedToMergeComponent from '~/vue_merge_request_widget/components/states/mr_widget_failed_to_merge';
+import failedToMergeComponent from '~/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
-
-const mr = {
- mergeError: 'Merge error happened.',
-};
-const createComponent = () => {
- const Component = Vue.extend(failedToMergeComponent);
- return new Component({
- el: document.createElement('div'),
- propsData: { mr },
- });
-};
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetFailedToMerge', () => {
- describe('data', () => {
- it('should have default data', () => {
- const data = failedToMergeComponent.data();
+ let Component;
+ let vm;
+
+ beforeEach(() => {
+ Component = Vue.extend(failedToMergeComponent);
+ spyOn(eventHub, '$emit');
+ vm = mountComponent(Component, { mr: {
+ mergeError: 'Merge error happened.',
+ } });
+ });
- expect(data.timer).toEqual(10);
- expect(data.isRefreshing).toBeFalsy();
- });
+ afterEach(() => {
+ vm.$destroy();
});
describe('computed', () => {
describe('timerText', () => {
it('should return correct timer text', () => {
- const vm = createComponent();
- expect(vm.timerText).toEqual('10 seconds');
+ expect(vm.timerText).toEqual('Refreshing in 10 seconds to show the updated status...');
vm.timer = 1;
- expect(vm.timerText).toEqual('a second');
+ expect(vm.timerText).toEqual('Refreshing in a second to show the updated status...');
});
});
});
describe('created', () => {
it('should disable polling', () => {
- spyOn(eventHub, '$emit');
- createComponent();
-
expect(eventHub.$emit).toHaveBeenCalledWith('DisablePolling');
});
});
@@ -47,13 +39,10 @@ describe('MRWidgetFailedToMerge', () => {
describe('methods', () => {
describe('refresh', () => {
it('should emit event to request component refresh', () => {
- spyOn(eventHub, '$emit');
- const vm = createComponent();
-
- expect(vm.isRefreshing).toBeFalsy();
+ expect(vm.isRefreshing).toEqual(false);
vm.refresh();
- expect(vm.isRefreshing).toBeTruthy();
+ expect(vm.isRefreshing).toEqual(true);
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
expect(eventHub.$emit).toHaveBeenCalledWith('EnablePolling');
});
@@ -61,12 +50,11 @@ describe('MRWidgetFailedToMerge', () => {
describe('updateTimer', () => {
it('should update timer and emit event when timer end', () => {
- const vm = createComponent();
spyOn(vm, 'refresh');
expect(vm.timer).toEqual(10);
- for (let i = 0; i < 10; i++) { // eslint-disable-line
+ for (let i = 0; i < 10; i += 1) {
expect(vm.timer).toEqual(10 - i);
vm.updateTimer();
}
@@ -76,47 +64,54 @@ describe('MRWidgetFailedToMerge', () => {
});
});
- describe('template', () => {
- let vm;
- let el;
+ describe('while it is refreshing', () => {
+ it('renders Refresing now', (done) => {
+ vm.isRefreshing = true;
- beforeEach(() => {
- vm = createComponent();
- el = vm.$el;
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.js-refresh-label').textContent.trim()).toEqual('Refreshing now');
+ done();
+ });
});
+ });
- it('should have correct elements', (done) => {
- expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.innerText).toContain('Merge error happened.');
- expect(el.innerText).toContain('Refreshing in 10 seconds');
- expect(el.innerText).not.toContain('Merge failed.');
- expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy();
- expect(el.querySelector('button').innerText).toContain('Merge');
- expect(el.querySelector('.js-refresh-button').innerText).toContain('Refresh now');
- expect(el.querySelector('.js-refresh-label')).toEqual(null);
- expect(el.innerText).not.toContain('Refreshing now');
- setTimeout(() => {
- expect(el.innerText).toContain('Refreshing in 9 seconds');
- done();
- }, 1010);
+ describe('while it is not regresing', () => {
+ it('renders warning icon and disabled merge button', () => {
+ expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-disabled-merge-button').getAttribute('disabled')).toEqual('disabled');
+ });
+
+ it('renders given error', () => {
+ expect(vm.$el.querySelector('.has-error-message').textContent.trim()).toEqual('Merge error happened..');
});
- it('should just generic merge failed message if merge_error is not available', (done) => {
- vm.mr.mergeError = null;
+ it('renders refresh button', () => {
+ expect(vm.$el.querySelector('.js-refresh-button').textContent.trim()).toEqual('Refresh now');
+ });
- Vue.nextTick(() => {
- expect(el.innerText).toContain('Merge failed.');
- expect(el.innerText).not.toContain('Merge error happened.');
- done();
- });
+ it('renders remaining time', () => {
+ expect(
+ vm.$el.querySelector('.has-custom-error').textContent.trim(),
+ ).toEqual('Refreshing in 10 seconds to show the updated status...');
+ });
+ });
+
+ it('should just generic merge failed message if merge_error is not available', (done) => {
+ vm.mr.mergeError = null;
+
+ Vue.nextTick(() => {
+ expect(vm.$el.innerText).toContain('Merge failed.');
+ expect(vm.$el.innerText).not.toContain('Merge error happened.');
+ done();
});
+ });
- it('should show refresh label when refresh requested', () => {
- vm.refresh();
- Vue.nextTick(() => {
- expect(el.innerText).not.toContain('Merge failed. Refreshing');
- expect(el.innerText).toContain('Refreshing now');
- });
+ it('should show refresh label when refresh requested', (done) => {
+ vm.refresh();
+ Vue.nextTick(() => {
+ expect(vm.$el.innerText).not.toContain('Merge failed. Refreshing');
+ expect(vm.$el.innerText).toContain('Refreshing now');
+ done();
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js
deleted file mode 100644
index 237035648cf..00000000000
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_locked_spec.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import Vue from 'vue';
-import mergingComponent from '~/vue_merge_request_widget/components/states/mr_widget_merging';
-
-describe('MRWidgetMerging', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr } = mergingComponent.props;
-
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
- });
- });
-
- describe('template', () => {
- it('should have correct elements', () => {
- const Component = Vue.extend(mergingComponent);
- const mr = {
- targetBranchPath: '/branch-path',
- targetBranch: 'branch',
- };
- const el = new Component({
- el: document.createElement('div'),
- propsData: { mr },
- }).$el;
-
- expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.innerText).toContain('This merge request is in the process of being merged');
- expect(el.innerText).toContain('changes will be merged into');
- expect(el.querySelector('.label-branch a').getAttribute('href')).toEqual(mr.targetBranchPath);
- expect(el.querySelector('.label-branch a').textContent).toContain(mr.targetBranch);
- });
- });
-});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
index 5f4df15bcd6..df56c4e2c5c 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
@@ -1,77 +1,50 @@
import Vue from 'vue';
-import mwpsComponent from '~/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds';
+import mwpsComponent from '~/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
-
-const targetBranchPath = '/foo/bar';
-const targetBranch = 'foo';
-const sha = '1EA2EZ34';
-
-const createComponent = () => {
- const Component = Vue.extend(mwpsComponent);
- const mr = {
- shouldRemoveSourceBranch: false,
- canRemoveSourceBranch: true,
- canCancelAutomaticMerge: true,
- mergeUserId: 1,
- currentUserId: 1,
- setToMWPSBy: {},
- sha,
- targetBranchPath,
- targetBranch,
- };
-
- const service = {
- cancelAutomaticMerge() {},
- mergeResource: {
- save() {},
- },
- };
-
- return new Component({
- el: document.createElement('div'),
- propsData: { mr, service },
- });
-};
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetMergeWhenPipelineSucceeds', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr, service } = mwpsComponent.props;
-
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
-
- expect(service.type instanceof Object).toBeTruthy();
- expect(service.required).toBeTruthy();
+ let vm;
+ const targetBranchPath = '/foo/bar';
+ const targetBranch = 'foo';
+ const sha = '1EA2EZ34';
+
+ beforeEach(() => {
+ const Component = Vue.extend(mwpsComponent);
+ spyOn(eventHub, '$emit');
+
+ vm = mountComponent(Component, {
+ mr: {
+ shouldRemoveSourceBranch: false,
+ canRemoveSourceBranch: true,
+ canCancelAutomaticMerge: true,
+ mergeUserId: 1,
+ currentUserId: 1,
+ setToMWPSBy: {},
+ sha,
+ targetBranchPath,
+ targetBranch,
+ },
+ service: {
+ cancelAutomaticMerge() {},
+ mergeResource: {
+ save() {},
+ },
+ },
});
});
- describe('components', () => {
- it('should have components added', () => {
- expect(mwpsComponent.components['mr-widget-author']).toBeDefined();
- });
- });
-
- describe('data', () => {
- it('should have default data', () => {
- const data = mwpsComponent.data();
-
- expect(data.isCancellingAutoMerge).toBeFalsy();
- expect(data.isRemovingSourceBranch).toBeFalsy();
- });
+ afterEach(() => {
+ vm.$destroy();
});
describe('computed', () => {
describe('canRemoveSourceBranch', () => {
it('should return true when user is able to remove source branch', () => {
- const vm = createComponent();
-
expect(vm.canRemoveSourceBranch).toBeTruthy();
});
it('should return false when user id is not the same with who set the MWPS', () => {
- const vm = createComponent();
-
vm.mr.mergeUserId = 2;
expect(vm.canRemoveSourceBranch).toBeFalsy();
@@ -83,15 +56,11 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
});
it('should return false when shouldRemoveSourceBranch set to false', () => {
- const vm = createComponent();
-
vm.mr.shouldRemoveSourceBranch = true;
expect(vm.canRemoveSourceBranch).toBeFalsy();
});
it('should return false if user is not able to remove the source branch', () => {
- const vm = createComponent();
-
vm.mr.canRemoveSourceBranch = false;
expect(vm.canRemoveSourceBranch).toBeFalsy();
});
@@ -101,11 +70,9 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
describe('methods', () => {
describe('cancelAutomaticMerge', () => {
it('should set flag and call service then tell main component to update the widget with data', (done) => {
- const vm = createComponent();
const mrObj = {
is_new_mr_data: true,
};
- spyOn(eventHub, '$emit');
spyOn(vm.service, 'cancelAutomaticMerge').and.returnValue(new Promise((resolve) => {
resolve({
data: mrObj,
@@ -123,8 +90,6 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
describe('removeSourceBranch', () => {
it('should set flag and call service then request main component to update the widget', (done) => {
- const vm = createComponent();
- spyOn(eventHub, '$emit');
spyOn(vm.service.mergeResource, 'save').and.returnValue(new Promise((resolve) => {
resolve({
data: {
@@ -148,31 +113,23 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
});
describe('template', () => {
- let vm;
- let el;
-
- beforeEach(() => {
- vm = createComponent();
- el = vm.$el;
- });
-
it('should have correct elements', () => {
- expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.innerText).toContain('to be merged automatically when the pipeline succeeds');
- expect(el.innerText).toContain('The changes will be merged into');
- expect(el.innerText).toContain(targetBranch);
- expect(el.innerText).toContain('The source branch will not be removed');
- expect(el.querySelector('.js-cancel-auto-merge').innerText).toContain('Cancel automatic merge');
- expect(el.querySelector('.js-cancel-auto-merge').getAttribute('disabled')).toBeFalsy();
- expect(el.querySelector('.js-remove-source-branch').innerText).toContain('Remove source branch');
- expect(el.querySelector('.js-remove-source-branch').getAttribute('disabled')).toBeFalsy();
+ expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
+ expect(vm.$el.innerText).toContain('to be merged automatically when the pipeline succeeds');
+ expect(vm.$el.innerText).toContain('The changes will be merged into');
+ expect(vm.$el.innerText).toContain(targetBranch);
+ expect(vm.$el.innerText).toContain('The source branch will not be removed');
+ expect(vm.$el.querySelector('.js-cancel-auto-merge').innerText).toContain('Cancel automatic merge');
+ expect(vm.$el.querySelector('.js-cancel-auto-merge').getAttribute('disabled')).toBeFalsy();
+ expect(vm.$el.querySelector('.js-remove-source-branch').innerText).toContain('Remove source branch');
+ expect(vm.$el.querySelector('.js-remove-source-branch').getAttribute('disabled')).toBeFalsy();
});
it('should disable cancel auto merge button when the action is in progress', (done) => {
vm.isCancellingAutoMerge = true;
Vue.nextTick(() => {
- expect(el.querySelector('.js-cancel-auto-merge').getAttribute('disabled')).toBeTruthy();
+ expect(vm.$el.querySelector('.js-cancel-auto-merge').getAttribute('disabled')).toBeTruthy();
done();
});
});
@@ -181,7 +138,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
vm.mr.shouldRemoveSourceBranch = true;
Vue.nextTick(() => {
- const normalizedText = el.innerText.replace(/\s+/g, ' ');
+ const normalizedText = vm.$el.innerText.replace(/\s+/g, ' ');
expect(normalizedText).toContain('The source branch will be removed');
expect(normalizedText).not.toContain('The source branch will not be removed');
done();
@@ -192,7 +149,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
vm.mr.currentUserId = 4;
Vue.nextTick(() => {
- expect(el.querySelector('.js-remove-source-branch')).toEqual(null);
+ expect(vm.$el.querySelector('.js-remove-source-branch')).toEqual(null);
done();
});
});
@@ -201,7 +158,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
vm.isRemovingSourceBranch = true;
Vue.nextTick(() => {
- expect(el.querySelector('.js-remove-source-branch').getAttribute('disabled')).toBeTruthy();
+ expect(vm.$el.querySelector('.js-remove-source-branch').getAttribute('disabled')).toBeTruthy();
done();
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
index 2dc3b72ea40..43a989393ba 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
@@ -1,108 +1,99 @@
import Vue from 'vue';
-import mergedComponent from '~/vue_merge_request_widget/components/states/mr_widget_merged';
+import mergedComponent from '~/vue_merge_request_widget/components/states/mr_widget_merged.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
-
-const targetBranch = 'foo';
-
-const createComponent = () => {
- const Component = Vue.extend(mergedComponent);
- const mr = {
- isRemovingSourceBranch: false,
- cherryPickInForkPath: false,
- canCherryPickInCurrentMR: true,
- revertInForkPath: false,
- canRevertInCurrentMR: true,
- canRemoveSourceBranch: true,
- sourceBranchRemoved: true,
- metrics: {
- mergedBy: {},
- mergedAt: 'mergedUpdatedAt',
- readableMergedAt: '',
- closedBy: {},
- closedAt: 'mergedUpdatedAt',
- readableClosedAt: '',
- },
- updatedAt: 'mrUpdatedAt',
- targetBranch,
- };
-
- const service = {
- removeSourceBranch() {},
- };
-
- return new Component({
- el: document.createElement('div'),
- propsData: { mr, service },
- });
-};
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetMerged', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr, service } = mergedComponent.props;
-
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
-
- expect(service.type instanceof Object).toBeTruthy();
- expect(service.required).toBeTruthy();
- });
- });
-
- describe('components', () => {
- it('should have components added', () => {
- expect(mergedComponent.components['mr-widget-author-and-time']).toBeDefined();
- });
+ let vm;
+ const targetBranch = 'foo';
+
+ beforeEach(() => {
+ const Component = Vue.extend(mergedComponent);
+ const mr = {
+ isRemovingSourceBranch: false,
+ cherryPickInForkPath: false,
+ canCherryPickInCurrentMR: true,
+ revertInForkPath: false,
+ canRevertInCurrentMR: true,
+ canRemoveSourceBranch: true,
+ sourceBranchRemoved: true,
+ metrics: {
+ mergedBy: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://localhost:3000/root',
+ avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ },
+ mergedAt: 'Jan 24, 2018 1:02pm GMT+0000',
+ readableMergedAt: '',
+ closedBy: {},
+ closedAt: 'Jan 24, 2018 1:02pm GMT+0000',
+ readableClosedAt: '',
+ },
+ updatedAt: 'mergedUpdatedAt',
+ targetBranch,
+ };
+
+ const service = {
+ removeSourceBranch() {},
+ };
+
+ spyOn(eventHub, '$emit');
+
+ vm = mountComponent(Component, { mr, service });
});
- describe('data', () => {
- it('should have default data', () => {
- const data = mergedComponent.data();
-
- expect(data.isMakingRequest).toBeFalsy();
- });
+ afterEach(() => {
+ vm.$destroy();
});
describe('computed', () => {
describe('shouldShowRemoveSourceBranch', () => {
- it('should correct value when fields changed', () => {
- const vm = createComponent();
+ it('returns true when sourceBranchRemoved is false', () => {
vm.mr.sourceBranchRemoved = false;
- expect(vm.shouldShowRemoveSourceBranch).toBeTruthy();
+ expect(vm.shouldShowRemoveSourceBranch).toEqual(true);
+ });
+ it('returns false wehn sourceBranchRemoved is true', () => {
vm.mr.sourceBranchRemoved = true;
- expect(vm.shouldShowRemoveSourceBranch).toBeFalsy();
+ expect(vm.shouldShowRemoveSourceBranch).toEqual(false);
+ });
+ it('returns false when canRemoveSourceBranch is false', () => {
vm.mr.sourceBranchRemoved = false;
vm.mr.canRemoveSourceBranch = false;
- expect(vm.shouldShowRemoveSourceBranch).toBeFalsy();
+ expect(vm.shouldShowRemoveSourceBranch).toEqual(false);
+ });
+ it('returns false when is making request', () => {
vm.mr.canRemoveSourceBranch = true;
vm.isMakingRequest = true;
- expect(vm.shouldShowRemoveSourceBranch).toBeFalsy();
+ expect(vm.shouldShowRemoveSourceBranch).toEqual(false);
+ });
+ it('returns true when all are true', () => {
vm.mr.isRemovingSourceBranch = true;
vm.mr.canRemoveSourceBranch = true;
vm.isMakingRequest = true;
- expect(vm.shouldShowRemoveSourceBranch).toBeFalsy();
+ expect(vm.shouldShowRemoveSourceBranch).toEqual(false);
});
});
+
describe('shouldShowSourceBranchRemoving', () => {
it('should correct value when fields changed', () => {
- const vm = createComponent();
vm.mr.sourceBranchRemoved = false;
- expect(vm.shouldShowSourceBranchRemoving).toBeFalsy();
+ expect(vm.shouldShowSourceBranchRemoving).toEqual(false);
vm.mr.sourceBranchRemoved = true;
- expect(vm.shouldShowRemoveSourceBranch).toBeFalsy();
+ expect(vm.shouldShowRemoveSourceBranch).toEqual(false);
vm.mr.sourceBranchRemoved = false;
vm.isMakingRequest = true;
- expect(vm.shouldShowSourceBranchRemoving).toBeTruthy();
+ expect(vm.shouldShowSourceBranchRemoving).toEqual(true);
vm.isMakingRequest = false;
vm.mr.isRemovingSourceBranch = true;
- expect(vm.shouldShowSourceBranchRemoving).toBeTruthy();
+ expect(vm.shouldShowSourceBranchRemoving).toEqual(true);
});
});
});
@@ -110,8 +101,6 @@ describe('MRWidgetMerged', () => {
describe('methods', () => {
describe('removeSourceBranch', () => {
it('should set flag and call service then request main component to update the widget', (done) => {
- const vm = createComponent();
- spyOn(eventHub, '$emit');
spyOn(vm.service, 'removeSourceBranch').and.returnValue(new Promise((resolve) => {
resolve({
data: {
@@ -123,7 +112,7 @@ describe('MRWidgetMerged', () => {
vm.removeSourceBranch();
setTimeout(() => {
const args = eventHub.$emit.calls.argsFor(0);
- expect(vm.isMakingRequest).toBeTruthy();
+ expect(vm.isMakingRequest).toEqual(true);
expect(args[0]).toEqual('MRWidgetUpdateRequested');
expect(args[1]).not.toThrow();
done();
@@ -132,53 +121,50 @@ describe('MRWidgetMerged', () => {
});
});
- describe('template', () => {
- let vm;
- let el;
+ it('has merged by information', () => {
+ expect(vm.$el.textContent).toContain('Merged by');
+ expect(vm.$el.textContent).toContain('Administrator');
+ });
- beforeEach(() => {
- vm = createComponent();
- el = vm.$el;
- });
+ it('renders branch information', () => {
+ expect(vm.$el.textContent).toContain('The changes were merged into');
+ expect(vm.$el.textContent).toContain(targetBranch);
+ });
- it('should have correct elements', () => {
- expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.querySelector('.js-mr-widget-author')).toBeDefined();
- expect(el.innerText).toContain('The changes were merged into');
- expect(el.innerText).toContain(targetBranch);
- expect(el.innerText).toContain('The source branch has been removed');
- expect(el.innerText).toContain('Revert');
- expect(el.innerText).toContain('Cherry-pick');
- expect(el.innerText).not.toContain('You can remove source branch now');
- expect(el.innerText).not.toContain('The source branch is being removed');
- });
+ it('renders information about branch being removed', () => {
+ expect(vm.$el.textContent).toContain('The source branch has been removed');
+ });
- it('should not show source branch removed text', (done) => {
- vm.mr.sourceBranchRemoved = false;
+ it('shows revert and cherry-pick buttons', () => {
+ expect(vm.$el.textContent).toContain('Revert');
+ expect(vm.$el.textContent).toContain('Cherry-pick');
+ });
- Vue.nextTick(() => {
- expect(el.innerText).toContain('You can remove source branch now');
- expect(el.innerText).not.toContain('The source branch has been removed');
- done();
- });
+ it('should not show source branch removed text', (done) => {
+ vm.mr.sourceBranchRemoved = false;
+
+ Vue.nextTick(() => {
+ expect(vm.$el.innerText).toContain('You can remove source branch now');
+ expect(vm.$el.innerText).not.toContain('The source branch has been removed');
+ done();
});
+ });
- it('should show source branch removing text', (done) => {
- vm.mr.isRemovingSourceBranch = true;
- vm.mr.sourceBranchRemoved = false;
+ it('should show source branch removing text', (done) => {
+ vm.mr.isRemovingSourceBranch = true;
+ vm.mr.sourceBranchRemoved = false;
- Vue.nextTick(() => {
- expect(el.innerText).toContain('The source branch is being removed');
- expect(el.innerText).not.toContain('You can remove source branch now');
- expect(el.innerText).not.toContain('The source branch has been removed');
- done();
- });
+ Vue.nextTick(() => {
+ expect(vm.$el.innerText).toContain('The source branch is being removed');
+ expect(vm.$el.innerText).not.toContain('You can remove source branch now');
+ expect(vm.$el.innerText).not.toContain('The source branch has been removed');
+ done();
});
+ });
- it('should use mergedEvent updatedAt as tooltip title', () => {
- expect(
- el.querySelector('time').getAttribute('title'),
- ).toBe('mergedUpdatedAt');
- });
+ it('should use mergedEvent mergedAt as tooltip title', () => {
+ expect(
+ vm.$el.querySelector('time').getAttribute('title'),
+ ).toBe('Jan 24, 2018 1:02pm GMT+0000');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merging_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merging_spec.js
new file mode 100644
index 00000000000..0b2ed2d4086
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merging_spec.js
@@ -0,0 +1,34 @@
+import Vue from 'vue';
+import mergingComponent from '~/vue_merge_request_widget/components/states/mr_widget_merging.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
+
+describe('MRWidgetMerging', () => {
+ let vm;
+ beforeEach(() => {
+ const Component = Vue.extend(mergingComponent);
+
+ vm = mountComponent(Component, { mr: {
+ targetBranchPath: '/branch-path',
+ targetBranch: 'branch',
+ } });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders information about merge request being merged', () => {
+ expect(
+ vm.$el.querySelector('.media-body').textContent.trim().replace(/\s\s+/g, ' ').replace(/[\r\n]+/g, ' '),
+ ).toContain('This merge request is in the process of being merged');
+ });
+
+ it('renders branch information', () => {
+ expect(
+ vm.$el.querySelector('.mr-info-list').textContent.trim().replace(/\s\s+/g, ' ').replace(/[\r\n]+/g, ' '),
+ ).toEqual('The changes will be merged into branch');
+ expect(
+ vm.$el.querySelector('a').getAttribute('href'),
+ ).toEqual('/branch-path');
+ });
+});
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 1127576617b..073f26cc78f 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
@@ -170,14 +170,14 @@ describe('MRWidgetReadyToMerge', () => {
expect(vm.iconClass).toEqual('success');
});
- it('shows x for failed status', () => {
+ it('shows warning icon for failed status', () => {
vm.mr.hasCI = true;
- expect(vm.iconClass).toEqual('failed');
+ expect(vm.iconClass).toEqual('warning');
});
- it('shows x for merge not allowed', () => {
+ it('shows warning icon for merge not allowed', () => {
vm.mr.hasCI = true;
- expect(vm.iconClass).toEqual('failed');
+ expect(vm.iconClass).toEqual('warning');
});
});
@@ -371,6 +371,10 @@ describe('MRWidgetReadyToMerge', () => {
});
});
+ beforeEach(() => {
+ loadFixtures('merge_requests/merge_request_of_current_user.html.raw');
+ });
+
it('should call start and stop polling when MR merged', (done) => {
spyOn(eventHub, '$emit');
spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
@@ -392,6 +396,47 @@ describe('MRWidgetReadyToMerge', () => {
}, 333);
});
+ it('updates status box', (done) => {
+ spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
+ spyOn(vm, 'initiateRemoveSourceBranchPolling');
+
+ vm.handleMergePolling(() => {}, () => {});
+
+ setTimeout(() => {
+ const statusBox = document.querySelector('.status-box');
+ expect(statusBox.classList.contains('status-box-mr-merged')).toBeTruthy();
+ expect(statusBox.textContent).toContain('Merged');
+
+ done();
+ });
+ });
+
+ it('hides close button', (done) => {
+ spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
+ spyOn(vm, 'initiateRemoveSourceBranchPolling');
+
+ vm.handleMergePolling(() => {}, () => {});
+
+ setTimeout(() => {
+ expect(document.querySelector('.btn-close').classList.contains('hidden')).toBeTruthy();
+
+ done();
+ });
+ });
+
+ it('updates merge request count badge', (done) => {
+ spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
+ spyOn(vm, 'initiateRemoveSourceBranchPolling');
+
+ vm.handleMergePolling(() => {}, () => {});
+
+ setTimeout(() => {
+ expect(document.querySelector('.js-merge-counter').textContent).toBe('0');
+
+ done();
+ });
+ });
+
it('should continue polling until MR is merged', (done) => {
spyOn(vm.service, 'poll').and.returnValue(returnPromise('some_other_state'));
spyOn(vm, 'initiateRemoveSourceBranchPolling');
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index ca29c9fee32..3dd75307484 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -14,7 +14,6 @@ export default {
"updated_by_id": null,
"created_at": "2017-04-07T12:27:26.718Z",
"updated_at": "2017-04-07T15:39:25.852Z",
- "deleted_at": null,
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
@@ -39,7 +38,7 @@ export default {
"username": "root",
"id": 1,
"state": "active",
- "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "http://localhost:3000/root"
},
"merged_at": "2017-04-07T15:39:25.696Z",
@@ -51,7 +50,7 @@ export default {
"username": "root",
"id": 1,
"state": "active",
- "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "http://localhost:3000/root"
},
"merge_user": null,
@@ -65,7 +64,7 @@ export default {
"username": "root",
"id": 1,
"state": "active",
- "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "http://localhost:3000/root"
},
"active": false,
@@ -160,10 +159,10 @@ export default {
"username": "root",
"id": 1,
"state": "active",
- "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "http://localhost:3000/root"
},
- "author_gravatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "author_gravatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"commit_url": "http://localhost:3000/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d",
"commit_path": "/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d"
},
diff --git a/spec/javascripts/vue_shared/components/clipboard_button_spec.js b/spec/javascripts/vue_shared/components/clipboard_button_spec.js
new file mode 100644
index 00000000000..08e4e1f8337
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/clipboard_button_spec.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('clipboard button', () => {
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(clipboardButton);
+ vm = mountComponent(Component, {
+ text: 'copy me',
+ title: 'Copy this value into Clipboard!',
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders a button for clipboard', () => {
+ expect(vm.$el.tagName).toEqual('BUTTON');
+ expect(vm.$el.getAttribute('data-clipboard-text')).toEqual('copy me');
+ expect(vm.$el.querySelector('i').className).toEqual('fa fa-clipboard');
+ });
+
+ it('should have a tooltip with default values', () => {
+ expect(vm.$el.getAttribute('data-original-title')).toEqual('Copy this value into Clipboard!');
+ expect(vm.$el.getAttribute('data-placement')).toEqual('top');
+ expect(vm.$el.getAttribute('data-container')).toEqual(null);
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/confirmation_input_spec.js b/spec/javascripts/vue_shared/components/confirmation_input_spec.js
new file mode 100644
index 00000000000..a6a12614e77
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/confirmation_input_spec.js
@@ -0,0 +1,63 @@
+import Vue from 'vue';
+import confirmationInput from '~/vue_shared/components/confirmation_input.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Confirmation input component', () => {
+ const Component = Vue.extend(confirmationInput);
+ const props = {
+ inputId: 'dummy-id',
+ confirmationKey: 'confirmation-key',
+ confirmationValue: 'confirmation-value',
+ };
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('props', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, props);
+ });
+
+ it('sets id of the input field to inputId', () => {
+ expect(vm.$refs.enteredValue.id).toBe(props.inputId);
+ });
+
+ it('sets name of the input field to confirmationKey', () => {
+ expect(vm.$refs.enteredValue.name).toBe(props.confirmationKey);
+ });
+ });
+
+ describe('computed', () => {
+ describe('inputLabel', () => {
+ it('escapes confirmationValue by default', () => {
+ vm = mountComponent(Component, { ...props, confirmationValue: 'n<e></e>ds escap"ng' });
+ expect(vm.inputLabel).toBe('Type <code>n&lt;e&gt;&lt;/e&gt;ds escap&quot;ng</code> to confirm:');
+ });
+
+ it('does not escape confirmationValue if escapeValue is false', () => {
+ vm = mountComponent(Component, { ...props, confirmationValue: 'n<e></e>ds escap"ng', shouldEscapeConfirmationValue: false });
+ expect(vm.inputLabel).toBe('Type <code>n<e></e>ds escap"ng</code> to confirm:');
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('hasCorrectValue', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, props);
+ });
+
+ it('returns false if entered value is incorrect', () => {
+ vm.$refs.enteredValue.value = 'incorrect';
+ expect(vm.hasCorrectValue()).toBe(false);
+ });
+
+ it('returns true if entered value is correct', () => {
+ vm.$refs.enteredValue.value = props.confirmationValue;
+ expect(vm.hasCorrectValue()).toBe(true);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/loading_icon_spec.js b/spec/javascripts/vue_shared/components/loading_icon_spec.js
index 1baf3537741..5cd3466f501 100644
--- a/spec/javascripts/vue_shared/components/loading_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/loading_icon_spec.js
@@ -16,7 +16,8 @@ describe('Loading Icon Component', () => {
).toEqual('fa fa-spin fa-spinner fa-1x');
expect(component.$el.tagName).toEqual('DIV');
- expect(component.$el.classList.contains('text-center')).toEqual(true);
+ expect(component.$el.classList).toContain('text-center');
+ expect(component.$el.classList).toContain('loading-container');
});
it('should render accessibility attributes', () => {
diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js
index 24209be83fe..5f980bbf36c 100644
--- a/spec/javascripts/vue_shared/components/markdown/field_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js
@@ -12,14 +12,14 @@ describe('Markdown field component', () => {
beforeEach((done) => {
vm = new Vue({
+ components: {
+ fieldComponent,
+ },
data() {
return {
text: 'testing\n123',
};
},
- components: {
- fieldComponent,
- },
template: `
<field-component
markdown-preview-path="/preview"
diff --git a/spec/javascripts/vue_shared/components/modal_spec.js b/spec/javascripts/vue_shared/components/modal_spec.js
index fe75a86cac8..a5f9c75be4e 100644
--- a/spec/javascripts/vue_shared/components/modal_spec.js
+++ b/spec/javascripts/vue_shared/components/modal_spec.js
@@ -25,7 +25,7 @@ describe('Modal', () => {
});
describe('with id', () => {
- it('does not render a primary button', () => {
+ describe('does not render a primary button', () => {
beforeEach(() => {
vm = mountComponent(modalComponent, {
id: 'my-modal',
diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
index 20363e78094..2de108da2ac 100644
--- a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
+++ b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
@@ -21,22 +21,21 @@ describe('collapsedGroupedDatePicker', () => {
});
});
- it('toggleCollapse events', () => {
- const toggleCollapse = jasmine.createSpy();
-
+ describe('toggleCollapse events', () => {
beforeEach((done) => {
+ spyOn(vm, 'toggleSidebar');
vm.minDate = new Date('07/17/2016');
Vue.nextTick(done);
});
it('should emit when sidebar is toggled', () => {
vm.$el.querySelector('.gutter-toggle').click();
- expect(toggleCollapse).toHaveBeenCalled();
+ expect(vm.toggleSidebar).toHaveBeenCalled();
});
it('should emit when collapsed-calendar-icon is clicked', () => {
vm.$el.querySelector('.sidebar-collapsed-icon').click();
- expect(toggleCollapse).toHaveBeenCalled();
+ expect(vm.toggleSidebar).toHaveBeenCalled();
});
});
diff --git a/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js b/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js
new file mode 100644
index 00000000000..6940b04573e
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js
@@ -0,0 +1,77 @@
+import Vue from 'vue';
+
+import stackedProgressBarComponent from '~/vue_shared/components/stacked_progress_bar.vue';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+const createComponent = (config) => {
+ const Component = Vue.extend(stackedProgressBarComponent);
+ const defaultConfig = Object.assign({}, {
+ successLabel: 'Synced',
+ failureLabel: 'Failed',
+ neutralLabel: 'Out of sync',
+ successCount: 10,
+ failureCount: 5,
+ totalCount: 20,
+ }, config);
+
+ return mountComponent(Component, defaultConfig);
+};
+
+describe('StackedProgressBarComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('neutralCount', () => {
+ it('returns neutralCount based on totalCount, successCount and failureCount', () => {
+ expect(vm.neutralCount).toBe(5); // 20 - 10 - 5
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('getPercent', () => {
+ it('returns percentage from provided count based on `totalCount`', () => {
+ expect(vm.getPercent(10)).toBe(50);
+ });
+ });
+
+ describe('barStyle', () => {
+ it('returns style string based on percentage provided', () => {
+ expect(vm.barStyle(50)).toBe('width: 50%;');
+ });
+ });
+
+ describe('getTooltip', () => {
+ it('returns label string based on label and count provided', () => {
+ expect(vm.getTooltip('Synced', 10)).toBe('Synced: 10');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders container element', () => {
+ expect(vm.$el.classList.contains('stacked-progress-bar')).toBeTruthy();
+ });
+
+ it('renders empty state when count is unavailable', () => {
+ const vmX = createComponent({ totalCount: 0, successCount: 0, failureCount: 0 });
+ expect(vmX.$el.querySelectorAll('.status-unavailable').length).not.toBe(0);
+ vmX.$destroy();
+ });
+
+ it('renders bar elements when count is available', () => {
+ expect(vm.$el.querySelectorAll('.status-green').length).not.toBe(0);
+ expect(vm.$el.querySelectorAll('.status-neutral').length).not.toBe(0);
+ expect(vm.$el.querySelectorAll('.status-red').length).not.toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 1465ef5855f..c63f15e5880 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -32,7 +32,7 @@ describe('Pagination component', () => {
change: spy,
});
- expect(component.$el.innerHTML).not.toBeDefined();
+ expect(component.$el.childNodes.length).toEqual(0);
});
describe('prev button', () => {
@@ -72,7 +72,6 @@ describe('Pagination component', () => {
});
component.$el.querySelector('.js-previous-button a').click();
-
expect(spy).toHaveBeenCalledWith(1);
});
});
diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
index 8450ad9dbcb..adf80d0c2bb 100644
--- a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
+++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 45a0bb0650f..8edba1f47a3 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -1,4 +1,4 @@
-/* global Mousetrap */
+import Mousetrap from 'mousetrap';
import Dropzone from 'dropzone';
import ZenMode from '~/zen_mode';
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index b68301a066a..5100f5737c2 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -194,6 +194,12 @@ describe Backup::Manager do
)
end
+ it 'prints the list of available backups' do
+ expect { subject.unpack }.to raise_error SystemExit
+ expect(progress).to have_received(:puts)
+ .with(a_string_matching('1451606400_2016_01_01_1.2.3\n 1451520000_2015_12_31'))
+ end
+
it 'fails the operation and prints an error' do
expect { subject.unpack }.to raise_error SystemExit
expect(progress).to have_received(:puts)
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 935146c17fc..a41a28a56f1 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -53,7 +53,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
doc = reference_filter("See (#{reference}.)")
exp = Regexp.escape(range.reference_link_text)
- expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>#{exp}</a>\.\)})
end
it 'ignores invalid commit IDs' do
@@ -222,7 +222,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
doc = reference_filter("Fixed (#{reference}.)")
exp = Regexp.escape(range.reference_link_text(project))
- expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>#{exp}</a>\.\)})
end
it 'ignores invalid commit IDs on the referenced project' do
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index 080a5f57da9..35f8792ff35 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -42,7 +42,7 @@ describe Banzai::Filter::CommitReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("See (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>#{commit.short_id}</a>\.\)})
end
it 'ignores invalid commit IDs' do
@@ -199,12 +199,12 @@ describe Banzai::Filter::CommitReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>#{commit.reference_link_text(project)}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>#{commit.reference_link_text(project)}</a>\.\)})
end
it 'ignores invalid commit IDs on the referenced project' do
act = "Committed #{invalidate_reference(reference)}"
- expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
+ expect(reference_filter(act).to_html).to match(%r{<a.+>#{Regexp.escape(invalidate_reference(reference))}</a>})
end
end
end
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 a0d391d981c..d9018a7e4fe 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -49,7 +49,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do
it 'links with adjacent text' do
doc = filter("Issue (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>#{reference}</a>\.\)})
end
it 'includes a title attribute' do
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
index 51920869545..c84b98eb225 100644
--- a/spec/lib/banzai/filter/image_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -14,7 +14,7 @@ describe Banzai::Filter::ImageLinkFilter do
it 'does not wrap a duplicate link' do
doc = filter(%Q(<a href="/whatever">#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')}</a>))
- expect(doc.to_html).to match /^<a href="\/whatever"><img[^>]*><\/a>$/
+ expect(doc.to_html).to match %r{^<a href="/whatever"><img[^>]*></a>$}
end
it 'works with external images' do
@@ -24,6 +24,6 @@ describe Banzai::Filter::ImageLinkFilter do
it 'works with inline images' do
doc = filter(%Q(<p>test #{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')} inline</p>))
- expect(doc.to_html).to match /^<p>test <a[^>]*><img[^>]*><\/a> inline<\/p>$/
+ expect(doc.to_html).to match %r{^<p>test <a[^>]*><img[^>]*></a> inline</p>$}
end
end
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 3a5f52ea23f..905fbb9434b 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -288,7 +288,7 @@ describe Banzai::Filter::IssueReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>#{Regexp.escape(issue.to_reference(project))} \(comment 123\)</a>\.\)})
end
it 'includes default classes' do
@@ -317,7 +317,7 @@ describe Banzai::Filter::IssueReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference_link}.)")
- expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>Reference</a>\.\)})
end
it 'includes default classes' do
@@ -346,7 +346,7 @@ describe Banzai::Filter::IssueReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference_link}.)")
- expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>Reference</a>\.\)})
end
it 'includes default classes' 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 158844e25ae..eeb82822f68 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -42,7 +42,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Merge (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>#{Regexp.escape(reference)}</a>\.\)})
end
it 'ignores invalid merge IDs' do
@@ -211,7 +211,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Merge (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)</a>\.\)})
end
end
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index f38f0776303..3ca4652f7cc 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -8,7 +8,8 @@ describe Banzai::Filter::RelativeLinkFilter do
group: group,
project_wiki: project_wiki,
ref: ref,
- requested_path: requested_path
+ requested_path: requested_path,
+ only_path: only_path
})
described_class.call(doc, contexts)
@@ -37,6 +38,7 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:commit) { project.commit(ref) }
let(:project_wiki) { nil }
let(:requested_path) { '/' }
+ let(:only_path) { true }
shared_examples :preserve_unchanged do
it 'does not modify any relative URL in anchor' do
@@ -240,26 +242,35 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:commit) { nil }
let(:ref) { nil }
let(:requested_path) { nil }
+ let(:upload_path) { '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' }
+ let(:relative_path) { "/#{project.full_path}#{upload_path}" }
context 'to a project upload' do
+ context 'with an absolute URL' do
+ let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
+ let(:only_path) { false }
+
+ it 'rewrites the link correctly' do
+ doc = filter(link(upload_path))
+
+ expect(doc.at_css('a')['href']).to eq(absolute_path)
+ end
+ end
+
it 'rebuilds relative URL for a link' do
- doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
- expect(doc.at_css('a')['href'])
- .to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ doc = filter(link(upload_path))
+ expect(doc.at_css('a')['href']).to eq(relative_path)
- doc = filter(nested(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')))
- expect(doc.at_css('a')['href'])
- .to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ doc = filter(nested(link(upload_path)))
+ expect(doc.at_css('a')['href']).to eq(relative_path)
end
it 'rebuilds relative URL for an image' do
- doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
- expect(doc.at_css('img')['src'])
- .to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ doc = filter(image(upload_path))
+ expect(doc.at_css('img')['src']).to eq(relative_path)
- doc = filter(nested(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')))
- expect(doc.at_css('img')['src'])
- .to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ doc = filter(nested(image(upload_path)))
+ expect(doc.at_css('img')['src']).to eq(relative_path)
end
it 'does not modify absolute URL' do
@@ -267,18 +278,19 @@ describe Banzai::Filter::RelativeLinkFilter do
expect(doc.at_css('a')['href']).to eq 'http://example.com'
end
- it 'supports Unicode filenames' do
+ it 'supports unescaped Unicode filenames' do
path = '/uploads/한글.png'
- escaped = Addressable::URI.escape(path)
+ doc = filter(link(path))
- # Stub these methods so the file doesn't actually need to be in the repo
- allow_any_instance_of(described_class)
- .to receive(:file_exists?).and_return(true)
- allow_any_instance_of(described_class)
- .to receive(:image?).with(path).and_return(true)
+ expect(doc.at_css('a')['href']).to eq("/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png")
+ end
+ it 'supports escaped Unicode filenames' do
+ path = '/uploads/한글.png'
+ escaped = Addressable::URI.escape(path)
doc = filter(image(escaped))
- expect(doc.at_css('img')['src']).to match "/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png"
+
+ expect(doc.at_css('img')['src']).to eq("/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png")
end
end
@@ -288,6 +300,17 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:project) { nil }
let(:relative_path) { "/groups/#{group.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" }
+ context 'with an absolute URL' do
+ let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
+ let(:only_path) { false }
+
+ it 'rewrites the link correctly' do
+ doc = filter(upload_link)
+
+ expect(doc.at_css('a')['href']).to eq(absolute_path)
+ end
+ end
+
it 'rewrites the link correctly' do
doc = filter(upload_link)
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index 3a07a6dc179..e068e02d4fc 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -28,7 +28,7 @@ describe Banzai::Filter::SnippetReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Snippet (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>#{Regexp.escape(reference)}</a>\.\)})
end
it 'ignores invalid snippet IDs' do
@@ -192,13 +192,13 @@ describe Banzai::Filter::SnippetReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("See (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>#{Regexp.escape(snippet.to_reference(project))}</a>\.\)})
end
it 'ignores invalid snippet IDs on the referenced project' do
act = "See #{invalidate_reference(reference)}"
- expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
+ expect(reference_filter(act).to_html).to match(%r{<a.+>#{Regexp.escape(invalidate_reference(reference))}</a>})
end
end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index c76adc262fc..2f86a046d28 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -146,7 +146,7 @@ describe Banzai::Filter::UserReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Mention me (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>#{reference}</a>\.\)})
end
it 'includes default classes' do
@@ -172,7 +172,7 @@ describe Banzai::Filter::UserReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Mention me (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>User<\/a>\.\)/)
+ expect(doc.to_html).to match(%r{\(<a.+>User</a>\.\)})
end
it 'includes a data-user attribute' do
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index 9596f004052..50d053011b3 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -10,15 +10,23 @@ describe Banzai::Filter::WikiLinkFilter do
it "doesn't rewrite absolute links" do
filtered_link = filter("<a href='http://example.com:8000/'>Link</a>", project_wiki: wiki).children[0]
+
expect(filtered_link.attribute('href').value).to eq('http://example.com:8000/')
end
+ it "doesn't rewrite links to project uploads" do
+ filtered_link = filter("<a href='/uploads/a.test'>Link</a>", project_wiki: wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to eq('/uploads/a.test')
+ end
+
describe "invalid links" do
invalid_links = ["http://:8080", "http://", "http://:8080/path"]
invalid_links.each do |invalid_link|
it "doesn't rewrite invalid invalid_links like #{invalid_link}" do
filtered_link = filter("<a href='#{invalid_link}'>Link</a>", project_wiki: wiki).children[0]
+
expect(filtered_link.attribute('href').value).to eq(invalid_link)
end
end
diff --git a/spec/lib/gitaly/server_spec.rb b/spec/lib/gitaly/server_spec.rb
new file mode 100644
index 00000000000..ed5d56e91d4
--- /dev/null
+++ b/spec/lib/gitaly/server_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Gitaly::Server do
+ describe '.all' do
+ let(:storages) { Gitlab.config.repositories.storages }
+
+ it 'includes all storages' do
+ expect(storages.count).to eq(described_class.all.count)
+ expect(storages.keys).to eq(described_class.all.map(&:storage))
+ end
+ end
+
+ subject { described_class.all.first }
+
+ it { is_expected.to respond_to(:server_version) }
+ it { is_expected.to respond_to(:git_binary_version) }
+ it { is_expected.to respond_to(:up_to_date?) }
+ it { is_expected.to respond_to(:address) }
+
+ describe 'request memoization' do
+ context 'when requesting multiple properties', :request_store do
+ it 'uses memoization for the info request' do
+ expect do
+ subject.server_version
+ subject.up_to_date?
+ end.to change { Gitlab::GitalyClient.get_request_count }.by(1)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
new file mode 100644
index 00000000000..726a3c1c83a
--- /dev/null
+++ b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe Gitlab::Auth::BlockedUserTracker do
+ set(:user) { create(:user) }
+
+ describe '.log_if_user_blocked' do
+ it 'does not log if user failed to login due to undefined reason' do
+ expect_any_instance_of(SystemHooksService).not_to receive(:execute_hooks_for)
+
+ expect(described_class.log_if_user_blocked({})).to be_nil
+ end
+
+ it 'gracefully handles malformed environment variables' do
+ env = { 'warden.options' => 'test' }
+
+ expect(described_class.log_if_user_blocked(env)).to be_nil
+ end
+
+ context 'failed login due to blocked user' do
+ let(:env) do
+ {
+ 'warden.options' => { message: User::BLOCKED_MESSAGE },
+ described_class::ACTIVE_RECORD_REQUEST_PARAMS => { 'user' => { 'login' => user.username } }
+ }
+ end
+
+ subject { described_class.log_if_user_blocked(env) }
+
+ before do
+ expect_any_instance_of(SystemHooksService).to receive(:execute_hooks_for).with(user, :failed_login)
+ end
+
+ it 'logs a blocked user' do
+ user.block!
+
+ expect(subject).to be_truthy
+ end
+
+ it 'logs a blocked user by e-mail' do
+ user.block!
+ env[described_class::ACTIVE_RECORD_REQUEST_PARAMS]['user']['login'] = user.email
+
+ expect(subject).to be_truthy
+ end
+
+ it 'logs a LDAP blocked user' do
+ user.ldap_block!
+
+ expect(subject).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
index 4637816570c..2733eef6611 100644
--- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
@@ -76,6 +76,16 @@ describe Gitlab::Auth::UserAuthFinders do
expect(find_user_from_rss_token).to be_nil
end
end
+
+ context 'when the request format is empty' do
+ it 'the method call does not modify the original value' do
+ env['action_dispatch.request.formats'] = nil
+
+ find_user_from_rss_token
+
+ expect(env['action_dispatch.request.formats']).to be_nil
+ end
+ end
end
describe '#find_user_from_access_token' do
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index a6fbec295b5..cc202ce8bca 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -136,8 +136,8 @@ describe Gitlab::Auth do
it 'grants deploy key write permissions' do
project = create(:project)
- key = create(:deploy_key, can_push: true)
- create(:deploy_keys_project, deploy_key: key, project: project)
+ key = create(:deploy_key)
+ create(:deploy_keys_project, :write_access, deploy_key: key, project: project)
token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
@@ -146,7 +146,7 @@ describe Gitlab::Auth do
it 'does not grant deploy key write permissions' do
project = create(:project)
- key = create(:deploy_key, can_push: true)
+ key = create(:deploy_key)
token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
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
new file mode 100644
index 00000000000..21a791f5695
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount, :migration, schema: 20180105212544 do
+ let(:projects_table) { table(:projects) }
+ let(:merge_requests_table) { table(:merge_requests) }
+ let(:merge_request_diffs_table) { table(:merge_request_diffs) }
+ let(:merge_request_diff_commits_table) { table(:merge_request_diff_commits) }
+
+ let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') }
+ let(:merge_request) do
+ merge_requests_table.create!(target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: 'mr name')
+ end
+
+ def create_diff!(name, commits: 0)
+ mr_diff = merge_request_diffs_table.create!(
+ merge_request_id: merge_request.id)
+
+ commits.times do |i|
+ merge_request_diff_commits_table.create!(
+ merge_request_diff_id: mr_diff.id,
+ relative_order: i, sha: i)
+ end
+
+ mr_diff
+ end
+
+ describe '#perform' do
+ it 'migrates diffs that have no commits' do
+ diff = create_diff!('with_multiple_commits', commits: 0)
+
+ subject.perform(diff.id, diff.id)
+
+ expect(diff.reload.commits_count).to eq(0)
+ end
+
+ it 'migrates multiple diffs to the correct values' do
+ diffs = Array.new(3).map.with_index { |_, i| create_diff!(i, commits: 3) }
+
+ subject.perform(diffs.first.id, diffs.last.id)
+
+ diffs.each do |diff|
+ expect(diff.reload.commits_count).to eq(3)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
index 84d9e635810..c8df6dd2118 100644
--- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
+++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :truncate, :migration, schema: 20171114162227 do
+describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :migration, schema: 20171114162227 do
let(:merge_request_diffs) { table(:merge_request_diffs) }
let(:merge_requests) { table(:merge_requests) }
@@ -10,6 +10,15 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t
let(:merge_request_diff) { MergeRequest.find(merge_request.id).create_merge_request_diff }
let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) }
+ before do
+ allow_any_instance_of(MergeRequestDiff)
+ .to receive(:commits_count=).and_return(nil)
+ end
+
+ after do
+ [Project, MergeRequest, MergeRequestDiff].each(&:reset_column_information)
+ end
+
def diffs_to_hashes(diffs)
diffs.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS).map(&:with_indifferent_access)
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
index 7b5a00c6111..021e1d14b18 100644
--- 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
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder do
+describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder, :delete do
let(:migration) { described_class.new }
before do
@@ -8,7 +8,7 @@ describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder do
end
describe '#perform' do
- it 'renames the path of system-uploads', :truncate 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/')
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
index dfe3b31f1c0..4cdb679c97f 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
@@ -1,6 +1,16 @@
require 'rails_helper'
describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, :migration, schema: 20171128214150 do
+ # commits_count attribute is added in a next migration
+ before do
+ allow_any_instance_of(MergeRequestDiff)
+ .to receive(:commits_count=).and_return(nil)
+ end
+
+ after do
+ [MergeRequest, MergeRequestDiff].each(&:reset_column_information)
+ end
+
describe '#perform' do
let(:mr_with_event) { create(:merge_request) }
let!(:merged_event) { create(:event, :merged, target: mr_with_event) }
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index b5d86df09d2..f302e412a6e 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -74,14 +74,18 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do
importer.create_project_if_needed
end
- it 'creates the Git repo in disk' do
+ it 'creates the Git repo on disk with the proper symlink for hooks' do
create_bare_repository("#{project_path}.git")
importer.create_project_if_needed
project = Project.find_by_full_path(project_path)
+ repo_path = File.join(project.repository_storage_path, project.disk_path + '.git')
+ hook_path = File.join(repo_path, 'hooks')
- expect(File).to exist(File.join(project.repository_storage_path, project.disk_path + '.git'))
+ expect(File).to exist(repo_path)
+ expect(File.symlink?(hook_path)).to be true
+ expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
end
context 'hashed storage enabled' do
diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb
index 05e2d94cbd6..7549e9941b6 100644
--- a/spec/lib/gitlab/ci/ansi2html_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2html_spec.rb
@@ -217,11 +217,58 @@ describe Gitlab::Ci::Ansi2html do
"#{section_end[0...-5]}</div>"
end
- it "prints light red" do
- text = "#{section_start}\e[91mHello\e[0m\n#{section_end}"
- html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}}
+ shared_examples 'forbidden char in section_name' do
+ it 'ignores sections' do
+ text = "#{section_start}Some text#{section_end}"
+ html = text.gsub("\033[0K", '').gsub('<', '&lt;')
- expect(convert_html(text)).to eq(html)
+ expect(convert_html(text)).to eq(html)
+ end
+ end
+
+ shared_examples 'a legit section' do
+ let(:text) { "#{section_start}Some text#{section_end}" }
+
+ it 'prints light red' do
+ text = "#{section_start}\e[91mHello\e[0m\n#{section_end}"
+ html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}}
+
+ expect(convert_html(text)).to eq(html)
+ end
+
+ it 'begins with a section_start html marker' do
+ expect(convert_html(text)).to start_with(section_start_html)
+ end
+
+ it 'ends with a section_end html marker' do
+ expect(convert_html(text)).to end_with(section_end_html)
+ end
+ end
+
+ it_behaves_like 'a legit section'
+
+ context 'section name includes $' do
+ let(:section_name) { 'my_$ection'}
+
+ it_behaves_like 'forbidden char in section_name'
+ end
+
+ context 'section name includes <' do
+ let(:section_name) { '<a_tag>'}
+
+ it_behaves_like 'forbidden char in section_name'
+ end
+
+ context 'section name contains .-_' do
+ let(:section_name) { 'a.Legit-SeCtIoN_namE' }
+
+ it_behaves_like 'a legit section'
+ end
+
+ it 'do not allow XSS injections' do
+ text = "#{section_start}section_end:1:2<script>alert('XSS Hack!');</script>#{section_end}"
+
+ expect(convert_html(text)).not_to include('<script>')
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/key_spec.rb b/spec/lib/gitlab/ci/config/entry/key_spec.rb
index 5d4de60bc8a..3cbf19bea8b 100644
--- a/spec/lib/gitlab/ci/config/entry/key_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/key_spec.rb
@@ -4,6 +4,26 @@ describe Gitlab::Ci::Config::Entry::Key do
let(:entry) { described_class.new(config) }
describe 'validations' do
+ shared_examples 'key with slash' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ end
+
+ it 'reports errors with config value' do
+ expect(entry.errors).to include 'key config cannot contain the "/" character'
+ end
+ end
+
+ shared_examples 'key with only dots' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ end
+
+ it 'reports errors with config value' do
+ expect(entry.errors).to include 'key config cannot be "." or ".."'
+ end
+ end
+
context 'when entry config value is correct' do
let(:config) { 'test' }
@@ -30,6 +50,48 @@ describe Gitlab::Ci::Config::Entry::Key do
end
end
end
+
+ context 'when entry value contains slash' do
+ let(:config) { 'key/with/some/slashes' }
+
+ it_behaves_like 'key with slash'
+ end
+
+ context 'when entry value contains URI encoded slash (%2F)' do
+ let(:config) { 'key%2Fwith%2Fsome%2Fslashes' }
+
+ it_behaves_like 'key with slash'
+ end
+
+ context 'when entry value is a dot' do
+ let(:config) { '.' }
+
+ it_behaves_like 'key with only dots'
+ end
+
+ context 'when entry value is two dots' do
+ let(:config) { '..' }
+
+ it_behaves_like 'key with only dots'
+ end
+
+ context 'when entry value is a URI encoded dot (%2E)' do
+ let(:config) { '%2e' }
+
+ it_behaves_like 'key with only dots'
+ end
+
+ context 'when entry value is two URI encoded dots (%2E)' do
+ let(:config) { '%2E%2e' }
+
+ it_behaves_like 'key with only dots'
+ end
+
+ context 'when entry value is one dot and one URI encoded dot' do
+ let(:config) { '.%2e' }
+
+ it_behaves_like 'key with only dots'
+ end
end
describe '.default' do
diff --git a/spec/lib/gitlab/ci/status/build/action_spec.rb b/spec/lib/gitlab/ci/status/build/action_spec.rb
index 8c25f72804b..d612d29e3e0 100644
--- a/spec/lib/gitlab/ci/status/build/action_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/action_spec.rb
@@ -37,16 +37,16 @@ describe Gitlab::Ci::Status::Build::Action do
describe '.matches?' do
subject { described_class.matches?(build, user) }
- context 'when build is an action' do
- let(:build) { create(:ci_build, :manual) }
+ context 'when build is playable action' do
+ let(:build) { create(:ci_build, :playable) }
it 'is a correct match' do
expect(subject).to be true
end
end
- context 'when build is not manual' do
- let(:build) { create(:ci_build) }
+ context 'when build is not playable action' do
+ let(:build) { create(:ci_build, :non_playable) }
it 'does not match' do
expect(subject).to be false
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 43761c2fe0c..1de3a14b809 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -1038,7 +1038,7 @@ describe Gitlab::Database::MigrationHelpers do
end
describe '#change_column_type_using_background_migration' do
- let!(:issue) { create(:issue) }
+ let!(:issue) { create(:issue, :closed, closed_at: Time.zone.now) }
let(:issue_model) do
Class.new(ActiveRecord::Base) 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 596cc435bd9..cc7cb3f23fd 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,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :truncate do
+describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
index 1143182531f..f31475dbd71 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,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :truncate do
+describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :delete do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
let(:namespace) { create(:group, name: 'the-path') }
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
index e850b5cd6a4..0958144643b 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,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :truncate do
+describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :delete do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
let(:project) do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
index 7695b95dc57..1d31f96159c 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
@@ -13,7 +13,7 @@ shared_examples 'renames child namespaces' do |type|
end
end
-describe Gitlab::Database::RenameReservedPathsMigration::V1, :truncate do
+describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do
let(:subject) { FakeRenameReservedPathMigrationV1.new }
before do
diff --git a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb
new file mode 100644
index 00000000000..5d22dcfb508
--- /dev/null
+++ b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Gitlab::Git::AttributesAtRefParser, seed_helper: true do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ subject { described_class.new(repository, 'lfs') }
+
+ it 'loads .gitattributes blob' do
+ repository.raw # Initialize repository in advance since this also checks attributes
+
+ expected_filter = 'filter=lfs diff=lfs merge=lfs'
+ receive_blob = receive(:new).with(a_string_including(expected_filter))
+ expect(Gitlab::Git::AttributesParser).to receive_blob.and_call_original
+
+ subject
+ end
+
+ it 'handles missing blobs' do
+ expect { described_class.new(repository, 'non-existant-branch') }.not_to raise_error
+ end
+
+ describe '#attributes' do
+ it 'returns the attributes as a Hash' do
+ expect(subject.attributes('test.lfs')['filter']).to eq('lfs')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/attributes_spec.rb b/spec/lib/gitlab/git/attributes_parser_spec.rb
index b715fc3410a..323334e99a5 100644
--- a/spec/lib/gitlab/git/attributes_spec.rb
+++ b/spec/lib/gitlab/git/attributes_parser_spec.rb
@@ -1,11 +1,10 @@
require 'spec_helper'
-describe Gitlab::Git::Attributes, seed_helper: true do
- let(:path) do
- File.join(SEED_STORAGE_PATH, 'with-git-attributes.git')
- end
+describe Gitlab::Git::AttributesParser, seed_helper: true do
+ let(:attributes_path) { File.join(SEED_STORAGE_PATH, 'with-git-attributes.git', 'info', 'attributes') }
+ let(:data) { File.read(attributes_path) }
- subject { described_class.new(path) }
+ subject { described_class.new(data) }
describe '#attributes' do
context 'using a path with attributes' do
@@ -66,6 +65,26 @@ describe Gitlab::Git::Attributes, seed_helper: true do
expect(subject.attributes('test.foo')).to eq({})
end
end
+
+ context 'when attributes data is a file handle' do
+ subject do
+ File.open(attributes_path, 'r') do |file_handle|
+ described_class.new(file_handle)
+ end
+ end
+
+ it 'returns the attributes as a Hash' do
+ expect(subject.attributes('test.txt')).to eq({ 'text' => true })
+ end
+ end
+
+ context 'when attributes data is nil' do
+ let(:data) { nil }
+
+ it 'returns an empty Hash' do
+ expect(subject.attributes('test.foo')).to eq({})
+ end
+ end
end
describe '#patterns' do
@@ -74,14 +93,14 @@ describe Gitlab::Git::Attributes, seed_helper: true do
end
it 'parses an entry that uses a tab to separate the pattern and attributes' do
- expect(subject.patterns[File.join(path, '*.md')])
+ expect(subject.patterns[File.join('/', '*.md')])
.to eq({ 'gitlab-language' => 'markdown' })
end
it 'stores patterns in reverse order' do
first = subject.patterns.to_a[0]
- expect(first[0]).to eq(File.join(path, 'bla/bla.txt'))
+ expect(first[0]).to eq(File.join('/', 'bla/bla.txt'))
end
# It's a bit hard to test for something _not_ being processed. As such we'll
@@ -89,14 +108,6 @@ describe Gitlab::Git::Attributes, seed_helper: true do
it 'ignores any comments and empty lines' do
expect(subject.patterns.length).to eq(10)
end
-
- it 'does not parse anything when the attributes file does not exist' do
- expect(File).to receive(:exist?)
- .with(File.join(path, 'info/attributes'))
- .and_return(false)
-
- expect(subject.patterns).to eq({})
- end
end
describe '#parse_attributes' do
@@ -132,17 +143,9 @@ describe Gitlab::Git::Attributes, seed_helper: true do
expect { |b| subject.each_line(&b) }.to yield_successive_args(*args)
end
- it 'does not yield when the attributes file does not exist' do
- expect(File).to receive(:exist?)
- .with(File.join(path, 'info/attributes'))
- .and_return(false)
-
- expect { |b| subject.each_line(&b) }.not_to yield_control
- end
-
it 'does not yield when the attributes file has an unsupported encoding' do
- path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git')
- attrs = described_class.new(path)
+ path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git', 'info', 'attributes')
+ attrs = described_class.new(File.read(path))
expect { |b| attrs.each_line(&b) }.not_to yield_control
end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 07eb5b82d5f..8ac960133c5 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -16,6 +16,18 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
shared_examples 'finding blobs' do
+ context 'nil path' do
+ let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, nil) }
+
+ it { expect(blob).to eq(nil) }
+ end
+
+ context 'blank path' do
+ let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, '') }
+
+ it { expect(blob).to eq(nil) }
+ end
+
context 'file in subdir' do
let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") }
@@ -248,29 +260,57 @@ describe Gitlab::Git::Blob, seed_helper: true do
)
end
- it 'returns a list of Gitlab::Git::Blob' do
- blobs = described_class.batch_lfs_pointers(repository, [lfs_blob.id])
+ shared_examples 'fetching batch of LFS pointers' do
+ it 'returns a list of Gitlab::Git::Blob' do
+ blobs = described_class.batch_lfs_pointers(repository, [lfs_blob.id])
- expect(blobs.count).to eq(1)
- expect(blobs).to all( be_a(Gitlab::Git::Blob) )
- end
+ expect(blobs.count).to eq(1)
+ expect(blobs).to all( be_a(Gitlab::Git::Blob) )
+ end
- it 'silently ignores tree objects' do
- blobs = described_class.batch_lfs_pointers(repository, [tree_object.oid])
+ it 'accepts blob IDs as a lazy enumerator' do
+ blobs = described_class.batch_lfs_pointers(repository, [lfs_blob.id].lazy)
- expect(blobs).to eq([])
- end
+ expect(blobs.count).to eq(1)
+ expect(blobs).to all( be_a(Gitlab::Git::Blob) )
+ end
+
+ it 'handles empty list of IDs gracefully' do
+ blobs_1 = described_class.batch_lfs_pointers(repository, [].lazy)
+ blobs_2 = described_class.batch_lfs_pointers(repository, [])
- it 'silently ignores non lfs objects' do
- blobs = described_class.batch_lfs_pointers(repository, [non_lfs_blob.id])
+ expect(blobs_1).to eq([])
+ expect(blobs_2).to eq([])
+ end
+
+ it 'silently ignores tree objects' do
+ blobs = described_class.batch_lfs_pointers(repository, [tree_object.oid])
+
+ expect(blobs).to eq([])
+ end
+
+ it 'silently ignores non lfs objects' do
+ blobs = described_class.batch_lfs_pointers(repository, [non_lfs_blob.id])
- expect(blobs).to eq([])
+ expect(blobs).to eq([])
+ end
+
+ it 'avoids loading large blobs into memory' do
+ # This line could call `lookup` on `repository`, so do here before mocking.
+ non_lfs_blob_id = non_lfs_blob.id
+
+ expect(repository).not_to receive(:lookup)
+
+ described_class.batch_lfs_pointers(repository, [non_lfs_blob_id])
+ end
end
- it 'avoids loading large blobs into memory' do
- expect(repository).not_to receive(:lookup)
+ context 'when Gitaly batch_lfs_pointers is enabled' do
+ it_behaves_like 'fetching batch of LFS pointers'
+ end
- described_class.batch_lfs_pointers(repository, [non_lfs_blob.id])
+ context 'when Gitaly batch_lfs_pointers is disabled', :disable_gitaly do
+ it_behaves_like 'fetching batch of LFS pointers'
end
end
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 6a07a3ca8b8..85e6efd7ca2 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -388,6 +388,84 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
end
end
+
+ describe '.extract_signature' do
+ subject { described_class.extract_signature(repository, commit_id) }
+
+ shared_examples '.extract_signature' do
+ context 'when the commit is signed' do
+ let(:commit_id) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
+
+ it 'returns signature and signed text' do
+ signature, signed_text = subject
+
+ expected_signature = <<~SIGNATURE
+ -----BEGIN PGP SIGNATURE-----
+ Version: GnuPG/MacGPG2 v2.0.22 (Darwin)
+ Comment: GPGTools - https://gpgtools.org
+
+ iQEcBAABCgAGBQJTDvaZAAoJEGJ8X1ifRn8XfvYIAMuB0yrbTGo1BnOSoDfyrjb0
+ Kw2EyUzvXYL72B63HMdJ+/0tlSDC6zONF3fc+bBD8z+WjQMTbwFNMRbSSy2rKEh+
+ mdRybOP3xBIMGgEph0/kmWln39nmFQBsPRbZBWoU10VfI/ieJdEOgOphszgryRar
+ TyS73dLBGE9y9NIININVaNISet9D9QeXFqc761CGjh4YIghvPpi+YihMWapGka6v
+ hgKhX+hc5rj+7IEE0CXmlbYR8OYvAbAArc5vJD7UTxAY4Z7/l9d6Ydt9GQ25khfy
+ ANFgltYzlR6evLFmDjssiP/mx/ZMN91AL0ueJ9nNGv411Mu2CUW+tDCaQf35mdc=
+ =j51i
+ -----END PGP SIGNATURE-----
+ SIGNATURE
+
+ expect(signature).to eq(expected_signature.chomp)
+ expect(signature).to be_a_binary_string
+
+ expected_signed_text = <<~SIGNED_TEXT
+ tree 22bfa2fbd217df24731f43ff43a4a0f8db759dae
+ parent ae73cb07c9eeaf35924a10f713b364d32b2dd34f
+ author Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> 1393489561 +0200
+ committer Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> 1393489561 +0200
+
+ Feature added
+
+ Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
+ SIGNED_TEXT
+
+ expect(signed_text).to eq(expected_signed_text)
+ expect(signed_text).to be_a_binary_string
+ end
+ end
+
+ context 'when the commit has no signature' do
+ let(:commit_id) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' }
+
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'when the commit cannot be found' do
+ let(:commit_id) { Gitlab::Git::BLANK_SHA }
+
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'when the commit ID is invalid' do
+ let(:commit_id) { '4b4918a572fa86f9771e5ba40fbd48e' }
+
+ it 'raises ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ context 'with gitaly' do
+ it_behaves_like '.extract_signature'
+ end
+
+ context 'without gitaly', :skip_gitaly_mock do
+ it_behaves_like '.extract_signature'
+ end
+ end
end
describe '#init_from_rugged' do
diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb
index a798b188a0d..f4b964e1ee9 100644
--- a/spec/lib/gitlab/git/gitlab_projects_spec.rb
+++ b/spec/lib/gitlab/git/gitlab_projects_spec.rb
@@ -25,51 +25,6 @@ describe Gitlab::Git::GitlabProjects do
it { expect(gl_projects.logger).to eq(logger) }
end
- describe '#mv_project' do
- let(:new_repo_path) { File.join(tmp_repos_path, 'repo.git') }
-
- it 'moves a repo directory' do
- expect(File.exist?(tmp_repo_path)).to be_truthy
-
- message = "Moving repository from <#{tmp_repo_path}> to <#{new_repo_path}>."
- expect(logger).to receive(:info).with(message)
-
- expect(gl_projects.mv_project('repo.git')).to be_truthy
-
- expect(File.exist?(tmp_repo_path)).to be_falsy
- expect(File.exist?(new_repo_path)).to be_truthy
- end
-
- it "fails if the source path doesn't exist" do
- expected_source_path = File.join(tmp_repos_path, 'bad-src.git')
- expect(logger).to receive(:error).with("mv-project failed: source path <#{expected_source_path}> does not exist.")
-
- result = build_gitlab_projects(tmp_repos_path, 'bad-src.git').mv_project('repo.git')
- expect(result).to be_falsy
- end
-
- it 'fails if the destination path already exists' do
- FileUtils.mkdir_p(File.join(tmp_repos_path, 'already-exists.git'))
-
- expected_distination_path = File.join(tmp_repos_path, 'already-exists.git')
- message = "mv-project failed: destination path <#{expected_distination_path}> already exists."
- expect(logger).to receive(:error).with(message)
-
- expect(gl_projects.mv_project('already-exists.git')).to be_falsy
- end
- end
-
- describe '#rm_project' do
- it 'removes a repo directory' do
- expect(File.exist?(tmp_repo_path)).to be_truthy
- expect(logger).to receive(:info).with("Removing repository <#{tmp_repo_path}>.")
-
- expect(gl_projects.rm_project).to be_truthy
-
- expect(File.exist?(tmp_repo_path)).to be_falsy
- end
- end
-
describe '#push_branches' do
let(:remote_name) { 'remote-name' }
let(:branch_name) { 'master' }
@@ -203,39 +158,55 @@ describe Gitlab::Git::GitlabProjects do
subject { gl_projects.import_project(import_url, timeout) }
- context 'success import' do
- it 'imports a repo' do
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
+ shared_examples 'importing repository' do
+ context 'success import' do
+ it 'imports a repo' do
+ expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
- message = "Importing project from <#{import_url}> to <#{tmp_repo_path}>."
- expect(logger).to receive(:info).with(message)
+ is_expected.to be_truthy
- is_expected.to be_truthy
+ expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_truthy
+ end
+ end
+
+ context 'already exists' do
+ it "doesn't import" do
+ FileUtils.mkdir_p(tmp_repo_path)
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_truthy
+ is_expected.to be_falsy
+ end
end
end
- context 'already exists' do
- it "doesn't import" do
- FileUtils.mkdir_p(tmp_repo_path)
+ context 'when Gitaly import_repository feature is enabled' do
+ it_behaves_like 'importing repository'
+ end
- is_expected.to be_falsy
+ context 'when Gitaly import_repository feature is disabled', :disable_gitaly do
+ describe 'logging' do
+ it 'imports a repo' do
+ message = "Importing project from <#{import_url}> to <#{tmp_repo_path}>."
+ expect(logger).to receive(:info).with(message)
+
+ subject
+ end
end
- end
- context 'timeout' do
- it 'does not import a repo' do
- stub_spawn_timeout(cmd, timeout, nil)
+ context 'timeout' do
+ it 'does not import a repo' do
+ stub_spawn_timeout(cmd, timeout, nil)
- message = "Importing project from <#{import_url}> to <#{tmp_repo_path}> failed."
- expect(logger).to receive(:error).with(message)
+ message = "Importing project from <#{import_url}> to <#{tmp_repo_path}> failed."
+ expect(logger).to receive(:error).with(message)
- is_expected.to be_falsy
+ is_expected.to be_falsy
- expect(gl_projects.output).to eq("Timed out\n")
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
+ expect(gl_projects.output).to eq("Timed out\n")
+ expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
+ end
end
+
+ it_behaves_like 'importing repository'
end
end
@@ -248,6 +219,9 @@ describe Gitlab::Git::GitlabProjects do
before do
FileUtils.mkdir_p(dest_repos_path)
+
+ # Undo spec_helper stub that deletes hooks
+ allow_any_instance_of(described_class).to receive(:fork_repository).and_call_original
end
after do
diff --git a/spec/lib/gitlab/git/info_attributes_spec.rb b/spec/lib/gitlab/git/info_attributes_spec.rb
new file mode 100644
index 00000000000..ea84909c3e0
--- /dev/null
+++ b/spec/lib/gitlab/git/info_attributes_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe Gitlab::Git::InfoAttributes, seed_helper: true do
+ let(:path) do
+ File.join(SEED_STORAGE_PATH, 'with-git-attributes.git')
+ end
+
+ subject { described_class.new(path) }
+
+ describe '#attributes' do
+ context 'using a path with attributes' do
+ it 'returns the attributes as a Hash' do
+ expect(subject.attributes('test.txt')).to eq({ 'text' => true })
+ end
+
+ it 'returns an empty Hash for a defined path without attributes' do
+ expect(subject.attributes('bla/bla.txt')).to eq({})
+ end
+ end
+ end
+
+ describe '#parser' do
+ it 'parses a file with entries' do
+ expect(subject.patterns).to be_an_instance_of(Hash)
+ expect(subject.patterns["/*.txt"]).to eq({ 'text' => true })
+ end
+
+ it 'does not parse anything when the attributes file does not exist' do
+ expect(File).to receive(:exist?)
+ .with(File.join(path, 'info/attributes'))
+ .and_return(false)
+
+ expect(subject.patterns).to eq({})
+ end
+
+ it 'does not parse attributes files with unsupported encoding' do
+ path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git')
+ subject = described_class.new(path)
+
+ expect(subject.patterns).to eq({})
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index f346a345f00..96a442f782f 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -2,6 +2,7 @@ require "spec_helper"
describe Gitlab::Git::Repository, seed_helper: true do
include Gitlab::EncodingHelper
+ using RSpec::Parameterized::TableSyntax
shared_examples 'wrapping gRPC errors' do |gitaly_client_class, gitaly_client_method|
it 'wraps gRPC not found error' do
@@ -19,6 +20,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
let(:storage_path) { TestEnv.repos_path }
+ let(:user) { build(:user) }
describe '.create_hooks' do
let(:repo_path) { File.join(storage_path, 'hook-test.git') }
@@ -248,7 +250,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
shared_examples 'archive check' do |extenstion|
- it { expect(metadata['ArchivePath']).to match(/tmp\/gitlab-git-test.git\/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}/) }
+ it { expect(metadata['ArchivePath']).to match(%r{tmp/gitlab-git-test.git/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}}) }
it { expect(metadata['ArchivePath']).to end_with extenstion }
end
@@ -442,6 +444,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
shared_examples 'simple commit counting' do
it { expect(repository.commit_count("master")).to eq(25) }
it { expect(repository.commit_count("feature")).to eq(9) }
+ it { expect(repository.commit_count("does-not-exist")).to eq(0) }
end
context 'when Gitaly commit_count feature is enabled' do
@@ -560,35 +563,39 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#delete_refs' do
- before(:all) do
- @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- end
+ shared_examples 'deleting refs' do
+ let(:repo) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
- it 'deletes the ref' do
- @repo.delete_refs('refs/heads/feature')
+ after do
+ ensure_seeds
+ end
- expect(@repo.rugged.references['refs/heads/feature']).to be_nil
- end
+ it 'deletes the ref' do
+ repo.delete_refs('refs/heads/feature')
+
+ expect(repo.rugged.references['refs/heads/feature']).to be_nil
+ end
- it 'deletes all refs' do
- refs = %w[refs/heads/wip refs/tags/v1.1.0]
- @repo.delete_refs(*refs)
+ it 'deletes all refs' do
+ refs = %w[refs/heads/wip refs/tags/v1.1.0]
+ repo.delete_refs(*refs)
- refs.each do |ref|
- expect(@repo.rugged.references[ref]).to be_nil
+ refs.each do |ref|
+ expect(repo.rugged.references[ref]).to be_nil
+ end
end
- end
- it 'raises an error if it failed' do
- expect(@repo).to receive(:popen).and_return(['Error', 1])
+ it 'raises an error if it failed' do
+ expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
+ end
+ end
- expect do
- @repo.delete_refs('refs/heads/fix')
- end.to raise_error(Gitlab::Git::Repository::GitError)
+ context 'when Gitaly delete_refs feature is enabled' do
+ it_behaves_like 'deleting refs'
end
- after(:all) do
- ensure_seeds
+ context 'when Gitaly delete_refs feature is disabled', :disable_gitaly do
+ it_behaves_like 'deleting refs'
end
end
@@ -687,7 +694,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe '#remote_tags' do
let(:remote_name) { 'upstream' }
let(:target_commit_id) { SeedRepo::Commit::ID }
- let(:user) { create(:user) }
let(:tag_name) { 'v0.0.1' }
let(:tag_message) { 'My tag' }
let(:remote_repository) do
@@ -899,44 +905,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- context "compare results between log_by_walk and log_by_shell" do
- let(:options) { { ref: "master" } }
- let(:commits_by_walk) { repository.log(options).map(&:id) }
- let(:commits_by_shell) { repository.log(options.merge({ disable_walk: true })).map(&:id) }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
-
- context "with limit" do
- let(:options) { { ref: "master", limit: 1 } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
- end
-
- context "with offset" do
- let(:options) { { ref: "master", offset: 1 } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
- end
-
- context "with skip_merges" do
- let(:options) { { ref: "master", skip_merges: true } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
- end
-
- context "with path" do
- let(:options) { { ref: "master", path: "encoding" } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
-
- context "with follow" do
- let(:options) { { ref: "master", path: "encoding", follow: true } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
- end
- end
- end
-
context "where provides 'after' timestamp" do
options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
@@ -981,6 +949,16 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
end
+
+ context 'limit validation' do
+ where(:limit) do
+ [0, nil, '', 'foo']
+ end
+
+ with_them do
+ it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
+ end
+ end
end
describe "#rugged_commits_between" do
@@ -1022,6 +1000,29 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.to eq(17) }
end
+ describe '#merge_base' do
+ shared_examples '#merge_base' do
+ where(:from, :to, :result) do
+ '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
+ '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
+ '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | 'foobar' | nil
+ 'foobar' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | nil
+ end
+
+ with_them do
+ it { expect(repository.merge_base(from, to)).to eq(result) }
+ end
+ end
+
+ context 'with gitaly' do
+ it_behaves_like '#merge_base'
+ end
+
+ context 'without gitaly', :skip_gitaly_mock do
+ it_behaves_like '#merge_base'
+ end
+ end
+
describe '#count_commits' do
shared_examples 'extended commit counting' do
context 'with after timestamp' do
@@ -1104,14 +1105,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- describe "branch_names_contains" do
- subject { repository.branch_names_contains(SeedRepo::LastCommit::ID) }
-
- it { is_expected.to include('master') }
- it { is_expected.not_to include('feature') }
- it { is_expected.not_to include('fix') }
- end
-
describe '#autocrlf' do
before(:all) do
@repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
@@ -1283,48 +1276,58 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#merged_branch_names' do
- context 'when branch names are passed' do
- it 'only returns the names we are asking' do
- names = repository.merged_branch_names(%w[merge-test])
+ shared_examples 'finding merged branch names' do
+ context 'when branch names are passed' do
+ it 'only returns the names we are asking' do
+ names = repository.merged_branch_names(%w[merge-test])
- expect(names).to contain_exactly('merge-test')
- end
+ expect(names).to contain_exactly('merge-test')
+ end
- it 'does not return unmerged branch names' do
- names = repository.merged_branch_names(%w[feature])
+ it 'does not return unmerged branch names' do
+ names = repository.merged_branch_names(%w[feature])
- expect(names).to be_empty
+ expect(names).to be_empty
+ end
end
- end
- context 'when no root ref is available' do
- it 'returns empty list' do
- project = create(:project, :empty_repo)
+ context 'when no root ref is available' do
+ it 'returns empty list' do
+ project = create(:project, :empty_repo)
- names = project.repository.merged_branch_names(%w[feature])
+ names = project.repository.merged_branch_names(%w[feature])
- expect(names).to be_empty
+ expect(names).to be_empty
+ end
end
- end
- context 'when no branch names are specified' do
- before do
- repository.create_branch('identical', 'master')
- end
+ context 'when no branch names are specified' do
+ before do
+ repository.create_branch('identical', 'master')
+ end
- after do
- ensure_seeds
- end
+ after do
+ ensure_seeds
+ end
- it 'returns all merged branch names except for identical one' do
- names = repository.merged_branch_names
+ it 'returns all merged branch names except for identical one' do
+ names = repository.merged_branch_names
- expect(names).to include('merge-test')
- expect(names).to include('fix-mode')
- expect(names).not_to include('feature')
- expect(names).not_to include('identical')
+ expect(names).to include('merge-test')
+ expect(names).to include('fix-mode')
+ expect(names).not_to include('feature')
+ expect(names).not_to include('identical')
+ end
end
end
+
+ context 'when Gitaly merged_branch_names feature is enabled' do
+ it_behaves_like 'finding merged branch names'
+ end
+
+ context 'when Gitaly merged_branch_names feature is disabled', :disable_gitaly do
+ it_behaves_like 'finding merged branch names'
+ end
end
describe "#ls_files" do
@@ -1708,7 +1711,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
shared_examples "user deleting a branch" do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository.raw }
- let(:user) { create(:user) }
let(:branch_name) { "to-be-deleted-soon" }
before do
@@ -1749,12 +1751,49 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
+ describe '#write_config' do
+ before do
+ repository.rugged.config["gitlab.fullpath"] = repository.path
+ end
+
+ shared_examples 'writing repo config' do
+ context 'is given a path' do
+ it 'writes it to disk' do
+ repository.write_config(full_path: "not-the/real-path.git")
+
+ config = File.read(File.join(repository.path, "config"))
+
+ expect(config).to include("[gitlab]")
+ expect(config).to include("fullpath = not-the/real-path.git")
+ end
+ end
+
+ context 'it is given an empty path' do
+ it 'does not write it to disk' do
+ repository.write_config(full_path: "")
+
+ config = File.read(File.join(repository.path, "config"))
+
+ expect(config).to include("[gitlab]")
+ expect(config).to include("fullpath = #{repository.path}")
+ end
+ end
+ end
+
+ context "when gitaly_write_config is enabled" do
+ it_behaves_like "writing repo config"
+ end
+
+ context "when gitaly_write_config is disabled", :disable_gitaly do
+ it_behaves_like "writing repo config"
+ end
+ end
+
describe '#merge' do
let(:repository) do
Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
end
let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
- let(:user) { build(:user) }
let(:target_branch) { 'test-merge-target-branch' }
before do
@@ -1807,7 +1846,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
- let(:user) { build(:user) }
let(:target_branch) { 'test-ff-target-branch' }
before do
@@ -1962,6 +2000,75 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { expect(subject.repository_relative_path).to eq(repository.relative_path) }
end
+ describe '#bundle_to_disk' do
+ shared_examples 'bundling to disk' do
+ let(:save_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
+
+ after do
+ FileUtils.rm_rf(save_path)
+ end
+
+ it 'saves a bundle to disk' do
+ repository.bundle_to_disk(save_path)
+
+ success = system(
+ *%W(#{Gitlab.config.git.bin_path} -C #{repository.path} bundle verify #{save_path}),
+ [:out, :err] => '/dev/null'
+ )
+ expect(success).to be true
+ end
+ end
+
+ context 'when Gitaly bundle_to_disk feature is enabled' do
+ it_behaves_like 'bundling to disk'
+ end
+
+ context 'when Gitaly bundle_to_disk feature is disabled', :disable_gitaly do
+ it_behaves_like 'bundling to disk'
+ end
+ end
+
+ describe '#create_from_bundle' do
+ shared_examples 'creating repo from bundle' do
+ let(:bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
+ let(:project) { create(:project) }
+ let(:imported_repo) { project.repository.raw }
+
+ before do
+ expect(repository.bundle_to_disk(bundle_path)).to be true
+ end
+
+ after do
+ FileUtils.rm_rf(bundle_path)
+ end
+
+ it 'creates a repo from a bundle file' do
+ expect(imported_repo).not_to exist
+
+ result = imported_repo.create_from_bundle(bundle_path)
+
+ expect(result).to be true
+ expect(imported_repo).to exist
+ expect { imported_repo.fsck }.not_to raise_exception
+ end
+
+ it 'creates a symlink to the global hooks dir' do
+ imported_repo.create_from_bundle(bundle_path)
+ hooks_path = File.join(imported_repo.path, 'hooks')
+
+ expect(File.readlink(hooks_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
+ end
+ end
+
+ context 'when Gitaly create_repo_from_bundle feature is enabled' do
+ it_behaves_like 'creating repo from bundle'
+ end
+
+ context 'when Gitaly create_repo_from_bundle feature is disabled', :disable_gitaly do
+ it_behaves_like 'creating repo from bundle'
+ end
+ end
+
context 'gitlab_projects commands' do
let(:gitlab_projects) { repository.gitlab_projects }
let(:timeout) { Gitlab.config.gitlab_shell.git_timeout }
@@ -2057,6 +2164,47 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error')
end
end
+
+ describe '#squash' do
+ let(:squash_id) { '1' }
+ let(:branch_name) { 'fix' }
+ let(:start_sha) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' }
+ let(:end_sha) { '12d65c8dd2b2676fa3ac47d955accc085a37a9c1' }
+
+ subject do
+ opts = {
+ branch: branch_name,
+ start_sha: start_sha,
+ end_sha: end_sha,
+ author: user,
+ message: 'Squash commit message'
+ }
+
+ repository.squash(user, squash_id, opts)
+ end
+
+ context 'sparse checkout' do
+ let(:expected_files) { %w(files files/js files/js/application.js) }
+
+ before do
+ allow(repository).to receive(:with_worktree).and_wrap_original do |m, *args|
+ m.call(*args) do
+ worktree_path = args[0]
+ files_pattern = File.join(worktree_path, '**', '*')
+ expected = expected_files.map do |path|
+ File.expand_path(path, worktree_path)
+ end
+
+ expect(Dir[files_pattern]).to eq(expected)
+ end
+ end
+ end
+
+ it 'checkouts only the files in the diff' do
+ subject
+ end
+ end
+ end
end
def create_remote_branch(repository, remote_name, branch_name, source_branch_name)
diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb
index eaf74951b0e..90fbef9d248 100644
--- a/spec/lib/gitlab/git/rev_list_spec.rb
+++ b/spec/lib/gitlab/git/rev_list_spec.rb
@@ -39,7 +39,7 @@ describe Gitlab::Git::RevList do
]
expect(rev_list).to receive(:popen).with(*params) do |*_, lazy_block:|
- lazy_block.call(output.split("\n").lazy)
+ lazy_block.call(output.lines.lazy.map(&:chomp))
end
end
@@ -64,6 +64,15 @@ describe Gitlab::Git::RevList do
expect(rev_list.new_objects(require_path: true)).to eq(%w[sha2])
end
+ it 'can handle non utf-8 paths' do
+ non_utf_char = [0x89].pack("c*").force_encoding("UTF-8")
+ stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha2 πå†h/†ø/ƒîlé#{non_utf_char}\nsha1")
+
+ rev_list.new_objects(require_path: true) do |object_ids|
+ expect(object_ids.force).to eq(%w[sha2])
+ end
+ end
+
it 'can yield a lazy enumerator' do
stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index 86f7bcb8e38..001e406a930 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -80,22 +80,18 @@ describe Gitlab::Git::Tree, seed_helper: true do
end
describe '#where' do
- context 'with gitaly disabled' do
- before do
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
- end
-
- it 'calls #tree_entries_from_rugged' do
- expect(described_class).to receive(:tree_entries_from_rugged)
-
- described_class.where(repository, SeedRepo::Commit::ID, '/')
+ shared_examples '#where' do
+ it 'returns an empty array when called with an invalid ref' do
+ expect(described_class.where(repository, 'foobar-does-not-exist')).to eq([])
end
end
- it 'gets the tree entries from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::CommitService).to receive(:tree_entries)
+ context 'with gitaly' do
+ it_behaves_like '#where'
+ end
- described_class.where(repository, SeedRepo::Commit::ID, '/')
+ context 'without gitaly', :skip_gitaly_mock do
+ it_behaves_like '#where'
end
end
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 4290fbb0087..2009a8ac48c 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -51,12 +51,12 @@ describe Gitlab::GitAccess do
context 'when the project exists' do
context 'when actor exists' do
context 'when actor is a DeployKey' do
- let(:deploy_key) { create(:deploy_key, user: user, can_push: true) }
+ let(:deploy_key) { create(:deploy_key, user: user) }
let(:actor) { deploy_key }
context 'when the DeployKey has access to the project' do
before do
- deploy_key.projects << project
+ deploy_key.deploy_keys_projects.create(project: project, can_push: true)
end
it 'allows push and pull access' do
@@ -696,15 +696,13 @@ describe Gitlab::GitAccess do
end
describe 'deploy key permissions' do
- let(:key) { create(:deploy_key, user: user, can_push: can_push) }
+ let(:key) { create(:deploy_key, user: user) }
let(:actor) { key }
context 'when deploy_key can push' do
- let(:can_push) { true }
-
context 'when project is authorized' do
before do
- key.projects << project
+ key.deploy_keys_projects.create(project: project, can_push: true)
end
it { expect { push_access_check }.not_to raise_error }
@@ -732,11 +730,9 @@ describe Gitlab::GitAccess do
end
context 'when deploy_key cannot push' do
- let(:can_push) { false }
-
context 'when project is authorized' do
before do
- key.projects << project
+ key.deploy_keys_projects.create(project: project, can_push: false)
end
it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index b2275119a04..3722a91c050 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -131,6 +131,29 @@ describe Gitlab::GitalyClient::CommitService do
end
end
+ describe '#commit_count' do
+ before do
+ expect_any_instance_of(Gitaly::CommitService::Stub)
+ .to receive(:count_commits)
+ .with(gitaly_request_with_path(storage_name, relative_path),
+ kind_of(Hash))
+ .and_return([])
+ end
+
+ it 'sends a commit_count message' do
+ client.commit_count(revision)
+ end
+
+ context 'with UTF-8 params strings' do
+ let(:revision) { "branch\u011F" }
+ let(:path) { "foo/\u011F.txt" }
+
+ it 'handles string encodings correctly' do
+ client.commit_count(revision, path: path)
+ end
+ end
+ end
+
describe '#find_commit' do
let(:revision) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' }
it 'sends an RPC request' do
diff --git a/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
new file mode 100644
index 00000000000..1c933410bd5
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/conflict_files_stitcher_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::ConflictFilesStitcher do
+ describe 'enumeration' do
+ it 'combines segregated ConflictFile messages together' do
+ target_project = create(:project, :repository)
+ target_repository = target_project.repository.raw
+ target_gitaly_repository = target_repository.gitaly_repository
+
+ our_path_1 = 'our/path/1'
+ their_path_1 = 'their/path/1'
+ our_mode_1 = 0744
+ commit_oid_1 = 'f00'
+ content_1 = 'content of the first file'
+
+ our_path_2 = 'our/path/2'
+ their_path_2 = 'their/path/2'
+ our_mode_2 = 0600
+ commit_oid_2 = 'ba7'
+ content_2 = 'content of the second file'
+
+ header_1 = double(repository: target_gitaly_repository, commit_oid: commit_oid_1,
+ our_path: our_path_1, their_path: their_path_1, our_mode: our_mode_1)
+ header_2 = double(repository: target_gitaly_repository, commit_oid: commit_oid_2,
+ our_path: our_path_2, their_path: their_path_2, our_mode: our_mode_2)
+
+ messages = [
+ double(files: [double(header: header_1), double(header: nil, content: content_1[0..5])]),
+ double(files: [double(header: nil, content: content_1[6..-1])]),
+ double(files: [double(header: header_2)]),
+ double(files: [double(header: nil, content: content_2[0..5]), double(header: nil, content: content_2[6..10])]),
+ double(files: [double(header: nil, content: content_2[11..-1])])
+ ]
+
+ conflict_files = described_class.new(messages).to_a
+
+ expect(conflict_files.size).to be(2)
+
+ expect(conflict_files[0].content).to eq(content_1)
+ expect(conflict_files[0].their_path).to eq(their_path_1)
+ expect(conflict_files[0].our_path).to eq(our_path_1)
+ expect(conflict_files[0].our_mode).to be(our_mode_1)
+ expect(conflict_files[0].repository).to eq(target_repository)
+ expect(conflict_files[0].commit_oid).to eq(commit_oid_1)
+
+ expect(conflict_files[1].content).to eq(content_2)
+ expect(conflict_files[1].their_path).to eq(their_path_2)
+ expect(conflict_files[1].our_path).to eq(our_path_2)
+ expect(conflict_files[1].our_mode).to be(our_mode_2)
+ expect(conflict_files[1].repository).to eq(target_repository)
+ expect(conflict_files[1].commit_oid).to eq(commit_oid_2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
index b9641de7eda..e4fe01a671f 100644
--- a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
@@ -19,41 +19,12 @@ describe Gitlab::GitalyClient::ConflictsService do
their_commit_oid: their_commit_oid
)
end
- let(:our_path) { 'our/path' }
- let(:their_path) { 'their/path' }
- let(:our_mode) { 0744 }
- let(:header) do
- double(repository: target_gitaly_repository, commit_oid: our_commit_oid,
- our_path: our_path, our_mode: 0744, their_path: their_path)
- end
- let(:response) do
- [
- double(files: [double(header: header), double(content: 'foo', header: nil)]),
- double(files: [double(content: 'bar', header: nil)])
- ]
- end
- let(:file) { subject[0] }
-
- subject { client.list_conflict_files }
it 'sends an RPC request' do
expect_any_instance_of(Gitaly::ConflictsService::Stub).to receive(:list_conflict_files)
- .with(request, kind_of(Hash)).and_return([])
-
- subject
- end
-
- it 'forms a Gitlab::Git::ConflictFile collection from the response' do
- allow_any_instance_of(Gitaly::ConflictsService::Stub).to receive(:list_conflict_files)
- .with(request, kind_of(Hash)).and_return(response)
+ .with(request, kind_of(Hash)).and_return([].to_enum)
- expect(subject.size).to be(1)
- expect(file.content).to eq('foobar')
- expect(file.their_path).to eq(their_path)
- expect(file.our_path).to eq(our_path)
- expect(file.our_mode).to be(our_mode)
- expect(file.repository).to eq(target_repository)
- expect(file.commit_oid).to eq(our_commit_oid)
+ client.list_conflict_files
end
end
diff --git a/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb b/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb
new file mode 100644
index 00000000000..2c7e5eb5787
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::HealthCheckService do
+ let(:project) { create(:project) }
+ let(:storage_name) { project.repository_storage }
+
+ subject { described_class.new(storage_name) }
+
+ describe '#check' do
+ it 'successfully sends a health check request' do
+ expect(Gitlab::GitalyClient).to receive(:call).with(
+ storage_name,
+ :health_check,
+ :check,
+ instance_of(Grpc::Health::V1::HealthCheckRequest),
+ timeout: Gitlab::GitalyClient.fast_timeout).and_call_original
+
+ expect(subject.check).to eq({ success: true })
+ end
+
+ it 'receives an unsuccessful health check request' do
+ expect_any_instance_of(Grpc::Health::V1::Health::Stub)
+ .to receive(:check)
+ .and_return(double(status: false))
+
+ expect(subject.check).to eq({ success: false })
+ end
+
+ it 'gracefully handles gRPC error' do
+ expect(Gitlab::GitalyClient).to receive(:call).with(
+ storage_name,
+ :health_check,
+ :check,
+ instance_of(Grpc::Health::V1::HealthCheckRequest),
+ timeout: Gitlab::GitalyClient.fast_timeout)
+ .and_raise(GRPC::Unavailable.new('Connection refused'))
+
+ expect(subject.check).to eq({ success: false, message: '14:Connection refused' })
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
index 951e146a30a..257e4c50f2d 100644
--- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
@@ -112,7 +112,7 @@ describe Gitlab::GitalyClient::RefService do
expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:delete_refs)
.with(gitaly_request_with_params(except_with_prefix: prefixes), kind_of(Hash))
- .and_return(double('delete_refs_response'))
+ .and_return(double('delete_refs_response', git_error: ""))
client.delete_refs(except_with_prefixes: prefixes)
end
diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
index 9d540446532..872377c93d8 100644
--- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
@@ -44,4 +44,18 @@ describe Gitlab::GitalyClient::RemoteService do
expect(client.fetch_internal_remote(remote_repository)).to be(true)
end
end
+
+ describe '#update_remote_mirror' do
+ let(:ref_name) { 'remote_mirror_1' }
+ let(:only_branches_matching) { ['my-branch', 'master'] }
+
+ it 'sends an update_remote_mirror message' do
+ expect_any_instance_of(Gitaly::RemoteService::Stub)
+ .to receive(:update_remote_mirror)
+ .with(kind_of(Enumerator), kind_of(Hash))
+ .and_return(double(:update_remote_mirror_response))
+
+ client.update_remote_mirror(ref_name, only_branches_matching)
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index 309b7338ef0..81bcd8c28ed 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -3,6 +3,31 @@ require 'spec_helper'
# We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want
# those stubs while testing the GitalyClient itself.
describe Gitlab::GitalyClient, skip_gitaly_mock: true do
+ describe '.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)
+ end
+
+ it 'returns a Gitaly stub' do
+ expect(described_class.stub_class(:ref_service)).to eq(::Gitaly::RefService::Stub)
+ end
+ end
+
+ describe '.stub_address' do
+ it 'returns the same result after being called multiple times' do
+ address = 'localhost:9876'
+ prefixed_address = "tcp://#{address}"
+
+ allow(Gitlab.config.repositories).to receive(:storages).and_return({
+ 'default' => { 'gitaly_address' => prefixed_address }
+ })
+
+ 2.times do
+ expect(described_class.stub_address('default')).to eq('localhost:9876')
+ end
+ end
+ end
+
describe '.stub' do
# Notice that this is referring to gRPC "stubs", not rspec stubs
before do
diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
index d72572cd510..44695acbe7d 100644
--- a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
@@ -244,7 +244,7 @@ describe Gitlab::GithubImport::Importer::PullRequestsImporter do
it 'returns true when a commit exists' do
expect(project.repository)
- .to receive(:lookup)
+ .to receive(:commit)
.with('123')
.and_return(double(:commit))
@@ -253,9 +253,9 @@ describe Gitlab::GithubImport::Importer::PullRequestsImporter do
it 'returns false when a commit does not exist' do
expect(project.repository)
- .to receive(:lookup)
+ .to receive(:commit)
.with('123')
- .and_raise(Rugged::OdbError)
+ .and_return(nil)
expect(importer.commit_exists?('123')).to eq(false)
end
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index a6c99bc07d4..e3bf2801406 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -38,8 +38,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
@@ -77,8 +77,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User3.signed_commit_signature,
@@ -116,8 +116,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
@@ -151,8 +151,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
@@ -187,8 +187,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
@@ -217,8 +217,8 @@ describe Gitlab::Gpg::Commit do
let!(:commit) { create :commit, project: project, sha: commit_sha }
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
index d6000af0ecd..c034eccf2a6 100644
--- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
+++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
@@ -26,8 +26,8 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
before do
allow_any_instance_of(Project).to receive(:commit).and_return(commit)
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(signature)
end
diff --git a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
new file mode 100644
index 00000000000..724beefff69
--- /dev/null
+++ b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe Gitlab::HealthChecks::GitalyCheck do
+ let(:result_class) { Gitlab::HealthChecks::Result }
+ let(:repository_storages) { ['default'] }
+
+ before do
+ allow(described_class).to receive(:repository_storages) { repository_storages }
+ end
+
+ describe '#readiness' do
+ subject { described_class.readiness }
+
+ before do
+ expect(Gitlab::GitalyClient::HealthCheckService).to receive(:new).and_return(gitaly_check)
+ end
+
+ context 'Gitaly server is up' do
+ let(:gitaly_check) { double(check: { success: true }) }
+
+ it { is_expected.to eq([result_class.new(true, nil, shard: 'default')]) }
+ end
+
+ context 'Gitaly server is down' do
+ let(:gitaly_check) { double(check: { success: false, message: 'Connection refused' }) }
+
+ it { is_expected.to eq([result_class.new(false, 'Connection refused', shard: 'default')]) }
+ end
+ end
+
+ describe '#metrics' do
+ subject { described_class.metrics }
+
+ before do
+ expect(Gitlab::GitalyClient::HealthCheckService).to receive(:new).and_return(gitaly_check)
+ end
+
+ context 'Gitaly server is up' do
+ let(:gitaly_check) { double(check: { success: true }) }
+
+ it 'provides metrics' do
+ expect(subject).to all(have_attributes(labels: { shard: 'default' }))
+ expect(subject).to include(an_object_having_attributes(name: 'gitaly_health_check_success', value: 1))
+ expect(subject).to include(an_object_having_attributes(name: 'gitaly_health_check_latency_seconds', value: be >= 0))
+ end
+ end
+
+ context 'Gitaly server is down' do
+ let(:gitaly_check) { double(check: { success: false, message: 'Connection refused' }) }
+
+ it 'provides metrics' do
+ expect(subject).to include(an_object_having_attributes(name: 'gitaly_health_check_success', value: 0))
+ expect(subject).to include(an_object_having_attributes(name: 'gitaly_health_check_latency_seconds', value: be >= 0))
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
index aeacd577d18..506b2c0be20 100644
--- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
@@ -14,7 +14,6 @@ describe Gitlab::HookData::IssueBuilder do
closed_at
confidential
created_at
- deleted_at
description
due_date
id
diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
index 78475403f9e..b61614e4790 100644
--- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
@@ -12,7 +12,6 @@ describe Gitlab::HookData::MergeRequestBuilder do
assignee_id
author_id
created_at
- deleted_at
description
head_pipeline_id
id
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index 162b776e107..5cdc5138fda 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -12,30 +12,61 @@ describe Gitlab::ImportExport::FileImporter do
stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
-
+ allow(SecureRandom).to receive(:hex).and_return('abcd')
setup_files
-
- described_class.import(archive_file: '', shared: shared)
end
after do
FileUtils.rm_rf(export_path)
end
- it 'removes symlinks in root folder' do
- expect(File.exist?(symlink_file)).to be false
- end
+ context 'normal run' do
+ before do
+ described_class.import(archive_file: '', shared: shared)
+ end
- it 'removes hidden symlinks in root folder' do
- expect(File.exist?(hidden_symlink_file)).to be false
- end
+ it 'removes symlinks in root folder' do
+ expect(File.exist?(symlink_file)).to be false
+ end
+
+ it 'removes hidden symlinks in root folder' do
+ expect(File.exist?(hidden_symlink_file)).to be false
+ end
+
+ it 'removes symlinks in subfolders' do
+ expect(File.exist?(subfolder_symlink_file)).to be false
+ end
- it 'removes symlinks in subfolders' do
- expect(File.exist?(subfolder_symlink_file)).to be false
+ it 'does not remove a valid file' do
+ expect(File.exist?(valid_file)).to be true
+ end
+
+ it 'creates the file in the right subfolder' do
+ expect(shared.export_path).to include('test/abcd')
+ end
end
- it 'does not remove a valid file' do
- expect(File.exist?(valid_file)).to be true
+ context 'error' do
+ before do
+ allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError)
+ described_class.import(archive_file: '', shared: shared)
+ end
+
+ it 'removes symlinks in root folder' do
+ expect(File.exist?(symlink_file)).to be false
+ end
+
+ it 'removes hidden symlinks in root folder' do
+ expect(File.exist?(hidden_symlink_file)).to be false
+ end
+
+ it 'removes symlinks in subfolders' do
+ expect(File.exist?(subfolder_symlink_file)).to be false
+ end
+
+ it 'does not remove a valid file' do
+ expect(File.exist?(valid_file)).to be true
+ end
end
def setup_files
diff --git a/spec/lib/gitlab/import_export/project.group.json b/spec/lib/gitlab/import_export/project.group.json
index 82a1fbd2fc5..1a561e81e4a 100644
--- a/spec/lib/gitlab/import_export/project.group.json
+++ b/spec/lib/gitlab/import_export/project.group.json
@@ -54,7 +54,6 @@
"iid": 1,
"updated_by_id": 1,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"lock_version": null,
@@ -134,7 +133,6 @@
"iid": 2,
"updated_by_id": 1,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"lock_version": null,
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 7580b62cfc0..b6c1f0c81cb 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -56,7 +56,6 @@
"iid": 10,
"updated_by_id": null,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"test_ee_field": "test",
@@ -350,7 +349,6 @@
"iid": 9,
"updated_by_id": null,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"milestone": {
@@ -586,7 +584,6 @@
"iid": 8,
"updated_by_id": null,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"label_links": [
@@ -820,7 +817,6 @@
"iid": 7,
"updated_by_id": null,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"notes": [
@@ -1033,7 +1029,6 @@
"iid": 6,
"updated_by_id": null,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"notes": [
@@ -1246,7 +1241,6 @@
"iid": 5,
"updated_by_id": null,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"notes": [
@@ -1459,7 +1453,6 @@
"iid": 4,
"updated_by_id": null,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"notes": [
@@ -1672,7 +1665,6 @@
"iid": 3,
"updated_by_id": null,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"notes": [
@@ -1885,7 +1877,6 @@
"iid": 2,
"updated_by_id": null,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"notes": [
@@ -2098,7 +2089,6 @@
"iid": 1,
"updated_by_id": null,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"notes": [
@@ -2504,7 +2494,6 @@
"merge_when_pipeline_succeeds": true,
"merge_user_id": null,
"merge_commit_sha": null,
- "deleted_at": null,
"notes": [
{
"id": 671,
@@ -2948,7 +2937,6 @@
"merge_when_pipeline_succeeds": false,
"merge_user_id": null,
"merge_commit_sha": null,
- "deleted_at": null,
"notes": [
{
"id": 679,
@@ -3228,7 +3216,6 @@
"merge_when_pipeline_succeeds": false,
"merge_user_id": null,
"merge_commit_sha": null,
- "deleted_at": null,
"notes": [
{
"id": 777,
@@ -3508,7 +3495,6 @@
"merge_when_pipeline_succeeds": false,
"merge_user_id": null,
"merge_commit_sha": null,
- "deleted_at": null,
"notes": [
{
"id": 785,
@@ -4198,7 +4184,6 @@
"merge_when_pipeline_succeeds": false,
"merge_user_id": null,
"merge_commit_sha": null,
- "deleted_at": null,
"notes": [
{
"id": 793,
@@ -4734,7 +4719,6 @@
"merge_when_pipeline_succeeds": false,
"merge_user_id": null,
"merge_commit_sha": null,
- "deleted_at": null,
"notes": [
{
"id": 801,
@@ -5223,7 +5207,6 @@
"merge_when_pipeline_succeeds": false,
"merge_user_id": null,
"merge_commit_sha": null,
- "deleted_at": null,
"notes": [
{
"id": 809,
@@ -5478,7 +5461,6 @@
"merge_when_pipeline_succeeds": false,
"merge_user_id": null,
"merge_commit_sha": null,
- "deleted_at": null,
"notes": [
{
"id": 817,
@@ -6168,7 +6150,6 @@
"merge_when_pipeline_succeeds": false,
"merge_user_id": null,
"merge_commit_sha": null,
- "deleted_at": null,
"notes": [
{
"id": 825,
@@ -6968,7 +6949,6 @@
"id": 123,
"token": "cdbfasdf44a5958c83654733449e585",
"project_id": 5,
- "deleted_at": null,
"created_at": "2017-01-16T15:25:28.637Z",
"updated_at": "2017-01-16T15:25:28.637Z"
}
@@ -7116,7 +7096,7 @@
"project_id": 5,
"created_at": "2016-06-14T15:01:51.232Z",
"updated_at": "2016-06-14T15:01:51.232Z",
- "active": false,
+ "active": true,
"properties": {
},
diff --git a/spec/lib/gitlab/import_export/project.light.json b/spec/lib/gitlab/import_export/project.light.json
index 02450478a77..5dbf0ed289b 100644
--- a/spec/lib/gitlab/import_export/project.light.json
+++ b/spec/lib/gitlab/import_export/project.light.json
@@ -54,7 +54,6 @@
"iid": 20,
"updated_by_id": 1,
"confidential": false,
- "deleted_at": null,
"due_date": null,
"moved_to_id": null,
"lock_version": null,
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 08e5bbbd400..5804c45871e 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -164,6 +164,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService')
end
+ it 'saves the properties for a service' do
+ expect(saved_project_json['services'].first['properties']).to eq('one' => 'value')
+ end
+
it 'has project feature' do
project_feature = saved_project_json['project_feature']
expect(project_feature).not_to be_empty
@@ -279,7 +283,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
commit_id: ci_build.pipeline.sha)
create(:event, :created, target: milestone, project: project, author: user)
- create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker')
+ create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker', properties: { one: 'value' })
create(:project_custom_attribute, project: project)
create(:project_custom_attribute, project: project)
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 1a554bd1a4e..847c77e0e80 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -14,7 +14,6 @@ Issue:
- iid
- updated_by_id
- confidential
-- deleted_at
- closed_at
- due_date
- moved_to_id
@@ -159,7 +158,6 @@ MergeRequest:
- merge_when_pipeline_succeeds
- merge_user_id
- merge_commit_sha
-- deleted_at
- in_progress_merge_commit_sha
- lock_version
- milestone_id
@@ -180,6 +178,7 @@ MergeRequestDiff:
- real_size
- head_commit_sha
- start_commit_sha
+- commits_count
MergeRequestDiffCommit:
- merge_request_diff_id
- relative_order
@@ -295,7 +294,6 @@ Ci::Trigger:
- id
- token
- project_id
-- deleted_at
- created_at
- updated_at
- owner_id
@@ -311,7 +309,6 @@ Ci::PipelineSchedule:
- project_id
- owner_id
- active
-- deleted_at
- created_at
- updated_at
Clusters::Cluster:
diff --git a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb
new file mode 100644
index 00000000000..6532579b1c9
--- /dev/null
+++ b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Gitlab::InsecureKeyFingerprint do
+ let(:key) do
+ 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn' \
+ '1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qk' \
+ 'r8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMg' \
+ 'Jw0='
+ end
+
+ let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" }
+
+ describe "#fingerprint" do
+ it "generates the key's fingerprint" do
+ expect(described_class.new(key.split[1]).fingerprint).to eq(fingerprint)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index 4afe48e72ad..63997a40d52 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -100,6 +100,25 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
is_expected.to eq(command)
end
end
+
+ context 'when chart values file is present' do
+ let(:install_command) { described_class.new(prometheus.name, chart: prometheus.chart, chart_values_file: prometheus.chart_values_file) }
+ let(:command) do
+ <<~MSG.chomp
+ set -eo pipefail
+ apk add -U ca-certificates openssl >/dev/null
+ wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
+ mv /tmp/linux-amd64/helm /usr/bin/
+
+ helm init --client-only >/dev/null
+ helm install #{prometheus.chart} --name #{prometheus.name} --namespace #{namespace.name} -f /data/helm/#{prometheus.name}/config/values.yaml >/dev/null
+ MSG
+ end
+
+ it 'should return appropriate command' do
+ is_expected.to eq(command)
+ end
+ end
end
describe "#pod_name" do
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index 906b10b96d4..0b8e97b8948 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -52,18 +52,20 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'should include volumes for the container' do
container = subject.generate.spec.containers.first
- expect(container.volumeMounts.first['name']).to eq('config-volume')
- expect(container.volumeMounts.first['mountPath']).to eq('/etc/config')
+ expect(container.volumeMounts.first['name']).to eq('configuration-volume')
+ expect(container.volumeMounts.first['mountPath']).to eq("/data/helm/#{app.name}/config")
end
it 'should include a volume inside the specification' do
spec = subject.generate.spec
- expect(spec.volumes.first['name']).to eq('config-volume')
+ expect(spec.volumes.first['name']).to eq('configuration-volume')
end
it 'should mount configMap specification in the volume' do
spec = subject.generate.spec
- expect(spec.volumes.first.configMap['name']).to eq('values-config')
+ expect(spec.volumes.first.configMap['name']).to eq('values-content-configuration')
+ expect(spec.volumes.first.configMap['items'].first['key']).to eq('values')
+ expect(spec.volumes.first.configMap['items'].first['path']).to eq('values.yaml')
end
end
diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb
index 41a9d1d9c90..d9379cfe674 100644
--- a/spec/lib/gitlab/metrics/method_call_spec.rb
+++ b/spec/lib/gitlab/metrics/method_call_spec.rb
@@ -5,6 +5,10 @@ describe Gitlab::Metrics::MethodCall do
let(:method_call) { described_class.new('Foo#bar', :Foo, '#bar', transaction) }
describe '#measure' do
+ after do
+ described_class.reload_metric!(:gitlab_method_call_duration_seconds)
+ end
+
it 'measures the performance of the supplied block' do
method_call.measure { 'foo' }
@@ -20,8 +24,6 @@ describe Gitlab::Metrics::MethodCall do
context 'prometheus instrumentation is enabled' do
before do
- allow(Feature.get(:prometheus_metrics_method_instrumentation)).to receive(:enabled?).and_call_original
- described_class.measurement_enabled_cache_expires_at.value = Time.now.to_i - 1
Feature.get(:prometheus_metrics_method_instrumentation).enable
end
@@ -31,30 +33,12 @@ describe Gitlab::Metrics::MethodCall do
end
end
- it 'caches subsequent invocations of feature check' do
- 10.times do
- method_call.measure { 'foo' }
- end
-
- expect(Feature.get(:prometheus_metrics_method_instrumentation)).to have_received(:enabled?).once
- end
-
- it 'expires feature check cache after 1 minute' do
- method_call.measure { 'foo' }
-
- Timecop.travel(1.minute.from_now) do
- method_call.measure { 'foo' }
- end
-
- Timecop.travel(1.minute.from_now + 1.second) do
- method_call.measure { 'foo' }
- end
-
- expect(Feature.get(:prometheus_metrics_method_instrumentation)).to have_received(:enabled?).twice
+ it 'metric is not a NullMetric' do
+ expect(described_class).not_to be_instance_of(Gitlab::Metrics::NullMetric)
end
it 'observes the performance of the supplied block' do
- expect(described_class.call_duration_histogram)
+ expect(described_class.gitlab_method_call_duration_seconds)
.to receive(:observe)
.with({ module: :Foo, method: '#bar' }, be_a_kind_of(Numeric))
@@ -64,14 +48,12 @@ describe Gitlab::Metrics::MethodCall do
context 'prometheus instrumentation is disabled' do
before do
- described_class.measurement_enabled_cache_expires_at.value = Time.now.to_i - 1
-
Feature.get(:prometheus_metrics_method_instrumentation).disable
end
- it 'does not observe the performance' do
- expect(described_class.call_duration_histogram)
- .not_to receive(:observe)
+ it 'observes using NullMetric' do
+ expect(described_class.gitlab_method_call_duration_seconds).to be_instance_of(Gitlab::Metrics::NullMetric)
+ expect(described_class.gitlab_method_call_duration_seconds).to receive(:observe)
method_call.measure { 'foo' }
end
@@ -81,12 +63,10 @@ describe Gitlab::Metrics::MethodCall do
context 'when measurement is below threshold' do
before do
allow(method_call).to receive(:above_threshold?).and_return(false)
-
- Feature.get(:prometheus_metrics_method_instrumentation).enable
end
it 'does not observe the performance' do
- expect(described_class.call_duration_histogram)
+ expect(described_class.gitlab_method_call_duration_seconds)
.not_to receive(:observe)
method_call.measure { 'foo' }
@@ -96,7 +76,7 @@ describe Gitlab::Metrics::MethodCall do
describe '#to_metric' do
it 'returns a Metric instance' do
- expect(method_call).to receive(:real_time).and_return(4.0001)
+ expect(method_call).to receive(:real_time).and_return(4.0001).twice
expect(method_call).to receive(:cpu_time).and_return(3.0001)
method_call.measure { 'foo' }
diff --git a/spec/lib/gitlab/metrics/methods_spec.rb b/spec/lib/gitlab/metrics/methods_spec.rb
new file mode 100644
index 00000000000..9d41ed2442b
--- /dev/null
+++ b/spec/lib/gitlab/metrics/methods_spec.rb
@@ -0,0 +1,137 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Methods do
+ subject { Class.new { include Gitlab::Metrics::Methods } }
+
+ shared_context 'metric' do |metric_type, *args|
+ let(:docstring) { 'description' }
+ let(:metric_name) { :sample_metric }
+
+ describe "#define_#{metric_type}" do
+ define_method(:call_define_metric_method) do |**args|
+ subject.__send__("define_#{metric_type}", metric_name, **args)
+ end
+
+ context 'metrics access method not defined' do
+ it "defines metrics accessing method" do
+ expect(subject).not_to respond_to(metric_name)
+
+ call_define_metric_method(docstring: docstring)
+
+ expect(subject).to respond_to(metric_name)
+ end
+ end
+
+ context 'metrics access method defined' do
+ before do
+ call_define_metric_method(docstring: docstring)
+ end
+
+ it 'raises error when trying to redefine method' do
+ expect { call_define_metric_method(docstring: docstring) }.to raise_error(ArgumentError)
+ end
+
+ context 'metric is not cached' do
+ it 'calls fetch_metric' do
+ expect(subject).to receive(:init_metric).with(metric_type, metric_name, docstring: docstring)
+
+ subject.public_send(metric_name)
+ end
+ end
+
+ context 'metric is cached' do
+ before do
+ subject.public_send(metric_name)
+ end
+
+ it 'returns cached metric' do
+ expect(subject).not_to receive(:init_metric)
+
+ subject.public_send(metric_name)
+ end
+ end
+ end
+ end
+
+ describe "#fetch_#{metric_type}" do
+ let(:null_metric) { Gitlab::Metrics::NullMetric.instance }
+
+ define_method(:call_fetch_metric_method) do |**args|
+ subject.__send__("fetch_#{metric_type}", metric_name, **args)
+ end
+
+ context "when #{metric_type} is not cached" do
+ it 'initializes counter metric' do
+ allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
+
+ call_fetch_metric_method(docstring: docstring)
+
+ expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
+ end
+ end
+
+ context "when #{metric_type} is cached" do
+ before do
+ call_fetch_metric_method(docstring: docstring)
+ end
+
+ it 'uses class metric cache' do
+ expect(Gitlab::Metrics).not_to receive(metric_type)
+
+ call_fetch_metric_method(docstring: docstring)
+ end
+
+ context 'when metric is reloaded' do
+ before do
+ subject.reload_metric!(metric_name)
+ end
+
+ it "initializes #{metric_type} metric" do
+ allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
+
+ call_fetch_metric_method(docstring: docstring)
+
+ expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
+ end
+ end
+ end
+
+ context 'when metric is configured with feature' do
+ let(:feature_name) { :some_metric_feature }
+ let(:metric) { call_fetch_metric_method(docstring: docstring, with_feature: feature_name) }
+
+ context 'when feature is enabled' do
+ before do
+ Feature.get(feature_name).enable
+ end
+
+ it "initializes #{metric_type} metric" do
+ allow(Gitlab::Metrics).to receive(metric_type).and_return(null_metric)
+
+ metric
+
+ expect(Gitlab::Metrics).to have_received(metric_type).with(metric_name, docstring, *args)
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ Feature.get(feature_name).disable
+ end
+
+ it "returns NullMetric" do
+ allow(Gitlab::Metrics).to receive(metric_type)
+
+ expect(metric).to be_instance_of(Gitlab::Metrics::NullMetric)
+
+ expect(Gitlab::Metrics).not_to have_received(metric_type)
+ end
+ end
+ end
+ end
+ end
+
+ include_examples 'metric', :counter, {}
+ include_examples 'metric', :gauge, {}, :all
+ include_examples 'metric', :histogram, {}, [0.005, 0.01, 0.1, 1, 10]
+end
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index 375cbf8a9ca..54781dd52fc 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -2,6 +2,11 @@ require 'spec_helper'
describe Gitlab::Metrics::Samplers::RubySampler do
let(:sampler) { described_class.new(5) }
+ let(:null_metric) { double('null_metric', set: nil, observe: nil) }
+
+ before do
+ allow(Gitlab::Metrics::NullMetric).to receive(:instance).and_return(null_metric)
+ end
after do
Allocations.stop if Gitlab::Metrics.mri?
@@ -17,12 +22,9 @@ describe Gitlab::Metrics::Samplers::RubySampler do
end
it 'adds a metric containing the memory usage' do
- expect(Gitlab::Metrics::System).to receive(:memory_usage)
- .and_return(9000)
+ expect(Gitlab::Metrics::System).to receive(:memory_usage).and_return(9000)
- expect(sampler.metrics[:memory_usage]).to receive(:set)
- .with({}, 9000)
- .and_call_original
+ expect(sampler.metrics[:memory_usage]).to receive(:set).with({}, 9000)
sampler.sample
end
@@ -31,9 +33,7 @@ describe Gitlab::Metrics::Samplers::RubySampler do
expect(Gitlab::Metrics::System).to receive(:file_descriptor_count)
.and_return(4)
- expect(sampler.metrics[:file_descriptors]).to receive(:set)
- .with({}, 4)
- .and_call_original
+ expect(sampler.metrics[:file_descriptors]).to receive(:set).with({}, 4)
sampler.sample
end
@@ -49,16 +49,14 @@ describe Gitlab::Metrics::Samplers::RubySampler do
it 'adds a metric containing garbage collection time statistics' do
expect(GC::Profiler).to receive(:total_time).and_return(0.24)
- expect(sampler.metrics[:total_time]).to receive(:set)
- .with({}, 240)
- .and_call_original
+ expect(sampler.metrics[:total_time]).to receive(:set).with({}, 240)
sampler.sample
end
it 'adds a metric containing garbage collection statistics' do
GC.stat.keys.each do |key|
- expect(sampler.metrics[key]).to receive(:set).with({}, anything).and_call_original
+ expect(sampler.metrics[key]).to receive(:set).with({}, anything)
end
sampler.sample
diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
index eca75a4fac1..9f3af1acef7 100644
--- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
@@ -32,7 +32,7 @@ describe Gitlab::Metrics::Subscribers::ActionView do
end
it 'observes view rendering time' do
- expect(subscriber.send(:metric_view_rendering_duration_seconds))
+ expect(described_class.gitlab_view_rendering_duration_seconds)
.to receive(:observe)
.with({ view: 'app/views/x.html.haml' }, 2.1)
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index 9b3698fb4a8..4e7bd433a9c 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -25,7 +25,7 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
expect(subscriber).to receive(:current_transaction)
.at_least(:once)
.and_return(transaction)
- expect(subscriber.send(:metric_sql_duration_seconds)).to receive(:observe).with({}, 0.002)
+ expect(described_class.send(:gitlab_sql_duration_seconds)).to receive(:observe).with({}, 0.002)
subscriber.sql(event)
end
diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
index 58e28592cf9..6795c1ab56b 100644
--- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
@@ -144,7 +144,10 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
end
context 'with a transaction' do
+ let(:metric_cache_misses_total) { double('metric_cache_misses_total', increment: nil) }
+
before do
+ allow(subscriber).to receive(:metric_cache_misses_total).and_return(metric_cache_misses_total)
allow(subscriber).to receive(:current_transaction)
.and_return(transaction)
end
@@ -157,9 +160,9 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
end
it 'increments the cache_read_miss total' do
- expect(subscriber.send(:metric_cache_misses_total)).to receive(:increment).with({})
-
subscriber.cache_generate(event)
+
+ expect(metric_cache_misses_total).to have_received(:increment).with({})
end
end
end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 1619fbd88b1..9e405e9f736 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::Metrics do
context 'prometheus metrics enabled in config' do
before do
- allow(described_class).to receive(:current_application_settings).and_return(prometheus_metrics_enabled: true)
+ allow(Gitlab::CurrentSettings).to receive(:current_application_settings).and_return(prometheus_metrics_enabled: true)
end
context 'when metrics folder is present' do
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 45fff4c5787..03e0a9e2a03 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -44,6 +44,18 @@ describe Gitlab::OAuth::User do
let(:provider) { 'twitter' }
+ describe 'when account exists on server' do
+ it 'does not mark the user as external' do
+ create(:omniauth_user, extern_uid: 'my-uid', provider: provider)
+ stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider])
+
+ oauth_user.save
+
+ expect(gl_user).to be_valid
+ expect(gl_user.external).to be_falsey
+ end
+ end
+
describe 'signup' do
context 'when signup is disabled' do
before do
@@ -51,7 +63,7 @@ describe Gitlab::OAuth::User do
end
it 'creates the user' do
- stub_omniauth_config(allow_single_sign_on: ['twitter'])
+ stub_omniauth_config(allow_single_sign_on: [provider])
oauth_user.save
@@ -65,7 +77,7 @@ describe Gitlab::OAuth::User do
end
it 'creates and confirms the user anyway' do
- stub_omniauth_config(allow_single_sign_on: ['twitter'])
+ stub_omniauth_config(allow_single_sign_on: [provider])
oauth_user.save
@@ -75,7 +87,7 @@ describe Gitlab::OAuth::User do
end
it 'marks user as having password_automatically_set' do
- stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['twitter'])
+ stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider])
oauth_user.save
@@ -86,7 +98,7 @@ describe Gitlab::OAuth::User do
shared_examples 'to verify compliance with allow_single_sign_on' do
context 'provider is marked as external' do
it 'marks user as external' do
- stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['twitter'])
+ stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider])
oauth_user.save
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
@@ -95,8 +107,8 @@ describe Gitlab::OAuth::User do
context 'provider was external, now has been removed' do
it 'does not mark external user as internal' do
- create(:omniauth_user, extern_uid: 'my-uid', provider: 'twitter', external: true)
- stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['facebook'])
+ create(:omniauth_user, extern_uid: 'my-uid', provider: provider, external: true)
+ stub_omniauth_config(allow_single_sign_on: [provider], external_providers: ['facebook'])
oauth_user.save
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
@@ -118,7 +130,7 @@ describe Gitlab::OAuth::User do
context 'with new allow_single_sign_on enabled syntax' do
before do
- stub_omniauth_config(allow_single_sign_on: ['twitter'])
+ stub_omniauth_config(allow_single_sign_on: [provider])
end
it "creates a user from Omniauth" do
@@ -127,7 +139,7 @@ describe Gitlab::OAuth::User do
expect(gl_user).to be_valid
identity = gl_user.identities.first
expect(identity.extern_uid).to eql uid
- expect(identity.provider).to eql 'twitter'
+ expect(identity.provider).to eql provider
end
end
@@ -142,7 +154,7 @@ describe Gitlab::OAuth::User do
expect(gl_user).to be_valid
identity = gl_user.identities.first
expect(identity.extern_uid).to eql uid
- expect(identity.provider).to eql 'twitter'
+ expect(identity.provider).to eql provider
end
end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 0ae90069b7f..85991c38363 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -121,7 +121,7 @@ describe Gitlab::PathRegex do
STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id}
NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*}
ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*}
- WILDCARD_SEGMENT = %r{\*}
+ WILDCARD_SEGMENT = /\*/
let(:namespaced_wildcard_routes) do
routes_without_format.select do |p|
p =~ %r{#{STARTING_WITH_NAMESPACE}/#{NON_PARAM_PARTS}/#{ANY_OTHER_PATH_PART}#{WILDCARD_SEGMENT}}
diff --git a/spec/lib/gitlab/popen/runner_spec.rb b/spec/lib/gitlab/popen/runner_spec.rb
new file mode 100644
index 00000000000..2e2cb4ca28f
--- /dev/null
+++ b/spec/lib/gitlab/popen/runner_spec.rb
@@ -0,0 +1,139 @@
+require 'spec_helper'
+
+describe Gitlab::Popen::Runner do
+ subject { described_class.new }
+
+ describe '#run' do
+ it 'runs the command and returns the result' do
+ run_command
+
+ expect(Gitlab::Popen).to have_received(:popen_with_detail)
+ end
+ end
+
+ describe '#all_success_and_clean?' do
+ it 'returns true when exit status is 0 and stderr is empty' do
+ run_command
+
+ expect(subject).to be_all_success_and_clean
+ end
+
+ it 'returns false when exit status is not 0' do
+ run_command(exitstatus: 1)
+
+ expect(subject).not_to be_all_success_and_clean
+ end
+
+ it 'returns false when exit stderr has something' do
+ run_command(stderr: 'stderr')
+
+ expect(subject).not_to be_all_success_and_clean
+ end
+ end
+
+ describe '#all_success?' do
+ it 'returns true when exit status is 0' do
+ run_command
+
+ expect(subject).to be_all_success
+ end
+
+ it 'returns false when exit status is not 0' do
+ run_command(exitstatus: 1)
+
+ expect(subject).not_to be_all_success
+ end
+
+ it 'returns true' do
+ run_command(stderr: 'stderr')
+
+ expect(subject).to be_all_success
+ end
+ end
+
+ describe '#all_stderr_empty?' do
+ it 'returns true when stderr is empty' do
+ run_command
+
+ expect(subject).to be_all_stderr_empty
+ end
+
+ it 'returns true when exit status is not 0' do
+ run_command(exitstatus: 1)
+
+ expect(subject).to be_all_stderr_empty
+ end
+
+ it 'returns false when exit stderr has something' do
+ run_command(stderr: 'stderr')
+
+ expect(subject).not_to be_all_stderr_empty
+ end
+ end
+
+ describe '#failed_results' do
+ it 'returns [] when everything is passed' do
+ run_command
+
+ expect(subject.failed_results).to be_empty
+ end
+
+ it 'returns the result when exit status is not 0' do
+ result = run_command(exitstatus: 1)
+
+ expect(subject.failed_results).to contain_exactly(result)
+ end
+
+ it 'returns [] when exit stderr has something' do
+ run_command(stderr: 'stderr')
+
+ expect(subject.failed_results).to be_empty
+ end
+ end
+
+ describe '#warned_results' do
+ it 'returns [] when everything is passed' do
+ run_command
+
+ expect(subject.warned_results).to be_empty
+ end
+
+ it 'returns [] when exit status is not 0' do
+ run_command(exitstatus: 1)
+
+ expect(subject.warned_results).to be_empty
+ end
+
+ it 'returns the result when exit stderr has something' do
+ result = run_command(stderr: 'stderr')
+
+ expect(subject.warned_results).to contain_exactly(result)
+ end
+ end
+
+ def run_command(
+ command: 'command',
+ stdout: 'stdout',
+ stderr: '',
+ exitstatus: 0,
+ status: double(exitstatus: exitstatus, success?: exitstatus.zero?),
+ duration: 0.1)
+
+ result =
+ Gitlab::Popen::Result.new(command, stdout, stderr, status, duration)
+
+ allow(Gitlab::Popen)
+ .to receive(:popen_with_detail)
+ .and_return(result)
+
+ subject.run([command]) do |cmd, &run|
+ expect(cmd).to eq(command)
+
+ cmd_result = run.call
+
+ expect(cmd_result).to eq(result)
+ end
+
+ subject.results.first
+ end
+end
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index b145ca36f26..1dbead16d5b 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -1,11 +1,23 @@
require 'spec_helper'
-describe 'Gitlab::Popen' do
+describe Gitlab::Popen do
let(:path) { Rails.root.join('tmp').to_s }
before do
@klass = Class.new(Object)
- @klass.send(:include, Gitlab::Popen)
+ @klass.send(:include, described_class)
+ end
+
+ describe '.popen_with_detail' do
+ subject { @klass.new.popen_with_detail(cmd) }
+
+ let(:cmd) { %W[#{Gem.ruby} -e $stdout.puts(1);$stderr.puts(2);exit(3)] }
+
+ it { expect(subject.cmd).to eq(cmd) }
+ it { expect(subject.stdout).to eq("1\n") }
+ it { expect(subject.stderr).to eq("2\n") }
+ it { expect(subject.status.exitstatus).to eq(3) }
+ it { expect(subject.duration).to be_kind_of(Numeric) }
end
context 'zero status' do
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
new file mode 100644
index 00000000000..4a43dbb2371
--- /dev/null
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -0,0 +1,156 @@
+require 'spec_helper'
+
+describe Gitlab::Profiler do
+ RSpec::Matchers.define_negated_matcher :not_change, :change
+
+ let(:null_logger) { Logger.new('/dev/null') }
+ let(:private_token) { 'private' }
+
+ describe '.profile' do
+ let(:app) { double(:app) }
+
+ before do
+ allow(ActionDispatch::Integration::Session).to receive(:new).and_return(app)
+ allow(app).to receive(:get)
+ end
+
+ it 'returns a profile result' do
+ expect(described_class.profile('/')).to be_an_instance_of(RubyProf::Profile)
+ end
+
+ it 'uses the custom logger given' do
+ expect(described_class).to receive(:create_custom_logger)
+ .with(null_logger, private_token: anything)
+ .and_call_original
+
+ described_class.profile('/', logger: null_logger)
+ end
+
+ it 'sends a POST request when data is passed' do
+ post_data = '{"a":1}'
+
+ expect(app).to receive(:post).with(anything, post_data, anything)
+
+ described_class.profile('/', post_data: post_data)
+ end
+
+ it 'uses the private_token for auth if given' do
+ expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token)
+ expect(app).to receive(:get).with('/api/v4/users')
+
+ described_class.profile('/', private_token: private_token)
+ end
+
+ it 'uses the user for auth if given' do
+ user = double(:user)
+ user_token = 'user'
+
+ allow(user).to receive_message_chain(:personal_access_tokens, :active, :pluck, :first).and_return(user_token)
+
+ expect(app).to receive(:get).with('/', nil, 'Private-Token' => user_token)
+ expect(app).to receive(:get).with('/api/v4/users')
+
+ described_class.profile('/', user: user)
+ end
+
+ it 'uses the private_token for auth if both it and user are set' do
+ user = double(:user)
+ user_token = 'user'
+
+ allow(user).to receive_message_chain(:personal_access_tokens, :active, :pluck, :first).and_return(user_token)
+
+ expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token)
+ expect(app).to receive(:get).with('/api/v4/users')
+
+ described_class.profile('/', user: user, private_token: private_token)
+ end
+ end
+
+ describe '.create_custom_logger' do
+ it 'does nothing when nil is passed' do
+ expect(described_class.create_custom_logger(nil)).to be_nil
+ end
+
+ context 'the new logger' do
+ let(:custom_logger) do
+ described_class.create_custom_logger(null_logger, private_token: private_token)
+ end
+
+ it 'does not affect the existing logger' do
+ expect(null_logger).not_to receive(:debug)
+ expect(custom_logger).to receive(:debug).and_call_original
+
+ custom_logger.debug('Foo')
+ end
+
+ it 'strips out the private token' do
+ expect(custom_logger).to receive(:add) do |severity, _progname, message|
+ expect(severity).to eq(Logger::DEBUG)
+ expect(message).to include('public').and include(described_class::FILTERED_STRING)
+ expect(message).not_to include(private_token)
+ end
+
+ custom_logger.debug("public #{private_token}")
+ end
+
+ it 'tracks model load times by model' do
+ custom_logger.debug('This is not a model load')
+ custom_logger.debug('User Load (1.2ms)')
+ custom_logger.debug('User Load (1.3ms)')
+ custom_logger.debug('Project Load (10.4ms)')
+
+ expect(custom_logger.load_times_by_model).to eq('User' => 2.5,
+ 'Project' => 10.4)
+ end
+
+ it 'logs the backtrace, ignoring lines as appropriate' do
+ # Skip Rails's backtrace cleaning.
+ allow(Rails.backtrace_cleaner).to receive(:clean, &:itself)
+
+ expect(custom_logger).to receive(:add)
+ .with(Logger::DEBUG,
+ anything,
+ a_string_matching(File.basename(__FILE__)))
+ .twice
+
+ expect(custom_logger).not_to receive(:add).with(Logger::DEBUG,
+ anything,
+ a_string_matching('lib/gitlab/profiler.rb'))
+
+ # Force a part of the backtrace to be in the (ignored) profiler source
+ # file.
+ described_class.with_custom_logger(nil) { custom_logger.debug('Foo') }
+ end
+ end
+ end
+
+ describe '.with_custom_logger' do
+ context 'when the logger is set' do
+ it 'uses the replacement logger for the duration of the block' do
+ expect(null_logger).to receive(:debug).and_call_original
+
+ expect { described_class.with_custom_logger(null_logger) { ActiveRecord::Base.logger.debug('foo') } }
+ .to not_change { ActiveRecord::Base.logger }
+ .and not_change { ActionController::Base.logger }
+ .and not_change { ActiveSupport::LogSubscriber.colorize_logging }
+ end
+
+ it 'returns the result of the block' do
+ expect(described_class.with_custom_logger(null_logger) { 2 }).to eq(2)
+ end
+ end
+
+ context 'when the logger is nil' do
+ it 'returns the result of the block' do
+ expect(described_class.with_custom_logger(nil) { 2 }).to eq(2)
+ end
+
+ it 'does not modify the standard Rails loggers' do
+ expect { described_class.with_custom_logger(nil) { } }
+ .to not_change { ActiveRecord::Base.logger }
+ .and not_change { ActionController::Base.logger }
+ .and not_change { ActiveSupport::LogSubscriber.colorize_logging }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 17937726f2c..1ebb0105cf5 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -70,15 +70,6 @@ describe Gitlab::ProjectSearchResults do
subject { described_class.parse_search_result(search_result) }
- it 'can correctly parse filenames including ":"' do
- special_char_result = "\nmaster:testdata/project::function1.yaml-1----\nmaster:testdata/project::function1.yaml:2:test: data1\n"
-
- blob = described_class.parse_search_result(special_char_result)
-
- expect(blob.ref).to eq('master')
- expect(blob.filename).to eq('testdata/project::function1.yaml')
- end
-
it "returns a valid FoundBlob" do
is_expected.to be_an Gitlab::SearchResults::FoundBlob
expect(subject.id).to be_nil
@@ -90,8 +81,32 @@ describe Gitlab::ProjectSearchResults do
expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n")
end
+ context 'when the matching filename contains a colon' do
+ let(:search_result) { "\nmaster:testdata/project::function1.yaml\x001\x00---\n" }
+
+ it 'returns a valid FoundBlob' do
+ expect(subject.filename).to eq('testdata/project::function1.yaml')
+ expect(subject.basename).to eq('testdata/project::function1')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(1)
+ expect(subject.data).to eq('---')
+ end
+ end
+
+ context 'when the matching content contains a number surrounded by colons' do
+ let(:search_result) { "\nmaster:testdata/foo.txt\x001\x00blah:9:blah" }
+
+ it 'returns a valid FoundBlob' do
+ expect(subject.filename).to eq('testdata/foo.txt')
+ expect(subject.basename).to eq('testdata/foo')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(1)
+ expect(subject.data).to eq('blah:9:blah')
+ end
+ end
+
context "when filename has extension" do
- let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" }
+ let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" }
it { expect(subject.path).to eq('CONTRIBUTE.md') }
it { expect(subject.filename).to eq('CONTRIBUTE.md') }
@@ -99,7 +114,7 @@ describe Gitlab::ProjectSearchResults do
end
context "when file under directory" do
- let(:search_result) { "master:a/b/c.md:5:a b c\n" }
+ let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" }
it { expect(subject.path).to eq('a/b/c.md') }
it { expect(subject.filename).to eq('a/b/c.md') }
@@ -144,7 +159,7 @@ describe Gitlab::ProjectSearchResults do
end
it 'finds by content' do
- expect(results).to include("master:Title.md:1:Content\n")
+ expect(results).to include("master:Title.md\x001\x00Content\n")
end
end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 68a57826647..8b54d72d6f7 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Regex do
it { is_expected.not_to match('?gitlab') }
end
- describe '.environment_slug_regex' do
+ describe '.environment_name_regex' do
subject { described_class.environment_name_regex }
it { is_expected.to match('foo') }
@@ -24,6 +24,7 @@ describe Gitlab::Regex do
it { is_expected.to match('foo.1') }
it { is_expected.not_to match('9&foo') }
it { is_expected.not_to match('foo-^') }
+ it { is_expected.not_to match('!!()()') }
end
describe '.environment_slug_regex' do
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index b5a9ac570e6..17b48b3d062 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -19,6 +19,12 @@ describe Gitlab::SearchResults do
project.add_developer(user)
end
+ describe '#objects' do
+ it 'returns without_page collection by default' do
+ expect(results.objects('projects')).to be_kind_of(Kaminari::PaginatableWithoutCount)
+ end
+ end
+
describe '#projects_count' do
it 'returns the total amount of projects' do
expect(results.projects_count).to eq(1)
@@ -43,6 +49,58 @@ describe Gitlab::SearchResults do
end
end
+ context "when count_limit is lower than total amount" do
+ before do
+ allow(results).to receive(:count_limit).and_return(1)
+ end
+
+ describe '#limited_projects_count' do
+ it 'returns the limited amount of projects' do
+ create(:project, name: 'foo2')
+
+ expect(results.limited_projects_count).to eq(1)
+ end
+ end
+
+ describe '#limited_merge_requests_count' do
+ it 'returns the limited amount of merge requests' do
+ create(:merge_request, :simple, source_project: project, title: 'foo2')
+
+ expect(results.limited_merge_requests_count).to eq(1)
+ end
+ end
+
+ describe '#limited_milestones_count' do
+ it 'returns the limited amount of milestones' do
+ create(:milestone, project: project, title: 'foo2')
+
+ expect(results.limited_milestones_count).to eq(1)
+ end
+ end
+
+ describe '#limited_issues_count' do
+ it 'runs single SQL query to get the limited amount of issues' do
+ create(:milestone, project: project, title: 'foo2')
+
+ expect(results).to receive(:issues).with(public_only: true).and_call_original
+ expect(results).not_to receive(:issues).with(no_args).and_call_original
+
+ expect(results.limited_issues_count).to eq(1)
+ end
+ end
+ end
+
+ context "when count_limit is higher than total amount" do
+ describe '#limited_issues_count' do
+ it 'runs multiple queries to get the limited amount of issues' do
+ expect(results).to receive(:issues).with(public_only: true).and_call_original
+ expect(results).to receive(:issues).with(no_args).and_call_original
+
+ expect(results.limited_issues_count).to eq(1)
+ end
+ end
+ end
+
it 'includes merge requests from source and target projects' do
forked_project = fork_project(project, user)
merge_request_2 = create(:merge_request, target_project: project, source_project: forked_project, title: 'foo')
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 81d9e6a8f82..2b61ce38418 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -4,6 +4,7 @@ require 'stringio'
describe Gitlab::Shell do
set(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
let(:gitlab_shell) { described_class.new }
let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
let(:gitlab_projects) { double('gitlab_projects') }
@@ -51,6 +52,311 @@ describe Gitlab::Shell do
end
end
+ describe '#add_key' do
+ context 'when authorized_keys_enabled is true' do
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
+ context 'when authorized_keys_enabled is false' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
+ context 'when authorized_keys_enabled is nil' do
+ before do
+ stub_application_setting(authorized_keys_enabled: nil)
+ end
+
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+ end
+
+ describe '#batch_add_keys' do
+ context 'when authorized_keys_enabled is true' do
+ it 'instantiates KeyAdder' do
+ expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar')
+
+ gitlab_shell.batch_add_keys do |adder|
+ adder.add_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+ end
+
+ context 'when authorized_keys_enabled is false' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect_any_instance_of(Gitlab::Shell::KeyAdder).not_to receive(:add_key)
+
+ gitlab_shell.batch_add_keys do |adder|
+ adder.add_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+ end
+
+ context 'when authorized_keys_enabled is nil' do
+ before do
+ stub_application_setting(authorized_keys_enabled: nil)
+ end
+
+ it 'instantiates KeyAdder' do
+ expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar')
+
+ gitlab_shell.batch_add_keys do |adder|
+ adder.add_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+ end
+ end
+
+ describe '#remove_key' do
+ context 'when authorized_keys_enabled is true' do
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+
+ context 'when authorized_keys_enabled is false' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+
+ gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+
+ context 'when authorized_keys_enabled is nil' do
+ before do
+ stub_application_setting(authorized_keys_enabled: nil)
+ end
+
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar']
+ )
+
+ gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ end
+ end
+
+ context 'when key content is not given' do
+ it 'calls rm-key with only one argument' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'rm-key', 'key-123']
+ )
+
+ gitlab_shell.remove_key('key-123')
+ end
+ end
+ end
+
+ describe '#remove_all_keys' do
+ context 'when authorized_keys_enabled is true' do
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with([:gitlab_shell_keys_path, 'clear'])
+
+ gitlab_shell.remove_all_keys
+ end
+ end
+
+ context 'when authorized_keys_enabled is false' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+
+ gitlab_shell.remove_all_keys
+ end
+ end
+
+ context 'when authorized_keys_enabled is nil' do
+ before do
+ stub_application_setting(authorized_keys_enabled: nil)
+ end
+
+ it 'removes trailing garbage' do
+ allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
+ expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
+ [:gitlab_shell_keys_path, 'clear']
+ )
+
+ gitlab_shell.remove_all_keys
+ end
+ end
+ end
+
+ describe '#remove_keys_not_found_in_db' do
+ context 'when keys are in the file that are not in the DB' do
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
+ @another_key = create(:key) # this one IS in the DB
+ end
+
+ it 'removes the keys' do
+ expect(find_in_authorized_keys_file(1234)).to be_truthy
+ expect(find_in_authorized_keys_file(9876)).to be_truthy
+ expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy
+ gitlab_shell.remove_keys_not_found_in_db
+ expect(find_in_authorized_keys_file(1234)).to be_falsey
+ expect(find_in_authorized_keys_file(9876)).to be_falsey
+ expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy
+ end
+ end
+
+ context 'when keys there are duplicate keys in the file that are not in the DB' do
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ end
+
+ it 'removes the keys' do
+ expect(find_in_authorized_keys_file(1234)).to be_truthy
+ gitlab_shell.remove_keys_not_found_in_db
+ expect(find_in_authorized_keys_file(1234)).to be_falsey
+ end
+
+ it 'does not run remove more than once per key (in a batch)' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234').once
+ gitlab_shell.remove_keys_not_found_in_db
+ end
+ end
+
+ context 'when keys there are duplicate keys in the file that ARE in the DB' do
+ before do
+ gitlab_shell.remove_all_keys
+ @key = create(:key)
+ gitlab_shell.add_key(@key.shell_id, @key.key)
+ end
+
+ it 'does not remove the key' do
+ gitlab_shell.remove_keys_not_found_in_db
+ expect(find_in_authorized_keys_file(@key.id)).to be_truthy
+ end
+
+ it 'does not need to run a SELECT query for that batch, on account of that key' do
+ expect_any_instance_of(ActiveRecord::Relation).not_to receive(:pluck)
+ gitlab_shell.remove_keys_not_found_in_db
+ end
+ end
+
+ unless ENV['CI'] # Skip in CI, it takes 1 minute
+ context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do
+ before do
+ gitlab_shell.remove_all_keys
+ 100.times { |i| create(:key) } # first batch is all in the DB
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ end
+
+ it 'removes the keys not in the DB' do
+ expect(find_in_authorized_keys_file(1234)).to be_truthy
+ gitlab_shell.remove_keys_not_found_in_db
+ expect(find_in_authorized_keys_file(1234)).to be_falsey
+ end
+ end
+ end
+ end
+
+ describe '#batch_read_key_ids' do
+ context 'when there are keys in the authorized_keys file' do
+ before do
+ gitlab_shell.remove_all_keys
+ (1..4).each do |i|
+ gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}")
+ end
+ end
+
+ it 'iterates over the key IDs in the file, in batches' do
+ loop_count = 0
+ first_batch = [1, 2]
+ second_batch = [3, 4]
+
+ gitlab_shell.batch_read_key_ids(batch_size: 2) do |batch|
+ expected = (loop_count == 0 ? first_batch : second_batch)
+ expect(batch).to eq(expected)
+ loop_count += 1
+ end
+ end
+ end
+ end
+
+ describe '#list_key_ids' do
+ context 'when there are keys in the authorized_keys file' do
+ before do
+ gitlab_shell.remove_all_keys
+ (1..4).each do |i|
+ gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}")
+ end
+ end
+
+ it 'outputs the key IDs in the file, separated by newlines' do
+ ids = []
+ gitlab_shell.list_key_ids do |io|
+ io.each do |line|
+ ids << line
+ end
+ end
+
+ expect(ids).to eq(%W{1\n 2\n 3\n 4\n})
+ end
+ end
+
+ context 'when there are no keys in the authorized_keys file' do
+ before do
+ gitlab_shell.remove_all_keys
+ end
+
+ it 'outputs nothing, not even an empty string' do
+ ids = []
+ gitlab_shell.list_key_ids do |io|
+ io.each do |line|
+ ids << line
+ end
+ end
+
+ expect(ids).to eq([])
+ end
+ end
+ end
+
describe Gitlab::Shell::KeyAdder do
describe '#add_key' do
it 'removes trailing garbage' do
@@ -96,17 +402,6 @@ describe Gitlab::Shell do
allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
end
- describe '#add_key' do
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
- )
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
- end
- end
-
describe '#add_repository' do
shared_examples '#add_repository' do
let(:repository_storage) { 'default' }
@@ -148,32 +443,44 @@ describe Gitlab::Shell do
end
describe '#remove_repository' do
- subject { gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path) }
+ let!(:project) { create(:project, :repository) }
+ let(:disk_path) { "#{project.disk_path}.git" }
it 'returns true when the command succeeds' do
- expect(gitlab_projects).to receive(:rm_project) { true }
+ expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(true)
- is_expected.to be_truthy
+ expect(gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path)).to be(true)
+
+ expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(false)
end
- it 'returns false when the command fails' do
- expect(gitlab_projects).to receive(:rm_project) { false }
+ it 'keeps the namespace directory' do
+ gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path)
- is_expected.to be_falsy
+ expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(false)
+ expect(gitlab_shell.exists?(project.repository_storage_path, project.disk_path.gsub(project.name, ''))).to be(true)
end
end
describe '#mv_repository' do
+ let!(:project2) { create(:project, :repository) }
+
it 'returns true when the command succeeds' do
- expect(gitlab_projects).to receive(:mv_project).with('project/newpath.git') { true }
+ old_path = project2.disk_path
+ new_path = "project/new_path"
+
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{old_path}.git")).to be(true)
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{new_path}.git")).to be(false)
- expect(gitlab_shell.mv_repository(project.repository_storage_path, project.disk_path, 'project/newpath')).to be_truthy
+ expect(gitlab_shell.mv_repository(project2.repository_storage_path, old_path, new_path)).to be_truthy
+
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{old_path}.git")).to be(false)
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{new_path}.git")).to be(true)
end
it 'returns false when the command fails' do
- expect(gitlab_projects).to receive(:mv_project).with('project/newpath.git') { false }
-
- expect(gitlab_shell.mv_repository(project.repository_storage_path, project.disk_path, 'project/newpath')).to be_falsy
+ expect(gitlab_shell.mv_repository(project2.repository_storage_path, project2.disk_path, '')).to be_falsy
+ expect(gitlab_shell.exists?(project2.repository_storage_path, "#{project2.disk_path}.git")).to be(true)
end
end
@@ -201,8 +508,6 @@ describe Gitlab::Shell do
end
shared_examples 'fetch_remote' do |gitaly_on|
- let(:repository) { project.repository }
-
def fetch_remote(ssh_auth = nil)
gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', ssh_auth: ssh_auth)
end
@@ -325,6 +630,23 @@ describe Gitlab::Shell do
describe '#fetch_remote gitaly' do
it_should_behave_like 'fetch_remote', true
+
+ context 'gitaly call' do
+ let(:remote_name) { 'remote-name' }
+ let(:ssh_auth) { double(:ssh_auth) }
+
+ subject do
+ gitlab_shell.fetch_remote(repository.raw_repository, remote_name,
+ forced: true, no_tags: true, ssh_auth: ssh_auth)
+ end
+
+ it 'passes the correct params to the gitaly service' do
+ expect(repository.gitaly_repository_client).to receive(:fetch_remote)
+ .with(remote_name, ssh_auth: ssh_auth, forced: true, no_tags: true, timeout: timeout)
+
+ subject
+ end
+ end
end
describe '#import_repository' do
@@ -396,4 +718,12 @@ describe Gitlab::Shell do
end
end
end
+
+ def find_in_authorized_keys_file(key_id)
+ gitlab_shell.batch_read_key_ids do |ids|
+ return true if ids.include?(key_id)
+ end
+
+ false
+ end
end
diff --git a/spec/lib/gitlab/slash_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
index e41e5254dde..35d01efc1bd 100644
--- a/spec/lib/gitlab/slash_commands/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::SlashCommands::IssueSearch do
let!(:issue) { create(:issue, project: project, title: 'find me') }
let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') }
let(:project) { create(:project) }
- let(:user) { issue.author }
+ let(:user) { create(:user) }
let(:regex_match) { described_class.match("issue search find") }
subject do
diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb
new file mode 100644
index 00000000000..7c97cee982a
--- /dev/null
+++ b/spec/lib/gitlab/utils/override_spec.rb
@@ -0,0 +1,158 @@
+require 'spec_helper'
+
+describe Gitlab::Utils::Override do
+ let(:base) { Struct.new(:good) }
+
+ let(:derived) { Class.new(base).tap { |m| m.extend described_class } }
+ let(:extension) { Module.new.tap { |m| m.extend described_class } }
+
+ let(:prepending_class) { base.tap { |m| m.prepend extension } }
+ let(:including_class) { base.tap { |m| m.include extension } }
+
+ let(:klass) { subject }
+
+ def good(mod)
+ mod.module_eval do
+ override :good
+ def good
+ super.succ
+ end
+ end
+
+ mod
+ end
+
+ def bad(mod)
+ mod.module_eval do
+ override :bad
+ def bad
+ true
+ end
+ end
+
+ mod
+ end
+
+ shared_examples 'checking as intended' do
+ it 'checks ok for overriding method' do
+ good(subject)
+ result = klass.new(0).good
+
+ expect(result).to eq(1)
+ described_class.verify!
+ end
+
+ it 'raises NotImplementedError when it is not overriding anything' do
+ expect do
+ bad(subject)
+ klass.new(0).bad
+ described_class.verify!
+ end.to raise_error(NotImplementedError)
+ end
+ end
+
+ shared_examples 'nothing happened' do
+ it 'does not complain when it is overriding something' do
+ good(subject)
+ result = klass.new(0).good
+
+ expect(result).to eq(1)
+ described_class.verify!
+ end
+
+ it 'does not complain when it is not overriding anything' do
+ bad(subject)
+ result = klass.new(0).bad
+
+ expect(result).to eq(true)
+ described_class.verify!
+ end
+ end
+
+ before do
+ # Make sure we're not touching the internal cache
+ allow(described_class).to receive(:extensions).and_return({})
+ end
+
+ describe '#override' do
+ context 'when STATIC_VERIFICATION is set' do
+ before do
+ stub_env('STATIC_VERIFICATION', 'true')
+ end
+
+ context 'when subject is a class' do
+ subject { derived }
+
+ it_behaves_like 'checking as intended'
+ end
+
+ context 'when subject is a module, and class is prepending it' do
+ subject { extension }
+ let(:klass) { prepending_class }
+
+ it_behaves_like 'checking as intended'
+ end
+
+ context 'when subject is a module, and class is including it' do
+ subject { extension }
+ let(:klass) { including_class }
+
+ it 'raises NotImplementedError because it is not overriding it' do
+ expect do
+ good(subject)
+ klass.new(0).good
+ described_class.verify!
+ end.to raise_error(NotImplementedError)
+ end
+
+ it 'raises NotImplementedError when it is not overriding anything' do
+ expect do
+ bad(subject)
+ klass.new(0).bad
+ described_class.verify!
+ end.to raise_error(NotImplementedError)
+ end
+ end
+ end
+ end
+
+ context 'when STATIC_VERIFICATION is not set' do
+ before do
+ stub_env('STATIC_VERIFICATION', nil)
+ end
+
+ context 'when subject is a class' do
+ subject { derived }
+
+ it_behaves_like 'nothing happened'
+ end
+
+ context 'when subject is a module, and class is prepending it' do
+ subject { extension }
+ let(:klass) { prepending_class }
+
+ it_behaves_like 'nothing happened'
+ end
+
+ context 'when subject is a module, and class is including it' do
+ subject { extension }
+ let(:klass) { including_class }
+
+ it 'does not complain when it is overriding something' do
+ good(subject)
+ result = klass.new(0).good
+
+ expect(result).to eq(0)
+ described_class.verify!
+ end
+
+ it 'does not complain when it is not overriding anything' do
+ bad(subject)
+ result = klass.new(0).bad
+
+ expect(result).to eq(true)
+ described_class.verify!
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index e872a5290c5..bda239b7871 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -17,6 +17,22 @@ describe Gitlab::Utils do
end
end
+ describe '.remove_line_breaks' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:original, :expected) do
+ "foo\nbar\nbaz" | "foobarbaz"
+ "foo\r\nbar\r\nbaz" | "foobarbaz"
+ "foobar" | "foobar"
+ end
+
+ with_them do
+ it "replace line breaks with an empty string" do
+ expect(described_class.remove_line_breaks(original)).to eq(expected)
+ end
+ end
+ end
+
describe '.to_boolean' do
it 'accepts booleans' do
expect(to_boolean(true)).to be(true)
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 0b34d71bfb2..dc2bb5b9747 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -26,11 +26,16 @@ describe Gitlab::Workhorse do
'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys
)
end
+ let(:cache_disabled) { false }
subject do
described_class.send_git_archive(repository, ref: ref, format: format)
end
+ before do
+ allow(described_class).to receive(:git_archive_cache_disabled?).and_return(cache_disabled)
+ end
+
context 'when Gitaly workhorse_archive feature is enabled' do
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
@@ -39,6 +44,15 @@ describe Gitlab::Workhorse do
expect(command).to eq('git-archive')
expect(params).to include(gitaly_params)
end
+
+ context 'when archive caching is disabled' do
+ let(:cache_disabled) { true }
+
+ it 'tells workhorse not to use the cache' do
+ _, _, params = decode_workhorse_header(subject)
+ expect(params).to include({ 'DisableCache' => true })
+ end
+ end
end
context 'when Gitaly workhorse_archive feature is disabled', :skip_gitaly_mock do
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index cbc8c67da61..59eda025108 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -71,6 +71,18 @@ describe Notify do
is_expected.to have_html_escaped_body_text issue.description
end
+ it 'does not add a reason header' do
+ is_expected.not_to have_header('X-GitLab-NotificationReason', /.+/)
+ end
+
+ context 'when sent with a reason' do
+ subject { described_class.new_issue_email(issue.assignees.first.id, issue.id, NotificationReason::ASSIGNED) }
+
+ it 'includes the reason in a header' do
+ is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+ end
+
context 'when enabled email_author_in_body' do
before do
stub_application_setting(email_author_in_body: true)
@@ -108,6 +120,14 @@ describe Notify do
is_expected.to have_body_text(project_issue_path(project, issue))
end
end
+
+ context 'when sent with a reason' do
+ subject { described_class.reassigned_issue_email(recipient.id, issue.id, [previous_assignee.id], current_user.id, NotificationReason::ASSIGNED) }
+
+ it 'includes the reason in a header' do
+ is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+ end
end
describe 'that have been relabeled' do
@@ -226,6 +246,14 @@ describe Notify do
is_expected.to have_html_escaped_body_text merge_request.description
end
+ context 'when sent with a reason' do
+ subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id, NotificationReason::ASSIGNED) }
+
+ it 'includes the reason in a header' do
+ is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+ end
+
context 'when enabled email_author_in_body' do
before do
stub_application_setting(email_author_in_body: true)
@@ -263,6 +291,27 @@ describe Notify do
is_expected.to have_html_escaped_body_text(assignee.name)
end
end
+
+ context 'when sent with a reason' do
+ subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, NotificationReason::ASSIGNED) }
+
+ it 'includes the reason in a header' do
+ is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+
+ it 'includes the reason in the footer' do
+ text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::ASSIGNED)
+ is_expected.to have_body_text(text)
+
+ new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, NotificationReason::MENTIONED)
+ text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::MENTIONED)
+ expect(new_subject).to have_body_text(text)
+
+ new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, nil)
+ text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(nil)
+ expect(new_subject).to have_body_text(text)
+ end
+ end
end
describe 'that have been relabeled' do
@@ -1308,7 +1357,7 @@ describe Notify do
matcher :have_part_with do |expected|
match do |actual|
- actual.body.parts.any? { |part| part.content_type.try(:match, %r(#{expected})) }
+ actual.body.parts.any? { |part| part.content_type.try(:match, /#{expected}/) }
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
index 84c2e9f7e52..63defcb39bf 100644
--- a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
+++ b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb')
-describe AddHeadPipelineForEachMergeRequest, :truncate do
+describe AddHeadPipelineForEachMergeRequest, :delete do
include ProjectForksHelper
let(:migration) { described_class.new }
diff --git a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
index 597d8eab51c..f3a46025376 100644
--- a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
+++ b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170803090603_calculate_conv_dev_index_percentages.rb')
-describe CalculateConvDevIndexPercentages, truncate: true do
+describe CalculateConvDevIndexPercentages, :delete do
let(:migration) { described_class.new }
let!(:conv_dev_index) do
create(:conversational_development_index_metric,
diff --git a/spec/migrations/fix_wrongly_renamed_routes_spec.rb b/spec/migrations/fix_wrongly_renamed_routes_spec.rb
index 5ef10b92a3a..543cf55f076 100644
--- a/spec/migrations/fix_wrongly_renamed_routes_spec.rb
+++ b/spec/migrations/fix_wrongly_renamed_routes_spec.rb
@@ -1,29 +1,35 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170518231126_fix_wrongly_renamed_routes.rb')
-describe FixWronglyRenamedRoutes, truncate: true do
+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
- namespace = create(:group, name: 'apiis')
- namespace.route.update_attribute(:path, 'api0is')
- namespace
+ 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 = create(:group, name: 'api0')
+ 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)
+ .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
- namespace = create(:group, name: 'uploads-test')
- namespace.route.update_attribute(:path, 'uploads0-test')
+ 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' },
@@ -36,38 +42,45 @@ describe FixWronglyRenamedRoutes, truncate: true do
describe '#routes_in_namespace_query' do
it 'includes only the required routes' do
- namespace = create(:group, path: 'hello')
- project = create(:project, namespace: namespace)
- _other_namespace = create(:group, path: 'hello0')
-
- result = Route.where(subject.routes_in_namespace_query('hello'))
-
- expect(result).to contain_exactly(namespace.route, project.route)
+ 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
- let(:broken_project) do
- project = create(:project, namespace: broken_namespace, path: 'broken-project')
- project.route.update_attribute(:path, 'api0is/broken-project')
- project
- end
-
it 'renames incorrectly named routes' do
- broken_project
+ 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
- expect(broken_project.route.reload.path).to eq('apiis/broken-project')
- expect(broken_namespace.route.reload.path).to eq('apiis')
+ 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
- namespace = create(:group, path: 'api0')
+ 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')
+ expect(namespace_route.reload.path).to eq('api0')
end
end
end
diff --git a/spec/migrations/migrate_issues_to_ghost_user_spec.rb b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
index cfd4021fbac..ff0d44e1ed2 100644
--- a/spec/migrations/migrate_issues_to_ghost_user_spec.rb
+++ b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
@@ -8,10 +8,10 @@ describe MigrateIssuesToGhostUser, :migration do
let(:users) { table(:users) }
before do
- projects.create!(name: 'gitlab')
+ project = projects.create!(name: 'gitlab')
user = users.create(email: 'test@example.com')
- issues.create(title: 'Issue 1', author_id: nil, project_id: 1)
- issues.create(title: 'Issue 2', author_id: user.id, project_id: 1)
+ 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
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
index 063829be546..a17c9c72bde 100644
--- 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
@@ -3,7 +3,7 @@
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, :truncate do
+describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :delete do
let(:migration) { described_class.new }
let!(:user_active_1) { create(:user) }
let!(:user_active_2) { create(:user) }
diff --git a/spec/migrations/migrate_user_project_view_spec.rb b/spec/migrations/migrate_user_project_view_spec.rb
index 5e16769d63a..31d16e17d7b 100644
--- a/spec/migrations/migrate_user_project_view_spec.rb
+++ b/spec/migrations/migrate_user_project_view_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170406142253_migrate_user_project_view.rb')
-describe MigrateUserProjectView, :truncate do
+describe MigrateUserProjectView, :delete do
let(:migration) { described_class.new }
let!(:user) { create(:user, project_view: 'readme') }
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
new file mode 100644
index 00000000000..0ff98933d5c
--- /dev/null
+++ b/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb
@@ -0,0 +1,43 @@
+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_duplicate_mr_events_spec.rb b/spec/migrations/remove_duplicate_mr_events_spec.rb
index e393374028f..e51872239ad 100644
--- a/spec/migrations/remove_duplicate_mr_events_spec.rb
+++ b/spec/migrations/remove_duplicate_mr_events_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170815060945_remove_duplicate_mr_events.rb')
-describe RemoveDuplicateMrEvents, truncate: true do
+describe RemoveDuplicateMrEvents, :delete do
let(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/remove_empty_fork_networks_spec.rb b/spec/migrations/remove_empty_fork_networks_spec.rb
index ca9086a84d0..f6d030ab25c 100644
--- a/spec/migrations/remove_empty_fork_networks_spec.rb
+++ b/spec/migrations/remove_empty_fork_networks_spec.rb
@@ -3,12 +3,19 @@ require Rails.root.join('db', 'post_migrate', '20171114104051_remove_empty_fork_
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) { create(:project) }
- let!(:empty_network) { create(:fork_network, id: 1, root_project_id: deleted_project.id) }
- let!(:other_network) { create(:fork_network, id: 2, root_project_id: create(:project).id) }
+ 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
diff --git a/spec/migrations/remove_soft_removed_objects_spec.rb b/spec/migrations/remove_soft_removed_objects_spec.rb
new file mode 100644
index 00000000000..ec089f9106d
--- /dev/null
+++ b/spec/migrations/remove_soft_removed_objects_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20171207150343_remove_soft_removed_objects.rb')
+
+describe RemoveSoftRemovedObjects, :migration do
+ describe '#up' do
+ it 'removes various soft removed objects' do
+ 5.times do
+ create_with_deleted_at(:issue)
+ end
+
+ regular_issue = create(:issue)
+
+ run_migration
+
+ expect(Issue.count).to eq(1)
+ expect(Issue.first).to eq(regular_issue)
+ end
+
+ it 'removes the temporary indexes once soft removed data has been removed' do
+ migration = described_class.new
+
+ run_migration
+
+ disable_migrations_output do
+ expect(migration.temporary_index_exists?(Issue)).to eq(false)
+ end
+ end
+
+ it 'removes routes of soft removed personal namespaces' do
+ namespace = create_with_deleted_at(:namespace)
+ group = create(:group)
+
+ expect(Route.where(source: namespace).exists?).to eq(true)
+ expect(Route.where(source: group).exists?).to eq(true)
+
+ run_migration
+
+ expect(Route.where(source: namespace).exists?).to eq(false)
+ expect(Route.where(source: group).exists?).to eq(true)
+ end
+
+ it 'schedules the removal of soft removed groups' do
+ group = create_with_deleted_at(:group)
+ admin = create(:user, admin: true)
+
+ expect_any_instance_of(GroupDestroyWorker)
+ .to receive(:perform)
+ .with(group.id, admin.id)
+
+ run_migration
+ end
+
+ it 'does not remove soft removed groups when no admin user could be found' do
+ create_with_deleted_at(:group)
+
+ expect_any_instance_of(GroupDestroyWorker)
+ .not_to receive(:perform)
+
+ run_migration
+ end
+ end
+
+ def run_migration
+ disable_migrations_output do
+ migrate!
+ end
+ end
+
+ def create_with_deleted_at(*args)
+ row = create(*args)
+
+ # We set "deleted_at" this way so we don't run into any column cache issues.
+ row.class.where(id: row.id).update_all(deleted_at: 1.year.ago)
+
+ row
+ end
+end
diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb
index ae3a4cb9b29..75310075cc5 100644
--- a/spec/migrations/rename_more_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_more_reserved_project_names_spec.rb
@@ -5,8 +5,8 @@ require Rails.root.join('db', 'post_migrate', '20170313133418_rename_more_reserv
# 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 TRUNCATE cleaning strategy.
-describe RenameMoreReservedProjectNames, truncate: true do
+# around this we use the DELETE cleaning strategy.
+describe RenameMoreReservedProjectNames, :delete do
let(:migration) { described_class.new }
let!(:project) { create(:project) }
diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb
index 462f4c08d63..e6555b1fe6b 100644
--- a/spec/migrations/rename_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_reserved_project_names_spec.rb
@@ -5,8 +5,8 @@ require Rails.root.join('db', 'post_migrate', '20161221153951_rename_reserved_pr
# 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 TRUNCATE cleaning strategy.
-describe RenameReservedProjectNames, truncate: true do
+# around this we use the DELETE cleaning strategy.
+describe RenameReservedProjectNames, :delete do
let(:migration) { described_class.new }
let!(:project) { create(:project) }
diff --git a/spec/migrations/rename_users_with_renamed_namespace_spec.rb b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
index 1e9aab3d9a1..cbc0ebeb44d 100644
--- a/spec/migrations/rename_users_with_renamed_namespace_spec.rb
+++ b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170518200835_rename_users_with_renamed_namespace.rb')
-describe RenameUsersWithRenamedNamespace, truncate: true do
+describe RenameUsersWithRenamedNamespace, :delete do
it 'renames a user that had their namespace renamed to the namespace path' do
other_user = create(:user, username: 'kodingu')
other_user1 = create(:user, username: 'api0')
diff --git a/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb b/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
index 2e6b2cff0ab..7494624066a 100644
--- a/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
+++ b/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
@@ -2,6 +2,12 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb')
describe SchedulePopulateMergeRequestMetricsWithEventsData, :migration, :sidekiq do
+ # commits_count attribute is added in a next migration
+ before do
+ allow_any_instance_of(MergeRequestDiff)
+ .to receive(:commits_count=).and_return(nil)
+ end
+
let!(:mrs) { create_list(:merge_request, 3) }
it 'correctly schedules background migrations' do
diff --git a/spec/migrations/update_retried_for_ci_build_spec.rb b/spec/migrations/update_retried_for_ci_build_spec.rb
index 3742b4dafe5..ccb77766b84 100644
--- a/spec/migrations/update_retried_for_ci_build_spec.rb
+++ b/spec/migrations/update_retried_for_ci_build_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170503004427_update_retried_for_ci_build.rb')
-describe UpdateRetriedForCiBuild, truncate: true do
+describe UpdateRetriedForCiBuild, :delete do
let(:pipeline) { create(:ci_pipeline) }
let!(:build_old) { create(:ci_build, pipeline: pipeline, name: 'test') }
let!(:build_new) { create(:ci_build, pipeline: pipeline, name: 'test') }
diff --git a/spec/migrations/update_upload_paths_to_system_spec.rb b/spec/migrations/update_upload_paths_to_system_spec.rb
index d4a1553fb0e..984b428a020 100644
--- a/spec/migrations/update_upload_paths_to_system_spec.rb
+++ b/spec/migrations/update_upload_paths_to_system_spec.rb
@@ -1,53 +1,59 @@
-require "spec_helper"
-require Rails.root.join("db", "post_migrate", "20170317162059_update_upload_paths_to_system.rb")
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170317162059_update_upload_paths_to_system.rb')
-describe UpdateUploadPathsToSystem do
+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, model: create(:ci_pipeline), path: "uploads/ci_pipeline/avatar.jpg")
- _upload_with_system_path = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg")
- _upload_with_other_path = create(:upload, model: create(:project), path: "thelongsecretforafileupload/avatar.jpg")
- old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg")
- group_upload = create(:upload, model: create(:group), path: "uploads/group/avatar.jpg")
+ 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(Upload.where(migration.uploads_to_switch_to_new_path)).to contain_exactly(old_upload, group_upload)
+ 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, model: create(:ci_pipeline), path: "uploads/ci_pipeline/avatar.jpg")
- upload_with_system_path = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg")
- _upload_with_other_path = create(:upload, model: create(:project), path: "thelongsecretforafileupload/avatar.jpg")
- _old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg")
+ 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(Upload.where(migration.uploads_to_switch_to_old_path)).to contain_exactly(upload_with_system_path)
+ expect(uploads_table.where(migration.uploads_to_switch_to_old_path)).to contain_exactly(upload_with_system_path)
end
end
- describe "#up", :truncate do
- it "updates old upload records to the new path" do
- old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg")
+ 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")
+ expect(old_upload.reload.path).to eq('uploads/-/system/project/avatar.jpg')
end
end
- describe "#down", :truncate do
- it "updates the new system patsh to the old paths" do
- new_upload = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg")
+ 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")
+ 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/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index b39105abb41..559e6f12565 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -25,6 +25,13 @@ describe Ci::Build do
it { is_expected.to be_a(ArtifactMigratable) }
+ describe 'associations' do
+ it 'has a bidirectional relationship with projects' do
+ expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:builds)
+ expect(Project.reflect_on_association(:builds).has_inverse?).to eq(:project)
+ end
+ end
+
describe 'callbacks' do
context 'when running after_create callback' do
it 'triggers asynchronous build hooks worker' do
@@ -328,7 +335,7 @@ describe Ci::Build do
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
end
- it { is_expected.to be_an(Array).and all(include(key: "key:1")) }
+ it { is_expected.to be_an(Array).and all(include(key: "key_1")) }
end
context 'when project does not have jobs_cache_index' do
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 9a278212efc..8ee15f0e734 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -12,7 +12,6 @@ describe Ci::PipelineSchedule do
it { is_expected.to respond_to(:cron_timezone) }
it { is_expected.to respond_to(:description) }
it { is_expected.to respond_to(:next_run_at) }
- it { is_expected.to respond_to(:deleted_at) }
describe 'validations' do
it 'does not allow invalid cron patters' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 7bef798a782..14d234f6aab 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -28,6 +28,13 @@ describe Ci::Pipeline, :mailer do
it { is_expected.to respond_to :short_sha }
it { is_expected.to delegate_method(:full_path).to(:project).with_prefix }
+ describe 'associations' do
+ it 'has a bidirectional relationship with projects' do
+ expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:pipelines)
+ expect(Project.reflect_on_association(:pipelines).has_inverse?).to eq(:project)
+ end
+ end
+
describe '#source' do
context 'when creating new pipeline' do
let(:pipeline) do
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 38829773599..f2efcd9d0e9 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -151,11 +151,11 @@ describe CommitRange do
.with(commit1, user)
.and_return(true)
- expect(commit1.has_been_reverted?(user, issue)).to eq(true)
+ expect(commit1.has_been_reverted?(user, issue.notes_with_associations)).to eq(true)
end
- it 'returns false a commit has not been reverted' do
- expect(commit1.has_been_reverted?(user, issue)).to eq(false)
+ it 'returns false if the commit has not been reverted' do
+ expect(commit1.has_been_reverted?(user, issue.notes_with_associations)).to eq(false)
end
end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 817254c7d1e..959383ff0b7 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -228,7 +228,7 @@ eos
it { expect(data).to be_a(Hash) }
it { expect(data[:message]).to include('adds bar folder and branch-test text file to check Repository merged_to_root_ref method') }
it { expect(data[:timestamp]).to eq('2016-09-27T14:37:46Z') }
- it { expect(data[:added]).to eq(["bar/branch-test.txt"]) }
+ it { expect(data[:added]).to contain_exactly("bar/branch-test.txt") }
it { expect(data[:modified]).to eq([]) }
it { expect(data[:removed]).to eq([]) }
end
@@ -439,15 +439,25 @@ eos
end
describe '#uri_type' do
- 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(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw)
- expect(commit.uri_type('files/js/application.js')).to be(:blob)
+ shared_examples 'URI type' do
+ 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(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw)
+ expect(commit.uri_type('files/js/application.js')).to be(:blob)
+ end
+
+ it "returns nil if the path doesn't exists" do
+ expect(commit.uri_type('this/path/doesnt/exist')).to be_nil
+ end
+ end
+
+ context 'when Gitaly commit_tree_entry feature is enabled' do
+ it_behaves_like 'URI type'
end
- it "returns nil if the path doesn't exists" do
- expect(commit.uri_type('this/path/doesnt/exist')).to be_nil
+ context 'when Gitaly commit_tree_entry feature is disabled', :disable_gitaly do
+ it_behaves_like 'URI type'
end
end
@@ -513,4 +523,17 @@ eos
expect(described_class.valid_hash?('a' * 41)).to be false
end
end
+
+ describe '#merge_requests' do
+ let!(:project) { create(:project, :repository) }
+ let!(:merge_request1) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'feature') }
+ let!(:merge_request2) { create(:merge_request, source_project: project, source_branch: 'merged-target', target_branch: 'feature') }
+ let(:commit1) { merge_request1.merge_request_diff.commits.last }
+ let(:commit2) { merge_request1.merge_request_diff.commits.first }
+
+ it 'returns merge_requests that introduced that commit' do
+ expect(commit1.merge_requests).to contain_exactly(merge_request1, merge_request2)
+ expect(commit2.merge_requests).to contain_exactly(merge_request1)
+ end
+ end
end
diff --git a/spec/models/concerns/avatarable_spec.rb b/spec/models/concerns/avatarable_spec.rb
index cbdc438be0b..3696e6f62fd 100644
--- a/spec/models/concerns/avatarable_spec.rb
+++ b/spec/models/concerns/avatarable_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
describe Avatarable do
- subject { create(:project, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) }
+ set(:project) { create(:project, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) }
let(:gitlab_host) { "https://gitlab.example.com" }
let(:relative_url_root) { "/gitlab" }
- let(:asset_host) { "https://gitlab-assets.example.com" }
+ let(:asset_host) { 'https://gitlab-assets.example.com' }
before do
stub_config_setting(base_url: gitlab_host)
@@ -15,29 +15,32 @@ describe Avatarable do
describe '#avatar_path' do
using RSpec::Parameterized::TableSyntax
- where(:has_asset_host, :visibility_level, :only_path, :avatar_path) do
- true | Project::PRIVATE | true | [gitlab_host, relative_url_root, subject.avatar.url]
- true | Project::PRIVATE | false | [gitlab_host, relative_url_root, subject.avatar.url]
- true | Project::INTERNAL | true | [gitlab_host, relative_url_root, subject.avatar.url]
- true | Project::INTERNAL | false | [gitlab_host, relative_url_root, subject.avatar.url]
- true | Project::PUBLIC | true | [subject.avatar.url]
- true | Project::PUBLIC | false | [asset_host, subject.avatar.url]
- false | Project::PRIVATE | true | [relative_url_root, subject.avatar.url]
- false | Project::PRIVATE | false | [gitlab_host, relative_url_root, subject.avatar.url]
- false | Project::INTERNAL | true | [relative_url_root, subject.avatar.url]
- false | Project::INTERNAL | false | [gitlab_host, relative_url_root, subject.avatar.url]
- false | Project::PUBLIC | true | [relative_url_root, subject.avatar.url]
- false | Project::PUBLIC | false | [gitlab_host, relative_url_root, subject.avatar.url]
+ where(:has_asset_host, :visibility_level, :only_path, :avatar_path_prefix) do
+ true | Project::PRIVATE | true | [gitlab_host, relative_url_root]
+ true | Project::PRIVATE | false | [gitlab_host, relative_url_root]
+ true | Project::INTERNAL | true | [gitlab_host, relative_url_root]
+ true | Project::INTERNAL | false | [gitlab_host, relative_url_root]
+ true | Project::PUBLIC | true | []
+ true | Project::PUBLIC | false | [asset_host]
+ false | Project::PRIVATE | true | [relative_url_root]
+ false | Project::PRIVATE | false | [gitlab_host, relative_url_root]
+ false | Project::INTERNAL | true | [relative_url_root]
+ false | Project::INTERNAL | false | [gitlab_host, relative_url_root]
+ false | Project::PUBLIC | true | [relative_url_root]
+ false | Project::PUBLIC | false | [gitlab_host, relative_url_root]
end
with_them do
before do
- allow(ActionController::Base).to receive(:asset_host).and_return(has_asset_host ? asset_host : nil)
- subject.visibility_level = visibility_level
+ allow(ActionController::Base).to receive(:asset_host) { has_asset_host && asset_host }
+
+ project.visibility_level = visibility_level
end
+ let(:avatar_path) { (avatar_path_prefix + [project.avatar.url]).join }
+
it 'returns the expected avatar path' do
- expect(subject.avatar_path(only_path: only_path)).to eq(avatar_path.join)
+ expect(project.avatar_path(only_path: only_path)).to eq(avatar_path)
end
end
end
diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb
index 2322eb206fb..30572ce9332 100644
--- a/spec/models/concerns/discussion_on_diff_spec.rb
+++ b/spec/models/concerns/discussion_on_diff_spec.rb
@@ -20,6 +20,16 @@ describe DiscussionOnDiff do
expect(truncated_lines).not_to include(be_meta)
end
end
+
+ context "when the diff line does not exist on a legacy diff note" do
+ it "returns an empty array" do
+ legacy_note = LegacyDiffNote.new
+
+ allow(subject).to receive(:first_note).and_return(legacy_note)
+
+ expect(truncated_lines).to eq([])
+ end
+ end
end
describe '#line_code_in_diffs' do
diff --git a/spec/models/concerns/triggerable_hooks_spec.rb b/spec/models/concerns/triggerable_hooks_spec.rb
new file mode 100644
index 00000000000..621d2d38eae
--- /dev/null
+++ b/spec/models/concerns/triggerable_hooks_spec.rb
@@ -0,0 +1,43 @@
+require 'rails_helper'
+
+RSpec.describe TriggerableHooks do
+ before do
+ class TestableHook < WebHook
+ include TriggerableHooks
+ triggerable_hooks [:push_hooks]
+ end
+ end
+
+ describe 'scopes' do
+ it 'defines a scope for each of the requested triggers' do
+ expect(TestableHook).to respond_to :push_hooks
+ expect(TestableHook).not_to respond_to :tag_push_hooks
+ end
+ end
+
+ describe '.hooks_for' do
+ context 'the model has the required trigger scope' do
+ it 'returns the record' do
+ hook = TestableHook.create!(url: 'http://example.com', push_events: true)
+
+ expect(TestableHook.hooks_for(:push_hooks)).to eq [hook]
+ end
+ end
+
+ context 'the model does not have the required trigger scope' do
+ it 'returns an empty relation' do
+ TestableHook.create!(url: 'http://example.com')
+
+ expect(TestableHook.hooks_for(:tag_push_hooks)).to eq []
+ end
+ end
+
+ context 'the stock scope ".all" is accepted' do
+ it 'returns the record' do
+ hook = TestableHook.create!(url: 'http://example.com')
+
+ expect(TestableHook.hooks_for(:all)).to eq [hook]
+ end
+ end
+ end
+end
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 0345fefb254..fca3090ff4a 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -8,7 +8,7 @@ describe DeployKeysProject do
describe "Validation" do
it { is_expected.to validate_presence_of(:project_id) }
- it { is_expected.to validate_presence_of(:deploy_key_id) }
+ it { is_expected.to validate_presence_of(:deploy_key) }
end
describe "Destroying" do
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 0e965f541d8..8bc45715dcd 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -7,7 +7,8 @@ describe SystemHook do
it 'sets defined default parameters' do
attrs = {
push_events: false,
- repository_update_events: true
+ repository_update_events: true,
+ merge_requests_events: false
}
expect(system_hook).to have_attributes(attrs)
end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 388120160ab..ea6d6e53ef5 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -29,6 +29,12 @@ describe WebHook do
expect(hook.url).to eq('https://example.com')
end
end
+
+ describe 'token' do
+ it { is_expected.to allow_value("foobar").for(:token) }
+
+ it { is_expected.not_to allow_values("foo\nbar", "foo\r\nbar").for(:token) }
+ end
end
describe 'execute' do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 5ced000cdb6..f5c9f551e65 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -18,11 +18,6 @@ describe Issue do
subject { create(:issue) }
- describe "act_as_paranoid" do
- it { is_expected.to have_db_column(:deleted_at) }
- it { is_expected.to have_db_index(:deleted_at) }
- end
-
describe 'callbacks' do
describe '#ensure_metrics' do
it 'creates metrics after saving' do
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 6aa0e7f49c3..c64cdf8f812 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -488,7 +488,7 @@ describe Member do
member.accept_invite!(user)
end
- it "refreshes user's authorized projects", :truncate do
+ it "refreshes user's authorized projects", :delete do
project = member.source
expect(user.authorized_projects).not_to include(project)
@@ -523,7 +523,7 @@ describe Member do
end
end
- describe "destroying a record", :truncate do
+ describe "destroying a record", :delete do
it "refreshes user's authorized projects" do
project = create(:project, :private)
user = create(:user)
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index d556004eccf..b4249d72fc8 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -15,6 +15,28 @@ describe MergeRequestDiff do
it { expect(subject.start_commit_sha).to eq('0b4bc9a49b562e85de7cc9e834518ea6828729b9') }
end
+ describe '.by_commit_sha' do
+ subject(:by_commit_sha) { described_class.by_commit_sha(sha) }
+
+ let!(:merge_request) { create(:merge_request, :with_diffs) }
+
+ context 'with sha contained in' do
+ let(:sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
+
+ it 'returns merge request diffs' do
+ expect(by_commit_sha).to eq([merge_request.merge_request_diff])
+ end
+ end
+
+ context 'with sha not contained in' do
+ let(:sha) { 'b83d6e3' }
+
+ it 'returns empty result' do
+ expect(by_commit_sha).to be_empty
+ end
+ end
+ end
+
describe '#latest' do
let!(:mr) { create(:merge_request, :with_diffs) }
let!(:first_diff) { mr.merge_request_diff }
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 07b3e1c1758..243eeddc7a8 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -24,11 +24,6 @@ describe MergeRequest do
it { is_expected.to include_module(Taskable) }
end
- describe "act_as_paranoid" do
- it { is_expected.to have_db_column(:deleted_at) }
- it { is_expected.to have_db_index(:deleted_at) }
- end
-
describe 'validation' do
it { is_expected.to validate_presence_of(:target_branch) }
it { is_expected.to validate_presence_of(:source_branch) }
@@ -92,6 +87,39 @@ describe MergeRequest do
it { is_expected.to respond_to(:merge_when_pipeline_succeeds) }
end
+ describe '.by_commit_sha' do
+ subject(:by_commit_sha) { described_class.by_commit_sha(sha) }
+
+ let!(:merge_request) { create(:merge_request, :with_diffs) }
+
+ context 'with sha contained in latest merge request diff' do
+ let(:sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
+
+ it 'returns merge requests' do
+ expect(by_commit_sha).to eq([merge_request])
+ end
+ end
+
+ context 'with sha contained not in latest merge request diff' do
+ let(:sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
+
+ it 'returns empty requests' do
+ latest_merge_request_diff = merge_request.merge_request_diffs.create
+ latest_merge_request_diff.merge_request_diff_commits.where(sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0').delete_all
+
+ expect(by_commit_sha).to be_empty
+ end
+ end
+
+ context 'with sha not contained in' do
+ let(:sha) { 'b83d6e3' }
+
+ it 'returns empty result' do
+ expect(by_commit_sha).to be_empty
+ end
+ end
+ end
+
describe '.in_projects' do
it 'returns the merge requests for a set of projects' do
expect(described_class.in_projects(Project.all)).to eq([subject])
@@ -1035,6 +1063,103 @@ describe MergeRequest do
end
end
+ describe '#can_be_reverted?' do
+ context 'when there is no merge_commit for the MR' do
+ before do
+ subject.metrics.update!(merged_at: Time.now.utc)
+ end
+
+ it 'returns false' do
+ expect(subject.can_be_reverted?(nil)).to be_falsey
+ end
+ end
+
+ context 'when the MR has been merged' do
+ before do
+ MergeRequests::MergeService
+ .new(subject.target_project, subject.author)
+ .execute(subject)
+ end
+
+ context 'when there is no revert commit' do
+ it 'returns true' do
+ expect(subject.can_be_reverted?(nil)).to be_truthy
+ end
+ end
+
+ context 'when there is no merged_at for the MR' do
+ before do
+ subject.metrics.update!(merged_at: nil)
+ end
+
+ it 'returns true' do
+ expect(subject.can_be_reverted?(nil)).to be_truthy
+ end
+ end
+
+ context 'when there is a revert commit' do
+ let(:current_user) { subject.author }
+ let(:branch) { subject.target_branch }
+ let(:project) { subject.target_project }
+
+ let(:revert_commit_id) do
+ params = {
+ commit: subject.merge_commit,
+ branch_name: branch,
+ start_branch: branch
+ }
+
+ Commits::RevertService.new(project, current_user, params).execute[:result]
+ end
+
+ before do
+ project.add_master(current_user)
+
+ ProcessCommitWorker.new.perform(project.id,
+ current_user.id,
+ project.commit(revert_commit_id).to_hash,
+ project.default_branch == branch)
+ end
+
+ context 'when the revert commit is mentioned in a note after the MR was merged' do
+ it 'returns false' do
+ expect(subject.can_be_reverted?(current_user)).to be_falsey
+ end
+ end
+
+ context 'when there is no merged_at for the MR' do
+ before do
+ subject.metrics.update!(merged_at: nil)
+ end
+
+ it 'returns false' do
+ expect(subject.can_be_reverted?(current_user)).to be_falsey
+ end
+ end
+
+ context 'when the revert commit is mentioned in a note just before the MR was merged' do
+ before do
+ subject.notes.last.update!(created_at: subject.metrics.merged_at - 30.seconds)
+ end
+
+ it 'returns false' do
+ expect(subject.can_be_reverted?(current_user)).to be_falsey
+ end
+ end
+
+ context 'when the revert commit is mentioned in a note long before the MR was merged' do
+ before do
+ subject.notes.last.update!(created_at: subject.metrics.merged_at - 2.minutes)
+ end
+
+ it 'returns true' do
+ expect(subject.can_be_reverted?(current_user)).to be_truthy
+ end
+ end
+ end
+ end
+ end
+
describe '#participants' do
let(:project) { create(:project, :public) }
@@ -1214,6 +1339,10 @@ describe MergeRequest do
it 'returns false' do
expect(subject.mergeable_state?).to be_falsey
end
+
+ it 'returns true when skipping discussions check' do
+ expect(subject.mergeable_state?(skip_discussions_check: true)).to be(true)
+ end
end
end
end
@@ -1414,7 +1543,7 @@ describe MergeRequest do
expect { subject.reload_diff }.to change { subject.merge_request_diffs.count }.by(1)
end
- it "executs diff cache service" do
+ it "executes diff cache service" do
expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject)
subject.reload_diff
@@ -1915,38 +2044,44 @@ describe MergeRequest do
end
describe '#rebase_in_progress?' do
- # Create merge request and project before we stub file calls
- before do
- subject
- end
+ shared_examples 'checking whether a rebase is in progress' do
+ let(:repo_path) { subject.source_project.repository.path }
+ let(:rebase_path) { File.join(repo_path, "gitlab-worktree", "rebase-#{subject.id}") }
- it 'returns true when there is a current rebase directory' do
- allow(File).to receive(:exist?).and_return(true)
- allow(File).to receive(:mtime).and_return(Time.now)
+ before do
+ system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} worktree add --detach #{rebase_path} master))
+ end
- expect(subject.rebase_in_progress?).to be_truthy
- end
+ it 'returns true when there is a current rebase directory' do
+ expect(subject.rebase_in_progress?).to be_truthy
+ end
- it 'returns false when there is no rebase directory' do
- allow(File).to receive(:exist?).and_return(false)
+ it 'returns false when there is no rebase directory' do
+ FileUtils.rm_rf(rebase_path)
- expect(subject.rebase_in_progress?).to be_falsey
- end
+ expect(subject.rebase_in_progress?).to be_falsey
+ end
- it 'returns false when the rebase directory has expired' do
- allow(File).to receive(:exist?).and_return(true)
- allow(File).to receive(:mtime).and_return(20.minutes.ago)
+ it 'returns false when the rebase directory has expired' do
+ time = 20.minutes.ago.to_time
+ File.utime(time, time, rebase_path)
+
+ expect(subject.rebase_in_progress?).to be_falsey
+ end
- expect(subject.rebase_in_progress?).to be_falsey
+ 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
+ end
end
- it 'returns false when the source project has been removed' do
- allow(subject).to receive(:source_project).and_return(nil)
- allow(File).to receive(:exist?).and_return(true)
- allow(File).to receive(:mtime).and_return(Time.now)
+ context 'when Gitaly rebase_in_progress is enabled' do
+ it_behaves_like 'checking whether a rebase is in progress'
+ end
- expect(File).not_to have_received(:exist?)
- expect(subject.rebase_in_progress?).to be_falsey
+ context 'when Gitaly rebase_in_progress is enabled', :disable_gitaly do
+ it_behaves_like 'checking whether a rebase is in progress'
end
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 138b2a4935f..6b7dbad128c 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -410,17 +410,6 @@ describe Namespace do
end
end
- describe '#soft_delete_without_removing_associations' do
- let(:project1) { create(:project_empty_repo, namespace: namespace) }
-
- it 'updates the deleted_at timestamp but preserves projects' do
- namespace.soft_delete_without_removing_associations
-
- expect(Project.all).to include(project1)
- expect(namespace.deleted_at).not_to be_nil
- end
- end
-
describe '#user_ids_for_project_authorizations' do
it 'returns the user IDs for which to refresh authorizations' do
expect(namespace.user_ids_for_project_authorizations)
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index 7d835511dfb..9d12f96c642 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -68,7 +68,7 @@ describe PagesDomain do
subject { domain.url }
context 'without the certificate' do
- let(:domain) { build(:pages_domain) }
+ let(:domain) { build(:pages_domain, certificate: '') }
it { is_expected.to eq('http://my.domain.com') }
end
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index 41e2ab20d69..1fccf92627a 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -30,7 +30,7 @@ describe ProjectGroupLink do
end
end
- describe "destroying a record", :truncate do
+ describe "destroying a record", :delete do
it "refreshes group users' authorized projects" do
project = create(:project, :private)
group = create(:group)
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index c9b3c6cf602..748c366efca 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -3,6 +3,29 @@ require 'spec_helper'
describe JiraService do
include Gitlab::Routing
+ describe '#options' do
+ let(:service) do
+ described_class.new(
+ project: build_stubbed(:project),
+ active: true,
+ username: 'username',
+ password: 'test',
+ jira_issue_transition_id: 24,
+ url: 'http://jira.test.com/path/'
+ )
+ end
+
+ it 'sets the URL properly' do
+ # jira-ruby gem parses the URI and handles trailing slashes
+ # fine: https://github.com/sumoheavy/jira-ruby/blob/v1.4.1/lib/jira/http_client.rb#L59
+ expect(service.options[:site]).to eq('http://jira.test.com/')
+ end
+
+ it 'leaves out trailing slashes in context' do
+ expect(service.options[:context_path]).to eq('/path')
+ end
+ end
+
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -182,7 +205,7 @@ describe JiraService do
@jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
expect(WebMock).to have_requested(:post, @comment_url).with(
- body: /#{custom_base_url}\/#{project.full_path}\/commit\/#{merge_request.diff_head_sha}/
+ body: %r{#{custom_base_url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}}
).once
end
@@ -197,7 +220,7 @@ describe JiraService do
@jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
expect(WebMock).to have_requested(:post, @comment_url).with(
- body: /#{Gitlab.config.gitlab.url}\/#{project.full_path}\/commit\/#{merge_request.diff_head_sha}/
+ body: %r{#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}}
).once
end
diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb
index 6a5d0decfec..733086e258f 100644
--- a/spec/models/project_services/microsoft_teams_service_spec.rb
+++ b/spec/models/project_services/microsoft_teams_service_spec.rb
@@ -92,6 +92,10 @@ describe MicrosoftTeamsService do
service.hook_data(merge_request, 'open')
end
+ before do
+ project.add_developer(user)
+ end
+
it "calls Microsoft Teams API" do
chat_service.execute(merge_sample_data)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 00afa09f1a3..31dcb543cbd 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1871,9 +1871,8 @@ describe Project do
end
it 'creates the new reference with rugged' do
- expect(project.repository.rugged.references).to receive(:create).with('HEAD',
- "refs/heads/#{project.default_branch}",
- force: true)
+ expect(project.repository.raw_repository).to receive(:write_ref).with('HEAD', "refs/heads/#{project.default_branch}", shell: false)
+
project.change_head(project.default_branch)
end
@@ -1952,6 +1951,10 @@ describe Project do
expect(second_fork.fork_source).to eq(project)
end
+
+ it 'returns nil if it is the root of the fork network' do
+ expect(project.fork_source).to be_nil
+ end
end
describe '#lfs_storage_project' do
@@ -3207,4 +3210,40 @@ describe Project do
expect { project.write_repository_config }.not_to raise_error
end
end
+
+ describe '#execute_hooks' do
+ it 'executes the projects hooks with the specified scope' do
+ hook1 = create(:project_hook, merge_requests_events: true, tag_push_events: false)
+ hook2 = create(:project_hook, merge_requests_events: false, tag_push_events: true)
+ project = create(:project, hooks: [hook1, hook2])
+
+ expect_any_instance_of(ProjectHook).to receive(:async_execute).once
+
+ project.execute_hooks({}, :tag_push_hooks)
+ end
+
+ it 'executes the system hooks with the specified scope' do
+ expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with({ data: 'data' }, :merge_request_hooks)
+
+ project = build(:project)
+ project.execute_hooks({ data: 'data' }, :merge_request_hooks)
+ end
+
+ it 'executes the system hooks when inside a transaction' do
+ allow_any_instance_of(WebHookService).to receive(:execute)
+
+ create(:system_hook, merge_requests_events: true)
+
+ project = build(:project)
+
+ # Ideally, we'd test that `WebHookWorker.jobs.size` increased by 1,
+ # but since the entire spec run takes place in a transaction, we never
+ # actually get to the `after_commit` hook that queues these jobs.
+ expect do
+ project.transaction do
+ project.execute_hooks({ data: 'data' }, :merge_request_hooks)
+ end
+ end.not_to raise_error # Sidekiq::Worker::EnqueueFromTransactionError
+ end
+ end
end
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index e78ed1df821..5cff2af4aca 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -146,6 +146,12 @@ describe ProjectStatistics do
expect(statistics.build_artifacts_size).to be(106365)
end
+
+ it 'calculates related build artifacts by project' do
+ expect(Ci::JobArtifact).to receive(:artifacts_size_for).with(project) { 0 }
+
+ statistics.update_build_artifacts_size
+ end
end
context 'when legacy artifacts are used' do
diff --git a/spec/models/push_event_spec.rb b/spec/models/push_event_spec.rb
index ad3c3a406d9..bfe7a30b96a 100644
--- a/spec/models/push_event_spec.rb
+++ b/spec/models/push_event_spec.rb
@@ -63,12 +63,14 @@ describe PushEvent do
let(:event2) { create(:push_event, project: project) }
let(:event3) { create(:push_event, project: project) }
let(:event4) { create(:push_event, project: project) }
+ let(:event5) { create(:push_event, project: project) }
before do
create(:push_event_payload, event: event1, ref: 'foo', action: :created)
create(:push_event_payload, event: event2, ref: 'bar', action: :created)
- create(:push_event_payload, event: event3, ref: 'baz', action: :removed)
- create(:push_event_payload, event: event4, ref: 'baz', ref_type: :tag)
+ create(:push_event_payload, event: event3, ref: 'qux', action: :created)
+ create(:push_event_payload, event: event4, ref: 'baz', action: :removed)
+ create(:push_event_payload, event: event5, ref: 'baz', ref_type: :tag)
project.repository.create_branch('bar', 'master')
@@ -78,6 +80,16 @@ describe PushEvent do
target_project: project,
source_branch: 'bar'
)
+
+ project.repository.create_branch('qux', 'master')
+
+ create(
+ :merge_request,
+ :closed,
+ source_project: project,
+ target_project: project,
+ source_branch: 'qux'
+ )
end
let(:relation) { described_class.without_existing_merge_requests }
@@ -86,16 +98,20 @@ describe PushEvent do
expect(relation).to include(event1)
end
- it 'does not include events that have a corresponding merge request' do
+ it 'does not include events that have a corresponding open merge request' do
expect(relation).not_to include(event2)
end
+ it 'includes events that has corresponding closed/merged merge requests' do
+ expect(relation).to include(event3)
+ end
+
it 'does not include events for removed refs' do
- expect(relation).not_to include(event3)
+ expect(relation).not_to include(event4)
end
it 'does not include events for pushing to tags' do
- expect(relation).not_to include(event4)
+ expect(relation).not_to include(event5)
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index c0db2c1b386..1102b1c9006 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -222,20 +222,20 @@ describe Repository do
it 'sets follow when path is a single path' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: true)).and_call_original.twice
- repository.commits('master', path: 'README.md')
- repository.commits('master', path: ['README.md'])
+ repository.commits('master', limit: 1, path: 'README.md')
+ repository.commits('master', limit: 1, path: ['README.md'])
end
it 'does not set follow when path is multiple paths' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original
- repository.commits('master', path: ['README.md', 'CHANGELOG'])
+ repository.commits('master', limit: 1, path: ['README.md', 'CHANGELOG'])
end
it 'does not set follow when there are no paths' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original
- repository.commits('master')
+ repository.commits('master', limit: 1)
end
end
@@ -358,28 +358,44 @@ describe Repository do
end
describe '#can_be_merged?' do
- context 'mergeable branches' do
- subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') }
+ shared_examples 'can be merged' do
+ context 'mergeable branches' do
+ subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') }
- it { is_expected.to be_truthy }
- end
+ it { is_expected.to be_truthy }
+ end
- context 'non-mergeable branches' do
- subject { repository.can_be_merged?('bb5206fee213d983da88c47f9cf4cc6caf9c66dc', 'feature') }
+ context 'non-mergeable branches without conflict sides missing' do
+ subject { repository.can_be_merged?('bb5206fee213d983da88c47f9cf4cc6caf9c66dc', 'feature') }
- it { is_expected.to be_falsey }
- end
+ it { is_expected.to be_falsey }
+ end
- context 'non merged branch' do
- subject { repository.merged_to_root_ref?('fix') }
+ context 'non-mergeable branches with conflict sides missing' do
+ subject { repository.can_be_merged?('conflict-missing-side', 'conflict-start') }
- it { is_expected.to be_falsey }
+ it { is_expected.to be_falsey }
+ end
+
+ context 'non merged branch' do
+ subject { repository.merged_to_root_ref?('fix') }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'non existent branch' do
+ subject { repository.merged_to_root_ref?('non_existent_branch') }
+
+ it { is_expected.to be_nil }
+ end
end
- context 'non existent branch' do
- subject { repository.merged_to_root_ref?('non_existent_branch') }
+ context 'when Gitaly can_be_merged feature is enabled' do
+ it_behaves_like 'can be merged'
+ end
- it { is_expected.to be_nil }
+ context 'when Gitaly can_be_merged feature is disabled', :disable_gitaly do
+ it_behaves_like 'can be merged'
end
end
@@ -412,12 +428,34 @@ describe Repository do
end
end
+ describe '#create_hooks' do
+ let(:hook_path) { File.join(repository.path_to_repo, 'hooks') }
+
+ it 'symlinks the global hooks directory' do
+ repository.create_hooks
+
+ expect(File.symlink?(hook_path)).to be true
+ expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
+ end
+
+ it 'replaces existing symlink with the right directory' do
+ FileUtils.mkdir_p(hook_path)
+
+ expect(File.symlink?(hook_path)).to be false
+
+ repository.create_hooks
+
+ expect(File.symlink?(hook_path)).to be true
+ expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
+ end
+ end
+
describe "#create_dir" do
it "commits a change that creates a new directory" do
expect do
repository.create_dir(user, 'newdir',
message: 'Create newdir', branch_name: 'master')
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
newdir = repository.tree('master', 'newdir')
expect(newdir.path).to eq('newdir')
@@ -431,7 +469,7 @@ describe Repository do
repository.create_dir(user, 'newdir',
message: 'Create newdir', branch_name: 'patch',
start_branch_name: 'master', start_project: forked_project)
- end.to change { repository.commits('master').count }.by(0)
+ end.to change { repository.count_commits(ref: 'master') }.by(0)
expect(repository.branch_exists?('patch')).to be_truthy
expect(forked_project.repository.branch_exists?('patch')).to be_falsy
@@ -448,7 +486,7 @@ describe Repository do
message: 'Add newdir',
branch_name: 'master',
author_email: author_email, author_name: author_name)
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
last_commit = repository.commit
@@ -464,7 +502,7 @@ describe Repository do
repository.create_file(user, 'NEWCHANGELOG', 'Changelog!',
message: 'Create changelog',
branch_name: 'master')
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
blob = repository.blob_at('master', 'NEWCHANGELOG')
@@ -476,7 +514,7 @@ describe Repository do
repository.create_file(user, 'new_dir/new_file.txt', 'File!',
message: 'Create new_file with new_dir',
branch_name: 'master')
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
expect(repository.tree('master', 'new_dir').path).to eq('new_dir')
expect(repository.blob_at('master', 'new_dir/new_file.txt').data).to eq('File!')
@@ -500,7 +538,7 @@ describe Repository do
branch_name: 'master',
author_email: author_email,
author_name: author_name)
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
last_commit = repository.commit
@@ -516,7 +554,7 @@ describe Repository do
repository.update_file(user, 'CHANGELOG', 'Changelog!',
message: 'Update changelog',
branch_name: 'master')
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
blob = repository.blob_at('master', 'CHANGELOG')
@@ -529,7 +567,7 @@ describe Repository do
branch_name: 'master',
previous_path: 'LICENSE',
message: 'Changes filename')
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
files = repository.ls_files('master')
@@ -546,7 +584,7 @@ describe Repository do
message: 'Update README',
author_email: author_email,
author_name: author_name)
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
last_commit = repository.commit
@@ -561,7 +599,7 @@ describe Repository do
expect do
repository.delete_file(user, 'README',
message: 'Remove README', branch_name: 'master')
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
expect(repository.blob_at('master', 'README')).to be_nil
end
@@ -572,7 +610,7 @@ describe Repository do
repository.delete_file(user, 'README',
message: 'Remove README', branch_name: 'master',
author_email: author_email, author_name: author_name)
- end.to change { repository.commits('master').count }.by(1)
+ end.to change { repository.count_commits(ref: 'master') }.by(1)
last_commit = repository.commit
@@ -625,7 +663,7 @@ describe Repository do
subject { results.first }
it { is_expected.to be_an String }
- it { expect(subject.lines[2]).to eq("master:CHANGELOG:190: - Feature: Replace teams with group membership\n") }
+ it { expect(subject.lines[2]).to eq("master:CHANGELOG\x00190\x00 - Feature: Replace teams with group membership\n") }
end
end
@@ -636,6 +674,18 @@ describe Repository do
expect(results.first).to eq('files/html/500.html')
end
+ it 'ignores leading slashes' do
+ results = repository.search_files_by_name('/files', 'master')
+
+ expect(results.first).to eq('files/html/500.html')
+ end
+
+ it 'properly handles when query is only slashes' do
+ results = repository.search_files_by_name('//', 'master')
+
+ expect(results).to match_array([])
+ end
+
it 'properly handles when query is not present' do
results = repository.search_files_by_name('', 'master')
@@ -722,8 +772,7 @@ describe Repository do
user, 'LICENSE', 'Copyright!',
message: 'Add LICENSE', branch_name: 'master')
- allow(repository).to receive(:file_on_head)
- .and_raise(Rugged::ReferenceError)
+ allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository)
expect(repository.license_blob).to be_nil
end
@@ -835,7 +884,7 @@ describe Repository do
end
it 'returns nil for empty repository' do
- allow(repository).to receive(:file_on_head).and_raise(Rugged::ReferenceError)
+ allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository)
expect(repository.gitlab_ci_yml).to be_nil
end
end
@@ -1887,8 +1936,7 @@ describe Repository do
describe '#avatar' do
it 'returns nil if repo does not exist' do
- expect(repository).to receive(:file_on_head)
- .and_raise(Rugged::ReferenceError)
+ allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository)
expect(repository.avatar).to eq(nil)
end
@@ -2306,7 +2354,7 @@ describe Repository do
let(:commit) { repository.commit }
let(:ancestor) { commit.parents.first }
- context 'with Gitaly enabled' do
+ shared_examples '#ancestor?' do
it 'it is an ancestor' do
expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true)
end
@@ -2320,27 +2368,19 @@ describe Repository do
expect(repository.ancestor?(ancestor.id, nil)).to eq(false)
expect(repository.ancestor?(nil, nil)).to eq(false)
end
- end
- context 'with Gitaly disabled' do
- before do
- allow(Gitlab::GitalyClient).to receive(:enabled?).and_return(false)
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:is_ancestor).and_return(false)
- end
-
- it 'it is an ancestor' do
- expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true)
+ it 'returns false for invalid commit IDs' do
+ expect(repository.ancestor?(commit.id, Gitlab::Git::BLANK_SHA)).to eq(false)
+ expect(repository.ancestor?( Gitlab::Git::BLANK_SHA, commit.id)).to eq(false)
end
+ end
- it 'it is not an ancestor' do
- expect(repository.ancestor?(commit.id, ancestor.id)).to eq(false)
- end
+ context 'with Gitaly enabled' do
+ it_behaves_like('#ancestor?')
+ end
- it 'returns false on nil-values' do
- expect(repository.ancestor?(nil, commit.id)).to eq(false)
- expect(repository.ancestor?(ancestor.id, nil)).to eq(false)
- expect(repository.ancestor?(nil, nil)).to eq(false)
- end
+ context 'with Gitaly disabled', :skip_gitaly_mock do
+ it_behaves_like('#ancestor?')
end
end
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index ddad6862a63..88f54fd10e5 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -16,9 +16,76 @@ describe Route do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:path) }
it { is_expected.to validate_uniqueness_of(:path).case_insensitive }
+
+ describe '#ensure_permanent_paths' do
+ context 'when the route is not yet persisted' do
+ let(:new_route) { described_class.new(path: 'foo', source: build(:group)) }
+
+ context 'when permanent conflicting redirects exist' do
+ it 'is invalid' do
+ redirect = build(:redirect_route, :permanent, path: 'foo/bar/baz')
+ redirect.save!(validate: false)
+
+ expect(new_route.valid?).to be_falsey
+ expect(new_route.errors.first[1]).to eq('foo has been taken before. Please use another one')
+ end
+ end
+
+ context 'when no permanent conflicting redirects exist' do
+ it 'is valid' do
+ expect(new_route.valid?).to be_truthy
+ end
+ end
+ end
+
+ context 'when path has changed' do
+ before do
+ route.path = 'foo'
+ end
+
+ context 'when permanent conflicting redirects exist' do
+ it 'is invalid' do
+ redirect = build(:redirect_route, :permanent, path: 'foo/bar/baz')
+ redirect.save!(validate: false)
+
+ expect(route.valid?).to be_falsey
+ expect(route.errors.first[1]).to eq('foo has been taken before. Please use another one')
+ end
+ end
+
+ context 'when no permanent conflicting redirects exist' do
+ it 'is valid' do
+ expect(route.valid?).to be_truthy
+ end
+ end
+ end
+
+ context 'when path has not changed' do
+ context 'when permanent conflicting redirects exist' do
+ it 'is valid' do
+ redirect = build(:redirect_route, :permanent, path: 'git_lab/foo/bar')
+ redirect.save!(validate: false)
+
+ expect(route.valid?).to be_truthy
+ end
+ end
+ context 'when no permanent conflicting redirects exist' do
+ it 'is valid' do
+ expect(route.valid?).to be_truthy
+ end
+ end
+ end
+ end
end
describe 'callbacks' do
+ context 'before validation' do
+ it 'calls #delete_conflicting_orphaned_routes' do
+ expect(route).to receive(:delete_conflicting_orphaned_routes)
+ route.valid?
+ end
+ end
+
context 'after update' do
it 'calls #create_redirect_for_old_path' do
expect(route).to receive(:create_redirect_for_old_path)
@@ -318,4 +385,58 @@ describe Route do
end
end
end
+
+ describe '#delete_conflicting_orphaned_routes' do
+ context 'when there is a conflicting route' do
+ let!(:conflicting_group) { create(:group, path: 'foo') }
+
+ before do
+ route.path = conflicting_group.route.path
+ end
+
+ context 'when the route is orphaned' do
+ let!(:offending_route) { conflicting_group.route }
+
+ before do
+ Group.delete(conflicting_group) # Orphan the route
+ end
+
+ it 'deletes the orphaned route' do
+ expect do
+ route.valid?
+ end.to change { described_class.count }.from(2).to(1)
+ end
+
+ it 'passes validation, as usual' do
+ expect(route.valid?).to be_truthy
+ end
+ end
+
+ context 'when the route is not orphaned' do
+ it 'does not delete the conflicting route' do
+ expect do
+ route.valid?
+ end.not_to change { described_class.count }
+ end
+
+ it 'fails validation, as usual' do
+ expect(route.valid?).to be_falsey
+ end
+ end
+ end
+
+ context 'when there are no conflicting routes' do
+ it 'does not delete any routes' do
+ route
+
+ expect do
+ route.valid?
+ end.not_to change { described_class.count }
+ end
+
+ it 'passes validation, as usual' do
+ expect(route.valid?).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index ab6678cab38..79f25dc4360 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -280,4 +280,38 @@ describe Service do
expect(KubernetesService.find_by_template).to eq(kubernetes_service)
end
end
+
+ describe '#api_field_names' do
+ let(:fake_service) do
+ Class.new(Service) do
+ def fields
+ [
+ { name: 'token' },
+ { name: 'api_token' },
+ { name: 'key' },
+ { name: 'api_key' },
+ { name: 'password' },
+ { name: 'password_field' },
+ { name: 'safe_field' }
+ ]
+ end
+ end
+ end
+
+ let(:service) do
+ fake_service.new(properties: [
+ { token: 'token-value' },
+ { api_token: 'api_token-value' },
+ { key: 'key-value' },
+ { api_key: 'api_key-value' },
+ { password: 'password-value' },
+ { password_field: 'password_field-value' },
+ { safe_field: 'safe_field-value' }
+ ])
+ end
+
+ it 'filters out sensitive fields' do
+ expect(service.api_field_names).to eq(['safe_field'])
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 8d0eaf565a7..594f23718da 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -966,6 +966,14 @@ describe User do
expect(described_class.search(user3.username.upcase)).to eq([user3])
end
end
+
+ it 'returns no matches for an empty string' do
+ expect(described_class.search('')).to be_empty
+ end
+
+ it 'returns no matches for nil' do
+ expect(described_class.search(nil)).to be_empty
+ end
end
describe '.search_with_secondary_emails' do
@@ -1020,6 +1028,14 @@ describe User do
it 'does not return users with a matching part of secondary email' do
expect(search_with_secondary_emails(email.email[1..4])).not_to include([email.user])
end
+
+ it 'returns no matches for an empty string' do
+ expect(search_with_secondary_emails('')).to be_empty
+ end
+
+ it 'returns no matches for nil' do
+ expect(search_with_secondary_emails(nil)).to be_empty
+ end
end
describe '.find_by_ssh_key_id' do
@@ -1553,7 +1569,7 @@ describe User do
it { is_expected.to eq([private_group]) }
end
- describe '#authorized_projects', :truncate do
+ describe '#authorized_projects', :delete do
context 'with a minimum access level' do
it 'includes projects for which the user is an owner' do
user = create(:user)
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index ea75434e399..9840afe6c4e 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -386,6 +386,27 @@ describe WikiPage do
end
end
+ describe '#formatted_content' do
+ shared_examples 'fetching page formatted content' do
+ it 'returns processed content of the page' do
+ subject.create({ title: "RDoc", content: "*bold*", format: "rdoc" })
+ page = wiki.find_page('RDoc')
+
+ expect(page.formatted_content).to eq("\n<p><strong>bold</strong></p>\n")
+
+ destroy_page('RDoc')
+ end
+ end
+
+ context 'when Gitaly wiki_page_formatted_data is enabled' do
+ it_behaves_like 'fetching page formatted content'
+ end
+
+ context 'when Gitaly wiki_page_formatted_data is disabled', :disable_gitaly do
+ it_behaves_like 'fetching page formatted content'
+ end
+ end
+
private
def remove_temp_repo(path)
diff --git a/spec/policies/ci/pipeline_schedule_policy_spec.rb b/spec/policies/ci/pipeline_schedule_policy_spec.rb
index 1b0e9fac355..c0c3eda4911 100644
--- a/spec/policies/ci/pipeline_schedule_policy_spec.rb
+++ b/spec/policies/ci/pipeline_schedule_policy_spec.rb
@@ -88,5 +88,19 @@ describe Ci::PipelineSchedulePolicy, :models do
expect(policy).to be_allowed :admin_pipeline_schedule
end
end
+
+ describe 'rules for non-owner of schedule' do
+ let(:owner) { create(:user) }
+
+ before do
+ project.add_master(owner)
+ project.add_master(user)
+ pipeline_schedule.update(owner: owner)
+ end
+
+ it 'includes abilities to take ownership' do
+ expect(policy).to be_allowed :take_ownership_pipeline_schedule
+ end
+ end
end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index f2593a1a75c..129344f105f 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -92,7 +92,7 @@ describe ProjectPolicy do
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)
+ issue = create(:issue, project: project, author: create(:user))
user = issue.author
expect(project.team.member?(issue.author)).to be false
diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb
new file mode 100644
index 00000000000..f56bc932f40
--- /dev/null
+++ b/spec/requests/api/applications_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+
+describe API::Applications, :api do
+ include ApiHelpers
+
+ let(:admin_user) { create(:user, admin: true) }
+ let(:user) { create(:user, admin: false) }
+
+ describe 'POST /applications' do
+ context 'authenticated and authorized user' do
+ it 'creates and returns an OAuth application' do
+ expect do
+ post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://application.url', scopes: ''
+ end.to change { Doorkeeper::Application.count }.by 1
+
+ application = Doorkeeper::Application.find_by(name: 'application_name', redirect_uri: 'http://application.url')
+
+ expect(response).to have_http_status 201
+ expect(json_response).to be_a Hash
+ expect(json_response['application_id']).to eq application.uid
+ expect(json_response['secret']).to eq application.secret
+ expect(json_response['callback_url']).to eq application.redirect_uri
+ end
+
+ it 'does not allow creating an application with the wrong redirect_uri format' do
+ expect do
+ post api('/applications', admin_user), name: 'application_name', redirect_uri: 'wrong_url_format', scopes: ''
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_http_status 400
+ expect(json_response).to be_a Hash
+ expect(json_response['message']['redirect_uri'][0]).to eq('must be an absolute URI.')
+ end
+
+ it 'does not allow creating an application without a name' do
+ expect do
+ post api('/applications', admin_user), redirect_uri: 'http://application.url', scopes: ''
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_http_status 400
+ expect(json_response).to be_a Hash
+ expect(json_response['error']).to eq('name is missing')
+ end
+
+ it 'does not allow creating an application without a redirect_uri' do
+ expect do
+ post api('/applications', admin_user), name: 'application_name', scopes: ''
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_http_status 400
+ expect(json_response).to be_a Hash
+ expect(json_response['error']).to eq('redirect_uri is missing')
+ end
+
+ it 'does not allow creating an application without scopes' do
+ expect do
+ post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://application.url'
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_http_status 400
+ expect(json_response).to be_a Hash
+ expect(json_response['error']).to eq('scopes is missing')
+ end
+ end
+
+ context 'authorized user without authorization' do
+ it 'does not create application' do
+ expect do
+ post api('/applications', user), name: 'application_name', redirect_uri: 'http://application.url', scopes: ''
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_http_status 403
+ end
+ end
+
+ context 'non-authenticated user' do
+ it 'does not create application' do
+ expect do
+ post api('/applications'), name: 'application_name', redirect_uri: 'http://application.url'
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_http_status 401
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index ffa17d296e8..f246bb79ab7 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -142,6 +142,7 @@ describe API::CommitStatuses do
expect(json_response['ref']).not_to be_empty
expect(json_response['target_url']).to be_nil
expect(json_response['description']).to be_nil
+
if status == 'failed'
expect(CommitStatus.find(json_response['id'])).to be_api_failure
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 0d2bd3207c0..ff5f207487b 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -62,7 +62,7 @@ describe API::Commits do
context "since optional parameter" do
it "returns project commits since provided parameter" do
- commits = project.repository.commits("master")
+ commits = project.repository.commits("master", limit: 2)
after = commits.second.created_at
get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
@@ -73,7 +73,7 @@ describe API::Commits do
end
it 'include correct pagination headers' do
- commits = project.repository.commits("master")
+ commits = project.repository.commits("master", limit: 2)
after = commits.second.created_at
commit_count = project.repository.count_commits(ref: 'master', after: after).to_s
@@ -87,12 +87,12 @@ describe API::Commits do
context "until optional parameter" do
it "returns project commits until provided parameter" do
- commits = project.repository.commits("master")
+ commits = project.repository.commits("master", limit: 20)
before = commits.second.created_at
get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
- if commits.size >= 20
+ if commits.size == 20
expect(json_response.size).to eq(20)
else
expect(json_response.size).to eq(commits.size - 1)
@@ -103,7 +103,7 @@ describe API::Commits do
end
it 'include correct pagination headers' do
- commits = project.repository.commits("master")
+ commits = project.repository.commits("master", limit: 2)
before = commits.second.created_at
commit_count = project.repository.count_commits(ref: 'master', before: before).to_s
@@ -181,7 +181,7 @@ describe API::Commits do
let(:page) { 3 }
it 'returns the third 5 commits' do
- commit = project.repository.commits('HEAD', offset: (page - 1) * per_page).first
+ commit = project.repository.commits('HEAD', limit: per_page, offset: (page - 1) * per_page).first
expect(json_response.size).to eq(per_page)
expect(json_response.first['id']).to eq(commit.id)
@@ -512,6 +512,31 @@ describe API::Commits do
end
end
+ context 'when stat param' do
+ let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}" }
+
+ it 'is not present return stats by default' do
+ get api(route, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'stats'
+ end
+
+ it "is false it does not include stats" do
+ get api(route, user), stats: false
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).not_to include 'stats'
+ end
+
+ it "is true it includes stats" do
+ get api(route, user), stats: true
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'stats'
+ end
+ end
+
context 'when unauthenticated', 'and project is public' do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 1f1e6ea17e4..0772b3f2e64 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -110,7 +110,7 @@ describe API::DeployKeys do
end
it 'accepts can_push parameter' do
- key_attrs = attributes_for :write_access_key
+ key_attrs = attributes_for(:another_key).merge(can_push: true)
post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
@@ -160,16 +160,6 @@ describe API::DeployKeys do
expect(json_response['title']).to eq('new title')
expect(json_response['can_push']).to eq(true)
end
-
- it 'updates a private ssh key from projects user has access with correct attributes' do
- create(:deploy_keys_project, project: project2, deploy_key: private_deploy_key)
-
- put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), { title: 'new title', can_push: true }
-
- expect(json_response['id']).to eq(private_deploy_key.id)
- expect(json_response['title']).to eq('new title')
- expect(json_response['can_push']).to eq(true)
- end
end
describe 'DELETE /projects/:id/deploy_keys/:key_id' do
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index 6732c99e329..51b70fda148 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -3,24 +3,65 @@ require 'spec_helper'
describe API::Deployments do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
- let(:project) { deployment.environment.project }
- let!(:deployment) { create(:deployment) }
before do
project.add_master(user)
end
describe 'GET /projects/:id/deployments' do
+ let(:project) { create(:project) }
+ let!(:deployment_1) { create(:deployment, project: project, iid: 11, ref: 'master', created_at: Time.now) }
+ let!(:deployment_2) { create(:deployment, project: project, iid: 12, ref: 'feature', created_at: 1.day.ago) }
+ let!(:deployment_3) { create(:deployment, project: project, iid: 8, ref: 'feature', created_at: 2.days.ago) }
+
context 'as member of the project' do
- it 'returns projects deployments' do
+ it 'returns projects deployments sorted by id asc' do
get api("/projects/#{project.id}/deployments", 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.size).to eq(1)
- expect(json_response.first['iid']).to eq(deployment.iid)
+ expect(json_response.size).to eq(3)
+ expect(json_response.first['iid']).to eq(deployment_1.iid)
expect(json_response.first['sha']).to match /\A\h{40}\z/
+ expect(json_response.second['iid']).to eq(deployment_2.iid)
+ expect(json_response.last['iid']).to eq(deployment_3.iid)
+ end
+
+ describe 'ordering' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:order_by) { nil }
+ let(:sort) { nil }
+
+ subject { get api("/projects/#{project.id}/deployments?order_by=#{order_by}&sort=#{sort}", user) }
+
+ def expect_deployments(ordered_deployments)
+ json_response.each_with_index do |deployment_json, index|
+ expect(deployment_json['id']).to eq(public_send(ordered_deployments[index]).id)
+ end
+ end
+
+ before do
+ subject
+ end
+
+ where(:order_by, :sort, :ordered_deployments) do
+ 'created_at' | 'asc' | [:deployment_3, :deployment_2, :deployment_1]
+ 'created_at' | 'desc' | [:deployment_1, :deployment_2, :deployment_3]
+ 'id' | 'asc' | [:deployment_1, :deployment_2, :deployment_3]
+ 'id' | 'desc' | [:deployment_3, :deployment_2, :deployment_1]
+ 'iid' | 'asc' | [:deployment_3, :deployment_1, :deployment_2]
+ 'iid' | 'desc' | [:deployment_2, :deployment_1, :deployment_3]
+ 'ref' | 'asc' | [:deployment_2, :deployment_3, :deployment_1]
+ 'ref' | 'desc' | [:deployment_1, :deployment_2, :deployment_3]
+ end
+
+ with_them do
+ it 'returns the deployments ordered' do
+ expect_deployments(ordered_deployments)
+ end
+ end
end
end
@@ -34,6 +75,9 @@ describe API::Deployments do
end
describe 'GET /projects/:id/deployments/:deployment_id' do
+ let(:project) { deployment.environment.project }
+ let!(:deployment) { create(:deployment) }
+
context 'as a member of the project' do
it 'returns the projects deployment' do
get api("/projects/#{project.id}/deployments/#{deployment.id}", user)
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 0462f494e15..837389451e8 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -68,6 +68,12 @@ describe API::Helpers do
end
it { is_expected.to eq(user) }
+
+ it 'sets the environment with data of the current user' do
+ subject
+
+ expect(env[API::Helpers::API_USER_ENV]).to eq({ user_id: subject.id, username: subject.username })
+ end
end
context "HEAD request" do
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 7b25047ea8f..884a258fd12 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -192,6 +192,54 @@ describe API::Internal do
end
end
+ describe "GET /internal/authorized_keys" do
+ context "using an existing key's fingerprint" do
+ it "finds the key" do
+ get(api('/internal/authorized_keys'), fingerprint: key.fingerprint, secret_token: secret_token)
+
+ expect(response.status).to eq(200)
+ expect(json_response["key"]).to eq(key.key)
+ end
+ end
+
+ context "non existing key's fingerprint" do
+ it "returns 404" do
+ get(api('/internal/authorized_keys'), fingerprint: "no:t-:va:li:d0", secret_token: secret_token)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context "using a partial fingerprint" do
+ it "returns 404" do
+ get(api('/internal/authorized_keys'), fingerprint: "#{key.fingerprint[0..5]}%", secret_token: secret_token)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context "sending the key" do
+ it "finds the key" do
+ get(api('/internal/authorized_keys'), key: key.key.split[1], secret_token: secret_token)
+
+ expect(response.status).to eq(200)
+ expect(json_response["key"]).to eq(key.key)
+ end
+
+ it "returns 404 with a partial key" do
+ get(api('/internal/authorized_keys'), key: key.key.split[1][0...-3], secret_token: secret_token)
+
+ expect(response.status).to eq(404)
+ end
+
+ it "returns 404 with an not valid base64 string" do
+ get(api('/internal/authorized_keys'), key: "whatever!", secret_token: secret_token)
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+
describe "POST /internal/allowed", :clean_gitlab_redis_shared_state do
context "access granted" do
around do |example|
@@ -269,35 +317,20 @@ describe API::Internal do
end
context "git pull" do
- context "gitaly disabled", :disable_gitaly do
- it "has the correct payload" do
- pull(key, project)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
- expect(json_response["gl_repository"]).to eq("project-#{project.id}")
- expect(json_response["gitaly"]).to be_nil
- expect(user).to have_an_activity_record
- end
- end
-
- context "gitaly enabled" do
- it "has the correct payload" do
- pull(key, project)
+ it "has the correct payload" do
+ pull(key, project)
- expect(response).to have_gitlab_http_status(200)
- expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
- expect(json_response["gl_repository"]).to eq("project-#{project.id}")
- expect(json_response["gitaly"]).not_to be_nil
- expect(json_response["gitaly"]["repository"]).not_to be_nil
- expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name)
- expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
- expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
- expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
- expect(user).to have_an_activity_record
- end
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response["status"]).to be_truthy
+ expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
+ expect(json_response["gl_repository"]).to eq("project-#{project.id}")
+ expect(json_response["gitaly"]).not_to be_nil
+ expect(json_response["gitaly"]["repository"]).not_to be_nil
+ expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name)
+ expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
+ expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
+ expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
+ expect(user).to have_an_activity_record
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 320217f2032..13db40d21a5 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -847,6 +847,15 @@ describe API::Issues, :mailer do
expect(json_response['assignee']['name']).to eq(user2.name)
expect(json_response['assignees'].first['name']).to eq(user2.name)
end
+
+ it 'creates a new project issue when assignee_id is empty' do
+ post api("/projects/#{project.id}/issues", user),
+ title: 'new issue', assignee_id: ''
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq('new issue')
+ expect(json_response['assignee']).to be_nil
+ end
end
context 'single assignee restrictions' do
@@ -1432,7 +1441,7 @@ describe API::Issues, :mailer do
context 'when source project does not exist' do
it 'returns 404 when trying to move an issue' do
- post api("/projects/123/issues/#{issue.iid}/move", user),
+ post api("/projects/12345/issues/#{issue.iid}/move", user),
to_project_id: target_project.id
expect(response).to have_gitlab_http_status(404)
@@ -1443,7 +1452,7 @@ describe API::Issues, :mailer do
context 'when target project does not exist' do
it 'returns 404 when trying to move an issue' do
post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
- to_project_id: 123
+ to_project_id: 12345
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 366bfeb4d72..983bef6cdd0 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -31,9 +31,10 @@ describe API::Jobs do
describe 'GET /projects/:id/jobs' do
let(:query) { Hash.new }
- before do
- job
- get api("/projects/#{project.id}/jobs", api_user), query
+ before do |example|
+ unless example.metadata[:skip_before_request]
+ get api("/projects/#{project.id}/jobs", api_user), query
+ end
end
context 'authorized user' do
@@ -58,6 +59,23 @@ describe API::Jobs do
expect(json_job['pipeline']['status']).to eq job.pipeline.status
end
+ it 'avoids N+1 queries', :skip_before_request do
+ first_build = create(:ci_build, :artifacts, pipeline: pipeline)
+ first_build.runner = create(:ci_runner)
+ first_build.user = create(:user)
+ first_build.save
+
+ control_count = ActiveRecord::QueryRecorder.new { go }.count
+
+ second_pipeline = create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch)
+ second_build = create(:ci_build, :artifacts, pipeline: second_pipeline)
+ second_build.runner = create(:ci_runner)
+ second_build.user = create(:user)
+ second_build.save
+
+ expect { go }.not_to exceed_query_limit(control_count)
+ end
+
context 'filter project with one scope element' do
let(:query) { { 'scope' => 'pending' } }
@@ -90,6 +108,10 @@ describe API::Jobs do
expect(response).to have_gitlab_http_status(401)
end
end
+
+ def go
+ get api("/projects/#{project.id}/jobs", api_user), query
+ end
end
describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do
@@ -336,37 +358,11 @@ describe API::Jobs do
end
end
end
-
- context 'authorized by job_token' do
- let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
-
- before do
- get api("/projects/#{project.id}/jobs/#{job.id}/artifacts"), job_token: job.token
- end
-
- context 'user is developer' do
- let(:api_user) { user }
-
- it_behaves_like 'downloads artifact'
- end
-
- context 'when artifacts are stored remotely' do
- let(:job) { create(:ci_build, :artifacts, :remote_store, pipeline: pipeline) }
-
- it 'returns location redirect' do
- expect(response).to have_http_status(302)
- end
- end
- end
-
- it 'does not return job artifacts if not uploaded' do
- expect(response).to have_gitlab_http_status(404)
- end
end
describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
let(:api_user) { reporter }
- let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
before do
stub_artifacts_object_storage(licensed: :skip)
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 5d4f81e07a6..ec500838eb2 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -44,6 +44,21 @@ describe API::Members do
end
end
+ it 'avoids N+1 queries' do
+ # Establish baseline
+ get api("/#{source_type.pluralize}/#{source.id}/members", master)
+
+ control = ActiveRecord::QueryRecorder.new do
+ get api("/#{source_type.pluralize}/#{source.id}/members", master)
+ end
+
+ project.add_developer(create(:user))
+
+ expect do
+ get api("/#{source_type.pluralize}/#{source.id}/members", master)
+ end.not_to exceed_query_limit(control)
+ end
+
it 'does not return invitees' do
create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil)
@@ -65,6 +80,16 @@ describe API::Members do
expect(json_response.count).to eq(1)
expect(json_response.first['username']).to eq(master.username)
end
+
+ it 'finds all members with no query specified' do
+ get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: ''
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(2)
+ expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
+ end
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 0c9fbb1f187..14dd9da119d 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -198,6 +198,8 @@ describe API::MergeRequests do
create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time)
+ create(:merge_request, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time)
+
expect do
get api("/projects/#{project.id}/merge_requests", user)
end.not_to exceed_query_limit(control)
@@ -551,6 +553,49 @@ describe API::MergeRequests do
end
end
+ describe 'GET /projects/:id/merge_requests/:merge_request_iid/pipelines' do
+ context 'when authorized' do
+ let!(:pipeline) { create(:ci_empty_pipeline, project: project, user: user, ref: merge_request.source_branch, sha: merge_request.diff_head_sha) }
+ let!(:pipeline2) { create(:ci_empty_pipeline, project: project) }
+
+ it 'returns a paginated array of corresponding pipelines' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines")
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['id']).to eq(pipeline.id)
+ end
+
+ it 'exposes basic attributes' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines")
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/pipelines')
+ end
+
+ it 'returns 404 if MR does not exist' do
+ get api("/projects/#{project.id}/merge_requests/777/pipelines")
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when unauthorized' do
+ it 'returns 403' do
+ project = create(:project, public_builds: false)
+ merge_request = create(:merge_request, :simple, source_project: project)
+ guest = create(:user)
+ project.add_guest(guest)
+
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines", guest)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+
describe "POST /projects/:id/merge_requests" do
context 'between branches projects' do
it "returns merge_request" do
@@ -711,16 +756,28 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(400)
end
- context 'when target_branch is specified' do
+ context 'when target_branch and target_project_id is specified' do
+ let(:params) do
+ { title: 'Test merge_request',
+ target_branch: 'master',
+ source_branch: 'markdown',
+ author: user2,
+ target_project_id: unrelated_project.id }
+ end
+
it 'returns 422 if targeting a different fork' do
- post api("/projects/#{forked_project.id}/merge_requests", user2),
- title: 'Test merge_request',
- target_branch: 'master',
- source_branch: 'markdown',
- author: user2,
- target_project_id: unrelated_project.id
+ unrelated_project.add_developer(user2)
+
+ post api("/projects/#{forked_project.id}/merge_requests", user2), params
+
expect(response).to have_gitlab_http_status(422)
end
+
+ it 'returns 403 if targeting a different fork which user can not access' do
+ post api("/projects/#{forked_project.id}/merge_requests", user2), params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
end
it "returns 201 when target_branch is specified and for the same project" do
diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb
index 08ea7314bb3..6c05c166bd6 100644
--- a/spec/requests/api/project_milestones_spec.rb
+++ b/spec/requests/api/project_milestones_spec.rb
@@ -14,6 +14,46 @@ describe API::ProjectMilestones do
let(:route) { "/projects/#{project.id}/milestones" }
end
+ describe 'DELETE /projects/:id/milestones/:milestone_id' do
+ let(:guest) { create(:user) }
+ let(:reporter) { create(:user) }
+
+ before do
+ project.add_reporter(reporter)
+ end
+
+ it 'returns 404 response when the project does not exists' do
+ delete api("/projects/999/milestones/#{milestone.id}", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 response when the milestone does not exists' do
+ delete api("/projects/#{project.id}/milestones/999", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it "returns 404 from guest user deleting a milestone" do
+ delete api("/projects/#{project.id}/milestones/#{milestone.id}", guest)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it "rejects a member with reporter access from deleting a milestone" do
+ delete api("/projects/#{project.id}/milestones/#{milestone.id}", reporter)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ it 'deletes the milestone when the user has developer access to the project' do
+ delete api("/projects/#{project.id}/milestones/#{milestone.id}", user)
+
+ expect(project.milestones.find_by_id(milestone.id)).to be_nil
+ expect(response).to have_gitlab_http_status(204)
+ end
+ end
+
describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do
it 'creates an activity event when an milestone is closed' do
expect(Event).to receive(:create!)
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index e741ac4b7bd..4a2289ca137 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -1,9 +1,9 @@
require 'rails_helper'
describe API::ProjectSnippets do
- let(:project) { create(:project, :public) }
- let(:user) { create(:user) }
- let(:admin) { create(:admin) }
+ set(:project) { create(:project, :public) }
+ set(:user) { create(:user) }
+ set(:admin) { create(:admin) }
describe "GET /projects/:project_id/snippets/:id/user_agent_detail" do
let(:snippet) { create(:project_snippet, :public, project: project) }
@@ -18,6 +18,13 @@ describe API::ProjectSnippets do
expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted)
end
+ it 'respects project scoping' do
+ other_project = create(:project)
+
+ get api("/projects/#{other_project.id}/snippets/#{snippet.id}/user_agent_detail", admin)
+ expect(response).to have_gitlab_http_status(404)
+ end
+
it "returns unautorized for non-admin users" do
get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/user_agent_detail", user)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index de1763015fa..97e7ffcd38e 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -150,6 +150,19 @@ describe API::Projects do
expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
end
+ context 'and with_issues_enabled=true' do
+ it 'only returns projects with issues enabled' do
+ project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
+
+ get api('/projects?with_issues_enabled=true', user)
+
+ expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).not_to include(project.id)
+ end
+ end
+
it "does not include statistics by default" do
get api('/projects', user)
@@ -352,6 +365,19 @@ describe API::Projects do
let(:current_user) { user2 }
let(:projects) { [public_project] }
end
+
+ context 'and with_issues_enabled=true' do
+ it 'does not return private issue projects' do
+ project.project_feature.update_attribute(:issues_access_level, ProjectFeature::PRIVATE)
+
+ get api('/projects?with_issues_enabled=true', user2)
+
+ expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).not_to include(project.id)
+ end
+ end
end
context 'when authenticated as admin' do
diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb
index 10e6a3c07c8..1d23e023bb6 100644
--- a/spec/requests/api/protected_branches_spec.rb
+++ b/spec/requests/api/protected_branches_spec.rb
@@ -80,6 +80,12 @@ describe API::ProtectedBranches do
it_behaves_like 'protected branch'
end
+
+ context 'when protected branch contains a period' do
+ let(:protected_name) { 'my.feature' }
+
+ it_behaves_like 'protected branch'
+ end
end
context 'when authenticated as a guest' do
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index d68bba0082c..42631499ca2 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -1150,6 +1150,7 @@ describe API::Runner do
else
{ 'file' => file }
end
+
post api("/jobs/#{job.id}/artifacts"), params, headers
end
end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 26d56c04862..236f8d7faf5 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -83,14 +83,14 @@ describe API::Services do
get api("/projects/#{project.id}/services/#{dashed_service}", admin)
expect(response).to have_gitlab_http_status(200)
- expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list.map)
+ expect(json_response['properties'].keys).to match_array(service_instance.api_field_names)
end
it "returns properties of service #{service} other than passwords when authenticated as project owner" do
get api("/projects/#{project.id}/services/#{dashed_service}", user)
expect(response).to have_gitlab_http_status(200)
- expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list_without_passwords)
+ expect(json_response['properties'].keys).to match_array(service_instance.api_field_names)
end
it "returns error when authenticated but not a project owner" do
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index c7a009e999e..6c57d443cbf 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -36,6 +36,7 @@ describe API::SystemHooks do
expect(json_response.first['url']).to eq(hook.url)
expect(json_response.first['push_events']).to be false
expect(json_response.first['tag_push_events']).to be false
+ expect(json_response.first['merge_requests_events']).to be false
expect(json_response.first['repository_update_events']).to be true
end
end
@@ -67,11 +68,28 @@ describe API::SystemHooks do
end
it 'sets default values for events' do
- post api('/hooks', admin), url: 'http://mep.mep', enable_ssl_verification: true
+ post api('/hooks', admin), url: 'http://mep.mep'
expect(response).to have_gitlab_http_status(201)
expect(json_response['enable_ssl_verification']).to be true
+ expect(json_response['push_events']).to be false
expect(json_response['tag_push_events']).to be false
+ expect(json_response['merge_requests_events']).to be false
+ end
+
+ it 'sets explicit values for events' do
+ post api('/hooks', admin),
+ url: 'http://mep.mep',
+ enable_ssl_verification: false,
+ push_events: true,
+ tag_push_events: true,
+ merge_requests_events: true
+
+ expect(response).to have_http_status(201)
+ expect(json_response['enable_ssl_verification']).to be false
+ expect(json_response['push_events']).to be true
+ expect(json_response['tag_push_events']).to be true
+ expect(json_response['merge_requests_events']).to be true
end
end
diff --git a/spec/requests/api/v3/builds_spec.rb b/spec/requests/api/v3/builds_spec.rb
index c6094c5ca5c..00f067889a0 100644
--- a/spec/requests/api/v3/builds_spec.rb
+++ b/spec/requests/api/v3/builds_spec.rb
@@ -4,20 +4,23 @@ describe API::V3::Builds do
set(:user) { create(:user) }
let(:api_user) { user }
set(:project) { create(:project, :repository, creator: user, public_builds: false) }
- set(:developer) { create(:project_member, :developer, user: user, project: project) }
- set(:reporter) { create(:project_member, :reporter, project: project) }
- set(:guest) { create(:project_member, :guest, project: project) }
- set(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
- let!(:build) { create(:ci_build, pipeline: pipeline) }
+ let!(:developer) { create(:project_member, :developer, user: user, project: project) }
+ let(:reporter) { create(:project_member, :reporter, project: project) }
+ let(:guest) { create(:project_member, :guest, project: project) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
describe 'GET /projects/:id/builds ' do
let(:query) { '' }
- before do
+ before do |example|
build
+
create(:ci_build, :skipped, pipeline: pipeline)
- get v3_api("/projects/#{project.id}/builds?#{query}", api_user)
+ unless example.metadata[:skip_before_request]
+ get v3_api("/projects/#{project.id}/builds?#{query}", api_user)
+ end
end
context 'authorized user' do
@@ -41,6 +44,23 @@ describe API::V3::Builds do
expect(json_build['pipeline']['status']).to eq build.pipeline.status
end
+ it 'avoids N+1 queries', :skip_before_request do
+ first_build = create(:ci_build, :artifacts, pipeline: pipeline)
+ first_build.runner = create(:ci_runner)
+ first_build.user = create(:user)
+ first_build.save
+
+ control_count = ActiveRecord::QueryRecorder.new { go }.count
+
+ second_pipeline = create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch)
+ second_build = create(:ci_build, :artifacts, pipeline: second_pipeline)
+ second_build.runner = create(:ci_runner)
+ second_build.user = create(:user)
+ second_build.save
+
+ expect { go }.not_to exceed_query_limit(control_count)
+ end
+
context 'filter project with one scope element' do
let(:query) { 'scope=pending' }
@@ -85,6 +105,10 @@ describe API::V3::Builds do
expect(response).to have_gitlab_http_status(401)
end
end
+
+ def go
+ get v3_api("/projects/#{project.id}/builds?#{query}", api_user)
+ end
end
describe 'GET /projects/:id/repository/commits/:sha/builds' do
diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb
index 8b115e01f47..9ef3b859001 100644
--- a/spec/requests/api/v3/commits_spec.rb
+++ b/spec/requests/api/v3/commits_spec.rb
@@ -36,7 +36,7 @@ describe API::V3::Commits do
context "since optional parameter" do
it "returns project commits since provided parameter" do
- commits = project.repository.commits("master")
+ commits = project.repository.commits("master", limit: 2)
since = commits.second.created_at
get v3_api("/projects/#{project.id}/repository/commits?since=#{since.utc.iso8601}", user)
@@ -49,12 +49,12 @@ describe API::V3::Commits do
context "until optional parameter" do
it "returns project commits until provided parameter" do
- commits = project.repository.commits("master")
+ commits = project.repository.commits("master", limit: 20)
before = commits.second.created_at
get v3_api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user)
- if commits.size >= 20
+ if commits.size == 20
expect(json_response.size).to eq(20)
else
expect(json_response.size).to eq(commits.size - 1)
@@ -403,6 +403,33 @@ describe API::V3::Commits do
expect(response).to have_gitlab_http_status(200)
expect(json_response['status']).to eq("created")
end
+
+ context 'when stat param' do
+ let(:project_id) { project.id }
+ let(:commit_id) { project.repository.commit.id }
+ let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}" }
+
+ it 'is not present return stats by default' do
+ get v3_api(route, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'stats'
+ end
+
+ it "is false it does not include stats" do
+ get v3_api(route, user), stats: false
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).not_to include 'stats'
+ end
+
+ it "is true it includes stats" do
+ get v3_api(route, user), stats: true
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'stats'
+ end
+ end
end
context "unauthorized user" do
diff --git a/spec/requests/api/v3/deploy_keys_spec.rb b/spec/requests/api/v3/deploy_keys_spec.rb
index 785bc1eb4ba..501af587ad4 100644
--- a/spec/requests/api/v3/deploy_keys_spec.rb
+++ b/spec/requests/api/v3/deploy_keys_spec.rb
@@ -107,7 +107,7 @@ describe API::V3::DeployKeys do
end
it 'accepts can_push parameter' do
- key_attrs = attributes_for :write_access_key
+ key_attrs = attributes_for(:another_key).merge(can_push: true)
post v3_api("/projects/#{project.id}/#{path}", admin), key_attrs
diff --git a/spec/requests/api/v3/members_spec.rb b/spec/requests/api/v3/members_spec.rb
index b91782ae511..de4339ecb8b 100644
--- a/spec/requests/api/v3/members_spec.rb
+++ b/spec/requests/api/v3/members_spec.rb
@@ -58,6 +58,16 @@ describe API::V3::Members do
expect(json_response.count).to eq(1)
expect(json_response.first['username']).to eq(master.username)
end
+
+ it 'finds all members with no query specified' do
+ get v3_api("/#{source_type.pluralize}/#{source.id}/members", developer), query: ''
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(2)
+ expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
+ end
end
end
diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb
index b8b7d9d1c40..6b748369f0d 100644
--- a/spec/requests/api/v3/merge_requests_spec.rb
+++ b/spec/requests/api/v3/merge_requests_spec.rb
@@ -371,16 +371,28 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(400)
end
- context 'when target_branch is specified' do
+ context 'when target_branch and target_project_id is specified' do
+ let(:params) do
+ { title: 'Test merge_request',
+ target_branch: 'master',
+ source_branch: 'markdown',
+ author: user2,
+ target_project_id: unrelated_project.id }
+ end
+
it 'returns 422 if targeting a different fork' do
- post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
- title: 'Test merge_request',
- target_branch: 'master',
- source_branch: 'markdown',
- author: user2,
- target_project_id: unrelated_project.id
+ unrelated_project.add_developer(user2)
+
+ post v3_api("/projects/#{forked_project.id}/merge_requests", user2), params
+
expect(response).to have_gitlab_http_status(422)
end
+
+ it 'returns 403 if targeting a different fork which user can not access' do
+ post v3_api("/projects/#{forked_project.id}/merge_requests", user2), params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
end
it "returns 201 when target_branch is specified and for the same project" do
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 0a8788fd57e..e26fe39ce7f 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -798,11 +798,11 @@ describe 'Git LFS API and storage' do
end
context 'when deploy key has project push access' do
- let(:key) { create(:deploy_key, can_push: true) }
+ let(:key) { create(:deploy_key) }
let(:authorization) { authorize_deploy_key }
let(:update_user_permissions) do
- project.deploy_keys << key
+ project.deploy_keys_projects.create(deploy_key: key, can_push: true)
end
it_behaves_like 'pushes new LFS objects'
diff --git a/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb b/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb
new file mode 100644
index 00000000000..21fc4584654
--- /dev/null
+++ b/spec/rubocop/cop/gitlab/predicate_memoization_spec.rb
@@ -0,0 +1,100 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/gitlab/predicate_memoization'
+
+describe RuboCop::Cop::Gitlab::PredicateMemoization do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ shared_examples('registering offense') do |options|
+ let(:offending_lines) { options[:offending_lines] }
+
+ it 'registers an offense when a predicate method is memoizing via ivar' do
+ inspect_source(source)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(offending_lines.size)
+ expect(cop.offenses.map(&:line)).to eq(offending_lines)
+ end
+ end
+ end
+
+ shared_examples('not registering offense') do
+ it 'does not register offenses' do
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when source is a predicate method memoizing via ivar' do
+ it_behaves_like 'registering offense', offending_lines: [3] do
+ let(:source) do
+ <<~RUBY
+ class C
+ def really?
+ @really ||= true
+ end
+ end
+ RUBY
+ end
+ end
+
+ it_behaves_like 'registering offense', offending_lines: [4] do
+ let(:source) do
+ <<~RUBY
+ class C
+ def really?
+ value = true
+ @really ||= value
+ end
+ end
+ RUBY
+ end
+ end
+ end
+
+ context 'when source is a predicate method using ivar with assignment' do
+ it_behaves_like 'not registering offense' do
+ let(:source) do
+ <<~RUBY
+ class C
+ def really?
+ @really = true
+ end
+ end
+ RUBY
+ end
+ end
+ end
+
+ context 'when source is a predicate method using local with ||=' do
+ it_behaves_like 'not registering offense' do
+ let(:source) do
+ <<~RUBY
+ class C
+ def really?
+ really ||= true
+ end
+ end
+ RUBY
+ end
+ end
+ end
+
+ context 'when source is a regular method memoizing via ivar' do
+ it_behaves_like 'not registering offense' do
+ let(:source) do
+ <<~RUBY
+ class C
+ def really
+ @really ||= true
+ end
+ end
+ RUBY
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/line_break_around_conditional_block_spec.rb b/spec/rubocop/cop/line_break_around_conditional_block_spec.rb
new file mode 100644
index 00000000000..7ddf9141fcd
--- /dev/null
+++ b/spec/rubocop/cop/line_break_around_conditional_block_spec.rb
@@ -0,0 +1,411 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/line_break_around_conditional_block'
+
+describe RuboCop::Cop::LineBreakAroundConditionalBlock do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ shared_examples 'examples with conditional' do |conditional|
+ it "flags violation for #{conditional} without line break before" do
+ source = <<~RUBY
+ do_something
+ #{conditional} condition
+ do_something_more
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses.size).to eq(1)
+ offense = cop.offenses.first
+
+ expect(offense.line).to eq(2)
+ expect(cop.highlights).to eq(["#{conditional} condition\n do_something_more\nend"])
+ expect(offense.message).to eq('Add a line break around conditional blocks')
+ end
+
+ it "flags violation for #{conditional} without line break after" do
+ source = <<~RUBY
+ #{conditional} condition
+ do_something
+ end
+ do_something_more
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses.size).to eq(1)
+ offense = cop.offenses.first
+
+ expect(offense.line).to eq(1)
+ expect(cop.highlights).to eq(["#{conditional} condition\n do_something\nend"])
+ expect(offense.message).to eq('Add a line break around conditional blocks')
+ end
+
+ it "doesn't flag violation for #{conditional} with line break before and after" do
+ source = <<~RUBY
+ #{conditional} condition
+ do_something
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by a method definition" do
+ source = <<~RUBY
+ def a_method
+ #{conditional} condition
+ do_something
+ end
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by a class definition" do
+ source = <<~RUBY
+ class Foo
+ #{conditional} condition
+ do_something
+ end
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by a module definition" do
+ source = <<~RUBY
+ module Foo
+ #{conditional} condition
+ do_something
+ end
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by a begin definition" do
+ source = <<~RUBY
+ begin
+ #{conditional} condition
+ do_something
+ end
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by an assign/begin definition" do
+ source = <<~RUBY
+ @project ||= begin
+ #{conditional} condition
+ do_something
+ end
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by a block definition" do
+ source = <<~RUBY
+ on_block(param_a) do |item|
+ #{conditional} condition
+ do_something
+ end
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by a block definition using brackets" do
+ source = <<~RUBY
+ on_block(param_a) { |item|
+ #{conditional} condition
+ do_something
+ end
+ }
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by a comment" do
+ source = <<~RUBY
+ # a short comment
+ #{conditional} condition
+ do_something
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by an assignment" do
+ source = <<~RUBY
+ foo =
+ #{conditional} condition
+ do_something
+ else
+ do_something_more
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by a multiline comment" do
+ source = <<~RUBY
+ =begin
+ a multiline comment
+ =end
+ #{conditional} condition
+ do_something
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by another conditional" do
+ source = <<~RUBY
+ #{conditional} condition_a
+ #{conditional} condition_b
+ do_something
+ end
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by an else" do
+ source = <<~RUBY
+ if condition_a
+ do_something
+ else
+ #{conditional} condition_b
+ do_something_extra
+ end
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by an elsif" do
+ source = <<~RUBY
+ if condition_a
+ do_something
+ elsif condition_b
+ #{conditional} condition_c
+ do_something_extra
+ end
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by an ensure" do
+ source = <<~RUBY
+ def a_method
+ ensure
+ #{conditional} condition_c
+ do_something_extra
+ end
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} preceded by a when" do
+ source = <<~RUBY
+ case field
+ when value
+ #{conditional} condition_c
+ do_something_extra
+ end
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} followed by an end" do
+ source = <<~RUBY
+ class Foo
+
+ #{conditional} condition
+ do_something
+ end
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} followed by an else" do
+ source = <<~RUBY
+ #{conditional} condition_a
+ #{conditional} condition_b
+ do_something
+ end
+ else
+ do_something_extra
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} followed by a when" do
+ source = <<~RUBY
+ case
+ when condition_a
+ #{conditional} condition_b
+ do_something
+ end
+ when condition_c
+ do_something_extra
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} followed by an elsif" do
+ source = <<~RUBY
+ if condition_a
+ #{conditional} condition_b
+ do_something
+ end
+ elsif condition_c
+ do_something_extra
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "doesn't flag violation for #{conditional} followed by a rescue" do
+ source = <<~RUBY
+ def a_method
+ #{conditional} condition
+ do_something
+ end
+ rescue
+ do_something_extra
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it "autocorrects #{conditional} without line break before" do
+ source = <<~RUBY
+ do_something
+ #{conditional} condition
+ do_something_more
+ end
+ RUBY
+ autocorrected = autocorrect_source(source)
+
+ expected_source = <<~RUBY
+ do_something
+
+ #{conditional} condition
+ do_something_more
+ end
+ RUBY
+ expect(autocorrected).to eql(expected_source)
+ end
+
+ it "autocorrects #{conditional} without line break after" do
+ source = <<~RUBY
+ #{conditional} condition
+ do_something
+ end
+ do_something_more
+ RUBY
+ autocorrected = autocorrect_source(source)
+
+ expected_source = <<~RUBY
+ #{conditional} condition
+ do_something
+ end
+
+ do_something_more
+ RUBY
+ expect(autocorrected).to eql(expected_source)
+ end
+
+ it "autocorrects #{conditional} without line break before and after" do
+ source = <<~RUBY
+ do_something
+ #{conditional} condition
+ do_something_more
+ end
+ do_something_extra
+ RUBY
+ autocorrected = autocorrect_source(source)
+
+ expected_source = <<~RUBY
+ do_something
+
+ #{conditional} condition
+ do_something_more
+ end
+
+ do_something_extra
+ RUBY
+ expect(autocorrected).to eql(expected_source)
+ end
+ end
+
+ %w[if unless].each do |example|
+ it_behaves_like 'examples with conditional', example
+ end
+
+ it "doesn't flag violation for if with elsif" do
+ source = <<~RUBY
+ if condition
+ do_something
+ elsif another_condition
+ do_something_more
+ end
+ RUBY
+ inspect_source(source)
+
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/serializers/deploy_key_entity_spec.rb b/spec/serializers/deploy_key_entity_spec.rb
index d3aefa2c9eb..2bd8162d1b7 100644
--- a/spec/serializers/deploy_key_entity_spec.rb
+++ b/spec/serializers/deploy_key_entity_spec.rb
@@ -21,18 +21,21 @@ describe DeployKeyEntity do
user_id: deploy_key.user_id,
title: deploy_key.title,
fingerprint: deploy_key.fingerprint,
- can_push: deploy_key.can_push,
destroyed_when_orphaned: true,
almost_orphaned: false,
created_at: deploy_key.created_at,
updated_at: deploy_key.updated_at,
can_edit: false,
- projects: [
+ deploy_keys_projects: [
{
- id: project.id,
- name: project.name,
- full_path: project_path(project),
- full_name: project.full_name
+ can_push: false,
+ project:
+ {
+ id: project.id,
+ name: project.name,
+ full_path: project_path(project),
+ full_name: project.full_name
+ }
}
]
}
diff --git a/spec/services/check_gcp_project_billing_service_spec.rb b/spec/services/check_gcp_project_billing_service_spec.rb
index f0e39ba6f49..3e68d906e71 100644
--- a/spec/services/check_gcp_project_billing_service_spec.rb
+++ b/spec/services/check_gcp_project_billing_service_spec.rb
@@ -1,29 +1,30 @@
require 'spec_helper'
describe CheckGcpProjectBillingService do
+ include GoogleApi::CloudPlatformHelpers
+
let(:service) { described_class.new }
- let(:projects) { [double(name: 'first_project'), double(name: 'second_project')] }
+ let(:project_id) { 'test-project-1234' }
describe '#execute' do
before do
- expect_any_instance_of(GoogleApi::CloudPlatform::Client)
- .to receive(:projects_list).and_return(projects)
-
- allow_any_instance_of(GoogleApi::CloudPlatform::Client)
- .to receive_message_chain(:projects_get_billing_info, :billingEnabled)
- .and_return(project_billing_enabled)
+ stub_cloud_platform_projects_list(project_id: project_id)
end
subject { service.execute('bogustoken') }
context 'google account has a billing enabled gcp project' do
- let(:project_billing_enabled) { true }
+ before do
+ stub_cloud_platform_projects_get_billing_info(project_id, true)
+ end
- it { is_expected.to eq(projects) }
+ it { is_expected.to all(satisfy { |project| project.project_id == project_id }) }
end
context 'google account does not have a billing enabled gcp project' do
- let(:project_billing_enabled) { false }
+ before do
+ stub_cloud_platform_projects_get_billing_info(project_id, false)
+ end
it { is_expected.to eq([]) }
end
diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb
index 43b0c9a63a9..16bfbdf3089 100644
--- a/spec/services/files/update_service_spec.rb
+++ b/spec/services/files/update_service_spec.rb
@@ -72,13 +72,15 @@ describe Files::UpdateService do
end
end
- context 'when target branch is different than source branch' do
- let(:branch_name) { "#{project.default_branch}-new" }
+ context 'with gitaly disabled', :skip_gitaly_mock do
+ context 'when target branch is different than source branch' do
+ let(:branch_name) { "#{project.default_branch}-new" }
- it 'fires hooks only once' do
- expect(Gitlab::Git::HooksService).to receive(:new).once.and_call_original
+ it 'fires hooks only once' do
+ expect(Gitlab::Git::HooksService).to receive(:new).once.and_call_original
- subject.execute
+ subject.execute
+ end
end
end
end
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 8897a64a138..47c1ebbeb81 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -4,7 +4,7 @@ describe Issues::CloseService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
- let(:issue) { create(:issue, assignees: [user2]) }
+ let(:issue) { create(:issue, assignees: [user2], author: create(:user)) }
let(:project) { issue.project }
let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index dfe9adbbcdc..e91faea00b7 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -179,13 +179,15 @@ describe Issues::MoveService do
{ system: true, note: 'Some system note' },
{ system: false, note: 'Some comment 2' }]
end
-
+ let(:award_names) { %w(thumbsup thumbsdown facepalm) }
let(:notes_contents) { notes_params.map { |n| n[:note] } }
before do
note_params = { noteable: old_issue, project: old_project, author: author }
- notes_params.each do |note|
- create(:note, note_params.merge(note))
+ notes_params.each_with_index do |note, index|
+ new_note = create(:note, note_params.merge(note))
+ award_emoji_params = { awardable: new_note, name: award_names[index] }
+ create(:award_emoji, award_emoji_params)
end
end
@@ -199,6 +201,10 @@ describe Issues::MoveService do
expect(all_notes.pluck(:note).first(3)).to eq notes_contents
end
+ it 'creates new emojis for the new notes' do
+ expect(all_notes.map(&:award_emoji).to_a.flatten.map(&:name)).to eq award_names
+ end
+
it 'adds a system note about move after rewritten notes' do
expect(system_notes.last.note).to match /^moved from/
end
@@ -291,9 +297,11 @@ describe Issues::MoveService do
end
context 'project issue hooks' do
- let(:hook) { create(:project_hook, project: old_project, issues_events: true) }
+ let!(:hook) { create(:project_hook, project: old_project, issues_events: true) }
it 'executes project issue hooks' do
+ allow_any_instance_of(WebHookService).to receive(:execute)
+
# Ideally, we'd test that `WebHookWorker.jobs.size` increased by 1,
# but since the entire spec run takes place in a transaction, we never
# actually get to the `after_commit` hook that queues these jobs.
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 1cb6f2e097f..41237dd7160 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -13,7 +13,8 @@ describe Issues::UpdateService, :mailer do
create(:issue, title: 'Old title',
description: "for #{user2.to_reference}",
assignee_ids: [user3.id],
- project: project)
+ project: project,
+ author: create(:user))
end
before do
diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb
index 8809b282127..aa9aba6bdff 100644
--- a/spec/services/labels/promote_service_spec.rb
+++ b/spec/services/labels/promote_service_spec.rb
@@ -85,6 +85,19 @@ describe Labels::PromoteService do
change(project_3.labels, :count).by(-1)
end
+ it 'keeps users\' subscriptions' do
+ user2 = create(:user)
+ project_label_1_1.subscriptions.create(user: user, subscribed: true)
+ project_label_2_1.subscriptions.create(user: user, subscribed: true)
+ project_label_3_2.subscriptions.create(user: user, subscribed: true)
+ project_label_2_1.subscriptions.create(user: user2, subscribed: true)
+
+ expect { service.execute(project_label_1_1) }.to change { Subscription.count }.from(4).to(3)
+
+ expect(new_label.subscribed?(user)).to be_truthy
+ expect(new_label.subscribed?(user2)).to be_truthy
+ end
+
it 'recreates priorities' do
service.execute(project_label_1_1)
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index a9605c6e4c6..cb4c3e72aa0 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -171,6 +171,24 @@ describe MergeRequests::BuildService do
end
end
end
+
+ context 'branch starts with external issue IID followed by a hyphen' do
+ let(:source_branch) { '12345-fix-issue' }
+
+ before do
+ allow(project).to receive(:external_issue_tracker).and_return(true)
+ end
+
+ it 'uses the title of the commit as the title of the merge request' do
+ expect(merge_request.title).to eq(commit_1.safe_message.split("\n").first)
+ end
+
+ it 'uses the description of the commit as the description of the merge request and appends the closes text' do
+ commit_description = commit_1.safe_message.split(/\n+/, 2).last
+
+ expect(merge_request.description).to eq("#{commit_description}\n\nCloses #12345")
+ end
+ end
end
context 'more than one commit in the diff' do
@@ -241,8 +259,12 @@ describe MergeRequests::BuildService do
allow(project).to receive(:external_issue_tracker).and_return(true)
end
- it 'sets the title to: Resolves External Issue $issue-iid' do
- expect(merge_request.title).to eq('Resolve External Issue 12345')
+ it 'sets the title to the humanized branch title' do
+ expect(merge_request.title).to eq('12345 fix issue')
+ end
+
+ it 'appends the closes text' do
+ expect(merge_request.description).to eq('Closes #12345')
end
end
end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 4d12de3ecce..216e0cd4266 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -4,7 +4,7 @@ describe MergeRequests::CloseService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
- let(:merge_request) { create(:merge_request, assignee: user2) }
+ let(:merge_request) { create(:merge_request, assignee: user2, author: create(:user)) }
let(:project) { merge_request.project }
let!(:todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) }
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 623b182b205..75553afc033 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -112,5 +112,24 @@ describe MergeRequests::CreateFromIssueService do
expect(result[:merge_request].assignee).to eq(user)
end
+
+ context 'when ref branch is set' do
+ subject { described_class.new(project, user, issue_iid: issue.iid, ref: 'feature').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
+
+ it 'sets the merge request target branch to the ref branch' do
+ expect(subject[:merge_request].target_branch).to eq('feature')
+ end
+
+ context 'when ref branch does not exist' do
+ it 'does not create a merge request' do
+ expect { described_class.new(project, user, issue_iid: issue.iid, ref: 'nobr').execute }
+ .not_to change { project.merge_requests.count }
+ end
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index dd8c803a2f7..5d226f34d2d 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -263,5 +263,66 @@ describe MergeRequests::CreateService do
expect(issue_ids).to match_array([first_issue.id, second_issue.id])
end
end
+
+ context 'when source and target projects are different' do
+ let(:target_project) { create(:project) }
+
+ let(:opts) do
+ {
+ title: 'Awesome merge_request',
+ source_branch: 'feature',
+ target_branch: 'master',
+ target_project_id: target_project.id
+ }
+ end
+
+ context 'when user can not access source project' do
+ before do
+ target_project.add_developer(assignee)
+ target_project.add_master(user)
+ end
+
+ it 'raises an error' do
+ expect { described_class.new(project, user, opts).execute }
+ .to raise_error Gitlab::Access::AccessDeniedError
+ end
+ end
+
+ context 'when user can not access target project' do
+ before do
+ target_project.add_developer(assignee)
+ target_project.add_master(user)
+ end
+
+ it 'raises an error' do
+ expect { described_class.new(project, user, opts).execute }
+ .to raise_error Gitlab::Access::AccessDeniedError
+ end
+ end
+ end
+
+ context 'when user sets source project id' do
+ let(:another_project) { create(:project) }
+
+ let(:opts) do
+ {
+ title: 'Awesome merge_request',
+ source_branch: 'feature',
+ target_branch: 'master',
+ source_project_id: another_project.id
+ }
+ end
+
+ before do
+ project.add_developer(assignee)
+ project.add_master(user)
+ end
+
+ it 'ignores source_project_id' do
+ merge_request = described_class.new(project, user, opts).execute
+
+ expect(merge_request.source_project_id).to eq(project.id)
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb
index aa90feeef89..5ef6365fcc9 100644
--- a/spec/services/merge_requests/ff_merge_service_spec.rb
+++ b/spec/services/merge_requests/ff_merge_service_spec.rb
@@ -7,7 +7,8 @@ describe MergeRequests::FfMergeService do
create(:merge_request,
source_branch: 'flatten-dir',
target_branch: 'improve/awesome',
- assignee: user2)
+ assignee: user2,
+ author: create(:user))
end
let(:project) { merge_request.project }
diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
index f17db70faf6..240aa638f79 100644
--- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
@@ -43,7 +43,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
it 'creates a system note' do
note = merge_request.notes.last
- expect(note.note).to match /enabled an automatic merge when the pipeline for (\w+\/\w+@)?\h{8}/
+ expect(note.note).to match %r{enabled an automatic merge when the pipeline for (\w+/\w+@)?\h{8}}
end
end
diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb
index d1b37cdd073..fc1c3d67203 100644
--- a/spec/services/merge_requests/rebase_service_spec.rb
+++ b/spec/services/merge_requests/rebase_service_spec.rb
@@ -32,70 +32,80 @@ describe MergeRequests::RebaseService do
it 'returns an error' do
expect(service.execute(merge_request)).to match(status: :error,
- message: 'Failed to rebase. Should be done manually')
+ message: described_class::REBASE_ERROR)
end
end
- context 'when unexpected error occurs' do
+ context 'when unexpected error occurs', :disable_gitaly do
before do
allow(repository).to receive(:run_git!).and_raise('Something went wrong')
end
- it 'saves the error message' do
+ it 'saves a generic error message' do
subject.execute(merge_request)
- expect(merge_request.reload.merge_error).to eq 'Something went wrong'
+ expect(merge_request.reload.merge_error).to eq described_class::REBASE_ERROR
end
it 'returns an error' do
expect(service.execute(merge_request)).to match(status: :error,
- message: 'Failed to rebase. Should be done manually')
+ message: described_class::REBASE_ERROR)
end
end
- context 'with git command failure' do
+ context 'with git command failure', :disable_gitaly do
before do
allow(repository).to receive(:run_git!).and_raise(Gitlab::Git::Repository::GitError, 'Something went wrong')
end
- it 'saves the error message' do
+ it 'saves a generic error message' do
subject.execute(merge_request)
- expect(merge_request.reload.merge_error).to eq 'Something went wrong'
+ expect(merge_request.reload.merge_error).to eq described_class::REBASE_ERROR
end
it 'returns an error' do
expect(service.execute(merge_request)).to match(status: :error,
- message: 'Failed to rebase. Should be done manually')
+ message: described_class::REBASE_ERROR)
end
end
context 'valid params' do
- before do
- service.execute(merge_request)
- end
+ shared_examples 'successful rebase' do
+ before do
+ service.execute(merge_request)
+ end
- it 'rebases source branch' do
- parent_sha = merge_request.source_project.repository.commit(merge_request.source_branch).parents.first.sha
- target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha
- expect(parent_sha).to eq(target_branch_sha)
- end
+ it 'rebases source branch' do
+ parent_sha = merge_request.source_project.repository.commit(merge_request.source_branch).parents.first.sha
+ target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha
+ expect(parent_sha).to eq(target_branch_sha)
+ end
+
+ it 'records the new SHA on the merge request' do
+ head_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha
+ expect(merge_request.reload.rebase_commit_sha).to eq(head_sha)
+ end
+
+ it 'logs correct author and commiter' do
+ head_commit = merge_request.source_project.repository.commit(merge_request.source_branch)
- it 'records the new SHA on the merge request' do
- head_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha
- expect(merge_request.reload.rebase_commit_sha).to eq(head_sha)
+ expect(head_commit.author_email).to eq('dmitriy.zaporozhets@gmail.com')
+ expect(head_commit.author_name).to eq('Dmitriy Zaporozhets')
+ expect(head_commit.committer_email).to eq(user.email)
+ expect(head_commit.committer_name).to eq(user.name)
+ end
end
- it 'logs correct author and commiter' do
- head_commit = merge_request.source_project.repository.commit(merge_request.source_branch)
+ context 'when Gitaly rebase feature is enabled' do
+ it_behaves_like 'successful rebase'
+ end
- expect(head_commit.author_email).to eq('dmitriy.zaporozhets@gmail.com')
- expect(head_commit.author_name).to eq('Dmitriy Zaporozhets')
- expect(head_commit.committer_email).to eq(user.email)
- expect(head_commit.committer_name).to eq(user.name)
+ context 'when Gitaly rebase feature is disabled', :disable_gitaly do
+ it_behaves_like 'successful rebase'
end
- context 'git commands' do
+ context 'git commands', :disable_gitaly do
it 'sets GL_REPOSITORY env variable when calling git commands' do
expect(repository).to receive(:popen).exactly(3)
.with(anything, anything, hash_including('GL_REPOSITORY'))
@@ -106,27 +116,37 @@ describe MergeRequests::RebaseService do
end
context 'fork' do
- let(:forked_project) do
- fork_project(project, user, repository: true)
+ shared_examples 'successful fork rebase' do
+ let(:forked_project) do
+ fork_project(project, user, repository: true)
+ end
+
+ let(:merge_request_from_fork) do
+ forked_project.repository.create_file(
+ user,
+ 'new-file-to-target',
+ '',
+ message: 'Add new file to target',
+ branch_name: 'master')
+
+ create(:merge_request,
+ source_branch: 'master', source_project: forked_project,
+ target_branch: 'master', target_project: project)
+ end
+
+ it 'rebases source branch' do
+ parent_sha = forked_project.repository.commit(merge_request_from_fork.source_branch).parents.first.sha
+ target_branch_sha = project.repository.commit(merge_request_from_fork.target_branch).sha
+ expect(parent_sha).to eq(target_branch_sha)
+ end
end
- let(:merge_request_from_fork) do
- forked_project.repository.create_file(
- user,
- 'new-file-to-target',
- '',
- message: 'Add new file to target',
- branch_name: 'master')
-
- create(:merge_request,
- source_branch: 'master', source_project: forked_project,
- target_branch: 'master', target_project: project)
+ context 'when Gitaly rebase feature is enabled' do
+ it_behaves_like 'successful fork rebase'
end
- it 'rebases source branch' do
- parent_sha = forked_project.repository.commit(merge_request_from_fork.source_branch).parents.first.sha
- target_branch_sha = project.repository.commit(merge_request_from_fork.target_branch).sha
- expect(parent_sha).to eq(target_branch_sha)
+ context 'when Gitaly rebase feature is disabled', :disable_gitaly do
+ it_behaves_like 'successful fork rebase'
end
end
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 7a01d3dd698..7c3374c6113 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -55,11 +55,12 @@ describe MergeRequests::RefreshService do
before do
allow(refresh_service).to receive(:execute_hooks)
- refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
- reload_mrs
end
it 'executes hooks with update action' do
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+
expect(refresh_service).to have_received(:execute_hooks)
.with(@merge_request, 'update', old_rev: @oldrev)
@@ -72,6 +73,26 @@ describe MergeRequests::RefreshService do
expect(@build_failed_todo).to be_done
expect(@fork_build_failed_todo).to be_done
end
+
+ context 'when source branch ref does not exists' do
+ before do
+ DeleteBranchService.new(@project, @user).execute(@merge_request.source_branch)
+ end
+
+ it 'closes MRs without source branch ref' do
+ expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') }
+ .to change { @merge_request.reload.state }
+ .from('opened')
+ .to('closed')
+
+ expect(@fork_merge_request.reload).to be_open
+ end
+
+ it 'does not change the merge request diff' do
+ expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') }
+ .not_to change { @merge_request.reload.merge_request_diff }
+ end
+ end
end
context 'when pipeline exists for the source branch' do
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index a44d63e5f9f..9ee37c51d95 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -4,7 +4,7 @@ describe MergeRequests::ReopenService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
- let(:merge_request) { create(:merge_request, :closed, assignee: user2) }
+ let(:merge_request) { create(:merge_request, :closed, assignee: user2, author: create(:user)) }
let(:project) { merge_request.project }
before do
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 2238da2d14d..c31259239ee 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -12,7 +12,8 @@ describe MergeRequests::UpdateService, :mailer do
create(:merge_request, :simple, title: 'Old title',
description: "FYI #{user2.to_reference}",
assignee_id: user3.id,
- source_project: project)
+ source_project: project,
+ author: create(:user))
end
before do
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 43e2643f709..35eb84e5e88 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe NotificationService, :mailer do
+ include EmailSpec::Matchers
+
let(:notification) { described_class.new }
let(:assignee) { create(:user) }
@@ -31,6 +33,14 @@ describe NotificationService, :mailer do
send_notifications(@u_disabled)
should_not_email_anyone
end
+
+ it 'sends the proper notification reason header' do
+ send_notifications(@u_watcher)
+ should_only_email(@u_watcher)
+ email = find_email_for(@u_watcher)
+
+ expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::MENTIONED)
+ end
end
# Next shared examples are intended to test notifications of "participants"
@@ -448,7 +458,7 @@ describe NotificationService, :mailer do
context "merge request diff note" do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request, source_project: project, assignee: user) }
+ let(:merge_request) { create(:merge_request, source_project: project, assignee: user, author: create(:user)) }
let(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) }
before do
@@ -459,11 +469,13 @@ describe NotificationService, :mailer do
describe '#new_note' do
it "records sent notifications" do
- # Ensure create SentNotification by noteable = merge_request 6 times, not noteable = note
+ # 3 SentNotification are sent: the MR assignee and author, and the @u_watcher
expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(3).times.and_call_original
notification.new_note(note)
+ expect(SentNotification.last(3).map(&:recipient).map(&:id))
+ .to contain_exactly(merge_request.assignee.id, merge_request.author.id, @u_watcher.id)
expect(SentNotification.last.in_reply_to_discussion_id).to eq(note.discussion_id)
end
end
@@ -511,12 +523,35 @@ describe NotificationService, :mailer do
should_not_email(issue.assignees.first)
end
+ it 'properly prioritizes notification reason' do
+ # have assignee be both assigned and mentioned
+ issue.update_attribute(:description, "/cc #{assignee.to_reference} #{@u_mentioned.to_reference}")
+
+ notification.new_issue(issue, @u_disabled)
+
+ email = find_email_for(assignee)
+ expect(email).to have_header('X-GitLab-NotificationReason', 'assigned')
+
+ email = find_email_for(@u_mentioned)
+ expect(email).to have_header('X-GitLab-NotificationReason', 'mentioned')
+ end
+
+ it 'adds "assigned" reason for assignees if any' do
+ notification.new_issue(issue, @u_disabled)
+
+ email = find_email_for(assignee)
+
+ expect(email).to have_header('X-GitLab-NotificationReason', 'assigned')
+ end
+
it "emails any mentioned users with the mention level" do
issue.description = @u_mentioned.to_reference
notification.new_issue(issue, @u_disabled)
- should_email(@u_mentioned)
+ email = find_email_for(@u_mentioned)
+ expect(email).not_to be_nil
+ expect(email).to have_header('X-GitLab-NotificationReason', 'mentioned')
end
it "emails the author if they've opted into notifications about their activity" do
@@ -620,6 +655,14 @@ describe NotificationService, :mailer do
should_not_email(@u_lazy_participant)
end
+ it 'adds "assigned" reason for new assignee' do
+ notification.reassigned_issue(issue, @u_disabled, [assignee])
+
+ email = find_email_for(assignee)
+
+ expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+
it 'emails previous assignee even if he has the "on mention" notif level' do
issue.assignees = [@u_mentioned]
notification.reassigned_issue(issue, @u_disabled, [@u_watcher])
@@ -910,6 +953,14 @@ describe NotificationService, :mailer do
should_not_email(@u_lazy_participant)
end
+ it 'adds "assigned" reason for assignee, if any' do
+ notification.new_merge_request(merge_request, @u_disabled)
+
+ email = find_email_for(merge_request.assignee)
+
+ expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+
it "emails any mentioned users with the mention level" do
merge_request.description = @u_mentioned.to_reference
@@ -924,6 +975,9 @@ describe NotificationService, :mailer do
notification.new_merge_request(merge_request, merge_request.author)
should_email(merge_request.author)
+
+ email = find_email_for(merge_request.author)
+ expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::OWN_ACTIVITY)
end
it "doesn't email the author if they haven't opted into notifications about their activity" do
@@ -1009,6 +1063,14 @@ describe NotificationService, :mailer do
should_not_email(@u_lazy_participant)
end
+ it 'adds "assigned" reason for new assignee' do
+ notification.reassigned_merge_request(merge_request, merge_request.author)
+
+ email = find_email_for(merge_request.assignee)
+
+ expect(email).to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
+ end
+
it_behaves_like 'participating notifications' do
let(:participant) { create(:user, username: 'user-participant') }
let(:issuable) { merge_request }
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index 7a8c54673f7..f7ff8b80bd7 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -93,26 +93,27 @@ describe Projects::AutocompleteService do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
- let!(:group_milestone) { create(:milestone, group: group) }
- let!(:project_milestone) { create(:milestone, project: project) }
+ let!(:group_milestone1) { create(:milestone, group: group, due_date: '2017-01-01', title: 'Second Title') }
+ let!(:group_milestone2) { create(:milestone, group: group, due_date: '2017-01-01', title: 'First Title') }
+ let!(:project_milestone) { create(:milestone, project: project, due_date: '2016-01-01') }
let(:milestone_titles) { described_class.new(project, user).milestones.map(&:title) }
- it 'includes project and group milestones' do
- expect(milestone_titles).to eq([group_milestone.title, project_milestone.title])
+ it 'includes project and group milestones and sorts them correctly' do
+ expect(milestone_titles).to eq([project_milestone.title, group_milestone2.title, group_milestone1.title])
end
it 'does not include closed milestones' do
- group_milestone.close
+ group_milestone1.close
- expect(milestone_titles).to eq([project_milestone.title])
+ expect(milestone_titles).to eq([project_milestone.title, group_milestone2.title])
end
it 'does not include milestones from other projects in the group' do
other_project = create(:project, group: group)
project_milestone.update!(project: other_project)
- expect(milestone_titles).to eq([group_milestone.title])
+ expect(milestone_titles).to eq([group_milestone2.title, group_milestone1.title])
end
end
end
diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb
new file mode 100644
index 00000000000..bb0e274c93e
--- /dev/null
+++ b/spec/services/projects/gitlab_projects_import_service_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Projects::GitlabProjectsImportService do
+ set(:namespace) { build(:namespace) }
+ let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+ subject { described_class.new(namespace.owner, { namespace_id: namespace.id, path: path, file: file }) }
+
+ describe '#execute' do
+ context 'with an invalid path' do
+ let(:path) { '/invalid-path/' }
+
+ it 'returns an invalid project' do
+ project = subject.execute
+
+ expect(project).not_to be_persisted
+ expect(project).not_to be_valid
+ end
+ end
+
+ context 'with a valid path' do
+ let(:path) { 'test-path' }
+
+ it 'creates a project' do
+ project = subject.execute
+
+ expect(project).to be_persisted
+ expect(project).to be_valid
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 39f6388c25e..ef68742a463 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -150,6 +150,7 @@ describe Projects::TransferService do
before do
group.add_owner(user)
+
unless gitlab_shell.add_repository(repository_storage, "#{group.full_path}/#{project.path}")
raise 'failed to add repository'
end
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index 46cd10cdc12..c40cd5b7548 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -105,12 +105,25 @@ describe SystemHooksService do
expect(data[:old_username]).to eq(user.username_was)
end
end
+
+ context 'user_failed_login' do
+ it 'contains state of user' do
+ user.ldap_block!
+
+ data = event_data(user, :failed_login)
+
+ expect(data).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username, :state)
+ expect(data[:username]).to eq(user.username)
+ expect(data[:state]).to eq('ldap_blocked')
+ end
+ end
end
context 'event names' do
it { expect(event_name(user, :create)).to eq "user_create" }
it { expect(event_name(user, :destroy)).to eq "user_destroy" }
it { expect(event_name(user, :rename)).to eq 'user_rename' }
+ it { expect(event_name(user, :failed_login)).to eq 'user_failed_login' }
it { expect(event_name(project, :create)).to eq "project_create" }
it { expect(event_name(project, :destroy)).to eq "project_destroy" }
it { expect(event_name(project, :rename)).to eq "project_rename" }
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 4e640a82dfc..ab3aa18cf4e 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -158,7 +158,7 @@ describe SystemNoteService do
end
it 'builds a correct phrase when assignee removed' do
- expect(build_note([assignee1], [])).to eq 'removed assignee'
+ expect(build_note([assignee1], [])).to eq "unassigned @#{assignee1.username}"
end
it 'builds a correct phrase when assignees changed' do
@@ -308,7 +308,7 @@ describe SystemNoteService do
end
it "posts the 'merge when pipeline succeeds' system note" do
- expect(subject.note).to match(/enabled an automatic merge when the pipeline for (\w+\/\w+@)?\h{40} succeeds/)
+ expect(subject.note).to match(%r{enabled an automatic merge when the pipeline for (\w+/\w+@)?\h{40} succeeds})
end
end
@@ -695,7 +695,7 @@ describe SystemNoteService do
commit = double(title: '<pre>This is a test</pre>', short_id: '12345678')
escaped = '&lt;pre&gt;This is a test&lt;&#x2F;pre&gt;'
- expect(described_class.new_commit_summary([commit])).to all(match(%r[- #{escaped}]))
+ expect(described_class.new_commit_summary([commit])).to all(match(/- #{escaped}/))
end
end
@@ -727,6 +727,7 @@ describe SystemNoteService do
else
"#{Settings.gitlab.base_url}/#{project.namespace.path}/#{project.path}/merge_requests/#{merge_request.iid}"
end
+
link = double(object: { 'url' => url })
links << link
expect(link).to receive(:save!)
diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb
index ff8b9595538..74d7715e50f 100644
--- a/spec/services/test_hooks/system_service_spec.rb
+++ b/spec/services/test_hooks/system_service_spec.rb
@@ -60,5 +60,25 @@ describe TestHooks::SystemService do
expect(service.execute).to include(success_result)
end
end
+
+ context 'merge_requests_events' do
+ let(:trigger) { 'merge_requests_events' }
+
+ it 'returns error message if the user does not have any repository with a merge request' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure one of your projects has merge requests.' })
+ end
+
+ it 'executes hook' do
+ trigger_key = :merge_request_hooks
+ sample_data = { data: 'sample' }
+ create(:project_member, user: current_user, project: project)
+ create(:merge_request, source_project: project)
+ allow_any_instance_of(MergeRequest).to receive(:to_hook_data).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
end
end
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index aeba9cd60bc..bb3d73edf8e 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -15,7 +15,7 @@ describe Users::DestroyService do
expect { user_data['email'].to eq(user.email) }
expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
- expect { Namespace.with_deleted.find(namespace.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { Namespace.find(namespace.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'will delete the project' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index f51bb44086b..85de0a14631 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -97,13 +97,19 @@ RSpec.configure do |config|
TestEnv.init
end
- config.after(:suite) do
- TestEnv.cleanup
- end
-
config.before(:example) do
# Skip pre-receive hook check so we can use the web editor and merge.
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
+
+ allow_any_instance_of(Gitlab::Git::GitlabProjects).to receive(:fork_repository).and_wrap_original do |m, *args|
+ m.call(*args)
+
+ shard_path, repository_relative_path = args
+ # We can't leave the hooks in place after a fork, as those would fail in tests
+ # The "internal" API is not available
+ FileUtils.rm_rf(File.join(shard_path, repository_relative_path, 'hooks'))
+ end
+
# Enable all features by default for testing
allow(Feature).to receive(:enabled?) { true }
end
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
index 26fd271ce31..d5ef80cfab2 100644
--- a/spec/support/cycle_analytics_helpers.rb
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -5,10 +5,16 @@ module CycleAnalyticsHelpers
end
def create_commit(message, project, user, branch_name, count: 1)
- oldrev = project.repository.commit(branch_name).sha
+ repository = project.repository
+ oldrev = repository.commit(branch_name).sha
+
+ if Timecop.frozen? && Gitlab::GitalyClient.feature_enabled?(:operation_user_commit_files)
+ mock_gitaly_multi_action_dates(repository.raw)
+ end
+
commit_shas = Array.new(count) do |index|
- commit_sha = project.repository.create_file(user, generate(:branch), "content", message: message, branch_name: branch_name)
- project.repository.commit(commit_sha)
+ commit_sha = repository.create_file(user, generate(:branch), "content", message: message, branch_name: branch_name)
+ repository.commit(commit_sha)
commit_sha
end
@@ -98,6 +104,25 @@ module CycleAnalyticsHelpers
pipeline: dummy_pipeline,
protected: false)
end
+
+ def mock_gitaly_multi_action_dates(raw_repository)
+ allow(raw_repository).to receive(:multi_action).and_wrap_original do |m, *args|
+ new_date = Time.now
+ branch_update = m.call(*args)
+
+ if branch_update.newrev
+ _, opts = args
+ commit = raw_repository.commit(branch_update.newrev).rugged_commit
+ branch_update.newrev = commit.amend(
+ update_ref: "#{Gitlab::Git::BRANCH_REF_PREFIX}#{opts[:branch_name]}",
+ author: commit.author.merge(time: new_date),
+ committer: commit.committer.merge(time: new_date)
+ )
+ end
+
+ branch_update
+ end
+ end
end
RSpec.configure do |config|
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index edaee03ea6c..1809ae1d141 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -1,10 +1,26 @@
+require 'database_cleaner/active_record/deletion'
+
+module FakeInformationSchema
+ # Work around a bug in DatabaseCleaner when using the deletion strategy:
+ # https://github.com/DatabaseCleaner/database_cleaner/issues/347
+ #
+ # On MySQL, if the information schema is said to exist, we use an inaccurate
+ # row count leading to some tables not being cleaned when they should
+ def information_schema_exists?(_connection)
+ false
+ end
+end
+
+DatabaseCleaner::ActiveRecord::Deletion.prepend(FakeInformationSchema)
+
RSpec.configure do |config|
+ # Ensure all sequences are reset at the start of the suite run
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.append_after(:context) do
- DatabaseCleaner.clean_with(:truncation, cache_tables: false)
+ DatabaseCleaner.clean_with(:deletion, cache_tables: false)
end
config.before(:each) do
@@ -12,15 +28,15 @@ RSpec.configure do |config|
end
config.before(:each, :js) do
- DatabaseCleaner.strategy = :truncation
+ DatabaseCleaner.strategy = :deletion
end
- config.before(:each, :truncate) do
- DatabaseCleaner.strategy = :truncation
+ config.before(:each, :delete) do
+ DatabaseCleaner.strategy = :deletion
end
config.before(:each, :migration) do
- DatabaseCleaner.strategy = :truncation, { cache_tables: false }
+ DatabaseCleaner.strategy = :deletion, { cache_tables: false }
end
config.before(:each) do
diff --git a/spec/support/devise_helpers.rb b/spec/support/devise_helpers.rb
index 890a2d9d287..66874e10f38 100644
--- a/spec/support/devise_helpers.rb
+++ b/spec/support/devise_helpers.rb
@@ -2,13 +2,16 @@ module DeviseHelpers
# explicitly tells Devise which mapping to use
# this is needed when we are testing a Devise controller bypassing the router
def set_devise_mapping(context:)
- env =
- if context.respond_to?(:env_config)
- context.env_config
- elsif context.respond_to?(:env)
- context.env
- end
+ env = env_from_context(context)
env['devise.mapping'] = Devise.mappings[:user] if env
end
+
+ def env_from_context(context)
+ if context.respond_to?(:env_config)
+ context.env_config
+ elsif context.respond_to?(:env)
+ context.env
+ end
+ end
end
diff --git a/spec/support/email_helpers.rb b/spec/support/email_helpers.rb
index b39052923dd..1fb8252459f 100644
--- a/spec/support/email_helpers.rb
+++ b/spec/support/email_helpers.rb
@@ -30,4 +30,8 @@ module EmailHelpers
def email_recipients(kind: :to)
ActionMailer::Base.deliveries.flat_map(&kind)
end
+
+ def find_email_for(user)
+ ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email) }
+ end
end
diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb
index c24940393f9..c8662d41769 100644
--- a/spec/support/features/discussion_comments_shared_example.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -113,6 +113,7 @@ shared_examples 'discussion comments' do |resource_name|
else
expect(find(submit_selector).value).to eq 'Start discussion'
end
+
expect(page).not_to have_selector menu_selector
end
@@ -142,15 +143,17 @@ shared_examples 'discussion comments' do |resource_name|
end
if resource_name == 'merge request'
+ let(:note_id) { find("#{comments_selector} .note", match: :first)['data-note-id'] }
+
it 'shows resolved discussion when toggled' do
click_button "Resolve discussion"
- expect(page).to have_selector('.note-row-1', visible: true)
+ expect(page).to have_selector(".note-row-#{note_id}", visible: true)
refresh
click_button "Toggle discussion"
- expect(page).to have_selector('.note-row-1', visible: true)
+ expect(page).to have_selector(".note-row-#{note_id}", visible: true)
end
end
end
@@ -200,6 +203,7 @@ shared_examples 'discussion comments' do |resource_name|
else
expect(find(submit_selector).value).to eq 'Comment'
end
+
expect(page).not_to have_selector menu_selector
end
diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/filtered_search_helpers.rb
index 05021ea9054..f3f96bd1f0a 100644
--- a/spec/support/filtered_search_helpers.rb
+++ b/spec/support/filtered_search_helpers.rb
@@ -61,9 +61,11 @@ module FilteredSearchHelpers
token_emoji = tokens[index][:emoji_name]
expect(el.find('.name')).to have_content(token_name)
+
if token_value
expect(el.find('.value')).to have_content(token_value)
end
+
# gl-emoji content is blank when the emoji unicode is not supported
if token_emoji
selector = %(gl-emoji[data-name="#{token_emoji}"])
diff --git a/spec/support/generate-seed-repo-rb b/spec/support/generate-seed-repo-rb
index 4ee33f9725b..876b3b8242d 100755
--- a/spec/support/generate-seed-repo-rb
+++ b/spec/support/generate-seed-repo-rb
@@ -24,6 +24,7 @@ def main
unless system(*%W[git clone --bare #{SOURCE} #{REPO_NAME}], chdir: dir)
abort "git clone failed"
end
+
repo = File.join(dir, REPO_NAME)
erb = ERB.new(DATA.read)
erb.run(binding)
diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb
index 99752ed396e..2fdbddd40c2 100644
--- a/spec/support/google_api/cloud_platform_helpers.rb
+++ b/spec/support/google_api/cloud_platform_helpers.rb
@@ -10,10 +10,14 @@ module GoogleApi
request.session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = 1.hour.ago.to_i.to_s
end
- def stub_google_project_billing_status
- redis_double = double
- allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double)
- allow(redis_double).to receive(:get).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for('token')).and_return('true')
+ def stub_cloud_platform_projects_list(options)
+ WebMock.stub_request(:get, cloud_platform_projects_list_url)
+ .to_return(cloud_platform_response(cloud_platform_projects_body(options)))
+ end
+
+ def stub_cloud_platform_projects_get_billing_info(project_id, billing_enabled)
+ WebMock.stub_request(:get, cloud_platform_projects_get_billing_info_url(project_id))
+ .to_return(cloud_platform_response(cloud_platform_projects_billing_info_body(project_id, billing_enabled)))
end
def stub_cloud_platform_get_zone_cluster(project_id, zone, cluster_id, **options)
@@ -46,6 +50,14 @@ module GoogleApi
.to_return(status: [500, "Internal Server Error"])
end
+ def cloud_platform_projects_list_url
+ "https://cloudresourcemanager.googleapis.com/v1/projects"
+ end
+
+ def cloud_platform_projects_get_billing_info_url(project_id)
+ "https://cloudbilling.googleapis.com/v1/projects/#{project_id}/billingInfo"
+ end
+
def cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id)
"https://container.googleapis.com/v1/projects/#{project_id}/zones/#{zone}/clusters/#{cluster_id}"
end
@@ -121,5 +133,32 @@ module GoogleApi
"endTime": options[:endTime] || ''
}
end
+
+ def cloud_platform_projects_body(**options)
+ {
+ "projects": [
+ {
+ "projectNumber": options[:project_number] || "1234",
+ "projectId": options[:project_id] || "test-project-1234",
+ "lifecycleState": "ACTIVE",
+ "name": options[:name] || "test-project",
+ "createTime": "2017-12-16T01:48:29.129Z",
+ "parent": {
+ "type": "organization",
+ "id": "12345"
+ }
+ }
+ ]
+ }
+ end
+
+ def cloud_platform_projects_billing_info_body(project_id, billing_enabled)
+ {
+ "name": "projects/#{project_id}/billingInfo",
+ "projectId": "#{project_id}",
+ "billingAccountName": "account-name",
+ "billingEnabled": billing_enabled
+ }
+ end
end
end
diff --git a/spec/support/javascript_fixtures_helpers.rb b/spec/support/javascript_fixtures_helpers.rb
index 923c8080e6c..2197bc9d853 100644
--- a/spec/support/javascript_fixtures_helpers.rb
+++ b/spec/support/javascript_fixtures_helpers.rb
@@ -1,6 +1,5 @@
require 'action_dispatch/testing/test_request'
require 'fileutils'
-require 'gitlab/popen'
module JavaScriptFixturesHelpers
include Gitlab::Popen
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index 50702a0ac88..b52b6a28c54 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -125,6 +125,13 @@ module LoginHelpers
})
end
+ def stub_omniauth_provider(provider, context: Rails.application)
+ env = env_from_context(context)
+
+ set_devise_mapping(context: context)
+ env['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
+ end
+
def stub_omniauth_saml_config(messages)
set_devise_mapping(context: Rails.application)
Rails.application.routes.disable_clear_and_finalize = true
diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb
index cdb62a5deee..42a9ed9ff34 100644
--- a/spec/support/matchers/access_matchers_for_controller.rb
+++ b/spec/support/matchers/access_matchers_for_controller.rb
@@ -43,6 +43,7 @@ module AccessMatchersForController
user = create(:user)
membership.public_send(:"add_#{role}", user)
end
+
user
end
diff --git a/spec/support/project_forks_helper.rb b/spec/support/project_forks_helper.rb
index d6680735aa1..2c501a2a27c 100644
--- a/spec/support/project_forks_helper.rb
+++ b/spec/support/project_forks_helper.rb
@@ -38,10 +38,6 @@ module ProjectForksHelper
# so we have to explicitely call this method to clear the @exists variable.
# of the instance we're returning here.
forked_project.repository.after_import
-
- # We can't leave the hooks in place after a fork, as those would fail in tests
- # The "internal" API is not available
- FileUtils.rm_rf("#{forked_project.repository.path}/hooks")
end
forked_project
diff --git a/spec/support/select2_helper.rb b/spec/support/select2_helper.rb
index 55da961e173..90618ba5b19 100644
--- a/spec/support/select2_helper.rb
+++ b/spec/support/select2_helper.rb
@@ -17,6 +17,7 @@ module Select2Helper
selector = options.fetch(:from)
first(selector, visible: false)
+
if options[:multiple]
execute_script("$('#{selector}').select2('val', ['#{value}']).trigger('change');")
else
diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb
index 3f1fd169b72..23f9b46ae0c 100644
--- a/spec/support/services_shared_context.rb
+++ b/spec/support/services_shared_context.rb
@@ -3,13 +3,9 @@ Service.available_services_names.each do |service|
let(:dashed_service) { service.dasherize }
let(:service_method) { "#{service}_service".to_sym }
let(:service_klass) { "#{service}_service".classify.constantize }
- let(:service_fields) { service_klass.new.fields }
+ let(:service_instance) { service_klass.new }
+ let(:service_fields) { service_instance.fields }
let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
- let(:service_attrs_list_without_passwords) do
- service_fields
- .select { |field| field[:type] != 'password' }
- .map { |field| field[:name].to_sym}
- end
let(:service_attrs) do
service_attrs_list.inject({}) do |hash, k|
if k =~ /^(token*|.*_token|.*_key)/
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
new file mode 100644
index 00000000000..5b0b609f7f2
--- /dev/null
+++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
@@ -0,0 +1,99 @@
+RSpec.shared_examples 'a creatable merge request' do
+ include WaitForRequests
+
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:target_project) { create(:project, :public, :repository) }
+ let(:source_project) { target_project }
+ let!(:milestone) { create(:milestone, project: target_project) }
+ let!(:label) { create(:label, project: target_project) }
+ let!(:label2) { create(:label, project: target_project) }
+
+ before do
+ source_project.add_master(user)
+ target_project.add_master(user)
+ target_project.add_master(user2)
+
+ sign_in(user)
+ visit project_new_merge_request_path(
+ target_project,
+ merge_request: {
+ source_project_id: source_project.id,
+ target_project_id: target_project.id,
+ source_branch: 'fix',
+ target_branch: 'master'
+ })
+ end
+
+ it 'creates new merge request', :js do
+ click_button 'Assignee'
+ page.within '.dropdown-menu-user' do
+ click_link user2.name
+ end
+ expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user2.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user2.name
+ end
+
+ click_link 'Assign to me'
+ expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user.name
+ end
+
+ click_button 'Milestone'
+ page.within '.issue-milestone' do
+ click_link milestone.title
+ end
+ expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
+ page.within '.js-milestone-select' do
+ expect(page).to have_content milestone.title
+ end
+
+ click_button 'Labels'
+ page.within '.dropdown-menu-labels' do
+ click_link label.title
+ click_link label2.title
+ end
+ page.within '.js-label-select' do
+ expect(page).to have_content label.title
+ end
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
+
+ click_button 'Submit merge request'
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content user.name
+ end
+
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
+
+ page.within '.labels' do
+ expect(page).to have_content label.title
+ expect(page).to have_content label2.title
+ end
+ end
+ end
+
+ it 'updates the branches when selecting a new target project' do
+ target_project_member = target_project.owner
+ CreateBranchService.new(target_project, target_project_member)
+ .execute('a-brand-new-branch-to-test', 'master')
+ visit project_new_merge_request_path(source_project)
+
+ first('.js-target-project').click
+ find('.dropdown-target-project .dropdown-content a', text: target_project.full_path).click
+
+ wait_for_requests
+
+ first('.js-target-branch').click
+
+ within('.dropdown-target-branch .dropdown-content') do
+ expect(page).to have_content('a-brand-new-branch-to-test')
+ end
+ end
+end
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
new file mode 100644
index 00000000000..645db41cddc
--- /dev/null
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -0,0 +1,140 @@
+RSpec.shared_examples 'an editable merge request' do
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let!(:milestone) { create(:milestone, project: target_project) }
+ let!(:label) { create(:label, project: target_project) }
+ let!(:label2) { create(:label, project: target_project) }
+ let(:target_project) { create(:project, :public, :repository) }
+ let(:source_project) { target_project }
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: source_project,
+ target_project: target_project,
+ source_branch: 'fix',
+ target_branch: 'master')
+ end
+
+ before do
+ source_project.add_master(user)
+ target_project.add_master(user)
+ target_project.add_master(user2)
+
+ sign_in(user)
+ visit edit_project_merge_request_path(target_project, merge_request)
+ end
+
+ it 'updates merge request', :js do
+ click_button 'Assignee'
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ end
+ expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user.name
+ end
+
+ click_button 'Milestone'
+ page.within '.issue-milestone' do
+ click_link milestone.title
+ end
+ expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
+ page.within '.js-milestone-select' do
+ expect(page).to have_content milestone.title
+ end
+
+ click_button 'Labels'
+ page.within '.dropdown-menu-labels' do
+ click_link label.title
+ click_link label2.title
+ end
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
+ page.within '.js-label-select' do
+ expect(page).to have_content label.title
+ end
+
+ click_button 'Save changes'
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content user.name
+ end
+
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
+
+ page.within '.labels' do
+ expect(page).to have_content label.title
+ expect(page).to have_content label2.title
+ end
+ end
+ end
+
+ it 'description has autocomplete', :js do
+ find('#merge_request_description').native.send_keys('')
+ fill_in 'merge_request_description', with: '@'
+
+ expect(page).to have_selector('.atwho-view')
+ end
+
+ it 'has class js-quick-submit in form' do
+ expect(page).to have_selector('.js-quick-submit')
+ end
+
+ it 'warns about version conflict' do
+ merge_request.update(title: "New title")
+
+ fill_in 'merge_request_title', with: 'bug 345'
+ fill_in 'merge_request_description', with: 'bug description'
+
+ click_button 'Save changes'
+
+ expect(page).to have_content 'Someone edited the merge request the same time you did'
+ end
+
+ it 'preserves description textarea height', :js do
+ long_description = %q(
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ac ornare ligula, ut tempus arcu. Etiam ultricies accumsan dolor vitae faucibus. Donec at elit lacus. Mauris orci ante, aliquam quis lorem eget, convallis faucibus arcu. Aenean at pulvinar lacus. Ut viverra quam massa, molestie ornare tortor dignissim a. Suspendisse tristique pellentesque tellus, id lacinia metus elementum id. Nam tristique, arcu rhoncus faucibus viverra, lacus ipsum sagittis ligula, vitae convallis odio lacus a nibh. Ut tincidunt est purus, ac vestibulum augue maximus in. Suspendisse vel erat et mi ultricies semper. Pellentesque volutpat pellentesque consequat.
+
+ Cras congue nec ligula tristique viverra. Curabitur fringilla fringilla fringilla. Donec rhoncus dignissim orci ut accumsan. Ut rutrum urna a rhoncus varius. Maecenas blandit, mauris nec accumsan gravida, augue nibh finibus magna, sed maximus turpis libero nec neque. Suspendisse at semper est. Nunc imperdiet dapibus dui, varius sollicitudin erat luctus non. Sed pellentesque ligula eget posuere facilisis. Donec dictum commodo volutpat. Donec egestas dui ac magna sollicitudin bibendum. Vivamus purus neque, ullamcorper ac feugiat et, tempus sit amet metus. Praesent quis viverra neque. Sed bibendum viverra est, eu aliquam mi ornare vitae. Proin et dapibus ipsum. Nunc tortor diam, malesuada nec interdum vel, placerat quis justo. Ut viverra at erat eu laoreet.
+
+ Pellentesque commodo, diam sit amet dignissim condimentum, tortor justo pretium est, non venenatis metus eros ut nunc. Etiam ut neque eget sem dapibus aliquam. Curabitur vel elit lorem. Nulla nec enim elit. Sed ut ex id justo facilisis convallis at ac augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nullam cursus egestas turpis non tristique. Suspendisse in erat sem. Fusce libero elit, fermentum gravida mauris id, auctor iaculis felis. Nullam vulputate tempor laoreet.
+
+ Nam tempor et magna sed convallis. Fusce sit amet sollicitudin risus, a ullamcorper lacus. Morbi gravida quis sem eget porttitor. Donec eu egestas mauris, in elementum tortor. Sed eget ex mi. Mauris iaculis tortor ut est auctor, nec dignissim quam sagittis. Suspendisse vel metus non quam suscipit tincidunt. Cras molestie lacus non justo finibus sodales quis vitae erat. In a porttitor nisi, id sollicitudin urna. Ut at felis tellus. Suspendisse potenti.
+
+ Maecenas leo ligula, varius at neque vitae, ornare maximus justo. Nullam convallis luctus risus et vulputate. Duis suscipit faucibus iaculis. Etiam quis tortor faucibus, tristique tellus sit amet, sodales neque. Nulla dapibus nisi vel aliquet consequat. Etiam faucibus, metus eget condimentum iaculis, enim urna lobortis sem, id efficitur eros sapien nec nisi. Aenean ut finibus ex.
+ )
+
+ fill_in 'merge_request_description', with: long_description
+
+ height = get_textarea_height
+ find('.js-md-preview-button').click
+ find('.js-md-write-button').click
+ new_height = get_textarea_height
+
+ expect(height).to eq(new_height)
+ end
+
+ context 'when "Remove source branch" is set' do
+ before do
+ merge_request.update!(merge_params: { 'force_remove_source_branch' => '1' })
+ end
+
+ it 'allows to unselect "Remove source branch"', :js do
+ expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
+
+ visit edit_project_merge_request_path(target_project, merge_request)
+ uncheck 'Remove source branch when merge request is accepted'
+
+ click_button 'Save changes'
+
+ expect(page).to have_unchecked_field 'remove-source-branch-input'
+ expect(page).to have_content 'Remove source branch'
+ end
+ end
+end
+
+def get_textarea_height
+ page.evaluate_script('document.getElementById("merge_request_description").offsetHeight')
+end
diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb
index 17f3a861ba8..e827a8da0b7 100644
--- a/spec/support/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/slack_mattermost_notifications_shared_examples.rb
@@ -57,6 +57,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
@issue = issue_service.execute
@issues_sample_data = issue_service.hook_data(@issue, 'open')
+ project.add_developer(user)
opts = {
title: 'Awesome merge_request',
description: 'please fix',
diff --git a/spec/support/stub_env.rb b/spec/support/stub_env.rb
index f621463e621..695152e2d4e 100644
--- a/spec/support/stub_env.rb
+++ b/spec/support/stub_env.rb
@@ -4,6 +4,7 @@ module StubENV
def stub_env(key_or_hash, value = nil)
init_stub unless env_stubbed?
+
if key_or_hash.is_a? Hash
key_or_hash.each { |k, v| add_stubbed_value(k, v) }
else
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 3b79d769e02..c275522159c 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -20,7 +20,7 @@ module TestEnv
'improve/awesome' => '5937ac0',
'merged-target' => '21751bf',
'markdown' => '0ed8c6c',
- 'lfs' => 'be93687',
+ 'lfs' => '55bc176',
'master' => 'b83d6e3',
'merge-test' => '5937ac0',
"'test'" => 'e56497b',
@@ -90,10 +90,6 @@ module TestEnv
setup_forked_repo
end
- def cleanup
- stop_gitaly
- end
-
def disable_mailer
allow_any_instance_of(NotificationService).to receive(:mailer)
.and_return(double.as_null_object)
@@ -163,6 +159,8 @@ module TestEnv
spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s
@gitaly_pid = Bundler.with_original_env { IO.popen([spawn_script], &:read).to_i }
+ Kernel.at_exit { stop_gitaly }
+
wait_gitaly
end
@@ -309,7 +307,7 @@ module TestEnv
# Before we used Git clone's --mirror option, bare repos could end up
# with missing refs, clearing them and retrying should fix the issue.
- cleanup && clean_gitlab_test_path && init unless reset.call
+ clean_gitlab_test_path && init unless reset.call
end
end
@@ -325,6 +323,7 @@ module TestEnv
if component_needs_update?(install_dir, version)
# Cleanup the component entirely to ensure we start fresh
FileUtils.rm_rf(install_dir)
+
unless system('rake', task)
raise ComponentFailedToInstallError
end
diff --git a/spec/support/wait_for_requests.rb b/spec/support/wait_for_requests.rb
index f4130d68271..fda0e29f983 100644
--- a/spec/support/wait_for_requests.rb
+++ b/spec/support/wait_for_requests.rb
@@ -53,6 +53,7 @@ module WaitForRequests
wait_until = Time.now + max_wait_time.seconds
loop do
break if yield
+
if Time.now > wait_until
raise "Condition not met: #{condition_name}"
else
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index b41c3b3958a..168facd51a6 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -165,7 +165,7 @@ describe 'gitlab:app namespace rake task' do
expect(tar_contents).to match('pages.tar.gz')
expect(tar_contents).to match('lfs.tar.gz')
expect(tar_contents).to match('registry.tar.gz')
- expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|pages.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/)
+ expect(tar_contents).not_to match(%r{^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|pages.tar.gz|artifacts.tar.gz|registry.tar.gz)/$})
end
it 'deletes temp directories' do
diff --git a/spec/tasks/gitlab/git_rake_spec.rb b/spec/tasks/gitlab/git_rake_spec.rb
index dacc5dc5ae7..9aebf7b0b4a 100644
--- a/spec/tasks/gitlab/git_rake_spec.rb
+++ b/spec/tasks/gitlab/git_rake_spec.rb
@@ -19,7 +19,7 @@ describe 'gitlab:git rake tasks' do
describe 'fsck' do
it 'outputs the integrity check for a repo' do
- expect { run_rake_task('gitlab:git:fsck') }.to output(/Performed Checking integrity at .*@hashed\/1\/2\/test.git/).to_stdout
+ expect { run_rake_task('gitlab:git:fsck') }.to output(%r{Performed Checking integrity at .*@hashed/1/2/test.git}).to_stdout
end
it 'errors out about config.lock issues' do
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index 6aba86fdc3c..b37d6ac831f 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -76,7 +76,11 @@ describe 'gitlab:gitaly namespace rake task' do
end
context 'when Rails.env is test' do
- let(:command) { %w[make BUNDLE_FLAGS=--no-deployment] }
+ let(:command) do
+ %W[make
+ BUNDLE_FLAGS=--no-deployment
+ BUNDLE_PATH=#{Bundler.bundle_path}]
+ end
before do
allow(Rails.env).to receive(:test?).and_return(true)
diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb
index fae5ec35c47..e9322ec4931 100644
--- a/spec/tasks/gitlab/task_helpers_spec.rb
+++ b/spec/tasks/gitlab/task_helpers_spec.rb
@@ -1,5 +1,4 @@
require 'spec_helper'
-require 'tasks/gitlab/task_helpers'
class TestHelpersTest
include Gitlab::TaskHelpers
diff --git a/spec/tasks/gitlab/uploads_rake_spec.rb b/spec/tasks/gitlab/uploads_rake_spec.rb
new file mode 100644
index 00000000000..ac0005e51e0
--- /dev/null
+++ b/spec/tasks/gitlab/uploads_rake_spec.rb
@@ -0,0 +1,27 @@
+require 'rake_helper'
+
+describe 'gitlab:uploads rake tasks' do
+ describe 'check' do
+ let!(:upload) { create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) }
+
+ before do
+ Rake.application.rake_require 'tasks/gitlab/uploads'
+ end
+
+ it 'outputs the integrity check for each uploaded file' do
+ expect { run_rake_task('gitlab:uploads:check') }.to output(/Checking file \(#{upload.id}\): #{Regexp.quote(upload.absolute_path)}/).to_stdout
+ end
+
+ it 'errors out about missing files on the file system' do
+ create(:upload)
+
+ expect { run_rake_task('gitlab:uploads:check') }.to output(/File does not exist on the file system/).to_stdout
+ end
+
+ it 'errors out about invalid checksum' do
+ upload.update_column(:checksum, '01a3156db2cf4f67ec823680b40b7302f89ab39179124ad219f94919b8a1769e')
+
+ expect { run_rake_task('gitlab:uploads:check') }.to output(/File checksum \(9e697aa09fe196909813ee36103e34f721fe47a5fdc8aac0e4e4ac47b9b38282\) does not match the one in the database \(#{upload.checksum}\)/).to_stdout
+ end
+ end
+end
diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb
index f4c4b69b773..0bcf28f2c1c 100644
--- a/spec/uploaders/job_artifact_uploader_spec.rb
+++ b/spec/uploaders/job_artifact_uploader_spec.rb
@@ -64,7 +64,7 @@ describe JobArtifactUploader do
it { is_expected.to start_with("#{uploader.root}/#{uploader.class.base_dir}") }
it { is_expected.to include("/#{job_artifact.created_at.utc.strftime('%Y_%m_%d')}/") }
- it { is_expected.to include("/#{job_artifact.project_id}/") }
+ it { is_expected.to include("/#{job_artifact.job_id}/#{job_artifact.id}/") }
it { is_expected.to end_with("ci_build_artifacts.zip") }
end
end
diff --git a/spec/views/projects/buttons/_dropdown.html.haml_spec.rb b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
new file mode 100644
index 00000000000..d0e692635b9
--- /dev/null
+++ b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe 'projects/buttons/_dropdown' do
+ let(:user) { create(:user) }
+
+ context 'user with all abilities' do
+ before do
+ assign(:project, project)
+
+ allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:can?).and_return(true)
+ end
+
+ context 'empty repository' do
+ let(:project) { create(:project, :empty_repo) }
+
+ it 'has a link to create a new file' do
+ render
+
+ expect(view).to render_template('projects/buttons/_dropdown')
+ expect(rendered).to have_link('New file')
+ end
+
+ it 'does not have a link to create a new branch' do
+ render
+
+ expect(view).to render_template('projects/buttons/_dropdown')
+ expect(rendered).not_to have_link('New branch')
+ end
+
+ it 'does not have a link to create a new tag' do
+ render
+
+ expect(view).to render_template('projects/buttons/_dropdown')
+ expect(rendered).not_to have_link('New tag')
+ end
+ end
+ end
+end
diff --git a/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
new file mode 100644
index 00000000000..6e7d8db99c4
--- /dev/null
+++ b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe 'projects/pipeline_schedules/_pipeline_schedule' do
+ let(:owner) { create(:user) }
+ let(:master) { create(:user) }
+ let(:project) { create(:project) }
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) }
+
+ before do
+ assign(:project, project)
+
+ allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:pipeline_schedule).and_return(pipeline_schedule)
+
+ allow(view).to receive(:can?).and_return(true)
+ end
+
+ context 'taking ownership of schedule' do
+ context 'when non-owner is signed in' do
+ let(:user) { master }
+
+ before do
+ allow(view).to receive(:can?).with(master, :take_ownership_pipeline_schedule, pipeline_schedule).and_return(true)
+ end
+
+ it 'non-owner can take ownership of pipeline' do
+ render
+
+ expect(rendered).to have_link('Take ownership')
+ end
+ end
+
+ context 'when owner is signed in' do
+ let(:user) { owner }
+
+ before do
+ allow(view).to receive(:can?).with(owner, :take_ownership_pipeline_schedule, pipeline_schedule).and_return(false)
+ end
+
+ it 'owner cannot take ownership of pipeline' do
+ render
+
+ expect(rendered).not_to have_link('Take ownership')
+ end
+ end
+ end
+end
diff --git a/spec/views/projects/pipelines_settings/_show.html.haml_spec.rb b/spec/views/projects/pipelines_settings/_show.html.haml_spec.rb
index 95f0be49412..7b300150874 100644
--- a/spec/views/projects/pipelines_settings/_show.html.haml_spec.rb
+++ b/spec/views/projects/pipelines_settings/_show.html.haml_spec.rb
@@ -13,8 +13,8 @@ describe 'projects/pipelines_settings/_show' do
render
expect(rendered).to have_css('.settings-message')
- expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name and the')
- expect(rendered).to have_link('Kubernetes service')
+ expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name and a')
+ expect(rendered).to have_link('Kubernetes cluster')
end
end
@@ -27,8 +27,8 @@ describe 'projects/pipelines_settings/_show' do
render
expect(rendered).to have_css('.settings-message')
- expect(rendered).to have_text('Auto Review Apps and Auto Deploy need the')
- expect(rendered).to have_link('Kubernetes service')
+ expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a')
+ expect(rendered).to have_link('Kubernetes cluster')
end
end
end
diff --git a/spec/workers/check_gcp_project_billing_worker_spec.rb b/spec/workers/check_gcp_project_billing_worker_spec.rb
index f52a903327c..7b7a7c1bc44 100644
--- a/spec/workers/check_gcp_project_billing_worker_spec.rb
+++ b/spec/workers/check_gcp_project_billing_worker_spec.rb
@@ -8,7 +8,7 @@ describe CheckGcpProjectBillingWorker do
context 'when there is a token in redis' do
before do
- allow_any_instance_of(described_class).to receive(:get_session_token).and_return(token)
+ allow(described_class).to receive(:get_session_token).and_return(token)
end
context 'when there is no lease' do
diff --git a/spec/workers/gitlab_shell_worker_spec.rb b/spec/workers/gitlab_shell_worker_spec.rb
new file mode 100644
index 00000000000..6b222af454d
--- /dev/null
+++ b/spec/workers/gitlab_shell_worker_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe GitlabShellWorker do
+ let(:worker) { described_class.new }
+
+ describe '#perform with add_key' do
+ it 'calls add_key on Gitlab::Shell' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:add_key).with('foo', 'bar')
+ worker.perform(:add_key, 'foo', 'bar')
+ end
+ end
+end
diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb
index 4e15ccc534b..baa8ddb59e5 100644
--- a/spec/workers/new_issue_worker_spec.rb
+++ b/spec/workers/new_issue_worker_spec.rb
@@ -44,8 +44,9 @@ describe NewIssueWorker do
expect { worker.perform(issue.id, user.id) }.to change { Event.count }.from(0).to(1)
end
- it 'creates a notification for the assignee' do
- expect(Notify).to receive(:new_issue_email).with(mentioned.id, issue.id).and_return(double(deliver_later: true))
+ it 'creates a notification for the mentioned user' do
+ expect(Notify).to receive(:new_issue_email).with(mentioned.id, issue.id, NotificationReason::MENTIONED)
+ .and_return(double(deliver_later: true))
worker.perform(issue.id, user.id)
end
diff --git a/spec/workers/new_merge_request_worker_spec.rb b/spec/workers/new_merge_request_worker_spec.rb
index 9e0cbde45b1..c3f29a40d58 100644
--- a/spec/workers/new_merge_request_worker_spec.rb
+++ b/spec/workers/new_merge_request_worker_spec.rb
@@ -46,8 +46,10 @@ describe NewMergeRequestWorker do
expect { worker.perform(merge_request.id, user.id) }.to change { Event.count }.from(0).to(1)
end
- it 'creates a notification for the assignee' do
- expect(Notify).to receive(:new_merge_request_email).with(mentioned.id, merge_request.id).and_return(double(deliver_later: true))
+ it 'creates a notification for the mentioned user' do
+ expect(Notify).to receive(:new_merge_request_email)
+ .with(mentioned.id, merge_request.id, NotificationReason::MENTIONED)
+ .and_return(double(deliver_later: true))
worker.perform(merge_request.id, user.id)
end
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 31598586f59..4912baa348c 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -47,6 +47,14 @@ describe RepositoryForkWorker do
perform!
end
+ it 'protects the default branch' do
+ expect_fork_repository.and_return(true)
+
+ perform!
+
+ expect(fork_project.protected_branches.first.name).to eq(fork_project.default_branch)
+ end
+
it 'flushes various caches' do
expect_fork_repository.and_return(true)
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index 7274a9f00f9..2b1a617ee62 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -49,9 +49,22 @@ describe RepositoryImportWorker do
expect do
subject.perform(project.id)
- end.to raise_error(StandardError, error)
+ end.to raise_error(RuntimeError, error)
expect(project.reload.import_jid).not_to be_nil
end
+
+ it 'updates the error on Import/Export' do
+ error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
+
+ project.update_attributes(import_jid: '123', import_type: 'gitlab_project')
+ expect_any_instance_of(Projects::ImportService).to receive(:execute).and_return({ status: :error, message: error })
+
+ expect do
+ subject.perform(project.id)
+ end.to raise_error(RuntimeError, error)
+
+ expect(project.reload.import_error).not_to be_nil
+ end
end
context 'when using an asynchronous importer' do