Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorClement Ho <ClemMakesApps@gmail.com>2017-06-09 22:52:37 +0300
committerClement Ho <ClemMakesApps@gmail.com>2017-06-09 22:52:37 +0300
commitf08a8ae18f74ad086695d62ff78ada2796e65829 (patch)
treef65d9be5c66eb0d1ad8ceb1c13eb5c0da6bc2496 /spec
parent66bbf30ed8bb006d9a968693fef266c86ec2325f (diff)
parentabc61f260074663e5711d3814d9b7d301d07a259 (diff)
Merge commit 'abc61f260074663e5711d3814d9b7d301d07a259' into 9-3-stable
Diffstat (limited to 'spec')
-rw-r--r--spec/bin/changelog_spec.rb4
-rw-r--r--spec/controllers/admin/groups_controller_spec.rb9
-rw-r--r--spec/controllers/admin/hooks_controller_spec.rb28
-rw-r--r--spec/controllers/admin/users_controller_spec.rb15
-rw-r--r--spec/controllers/application_controller_spec.rb36
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb14
-rw-r--r--spec/controllers/groups_controller_spec.rb2
-rw-r--r--spec/controllers/health_controller_spec.rb39
-rw-r--r--spec/controllers/import/bitbucket_controller_spec.rb21
-rw-r--r--spec/controllers/import/gitlab_controller_spec.rb21
-rw-r--r--spec/controllers/metrics_controller_spec.rb70
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb2
-rw-r--r--spec/controllers/profiles_controller_spec.rb31
-rw-r--r--spec/controllers/projects/artifacts_controller_spec.rb48
-rw-r--r--spec/controllers/projects/boards/lists_controller_spec.rb2
-rw-r--r--spec/controllers/projects/deployments_controller_spec.rb71
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb28
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb28
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb (renamed from spec/controllers/projects/builds_controller_spec.rb)122
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb11
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb38
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb6
-rw-r--r--spec/controllers/projects/services_controller_spec.rb112
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb12
-rw-r--r--spec/controllers/projects_controller_spec.rb44
-rw-r--r--spec/controllers/registrations_controller_spec.rb2
-rw-r--r--spec/controllers/sessions_controller_spec.rb31
-rw-r--r--spec/controllers/snippets_controller_spec.rb40
-rw-r--r--spec/controllers/uploads_controller_spec.rb34
-rw-r--r--spec/db/production/settings.rb17
-rw-r--r--spec/db/production/settings_spec.rb58
-rw-r--r--spec/factories/ci/builds.rb13
-rw-r--r--spec/factories/ci/pipelines.rb31
-rw-r--r--spec/factories/ci/stages.rb8
-rw-r--r--spec/factories/ci/trigger_requests.rb4
-rw-r--r--spec/factories/ci/variables.rb6
-rw-r--r--spec/factories/commits.rb9
-rw-r--r--spec/factories/conversational_development_index_metrics.rb33
-rw-r--r--spec/factories/file_uploaders.rb (renamed from spec/factories/file_uploader.rb)2
-rw-r--r--spec/factories/forked_project_links.rb4
-rw-r--r--spec/factories/keys.rb19
-rw-r--r--spec/factories/lists.rb6
-rw-r--r--spec/factories/project_hooks.rb2
-rw-r--r--spec/factories/project_statistics.rb8
-rw-r--r--spec/factories/project_wikis.rb2
-rw-r--r--spec/factories/projects.rb30
-rw-r--r--spec/factories/services.rb9
-rw-r--r--spec/factories/snippets.rb1
-rw-r--r--spec/factories/users.rb4
-rw-r--r--spec/factories/web_hook_log.rb14
-rw-r--r--spec/factories/wiki_directories.rb2
-rw-r--r--spec/factories_spec.rb14
-rw-r--r--spec/features/admin/admin_builds_spec.rb16
-rw-r--r--spec/features/admin/admin_conversational_development_index_spec.rb40
-rw-r--r--spec/features/admin/admin_deploy_keys_spec.rb63
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb2
-rw-r--r--spec/features/admin/admin_groups_spec.rb4
-rw-r--r--spec/features/admin/admin_hook_logs_spec.rb40
-rw-r--r--spec/features/admin/admin_hooks_spec.rb15
-rw-r--r--spec/features/admin/admin_labels_spec.rb4
-rw-r--r--spec/features/admin/admin_system_info_spec.rb3
-rw-r--r--spec/features/admin/admin_users_spec.rb8
-rw-r--r--spec/features/admin/admin_uses_repository_checks_spec.rb2
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb15
-rw-r--r--spec/features/atom/dashboard_spec.rb9
-rw-r--r--spec/features/atom/issues_spec.rb23
-rw-r--r--spec/features/atom/users_spec.rb9
-rw-r--r--spec/features/auto_deploy_spec.rb11
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb18
-rw-r--r--spec/features/boards/boards_spec.rb251
-rw-r--r--spec/features/boards/issue_ordering_spec.rb63
-rw-r--r--spec/features/boards/keyboard_shortcut_spec.rb4
-rw-r--r--spec/features/boards/modal_filter_spec.rb32
-rw-r--r--spec/features/boards/new_issue_spec.rb16
-rw-r--r--spec/features/boards/sidebar_spec.rb56
-rw-r--r--spec/features/boards/sub_group_project_spec.rb6
-rw-r--r--spec/features/calendar_spec.rb10
-rw-r--r--spec/features/commits_spec.rb20
-rw-r--r--spec/features/container_registry_spec.rb2
-rw-r--r--spec/features/copy_as_gfm_spec.rb41
-rw-r--r--spec/features/cycle_analytics_spec.rb22
-rw-r--r--spec/features/dashboard/activity_spec.rb6
-rw-r--r--spec/features/dashboard/datetime_on_tooltips_spec.rb4
-rw-r--r--spec/features/dashboard/group_spec.rb2
-rw-r--r--spec/features/dashboard/groups_list_spec.rb6
-rw-r--r--spec/features/dashboard/issues_spec.rb89
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb22
-rw-r--r--spec/features/dashboard/milestone_filter_spec.rb58
-rw-r--r--spec/features/dashboard/project_member_activity_index_spec.rb2
-rw-r--r--spec/features/dashboard/projects_spec.rb25
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb31
-rw-r--r--spec/features/dashboard_issues_spec.rb4
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb33
-rw-r--r--spec/features/explore/groups_list_spec.rb6
-rw-r--r--spec/features/explore/new_menu_spec.rb172
-rw-r--r--spec/features/gitlab_flavored_markdown_spec.rb2
-rw-r--r--spec/features/groups/activity_spec.rb8
-rw-r--r--spec/features/groups/issues_spec.rb10
-rw-r--r--spec/features/groups/members/sorting_spec.rb4
-rw-r--r--spec/features/groups/show_spec.rb4
-rw-r--r--spec/features/issuables/issuable_list_spec.rb3
-rw-r--r--spec/features/issues/award_emoji_spec.rb16
-rw-r--r--spec/features/issues/award_spec.rb8
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb78
-rw-r--r--spec/features/issues/create_branch_merge_request_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb19
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb21
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb28
-rw-r--r--spec/features/issues/filtered_search/recent_searches_spec.rb54
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb8
-rw-r--r--spec/features/issues/form_spec.rb83
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb10
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb23
-rw-r--r--spec/features/issues/move_spec.rb8
-rw-r--r--spec/features/issues/note_polling_spec.rb17
-rw-r--r--spec/features/issues/notes_on_issues_spec.rb2
-rw-r--r--spec/features/issues/update_issues_spec.rb28
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb2
-rw-r--r--spec/features/issues_spec.rb30
-rw-r--r--spec/features/login_spec.rb4
-rw-r--r--spec/features/merge_requests/closes_issues_spec.rb4
-rw-r--r--spec/features/merge_requests/conflicts_spec.rb26
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb4
-rw-r--r--spec/features/merge_requests/deleted_source_branch_spec.rb2
-rw-r--r--spec/features/merge_requests/diff_notes_avatars_spec.rb18
-rw-r--r--spec/features/merge_requests/diff_notes_resolve_spec.rb2
-rw-r--r--spec/features/merge_requests/diffs_spec.rb6
-rw-r--r--spec/features/merge_requests/discussion_spec.rb43
-rw-r--r--spec/features/merge_requests/edit_mr_spec.rb13
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb18
-rw-r--r--spec/features/merge_requests/form_spec.rb2
-rw-r--r--spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb18
-rw-r--r--spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb15
-rw-r--r--spec/features/merge_requests/mini_pipeline_graph_spec.rb34
-rw-r--r--spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb22
-rw-r--r--spec/features/merge_requests/pipelines_spec.rb2
-rw-r--r--spec/features/merge_requests/update_merge_requests_spec.rb17
-rw-r--r--spec/features/merge_requests/user_posts_diff_notes_spec.rb6
-rw-r--r--spec/features/merge_requests/user_posts_notes_spec.rb12
-rw-r--r--spec/features/merge_requests/user_uses_slash_commands_spec.rb4
-rw-r--r--spec/features/merge_requests/versions_spec.rb8
-rw-r--r--spec/features/merge_requests/widget_deployments_spec.rb4
-rw-r--r--spec/features/merge_requests/widget_spec.rb54
-rw-r--r--spec/features/milestones/milestones_spec.rb6
-rw-r--r--spec/features/profile_spec.rb15
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb6
-rw-r--r--spec/features/profiles/preferences_spec.rb2
-rw-r--r--spec/features/projects/activity/rss_spec.rb4
-rw-r--r--spec/features/projects/artifacts/browse_spec.rb25
-rw-r--r--spec/features/projects/artifacts/download_spec.rb61
-rw-r--r--spec/features/projects/artifacts/file_spec.rb24
-rw-r--r--spec/features/projects/artifacts/raw_spec.rb25
-rw-r--r--spec/features/projects/blobs/blob_line_permalink_updater_spec.rb2
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb140
-rw-r--r--spec/features/projects/blobs/edit_spec.rb2
-rw-r--r--spec/features/projects/blobs/user_create_spec.rb2
-rw-r--r--spec/features/projects/branches_spec.rb85
-rw-r--r--spec/features/projects/commit/cherry_pick_spec.rb4
-rw-r--r--spec/features/projects/commit/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/projects/commit/rss_spec.rb8
-rw-r--r--spec/features/projects/compare_spec.rb9
-rw-r--r--spec/features/projects/developer_views_empty_project_instructions_spec.rb12
-rw-r--r--spec/features/projects/environments/environment_spec.rb57
-rw-r--r--spec/features/projects/environments/environments_spec.rb8
-rw-r--r--spec/features/projects/features_visibility_spec.rb14
-rw-r--r--spec/features/projects/files/browse_files_spec.rb16
-rw-r--r--spec/features/projects/files/dockerfile_dropdown_spec.rb4
-rw-r--r--spec/features/projects/files/find_file_keyboard_spec.rb2
-rw-r--r--spec/features/projects/files/gitignore_dropdown_spec.rb4
-rw-r--r--spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb4
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb2
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb2
-rw-r--r--spec/features/projects/files/undo_template_spec.rb2
-rw-r--r--spec/features/projects/gfm_autocomplete_load_spec.rb2
-rw-r--r--spec/features/projects/guest_navigation_menu_spec.rb62
-rw-r--r--spec/features/projects/issuable_templates_spec.rb14
-rw-r--r--spec/features/projects/issues/rss_spec.rb8
-rw-r--r--spec/features/projects/jobs_spec.rb (renamed from spec/features/projects/builds_spec.rb)161
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb10
-rw-r--r--spec/features/projects/main/rss_spec.rb4
-rw-r--r--spec/features/projects/members/group_links_spec.rb6
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb2
-rw-r--r--spec/features/projects/members/sorting_spec.rb4
-rw-r--r--spec/features/projects/new_project_spec.rb4
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb28
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb20
-rw-r--r--spec/features/projects/ref_switcher_spec.rb6
-rw-r--r--spec/features/projects/services/jira_service_spec.rb92
-rw-r--r--spec/features/projects/services/mattermost_slash_command_spec.rb18
-rw-r--r--spec/features/projects/services/slack_slash_command_spec.rb14
-rw-r--r--spec/features/projects/settings/integration_settings_spec.rb54
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb78
-rw-r--r--spec/features/projects/settings/visibility_settings_spec.rb4
-rw-r--r--spec/features/projects/snippets/create_snippet_spec.rb86
-rw-r--r--spec/features/projects/snippets/show_spec.rb10
-rw-r--r--spec/features/projects/sub_group_issuables_spec.rb32
-rw-r--r--spec/features/projects/tree/rss_spec.rb4
-rw-r--r--spec/features/projects/view_on_env_spec.rb12
-rw-r--r--spec/features/projects/wiki/markdown_preview_spec.rb8
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb38
-rw-r--r--spec/features/projects/wiki/user_git_access_wiki_page_spec.rb2
-rw-r--r--spec/features/protected_branches_spec.rb3
-rw-r--r--spec/features/protected_tags_spec.rb1
-rw-r--r--spec/features/reportable_note/commit_spec.rb33
-rw-r--r--spec/features/reportable_note/issue_spec.rb17
-rw-r--r--spec/features/reportable_note/merge_request_spec.rb26
-rw-r--r--spec/features/reportable_note/snippets_spec.rb33
-rw-r--r--spec/features/search_spec.rb27
-rw-r--r--spec/features/security/project/internal_access_spec.rb6
-rw-r--r--spec/features/security/project/private_access_spec.rb6
-rw-r--r--spec/features/security/project/public_access_spec.rb6
-rw-r--r--spec/features/snippets/create_snippet_spec.rb77
-rw-r--r--spec/features/snippets/edit_snippet_spec.rb38
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb14
-rw-r--r--spec/features/snippets/public_snippets_spec.rb2
-rw-r--r--spec/features/snippets/show_spec.rb10
-rw-r--r--spec/features/task_lists_spec.rb17
-rw-r--r--spec/features/todos/todos_filtering_spec.rb8
-rw-r--r--spec/features/todos/todos_spec.rb12
-rw-r--r--spec/features/u2f_spec.rb2
-rw-r--r--spec/features/unsubscribe_links_spec.rb4
-rw-r--r--spec/features/uploads/user_uploads_file_to_note_spec.rb76
-rw-r--r--spec/features/user_callout_spec.rb2
-rw-r--r--spec/features/users/projects_spec.rb2
-rw-r--r--spec/features/users/rss_spec.rb4
-rw-r--r--spec/features/users/snippets_spec.rb6
-rw-r--r--spec/features/users_spec.rb8
-rw-r--r--spec/features/variables_spec.rb48
-rw-r--r--spec/finders/events_finder_spec.rb44
-rw-r--r--spec/finders/projects_finder_spec.rb15
-rw-r--r--spec/finders/users_finder_spec.rb66
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request.json4
-rw-r--r--spec/fixtures/api/schemas/list.json2
-rw-r--r--spec/fixtures/api/schemas/pipeline_schedule.json41
-rw-r--r--spec/fixtures/api/schemas/pipeline_schedules.json4
-rw-r--r--spec/helpers/application_helper_spec.rb25
-rw-r--r--spec/helpers/avatars_helper_spec.rb103
-rw-r--r--spec/helpers/blob_helper_spec.rb31
-rw-r--r--spec/helpers/diff_helper_spec.rb41
-rw-r--r--spec/helpers/issuables_helper_spec.rb18
-rw-r--r--spec/helpers/notes_helper_spec.rb18
-rw-r--r--spec/helpers/notifications_helper_spec.rb6
-rw-r--r--spec/helpers/profiles_helper_spec.rb36
-rw-r--r--spec/helpers/projects_helper_spec.rb8
-rw-r--r--spec/helpers/rss_helper_spec.rb8
-rw-r--r--spec/helpers/submodule_helper_spec.rb26
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb2
-rw-r--r--spec/javascripts/abuse_reports_spec.js4
-rw-r--r--spec/javascripts/activities_spec.js6
-rw-r--r--spec/javascripts/ajax_loading_spinner_spec.js8
-rw-r--r--spec/javascripts/api_spec.js281
-rw-r--r--spec/javascripts/awards_handler_spec.js2
-rw-r--r--spec/javascripts/behaviors/autosize_spec.js2
-rw-r--r--spec/javascripts/behaviors/bind_in_out_spec.js12
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js2
-rw-r--r--spec/javascripts/behaviors/requires_input_spec.js2
-rw-r--r--spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js51
-rw-r--r--spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js38
-rw-r--r--spec/javascripts/blob/create_branch_dropdown_spec.js7
-rw-r--r--spec/javascripts/blob/target_branch_dropdown_spec.js11
-rw-r--r--spec/javascripts/boards/board_card_spec.js10
-rw-r--r--spec/javascripts/boards/board_new_issue_spec.js49
-rw-r--r--spec/javascripts/boards/components/board_spec.js112
-rw-r--r--spec/javascripts/boards/issue_card_spec.js2
-rw-r--r--spec/javascripts/build_spec.js310
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_spec.js11
-rw-r--r--spec/javascripts/commits_spec.js32
-rw-r--r--spec/javascripts/copy_as_gfm_spec.js49
-rw-r--r--spec/javascripts/datetime_utility_spec.js22
-rw-r--r--spec/javascripts/deploy_keys/components/key_spec.js18
-rw-r--r--spec/javascripts/diff_comments_store_spec.js6
-rw-r--r--spec/javascripts/droplab/drop_down_spec.js53
-rw-r--r--spec/javascripts/droplab/hook_spec.js8
-rw-r--r--spec/javascripts/droplab/plugins/ajax_filter_spec.js72
-rw-r--r--spec/javascripts/environments/environment_spec.js4
-rw-r--r--spec/javascripts/environments/environment_table_spec.js2
-rw-r--r--spec/javascripts/environments/environments_store_spec.js9
-rw-r--r--spec/javascripts/extensions/array_spec.js2
-rw-r--r--spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js4
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js10
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js58
-rw-r--r--spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js8
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js98
-rw-r--r--spec/javascripts/filtered_search/filtered_search_token_keys_spec.js4
-rw-r--r--spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js28
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js410
-rw-r--r--spec/javascripts/filtered_search/services/recent_searches_service_spec.js52
-rw-r--r--spec/javascripts/fixtures/balsamiq.rb18
-rw-r--r--spec/javascripts/fixtures/balsamiq_viewer.html.haml1
-rw-r--r--spec/javascripts/fixtures/boards.rb28
-rw-r--r--spec/javascripts/fixtures/issuable_filter.html.haml2
-rw-r--r--spec/javascripts/fixtures/issues.rb11
-rw-r--r--spec/javascripts/fixtures/jobs.rb (renamed from spec/javascripts/fixtures/builds.rb)2
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb16
-rw-r--r--spec/javascripts/fixtures/pipelines.rb35
-rw-r--r--spec/javascripts/fixtures/raw.rb6
-rw-r--r--spec/javascripts/fixtures/services.rb31
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js22
-rw-r--r--spec/javascripts/gl_dropdown_spec.js7
-rw-r--r--spec/javascripts/gl_emoji_spec.js31
-rw-r--r--spec/javascripts/gl_field_errors_spec.js2
-rw-r--r--spec/javascripts/gl_form_spec.js28
-rw-r--r--spec/javascripts/header_spec.js4
-rw-r--r--spec/javascripts/helpers/class_spec_helper.js4
-rw-r--r--spec/javascripts/helpers/class_spec_helper_spec.js4
-rw-r--r--spec/javascripts/helpers/filtered_search_spec_helper.js17
-rw-r--r--spec/javascripts/integrations/integration_settings_form_spec.js199
-rw-r--r--spec/javascripts/issuable_spec.js18
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js377
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js99
-rw-r--r--spec/javascripts/issue_show/components/edit_actions_spec.js147
-rw-r--r--spec/javascripts/issue_show/components/fields/description_spec.js56
-rw-r--r--spec/javascripts/issue_show/components/fields/description_template_spec.js49
-rw-r--r--spec/javascripts/issue_show/components/fields/project_move_spec.js38
-rw-r--r--spec/javascripts/issue_show/components/fields/title_spec.js30
-rw-r--r--spec/javascripts/issue_show/components/form_spec.js68
-rw-r--r--spec/javascripts/issue_show/components/title_spec.js75
-rw-r--r--spec/javascripts/issue_show/mock_data.js3
-rw-r--r--spec/javascripts/issue_spec.js2
-rw-r--r--spec/javascripts/labels_issue_sidebar_spec.js17
-rw-r--r--spec/javascripts/lib/utils/ajax_cache_spec.js88
-rw-r--r--spec/javascripts/lib/utils/cache_spec.js65
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js14
-rw-r--r--spec/javascripts/lib/utils/number_utility_spec.js9
-rw-r--r--spec/javascripts/lib/utils/poll_spec.js93
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js2
-rw-r--r--spec/javascripts/lib/utils/users_cache_spec.js136
-rw-r--r--spec/javascripts/line_highlighter_spec.js10
-rw-r--r--spec/javascripts/merge_request_notes_spec.js61
-rw-r--r--spec/javascripts/merge_request_spec.js6
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js73
-rw-r--r--spec/javascripts/new_branch_spec.js2
-rw-r--r--spec/javascripts/notebook/cells/markdown_spec.js57
-rw-r--r--spec/javascripts/notes_spec.js143
-rw-r--r--spec/javascripts/pager_spec.js2
-rw-r--r--spec/javascripts/pipelines/graph/action_component_spec.js6
-rw-r--r--spec/javascripts/pipelines/graph/dropdown_action_component_spec.js4
-rw-r--r--spec/javascripts/pipelines/graph/graph_component_spec.js59
-rw-r--r--spec/javascripts/pipelines/graph/job_component_spec.js24
-rw-r--r--spec/javascripts/pipelines/header_component_spec.js63
-rw-r--r--spec/javascripts/pipelines/mock_data.js107
-rw-r--r--spec/javascripts/pipelines/pipeline_details_mediator_spec.js41
-rw-r--r--spec/javascripts/pipelines/pipeline_store_spec.js27
-rw-r--r--spec/javascripts/pipelines/pipeline_url_spec.js5
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js9
-rw-r--r--spec/javascripts/pretty_time_spec.js2
-rw-r--r--spec/javascripts/project_title_spec.js11
-rw-r--r--spec/javascripts/raven/raven_config_spec.js18
-rw-r--r--spec/javascripts/search_autocomplete_spec.js9
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js6
-rw-r--r--spec/javascripts/sidebar/sidebar_assignees_spec.js1
-rw-r--r--spec/javascripts/sidebar/sidebar_bundle_spec.js42
-rw-r--r--spec/javascripts/sidebar/sidebar_mediator_spec.js1
-rw-r--r--spec/javascripts/sidebar/sidebar_service_spec.js1
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js2
-rw-r--r--spec/javascripts/smart_interval_spec.js2
-rw-r--r--spec/javascripts/syntax_highlight_spec.js2
-rw-r--r--spec/javascripts/test_bundle.js18
-rw-r--r--spec/javascripts/todos_spec.js4
-rw-r--r--spec/javascripts/u2f/authenticate_spec.js10
-rw-r--r--spec/javascripts/u2f/mock_u2f_device.js6
-rw-r--r--spec/javascripts/u2f/register_spec.js10
-rw-r--r--spec/javascripts/version_check_image_spec.js7
-rw-r--r--spec/javascripts/visibility_select_spec.js8
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js51
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js16
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js37
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js37
-rw-r--r--spec/javascripts/vue_shared/components/commit_spec.js14
-rw-r--r--spec/javascripts/vue_shared/components/header_ci_component_spec.js93
-rw-r--r--spec/javascripts/vue_shared/components/loading_icon_spec.js53
-rw-r--r--spec/javascripts/vue_shared/components/markdown/field_spec.js121
-rw-r--r--spec/javascripts/vue_shared/components/markdown/header_spec.js67
-rw-r--r--spec/javascripts/vue_shared/components/pipelines_table_row_spec.js96
-rw-r--r--spec/javascripts/vue_shared/components/pipelines_table_spec.js8
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js68
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar_image_spec.js54
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar_link_spec.js50
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar_svg_spec.js29
-rw-r--r--spec/javascripts/zen_mode_spec.js2
-rw-r--r--spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb15
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb16
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb5
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb25
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb168
-rw-r--r--spec/lib/container_registry/blob_spec.rb2
-rw-r--r--spec/lib/container_registry/client_spec.rb39
-rw-r--r--spec/lib/expand_variables_spec.rb6
-rw-r--r--spec/lib/feature_spec.rb26
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb25
-rw-r--r--spec/lib/gitlab/auth_spec.rb30
-rw-r--r--spec/lib/gitlab/backup/manager_spec.rb4
-rw-r--r--spec/lib/gitlab/backup/repository_spec.rb63
-rw-r--r--spec/lib/gitlab/chat_commands/command_spec.rb7
-rw-r--r--spec/lib/gitlab/chat_commands/deploy_spec.rb7
-rw-r--r--spec/lib/gitlab/checks/change_access_spec.rb106
-rw-r--r--spec/lib/gitlab/ci/config/entry/global_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/entry/variables_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/stage/seed_spec.rb57
-rw-r--r--spec/lib/gitlab/ci/status/build/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/status/build/play_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb43
-rw-r--r--spec/lib/gitlab/ci_access_spec.rb15
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb2
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb7
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb10
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb1
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb19
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb52
-rw-r--r--spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb74
-rw-r--r--spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb82
-rw-r--r--spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb60
-rw-r--r--spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb66
-rw-r--r--spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb84
-rw-r--r--spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb94
-rw-r--r--spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb53
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb96
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb69
-rw-r--r--spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb87
-rw-r--r--spec/lib/gitlab/dependency_linker_spec.rb85
-rw-r--r--spec/lib/gitlab/diff/diff_refs_spec.rb61
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb4
-rw-r--r--spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb14
-rw-r--r--spec/lib/gitlab/diff/inline_diff_marker_spec.rb18
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb54
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb323
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb (renamed from spec/lib/gitlab/git/encoding_helper_spec.rb)22
-rw-r--r--spec/lib/gitlab/etag_caching/middleware_spec.rb19
-rw-r--r--spec/lib/gitlab/etag_caching/router_spec.rb60
-rw-r--r--spec/lib/gitlab/file_finder_spec.rb21
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb45
-rw-r--r--spec/lib/gitlab/git/compare_spec.rb4
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb67
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb66
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb44
-rw-r--r--spec/lib/gitlab/git/util_spec.rb2
-rw-r--r--spec/lib/gitlab/git_access_spec.rb290
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb7
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_spec.rb54
-rw-r--r--spec/lib/gitlab/gitaly_client/diff_spec.rb30
-rw-r--r--spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb59
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_spec.rb30
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb82
-rw-r--r--spec/lib/gitlab/health_checks/fs_shards_check_spec.rb83
-rw-r--r--spec/lib/gitlab/health_checks/prometheus_text_format_spec.rb41
-rw-r--r--spec/lib/gitlab/highlight_spec.rb9
-rw-r--r--spec/lib/gitlab/i18n_spec.rb32
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml12
-rw-r--r--spec/lib/gitlab/import_export/project.json36
-rw-r--r--spec/lib/gitlab/import_export/relation_factory_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml17
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb14
-rw-r--r--spec/lib/gitlab/metrics_spec.rb145
-rw-r--r--spec/lib/gitlab/o_auth/provider_spec.rb42
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb44
-rw-r--r--spec/lib/gitlab/otp_key_rotator_spec.rb70
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb384
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb6
-rw-r--r--spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb37
-rw-r--r--spec/lib/gitlab/prometheus_client_spec.rb (renamed from spec/lib/gitlab/prometheus_spec.rb)2
-rw-r--r--spec/lib/gitlab/regex_spec.rb392
-rw-r--r--spec/lib/gitlab/repo_path_spec.rb2
-rw-r--r--spec/lib/gitlab/string_range_marker_spec.rb36
-rw-r--r--spec/lib/gitlab/string_regex_marker_spec.rb18
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb11
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb9
-rw-r--r--spec/lib/gitlab/user_access_spec.rb48
-rw-r--r--spec/lib/gitlab/utils_spec.rb11
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb4
-rw-r--r--spec/lib/system_check/simple_executor_spec.rb223
-rw-r--r--spec/lib/system_check_spec.rb36
-rw-r--r--spec/mailers/notify_spec.rb9
-rw-r--r--spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb33
-rw-r--r--spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb32
-rw-r--r--spec/migrations/migrate_build_events_to_pipeline_events_spec.rb74
-rw-r--r--spec/migrations/migrate_build_stage_reference_spec.rb62
-rw-r--r--spec/migrations/migrate_old_artifacts_spec.rb117
-rw-r--r--spec/migrations/migrate_pipeline_stages_spec.rb56
-rw-r--r--spec/migrations/migrate_user_project_view_spec.rb7
-rw-r--r--spec/migrations/update_retried_for_ci_build_spec.rb17
-rw-r--r--spec/models/abuse_report_spec.rb4
-rw-r--r--spec/models/application_setting_spec.rb2
-rw-r--r--spec/models/blob_spec.rb46
-rw-r--r--spec/models/blob_viewer/base_spec.rb159
-rw-r--r--spec/models/blob_viewer/changelog_spec.rb27
-rw-r--r--spec/models/blob_viewer/composer_json_spec.rb25
-rw-r--r--spec/models/blob_viewer/gemspec_spec.rb25
-rw-r--r--spec/models/blob_viewer/gitlab_ci_yml_spec.rb32
-rw-r--r--spec/models/blob_viewer/license_spec.rb34
-rw-r--r--spec/models/blob_viewer/package_json_spec.rb25
-rw-r--r--spec/models/blob_viewer/podspec_json_spec.rb25
-rw-r--r--spec/models/blob_viewer/podspec_spec.rb25
-rw-r--r--spec/models/blob_viewer/route_map_spec.rb38
-rw-r--r--spec/models/blob_viewer/server_side_spec.rb41
-rw-r--r--spec/models/ci/build_spec.rb169
-rw-r--r--spec/models/ci/legacy_stage_spec.rb (renamed from spec/models/ci/stage_spec.rb)2
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb8
-rw-r--r--spec/models/ci/pipeline_spec.rb81
-rw-r--r--spec/models/ci/variable_spec.rb35
-rw-r--r--spec/models/commit_spec.rb28
-rw-r--r--spec/models/concerns/discussion_on_diff_spec.rb26
-rw-r--r--spec/models/concerns/mentionable_spec.rb49
-rw-r--r--spec/models/cycle_analytics/test_spec.rb2
-rw-r--r--spec/models/deployment_spec.rb22
-rw-r--r--spec/models/diff_discussion_spec.rb7
-rw-r--r--spec/models/diff_note_spec.rb31
-rw-r--r--spec/models/environment_spec.rb31
-rw-r--r--spec/models/forked_project_link_spec.rb4
-rw-r--r--spec/models/global_milestone_spec.rb2
-rw-r--r--spec/models/group_spec.rb16
-rw-r--r--spec/models/hooks/service_hook_spec.rb35
-rw-r--r--spec/models/hooks/system_hook_spec.rb43
-rw-r--r--spec/models/hooks/web_hook_log_spec.rb30
-rw-r--r--spec/models/hooks/web_hook_spec.rb93
-rw-r--r--spec/models/key_spec.rb10
-rw-r--r--spec/models/label_spec.rb17
-rw-r--r--spec/models/merge_request_diff_spec.rb11
-rw-r--r--spec/models/merge_request_spec.rb71
-rw-r--r--spec/models/milestone_spec.rb13
-rw-r--r--spec/models/namespace_spec.rb4
-rw-r--r--spec/models/pages_domain_spec.rb51
-rw-r--r--spec/models/personal_access_token_spec.rb20
-rw-r--r--spec/models/project_authorization_spec.rb2
-rw-r--r--spec/models/project_services/asana_service_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/issue_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/merge_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/note_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/pipeline_message_spec.rb12
-rw-r--r--spec/models/project_services/chat_message/push_message_spec.rb20
-rw-r--r--spec/models/project_services/chat_message/wiki_page_message_spec.rb4
-rw-r--r--spec/models/project_services/jira_service_spec.rb184
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb41
-rw-r--r--spec/models/project_services/pivotaltracker_service_spec.rb2
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb32
-rw-r--r--spec/models/project_snippet_spec.rb3
-rw-r--r--spec/models/project_spec.rb157
-rw-r--r--spec/models/project_statistics_spec.rb4
-rw-r--r--spec/models/project_team_spec.rb151
-rw-r--r--spec/models/project_wiki_spec.rb18
-rw-r--r--spec/models/protected_branch/merge_access_level_spec.rb5
-rw-r--r--spec/models/protected_branch/push_access_level_spec.rb5
-rw-r--r--spec/models/protected_branch_spec.rb3
-rw-r--r--spec/models/repository_spec.rb110
-rw-r--r--spec/models/user_spec.rb113
-rw-r--r--spec/policies/deploy_key_policy_spec.rb56
-rw-r--r--spec/policies/group_policy_spec.rb32
-rw-r--r--spec/policies/project_policy_spec.rb2
-rw-r--r--spec/policies/project_snippet_policy_spec.rb4
-rw-r--r--spec/presenters/conversational_development_index/metric_presenter_spec.rb36
-rw-r--r--spec/presenters/projects/settings/deploy_keys_presenter_spec.rb4
-rw-r--r--spec/requests/api/branches_spec.rb13
-rw-r--r--spec/requests/api/commit_statuses_spec.rb4
-rw-r--r--spec/requests/api/commits_spec.rb4
-rw-r--r--spec/requests/api/deploy_keys_spec.rb69
-rw-r--r--spec/requests/api/events_spec.rb142
-rw-r--r--spec/requests/api/features_spec.rb104
-rw-r--r--spec/requests/api/files_spec.rb21
-rw-r--r--spec/requests/api/groups_spec.rb4
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb297
-rw-r--r--spec/requests/api/pipelines_spec.rb26
-rw-r--r--spec/requests/api/project_hooks_spec.rb4
-rw-r--r--spec/requests/api/project_snippets_spec.rb28
-rw-r--r--spec/requests/api/projects_spec.rb114
-rw-r--r--spec/requests/api/snippets_spec.rb27
-rw-r--r--spec/requests/api/system_hooks_spec.rb3
-rw-r--r--spec/requests/api/users_spec.rb114
-rw-r--r--spec/requests/api/v3/branches_spec.rb13
-rw-r--r--spec/requests/api/v3/commits_spec.rb4
-rw-r--r--spec/requests/api/v3/deploy_keys_spec.rb9
-rw-r--r--spec/requests/api/v3/files_spec.rb4
-rw-r--r--spec/requests/api/v3/groups_spec.rb4
-rw-r--r--spec/requests/api/v3/project_hooks_spec.rb4
-rw-r--r--spec/requests/api/v3/projects_spec.rb6
-rw-r--r--spec/requests/api/v3/system_hooks_spec.rb3
-rw-r--r--spec/requests/api/variables_spec.rb7
-rw-r--r--spec/requests/ci/api/builds_spec.rb2
-rw-r--r--spec/requests/git_http_spec.rb662
-rw-r--r--spec/requests/jwt_controller_spec.rb13
-rw-r--r--spec/requests/lfs_http_spec.rb44
-rw-r--r--spec/requests/openid_connect_spec.rb6
-rw-r--r--spec/requests/projects/cycle_analytics_events_spec.rb6
-rw-r--r--spec/routing/admin_routing_spec.rb12
-rw-r--r--spec/routing/project_routing_spec.rb16
-rw-r--r--spec/routing/routing_spec.rb41
-rw-r--r--spec/rubocop/cop/activerecord_serialize_spec.rb33
-rw-r--r--spec/rubocop/cop/migration/update_column_in_batches_spec.rb94
-rw-r--r--spec/rubocop/cop/polymorphic_associations_spec.rb33
-rw-r--r--spec/rubocop/cop/redirect_with_status_spec.rb86
-rw-r--r--spec/serializers/analytics_issue_entity_spec.rb2
-rw-r--r--spec/serializers/analytics_issue_serializer_spec.rb2
-rw-r--r--spec/serializers/build_action_entity_spec.rb12
-rw-r--r--spec/serializers/build_artifact_entity_spec.rb22
-rw-r--r--spec/serializers/build_details_entity_spec.rb67
-rw-r--r--spec/serializers/build_entity_spec.rb9
-rw-r--r--spec/serializers/deploy_key_entity_spec.rb61
-rw-r--r--spec/serializers/merge_request_entity_spec.rb19
-rw-r--r--spec/serializers/pipeline_details_entity_spec.rb120
-rw-r--r--spec/serializers/pipeline_entity_spec.rb52
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb2
-rw-r--r--spec/serializers/runner_entity_spec.rb23
-rw-r--r--spec/serializers/user_entity_spec.rb6
-rw-r--r--spec/services/boards/create_service_spec.rb5
-rw-r--r--spec/services/boards/issues/list_service_spec.rb11
-rw-r--r--spec/services/boards/lists/list_service_spec.rb31
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb85
-rw-r--r--spec/services/ci/create_trigger_request_service_spec.rb2
-rw-r--r--spec/services/ci/play_build_service_spec.rb17
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb7
-rw-r--r--spec/services/ci/retry_build_service_spec.rb18
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb7
-rw-r--r--spec/services/cohorts_service_spec.rb2
-rw-r--r--spec/services/create_deployment_service_spec.rb246
-rw-r--r--spec/services/delete_merged_branches_service_spec.rb31
-rw-r--r--spec/services/discussions/update_diff_position_service_spec.rb (renamed from spec/services/notes/diff_position_update_service_spec.rb)38
-rw-r--r--spec/services/git_push_service_spec.rb27
-rw-r--r--spec/services/git_tag_push_service_spec.rb14
-rw-r--r--spec/services/gravatar_service_spec.rb20
-rw-r--r--spec/services/issuable/bulk_update_service_spec.rb4
-rw-r--r--spec/services/issues/build_service_spec.rb2
-rw-r--r--spec/services/issues/close_service_spec.rb20
-rw-r--r--spec/services/issues/reopen_service_spec.rb7
-rw-r--r--spec/services/issues/resolve_discussions_spec.rb2
-rw-r--r--spec/services/members/create_service_spec.rb16
-rw-r--r--spec/services/merge_requests/close_service_spec.rb2
-rw-r--r--spec/services/merge_requests/conflicts/resolve_service_spec.rb42
-rw-r--r--spec/services/merge_requests/create_service_spec.rb41
-rw-r--r--spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb6
-rw-r--r--spec/services/merge_requests/post_merge_service_spec.rb15
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb2
-rw-r--r--spec/services/merge_requests/reopen_service_spec.rb2
-rw-r--r--spec/services/merge_requests/update_service_spec.rb47
-rw-r--r--spec/services/notification_service_spec.rb2
-rw-r--r--spec/services/projects/create_service_spec.rb10
-rw-r--r--spec/services/projects/fork_service_spec.rb8
-rw-r--r--spec/services/projects/import_service_spec.rb2
-rw-r--r--spec/services/projects/participants_service_spec.rb5
-rw-r--r--spec/services/projects/transfer_service_spec.rb1
-rw-r--r--spec/services/search_service_spec.rb9
-rw-r--r--spec/services/slash_commands/interpret_service_spec.rb2
-rw-r--r--spec/services/submit_usage_ping_service_spec.rb101
-rw-r--r--spec/services/system_note_service_spec.rb81
-rw-r--r--spec/services/users/destroy_service_spec.rb8
-rw-r--r--spec/services/web_hook_service_spec.rb137
-rw-r--r--spec/services/wiki_pages/create_service_spec.rb40
-rw-r--r--spec/services/wiki_pages/destroy_service_spec.rb19
-rw-r--r--spec/services/wiki_pages/update_service_spec.rb42
-rw-r--r--spec/sidekiq/cron/job_gem_dependency_spec.rb18
-rw-r--r--spec/spec_helper.rb19
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_examples.rb16
-rw-r--r--spec/support/cycle_analytics_helpers.rb47
-rw-r--r--spec/support/db_cleaner.rb4
-rw-r--r--spec/support/dropzone_helper.rb40
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb4
-rw-r--r--spec/support/features/reportable_note_shared_examples.rb36
-rw-r--r--spec/support/features/rss_shared_examples.rb24
-rw-r--r--spec/support/filtered_search_helpers.rb6
-rwxr-xr-xspec/support/generate-seed-repo-rb162
-rw-r--r--spec/support/git_http_helpers.rb22
-rw-r--r--spec/support/gitaly.rb3
-rw-r--r--spec/support/helpers/key_generator_helper.rb41
-rw-r--r--spec/support/helpers/note_interaction_helpers.rb8
-rw-r--r--spec/support/import_spec_helper.rb2
-rw-r--r--spec/support/issuable_shared_examples.rb7
-rw-r--r--spec/support/javascript_fixtures_helpers.rb2
-rw-r--r--spec/support/kubernetes_helpers.rb10
-rw-r--r--spec/support/matchers/execute_check.rb23
-rw-r--r--spec/support/matchers/gitaly_matchers.rb6
-rw-r--r--spec/support/migrations_helpers.rb29
-rw-r--r--spec/support/prometheus_helpers.rb10
-rw-r--r--spec/support/protected_branches/access_control_ce_shared_examples.rb (renamed from spec/features/protected_branches/access_control_ce_spec.rb)6
-rw-r--r--spec/support/protected_tags/access_control_ce_shared_examples.rb (renamed from spec/features/protected_tags/access_control_ce_spec.rb)2
-rw-r--r--spec/support/rake_helpers.rb5
-rw-r--r--spec/support/repo_helpers.rb4
-rw-r--r--spec/support/seed_repo.rb11
-rw-r--r--spec/support/snippets_shared_examples.rb2
-rw-r--r--spec/support/stub_configuration.rb4
-rw-r--r--spec/support/target_branch_helpers.rb2
-rw-r--r--spec/support/test_env.rb88
-rw-r--r--spec/support/time_tracking_shared_examples.rb8
-rw-r--r--spec/support/wait_for_ajax.rb18
-rw-r--r--spec/support/wait_for_requests.rb38
-rw-r--r--spec/support/workhorse_helpers.rb2
-rw-r--r--spec/tasks/gitlab/gitaly_rake_spec.rb24
-rw-r--r--spec/tasks/tokens_spec.rb6
-rw-r--r--spec/uploaders/artifact_uploader_spec.rb38
-rw-r--r--spec/uploaders/file_mover_spec.rb63
-rw-r--r--spec/uploaders/gitlab_uploader_spec.rb56
-rw-r--r--spec/uploaders/lfs_object_uploader_spec.rb31
-rw-r--r--spec/uploaders/records_uploads_spec.rb9
-rw-r--r--spec/validators/dynamic_path_validator_spec.rb35
-rw-r--r--spec/views/ci/status/_badge.html.haml_spec.rb2
-rw-r--r--spec/views/projects/_last_commit.html.haml_spec.rb22
-rw-r--r--spec/views/projects/blob/_viewer.html.haml_spec.rb18
-rw-r--r--spec/views/projects/jobs/_build.html.haml_spec.rb (renamed from spec/views/projects/builds/_build.html.haml_spec.rb)2
-rw-r--r--spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb (renamed from spec/views/projects/builds/_generic_commit_status.html.haml_spec.rb)0
-rw-r--r--spec/views/projects/jobs/show.html.haml_spec.rb (renamed from spec/views/projects/builds/show.html.haml_spec.rb)4
-rw-r--r--spec/views/projects/tree/show.html.haml_spec.rb8
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb2
-rw-r--r--spec/workers/gitlab_usage_ping_worker_spec.rb18
-rw-r--r--spec/workers/namespaceless_project_destroy_worker_spec.rb79
-rw-r--r--spec/workers/pipeline_metrics_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_schedule_worker_spec.rb52
-rw-r--r--spec/workers/post_receive_spec.rb30
-rw-r--r--spec/workers/process_commit_worker_spec.rb12
-rw-r--r--spec/workers/remove_old_web_hook_logs_worker_spec.rb18
-rw-r--r--spec/workers/repository_check/clear_worker_spec.rb2
-rw-r--r--spec/workers/repository_fork_worker_spec.rb26
-rw-r--r--spec/workers/repository_import_worker_spec.rb23
711 files changed, 18522 insertions, 5097 deletions
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb
index 7f4298db59f..91aff0db7cc 100644
--- a/spec/bin/changelog_spec.rb
+++ b/spec/bin/changelog_spec.rb
@@ -46,9 +46,7 @@ describe 'bin/changelog' do
it 'parses -h' do
expect do
- $stdout = StringIO.new
-
- described_class.parse(%w[foo -h bar])
+ expect { described_class.parse(%w[foo -h bar]) }.to output.to_stdout
end.to raise_error(SystemExit)
end
diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb
index c29b2fe8946..ddf38967dd7 100644
--- a/spec/controllers/admin/groups_controller_spec.rb
+++ b/spec/controllers/admin/groups_controller_spec.rb
@@ -36,6 +36,15 @@ describe Admin::GroupsController do
expect(group.users).to include group_user
end
+ it 'can add unlimited members' do
+ put :members_update, id: group,
+ user_ids: 1.upto(1000).to_a.join(','),
+ access_level: Gitlab::Access::GUEST
+
+ expect(response).to set_flash.to 'Users were successfully added.'
+ expect(response).to redirect_to(admin_group_path(group))
+ end
+
it 'adds no user to members' do
put :members_update, id: group,
user_ids: '',
diff --git a/spec/controllers/admin/hooks_controller_spec.rb b/spec/controllers/admin/hooks_controller_spec.rb
new file mode 100644
index 00000000000..1d1070e90f4
--- /dev/null
+++ b/spec/controllers/admin/hooks_controller_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Admin::HooksController do
+ let(:admin) { create(:admin) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe 'POST #create' do
+ it 'sets all parameters' do
+ hook_params = {
+ enable_ssl_verification: true,
+ push_events: true,
+ tag_push_events: true,
+ repository_update_events: true,
+ token: "TEST TOKEN",
+ url: "http://example.com"
+ }
+
+ post :create, hook: hook_params
+
+ expect(response).to have_http_status(302)
+ expect(SystemHook.all.size).to eq(1)
+ expect(SystemHook.first).to have_attributes(hook_params)
+ end
+ end
+end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 2ab2ca1b667..7d6c317482f 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -10,15 +10,26 @@ describe Admin::UsersController do
describe 'DELETE #user with projects' do
let(:project) { create(:empty_project, namespace: user.namespace) }
+ let!(:issue) { create(:issue, author: user) }
before do
project.team << [user, :developer]
end
- it 'deletes user' do
+ it 'deletes user and ghosts their contributions' do
delete :destroy, id: user.username, format: :json
+
+ expect(response).to have_http_status(200)
+ expect(User.exists?(user.id)).to be_falsy
+ expect(issue.reload.author).to be_ghost
+ end
+
+ it 'deletes the user and their contributions when hard delete is specified' do
+ delete :destroy, id: user.username, hard_delete: true, format: :json
+
expect(response).to have_http_status(200)
- expect { User.find(user.id) }.to raise_exception(ActiveRecord::RecordNotFound)
+ expect(User.exists?(user.id)).to be_falsy
+ expect(Issue.exists?(issue.id)).to be_falsy
end
end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index d40aae04fc3..3f99e2ff596 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -99,6 +99,42 @@ describe ApplicationController do
end
end
+ describe '#authenticate_user_from_rss_token' do
+ describe "authenticating a user from an RSS token" do
+ controller(described_class) do
+ def index
+ render text: 'authenticated'
+ end
+ end
+
+ context "when the 'rss_token' param is populated with the RSS token" do
+ context 'when the request format is atom' do
+ it "logs the user in" do
+ get :index, rss_token: user.rss_token, format: :atom
+ expect(response).to have_http_status 200
+ expect(response.body).to eq 'authenticated'
+ end
+ end
+
+ context 'when the request format is not atom' do
+ it "doesn't log the user in" do
+ get :index, rss_token: user.rss_token
+ expect(response.status).not_to have_http_status 200
+ expect(response.body).not_to eq 'authenticated'
+ end
+ end
+ end
+
+ context "when the 'rss_token' param is populated with an invalid RSS token" do
+ it "doesn't log the user" do
+ get :index, rss_token: "token"
+ expect(response.status).not_to eq 200
+ expect(response.body).not_to eq 'authenticated'
+ end
+ end
+ end
+ end
+
describe '#route_not_found' do
it 'renders 404 if authenticated' do
allow(controller).to receive(:current_user).and_return(user)
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 0c624def135..4c3a5ec49ef 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -97,6 +97,20 @@ describe AutocompleteController do
it { expect(body.size).to eq User.count }
end
+ context 'limited users per page' do
+ let(:per_page) { 2 }
+
+ before do
+ sign_in(user)
+ get(:users, per_page: per_page)
+ end
+
+ let(:body) { JSON.parse(response.body) }
+
+ it { expect(body).to be_kind_of(Array) }
+ it { expect(body.size).to eq per_page }
+ end
+
context 'unauthenticated user' do
let(:public_project) { create(:project, :public) }
let(:body) { JSON.parse(response.body) }
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index c8c1797e4ba..b0b24b1de1b 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -68,7 +68,7 @@ describe GroupsController do
before do
create_list(:award_emoji, 3, awardable: issue_2)
create_list(:award_emoji, 2, awardable: issue_1)
- create_list(:award_emoji, 2, :downvote, awardable: issue_2,)
+ create_list(:award_emoji, 2, :downvote, awardable: issue_2)
sign_in(user)
end
diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb
index b8b6e0c3a88..e7c19b47a6a 100644
--- a/spec/controllers/health_controller_spec.rb
+++ b/spec/controllers/health_controller_spec.rb
@@ -54,43 +54,4 @@ describe HealthController do
end
end
end
-
- describe '#metrics' do
- context 'authorization token provided' do
- before do
- request.headers['TOKEN'] = token
- end
-
- it 'returns DB ping metrics' do
- get :metrics
- expect(response.body).to match(/^db_ping_timeout 0$/)
- expect(response.body).to match(/^db_ping_success 1$/)
- expect(response.body).to match(/^db_ping_latency [0-9\.]+$/)
- end
-
- it 'returns Redis ping metrics' do
- get :metrics
- expect(response.body).to match(/^redis_ping_timeout 0$/)
- expect(response.body).to match(/^redis_ping_success 1$/)
- expect(response.body).to match(/^redis_ping_latency [0-9\.]+$/)
- end
-
- it 'returns file system check metrics' do
- get :metrics
- expect(response.body).to match(/^filesystem_access_latency{shard="default"} [0-9\.]+$/)
- expect(response.body).to match(/^filesystem_accessible{shard="default"} 1$/)
- expect(response.body).to match(/^filesystem_write_latency{shard="default"} [0-9\.]+$/)
- expect(response.body).to match(/^filesystem_writable{shard="default"} 1$/)
- expect(response.body).to match(/^filesystem_read_latency{shard="default"} [0-9\.]+$/)
- expect(response.body).to match(/^filesystem_readable{shard="default"} 1$/)
- end
- end
-
- context 'without authorization token' do
- it 'returns proper response' do
- get :metrics
- expect(response.status).to eq(404)
- end
- end
- end
end
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index 010e3180ea4..0be7bc6a045 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -133,9 +133,13 @@ describe Import::BitbucketController do
end
context "when a namespace with the Bitbucket user's username already exists" do
- let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
+ let!(:existing_namespace) { create(:group, name: other_username) }
context "when the namespace is owned by the GitLab user" do
+ before do
+ existing_namespace.add_owner(user)
+ end
+
it "takes the existing namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).with(bitbucket_repo, bitbucket_repo.name, existing_namespace, user, access_params).
@@ -146,11 +150,6 @@ describe Import::BitbucketController do
end
context "when the namespace is not owned by the GitLab user" do
- before do
- existing_namespace.owner = create(:user)
- existing_namespace.save
- end
-
it "doesn't create a project" do
expect(Gitlab::BitbucketImport::ProjectCreator).
not_to receive(:new)
@@ -202,10 +201,14 @@ describe Import::BitbucketController do
end
context 'user has chosen an existing nested namespace and name for the project' do
- let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
- let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) }
+ let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
+ let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' }
+ before do
+ nested_namespace.add_owner(user)
+ end
+
it 'takes the selected namespace and name' do
expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).with(bitbucket_repo, test_name, nested_namespace, user, access_params).
@@ -248,7 +251,7 @@ describe Import::BitbucketController do
context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
- let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
+ let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
it 'takes the selected namespace and name' do
expect(Gitlab::BitbucketImport::ProjectCreator).
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index 3270ea059fa..3afd09063d7 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -108,9 +108,13 @@ describe Import::GitlabController do
end
context "when a namespace with the GitLab.com user's username already exists" do
- let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
+ let!(:existing_namespace) { create(:group, name: other_username) }
context "when the namespace is owned by the GitLab server user" do
+ before do
+ existing_namespace.add_owner(user)
+ end
+
it "takes the existing namespace" do
expect(Gitlab::GitlabImport::ProjectCreator).
to receive(:new).with(gitlab_repo, existing_namespace, user, access_params).
@@ -121,11 +125,6 @@ describe Import::GitlabController do
end
context "when the namespace is not owned by the GitLab server user" do
- before do
- existing_namespace.owner = create(:user)
- existing_namespace.save
- end
-
it "doesn't create a project" do
expect(Gitlab::GitlabImport::ProjectCreator).
not_to receive(:new)
@@ -176,8 +175,12 @@ describe Import::GitlabController do
end
context 'user has chosen an existing nested namespace for the project' do
- let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
- let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) }
+ let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
+ let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
+
+ before do
+ nested_namespace.add_owner(user)
+ end
it 'takes the selected namespace and name' do
expect(Gitlab::GitlabImport::ProjectCreator).
@@ -221,7 +224,7 @@ describe Import::GitlabController do
context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
- let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
+ let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
it 'takes the selected namespace and name' do
expect(Gitlab::GitlabImport::ProjectCreator).
diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb
new file mode 100644
index 00000000000..044c9f179ed
--- /dev/null
+++ b/spec/controllers/metrics_controller_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+describe MetricsController do
+ include StubENV
+
+ let(:token) { current_application_settings.health_check_access_token }
+ let(:json_response) { JSON.parse(response.body) }
+ let(:metrics_multiproc_dir) { Dir.mktmpdir }
+
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ stub_env('prometheus_multiproc_dir', metrics_multiproc_dir)
+ allow(Gitlab::Metrics).to receive(:prometheus_metrics_enabled?).and_return(true)
+ end
+
+ describe '#index' do
+ context 'authorization token provided' do
+ before do
+ request.headers['TOKEN'] = token
+ end
+
+ it 'returns DB ping metrics' do
+ get :index
+
+ expect(response.body).to match(/^db_ping_timeout 0$/)
+ expect(response.body).to match(/^db_ping_success 1$/)
+ expect(response.body).to match(/^db_ping_latency [0-9\.]+$/)
+ end
+
+ it 'returns Redis ping metrics' do
+ get :index
+
+ expect(response.body).to match(/^redis_ping_timeout 0$/)
+ expect(response.body).to match(/^redis_ping_success 1$/)
+ expect(response.body).to match(/^redis_ping_latency [0-9\.]+$/)
+ end
+
+ it 'returns file system check metrics' do
+ get :index
+
+ expect(response.body).to match(/^filesystem_access_latency{shard="default"} [0-9\.]+$/)
+ expect(response.body).to match(/^filesystem_accessible{shard="default"} 1$/)
+ expect(response.body).to match(/^filesystem_write_latency{shard="default"} [0-9\.]+$/)
+ expect(response.body).to match(/^filesystem_writable{shard="default"} 1$/)
+ expect(response.body).to match(/^filesystem_read_latency{shard="default"} [0-9\.]+$/)
+ expect(response.body).to match(/^filesystem_readable{shard="default"} 1$/)
+ end
+
+ context 'prometheus metrics are disabled' do
+ before do
+ allow(Gitlab::Metrics).to receive(:prometheus_metrics_enabled?).and_return(false)
+ end
+
+ it 'returns proper response' do
+ get :index
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+
+ context 'without authorization token' do
+ it 'returns proper response' do
+ get :index
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 61e4fae46fb..363ed410bc0 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -49,7 +49,7 @@ describe Profiles::KeysController do
expect(response.body).to eq(user.all_ssh_keys.join("\n"))
expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
- expect(response.body).to include(another_key.key)
+ expect(response.body).to include(another_key.key.sub(' dummy@gitlab.com', ''))
expect(response.body).not_to include(deploy_key.key)
end
diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb
new file mode 100644
index 00000000000..9d60dab12d1
--- /dev/null
+++ b/spec/controllers/profiles_controller_spec.rb
@@ -0,0 +1,31 @@
+require('spec_helper')
+
+describe ProfilesController do
+ describe "PUT update" do
+ it "allows an email update from a user without an external email address" do
+ user = create(:user)
+ sign_in(user)
+
+ put :update,
+ user: { email: "john@gmail.com", name: "John" }
+
+ user.reload
+
+ expect(response.status).to eq(302)
+ expect(user.unconfirmed_email).to eq('john@gmail.com')
+ end
+
+ it "ignores an email update from a user with an external email address" do
+ ldap_user = create(:omniauth_user, external_email: true)
+ sign_in(ldap_user)
+
+ put :update,
+ user: { email: "john@gmail.com", name: "John" }
+
+ ldap_user.reload
+
+ expect(response.status).to eq(302)
+ expect(ldap_user.unconfirmed_email).not_to eq('john@gmail.com')
+ end
+ end
+end
diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb
index eff9fab8da2..428bc45b842 100644
--- a/spec/controllers/projects/artifacts_controller_spec.rb
+++ b/spec/controllers/projects/artifacts_controller_spec.rb
@@ -12,7 +12,7 @@ describe Projects::ArtifactsController do
status: 'success')
end
- let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
before do
project.team << [user, :developer]
@@ -22,16 +22,16 @@ describe Projects::ArtifactsController do
describe 'GET download' do
it 'sends the artifacts file' do
- expect(controller).to receive(:send_file).with(build.artifacts_file.path, disposition: 'attachment').and_call_original
+ expect(controller).to receive(:send_file).with(job.artifacts_file.path, disposition: 'attachment').and_call_original
- get :download, namespace_id: project.namespace, project_id: project, build_id: build
+ get :download, namespace_id: project.namespace, project_id: project, job_id: job
end
end
describe 'GET browse' do
context 'when the directory exists' do
it 'renders the browse view' do
- get :browse, namespace_id: project.namespace, project_id: project, build_id: build, path: 'other_artifacts_0.1.2'
+ get :browse, namespace_id: project.namespace, project_id: project, job_id: job, path: 'other_artifacts_0.1.2'
expect(response).to render_template('projects/artifacts/browse')
end
@@ -39,7 +39,7 @@ describe Projects::ArtifactsController do
context 'when the directory does not exist' do
it 'responds Not Found' do
- get :browse, namespace_id: project.namespace, project_id: project, build_id: build, path: 'unknown'
+ get :browse, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown'
expect(response).to be_not_found
end
@@ -49,7 +49,7 @@ describe Projects::ArtifactsController do
describe 'GET file' do
context 'when the file exists' do
it 'renders the file view' do
- get :file, namespace_id: project.namespace, project_id: project, build_id: build, path: 'ci_artifacts.txt'
+ get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt'
expect(response).to render_template('projects/artifacts/file')
end
@@ -57,7 +57,7 @@ describe Projects::ArtifactsController do
context 'when the file does not exist' do
it 'responds Not Found' do
- get :file, namespace_id: project.namespace, project_id: project, build_id: build, path: 'unknown'
+ get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown'
expect(response).to be_not_found
end
@@ -67,7 +67,7 @@ describe Projects::ArtifactsController do
describe 'GET raw' do
context 'when the file exists' do
it 'serves the file using workhorse' do
- get :raw, namespace_id: project.namespace, project_id: project, build_id: build, path: 'ci_artifacts.txt'
+ get :raw, namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt'
send_data = response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]
@@ -84,7 +84,7 @@ describe Projects::ArtifactsController do
context 'when the file does not exist' do
it 'responds Not Found' do
- get :raw, namespace_id: project.namespace, project_id: project, build_id: build, path: 'unknown'
+ get :raw, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown'
expect(response).to be_not_found
end
@@ -92,29 +92,29 @@ describe Projects::ArtifactsController do
end
describe 'GET latest_succeeded' do
- def params_from_ref(ref = pipeline.ref, job = build.name, path = 'browse')
+ def params_from_ref(ref = pipeline.ref, job_name = job.name, path = 'browse')
{
namespace_id: project.namespace,
project_id: project,
ref_name_and_path: File.join(ref, path),
- job: job
+ job: job_name
}
end
- context 'cannot find the build' do
+ context 'cannot find the job' do
shared_examples 'not found' do
it { expect(response).to have_http_status(:not_found) }
end
context 'has no such ref' do
before do
- get :latest_succeeded, params_from_ref('TAIL', build.name)
+ get :latest_succeeded, params_from_ref('TAIL', job.name)
end
it_behaves_like 'not found'
end
- context 'has no such build' do
+ context 'has no such job' do
before do
get :latest_succeeded, params_from_ref(pipeline.ref, 'NOBUILD')
end
@@ -124,20 +124,20 @@ describe Projects::ArtifactsController do
context 'has no path' do
before do
- get :latest_succeeded, params_from_ref(pipeline.sha, build.name, '')
+ get :latest_succeeded, params_from_ref(pipeline.sha, job.name, '')
end
it_behaves_like 'not found'
end
end
- context 'found the build and redirect' do
- shared_examples 'redirect to the build' do
+ context 'found the job and redirect' do
+ shared_examples 'redirect to the job' do
it 'redirects' do
- path = browse_namespace_project_build_artifacts_path(
+ path = browse_namespace_project_job_artifacts_path(
project.namespace,
project,
- build)
+ job)
expect(response).to redirect_to(path)
end
@@ -151,7 +151,7 @@ describe Projects::ArtifactsController do
get :latest_succeeded, params_from_ref('master')
end
- it_behaves_like 'redirect to the build'
+ it_behaves_like 'redirect to the job'
end
context 'with branch name containing slash' do
@@ -162,7 +162,7 @@ describe Projects::ArtifactsController do
get :latest_succeeded, params_from_ref('improve/awesome')
end
- it_behaves_like 'redirect to the build'
+ it_behaves_like 'redirect to the job'
end
context 'with branch name and path containing slashes' do
@@ -170,14 +170,14 @@ describe Projects::ArtifactsController do
pipeline.update(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
- get :latest_succeeded, params_from_ref('improve/awesome', build.name, 'file/README.md')
+ get :latest_succeeded, params_from_ref('improve/awesome', job.name, 'file/README.md')
end
it 'redirects' do
- path = file_namespace_project_build_artifacts_path(
+ path = file_namespace_project_job_artifacts_path(
project.namespace,
project,
- build,
+ job,
'README.md')
expect(response).to redirect_to(path)
diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb
index 432f3c53c90..0f2664262e8 100644
--- a/spec/controllers/projects/boards/lists_controller_spec.rb
+++ b/spec/controllers/projects/boards/lists_controller_spec.rb
@@ -27,7 +27,7 @@ describe Projects::Boards::ListsController do
parsed_response = JSON.parse(response.body)
expect(response).to match_response_schema('lists')
- expect(parsed_response.length).to eq 2
+ expect(parsed_response.length).to eq 3
end
context 'with unauthorized user' do
diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb
index 3de38bb4dac..4c69443314d 100644
--- a/spec/controllers/projects/deployments_controller_spec.rb
+++ b/spec/controllers/projects/deployments_controller_spec.rb
@@ -42,39 +42,68 @@ describe Projects::DeploymentsController do
before do
allow(controller).to receive(:deployment).and_return(deployment)
end
-
- context 'when environment has no metrics' do
+ context 'when metrics are disabled' do
before do
- expect(deployment).to receive(:metrics).and_return(nil)
+ allow(deployment).to receive(:has_metrics?).and_return false
end
- it 'returns a empty response 204 resposne' do
+ it 'responds with not found' do
get :metrics, deployment_params(id: deployment.id)
- expect(response).to have_http_status(204)
- expect(response.body).to eq('')
+
+ expect(response).to be_not_found
end
end
- context 'when environment has some metrics' do
- let(:empty_metrics) do
- {
- success: true,
- metrics: {},
- last_update: 42
- }
+ context 'when metrics are enabled' do
+ before do
+ allow(deployment).to receive(:has_metrics?).and_return true
end
- before do
- expect(deployment).to receive(:metrics).and_return(empty_metrics)
+ context 'when environment has no metrics' do
+ before do
+ expect(deployment).to receive(:metrics).and_return(nil)
+ end
+
+ it 'returns a empty response 204 resposne' do
+ get :metrics, deployment_params(id: deployment.id)
+ expect(response).to have_http_status(204)
+ expect(response.body).to eq('')
+ end
end
- it 'returns a metrics JSON document' do
- get :metrics, deployment_params(id: deployment.id)
+ context 'when environment has some metrics' do
+ let(:empty_metrics) do
+ {
+ success: true,
+ metrics: {},
+ last_update: 42
+ }
+ end
+
+ before do
+ expect(deployment).to receive(:metrics).and_return(empty_metrics)
+ end
+
+ it 'returns a metrics JSON document' do
+ get :metrics, deployment_params(id: deployment.id)
+
+ expect(response).to be_ok
+ expect(json_response['success']).to be(true)
+ expect(json_response['metrics']).to eq({})
+ expect(json_response['last_update']).to eq(42)
+ end
+ end
+
+ context 'when metrics service does not implement deployment metrics' do
+ before do
+ allow(deployment).to receive(:metrics).and_raise(NotImplementedError)
+ end
+
+ it 'responds with not found' do
+ get :metrics, deployment_params(id: deployment.id)
- expect(response).to be_ok
- expect(json_response['success']).to be(true)
- expect(json_response['metrics']).to eq({})
- expect(json_response['last_update']).to eq(42)
+ expect(response).to be_not_found
+ end
end
end
end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index c0f8c36a018..f6840578145 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -1,25 +1,25 @@
require 'spec_helper'
describe Projects::EnvironmentsController do
- let(:user) { create(:user) }
- let(:project) { create(:empty_project) }
+ set(:user) { create(:user) }
+ set(:project) { create(:empty_project) }
- let(:environment) do
+ set(:environment) do
create(:environment, name: 'production', project: project)
end
before do
- project.team << [user, :master]
+ project.add_master(user)
sign_in(user)
end
describe 'GET index' do
- context 'when standardrequest has been made' do
+ context 'when a request for the HTML is made' do
it 'responds with status code 200' do
get :index, environment_params
- expect(response).to be_ok
+ expect(response).to have_http_status(:ok)
end
end
@@ -57,6 +57,11 @@ describe Projects::EnvironmentsController do
expect(json_response['available_count']).to eq 3
expect(json_response['stopped_count']).to eq 1
end
+
+ it 'sets the polling interval header' do
+ expect(response).to have_http_status(:ok)
+ expect(response.headers['Poll-Interval']).to eq("3000")
+ end
end
context 'when requesting stopped environments scope' do
@@ -84,6 +89,9 @@ describe Projects::EnvironmentsController do
create(:environment, project: project,
name: 'staging-1.0/review',
state: :available)
+ create(:environment, project: project,
+ name: 'staging-1.0/zzz',
+ state: :available)
end
context 'when using default format' do
@@ -98,7 +106,7 @@ describe Projects::EnvironmentsController do
end
context 'when using JSON format' do
- it 'responds with JSON' do
+ it 'sorts the subfolders lexicographically' do
get :folder, namespace_id: project.namespace,
project_id: project,
id: 'staging-1.0',
@@ -108,6 +116,8 @@ describe Projects::EnvironmentsController do
expect(response).not_to render_template 'folder'
expect(json_response['environments'][0])
.to include('name' => 'staging-1.0/review')
+ expect(json_response['environments'][1])
+ .to include('name' => 'staging-1.0/zzz')
end
end
end
@@ -172,7 +182,7 @@ describe Projects::EnvironmentsController do
expect(response).to have_http_status(200)
expect(json_response).to eq(
{ 'redirect_url' =>
- "http://test.host/#{project.path_with_namespace}/builds/#{action.id}" })
+ namespace_project_job_url(project.namespace, project, action) })
end
end
@@ -186,7 +196,7 @@ describe Projects::EnvironmentsController do
expect(response).to have_http_status(200)
expect(json_response).to eq(
{ 'redirect_url' =>
- "http://test.host/#{project.path_with_namespace}/environments/#{environment.id}" })
+ namespace_project_environment_url(project.namespace, project, environment) })
end
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 1f79e72495a..a38ae2eb990 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -156,6 +156,32 @@ describe Projects::IssuesController do
end
end
+ describe 'Redirect after sign in' do
+ context 'with an AJAX request' do
+ it 'does not store the visited URL' do
+ xhr :get,
+ :show,
+ format: :json,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: issue.iid
+
+ expect(session['user_return_to']).to be_blank
+ end
+ end
+
+ context 'without an AJAX request' do
+ it 'stores the visited URL' do
+ get :show,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: issue.iid
+
+ expect(session['user_return_to']).to eq("/#{project.namespace.to_param}/#{project.to_param}/issues/#{issue.iid}")
+ end
+ end
+ end
+
describe 'PUT #update' do
before do
sign_in(user)
@@ -178,7 +204,7 @@ describe Projects::IssuesController do
body = JSON.parse(response.body)
expect(body['assignees'].first.keys)
- .to match_array(%w(id name username avatar_url))
+ .to match_array(%w(id name username avatar_url state web_url))
end
end
diff --git a/spec/controllers/projects/builds_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 3ce23c17cdc..7211acc53dc 100644
--- a/spec/controllers/projects/builds_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::BuildsController do
+describe Projects::JobsController do
include ApiHelpers
let(:project) { create(:empty_project, :public) }
@@ -101,26 +101,49 @@ describe Projects::BuildsController do
end
describe 'GET show' do
- context 'when build exists' do
- let!(:build) { create(:ci_build, pipeline: pipeline) }
+ let!(:build) { create(:ci_build, :failed, pipeline: pipeline) }
- before do
- get_show(id: build.id)
+ context 'when requesting HTML' do
+ context 'when build exists' do
+ before do
+ get_show(id: build.id)
+ end
+
+ it 'has a build' do
+ expect(response).to have_http_status(:ok)
+ expect(assigns(:build).id).to eq(build.id)
+ end
end
- it 'has a build' do
- expect(response).to have_http_status(:ok)
- expect(assigns(:build).id).to eq(build.id)
+ context 'when build does not exist' do
+ before do
+ get_show(id: 1234)
+ end
+
+ it 'renders not_found' do
+ expect(response).to have_http_status(:not_found)
+ end
end
end
- context 'when build does not exist' do
+ context 'when requesting JSON' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
before do
- get_show(id: 1234)
+ project.add_developer(user)
+ sign_in(user)
+
+ allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request)
+
+ get_show(id: build.id, format: :json)
end
- it 'renders not_found' do
- expect(response).to have_http_status(:not_found)
+ it 'exposes needed information' do
+ expect(response).to have_http_status(:ok)
+ expect(json_response['raw_path']).to match(/builds\/\d+\/raw\z/)
+ expect(json_response.dig('merge_request', 'path')).to match(/merge_requests\/\d+\z/)
+ expect(json_response['new_issue_path'])
+ .to include('/issues/new')
end
end
@@ -144,6 +167,8 @@ describe Projects::BuildsController do
it 'returns a trace' do
expect(response).to have_http_status(:ok)
+ expect(json_response['id']).to eq build.id
+ expect(json_response['status']).to eq build.status
expect(json_response['html']).to eq('BUILD TRACE')
end
end
@@ -153,10 +178,23 @@ describe Projects::BuildsController do
it 'returns no traces' do
expect(response).to have_http_status(:ok)
+ expect(json_response['id']).to eq build.id
+ expect(json_response['status']).to eq build.status
expect(json_response['html']).to be_nil
end
end
+ context 'when build has a trace with ANSI sequence and Unicode' do
+ let(:build) { create(:ci_build, :unicode_trace, pipeline: pipeline) }
+
+ it 'returns a trace with Unicode' do
+ expect(response).to have_http_status(:ok)
+ expect(json_response['id']).to eq build.id
+ expect(json_response['status']).to eq build.status
+ expect(json_response['html']).to include("ヾ(´༎ຶД༎ຶ`)ノ")
+ end
+ end
+
def get_trace
get :trace, namespace_id: project.namespace,
project_id: project,
@@ -185,48 +223,6 @@ describe Projects::BuildsController do
end
end
- describe 'GET trace.json' do
- let(:pipeline) { create(:ci_pipeline, project: project) }
- let(:build) { create(:ci_build, pipeline: pipeline) }
- let(:user) { create(:user) }
-
- context 'when user is logged in as developer' do
- before do
- project.add_developer(user)
- sign_in(user)
-
- get_trace
- end
-
- it 'traces build log' do
- expect(response).to have_http_status(:ok)
- expect(json_response['id']).to eq build.id
- expect(json_response['status']).to eq build.status
- end
- end
-
- context 'when user is logged in as non member' do
- before do
- sign_in(user)
-
- get_trace
- end
-
- it 'traces build log' do
- expect(response).to have_http_status(:ok)
- expect(json_response['id']).to eq build.id
- expect(json_response['status']).to eq build.status
- end
- end
-
- def get_trace
- get :trace, namespace_id: project.namespace,
- project_id: project,
- id: build.id,
- format: :json
- end
- end
-
describe 'POST retry' do
before do
project.add_developer(user)
@@ -240,7 +236,7 @@ describe Projects::BuildsController do
it 'redirects to the retried build page' do
expect(response).to have_http_status(:found)
- expect(response).to redirect_to(namespace_project_build_path(id: Ci::Build.last.id))
+ expect(response).to redirect_to(namespace_project_job_path(id: Ci::Build.last.id))
end
end
@@ -261,7 +257,11 @@ describe Projects::BuildsController do
describe 'POST play' do
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
+
sign_in(user)
post_play
@@ -272,7 +272,7 @@ describe Projects::BuildsController do
it 'redirects to the played build page' do
expect(response).to have_http_status(:found)
- expect(response).to redirect_to(namespace_project_build_path(id: build.id))
+ expect(response).to redirect_to(namespace_project_job_path(id: build.id))
end
it 'transits to pending' do
@@ -308,7 +308,7 @@ describe Projects::BuildsController do
it 'redirects to the canceled build page' do
expect(response).to have_http_status(:found)
- expect(response).to redirect_to(namespace_project_build_path(id: build.id))
+ expect(response).to redirect_to(namespace_project_job_path(id: build.id))
end
it 'transits to canceled' do
@@ -346,7 +346,7 @@ describe Projects::BuildsController do
it 'redirects to a index page' do
expect(response).to have_http_status(:found)
- expect(response).to redirect_to(namespace_project_builds_path)
+ expect(response).to redirect_to(namespace_project_jobs_path)
end
it 'transits to canceled' do
@@ -363,7 +363,7 @@ describe Projects::BuildsController do
it 'redirects to a index page' do
expect(response).to have_http_status(:found)
- expect(response).to redirect_to(namespace_project_builds_path)
+ expect(response).to redirect_to(namespace_project_jobs_path)
end
end
@@ -386,7 +386,7 @@ describe Projects::BuildsController do
it 'redirects to the erased build page' do
expect(response).to have_http_status(:found)
- expect(response).to redirect_to(namespace_project_build_path(id: build.id))
+ expect(response).to redirect_to(namespace_project_job_path(id: build.id))
end
it 'erases artifacts' do
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 8192f3e6fb6..08024a2148b 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -126,7 +126,7 @@ describe Projects::MergeRequestsController do
recorded = ActiveRecord::QueryRecorder.new { go(format: :json) }
- expect(recorded.count).to be_within(1).of(51)
+ expect(recorded.count).to be_within(5).of(50)
expect(recorded.cached_count).to eq(0)
end
end
@@ -358,7 +358,7 @@ describe Projects::MergeRequestsController do
end
before do
- create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch)
+ create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch, head_pipeline_of: merge_request)
end
it 'returns :merge_when_pipeline_succeeds' do
@@ -1175,12 +1175,15 @@ describe Projects::MergeRequestsController do
let!(:pipeline) do
create(:ci_pipeline, project: merge_request.source_project,
ref: merge_request.source_branch,
- sha: merge_request.diff_head_sha)
+ sha: merge_request.diff_head_sha,
+ head_pipeline_of: merge_request)
end
let(:status) { pipeline.detailed_status(double('user')) }
- before { get_pipeline_status }
+ before do
+ get_pipeline_status
+ end
it 'return a detailed head_pipeline status in json' do
expect(response).to have_http_status(:ok)
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index fb4a4721a58..c880da1e36a 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -38,7 +38,7 @@ describe Projects::PipelinesController do
end
describe 'GET show JSON' do
- let!(:pipeline) { create(:ci_pipeline_with_one_job, project: project) }
+ let(:pipeline) { create(:ci_pipeline_with_one_job, project: project) }
it 'returns the pipeline' do
get_pipeline_json
@@ -49,20 +49,48 @@ describe Projects::PipelinesController do
expect(json_response['details']).to have_key 'stages'
end
- context 'when the pipeline has multiple jobs' do
+ context 'when the pipeline has multiple stages and groups' do
+ before do
+ RequestStore.begin!
+
+ create_build('build', 0, 'build')
+ create_build('test', 1, 'rspec 0')
+ create_build('deploy', 2, 'production')
+ create_build('post deploy', 3, 'pages 0')
+ end
+
+ after do
+ RequestStore.end!
+ RequestStore.clear!
+ end
+
+ let(:project) { create(:project) }
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project, user: user, sha: project.commit.id)
+ end
+
it 'does not perform N + 1 queries' do
control_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count
- create(:ci_build, pipeline: pipeline)
+ create_build('test', 1, 'rspec 1')
+ create_build('test', 1, 'spinach 0')
+ create_build('test', 1, 'spinach 1')
+ create_build('test', 1, 'audit')
+ create_build('post deploy', 3, 'pages 1')
+ create_build('post deploy', 3, 'pages 2')
- # The plus 2 is needed to group and sort
- expect { get_pipeline_json }.not_to exceed_query_limit(control_count + 2)
+ new_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count
+ expect(new_count).to be_within(12).of(control_count)
end
end
def get_pipeline_json
get :show, namespace_id: project.namespace, project_id: project, id: pipeline, format: :json
end
+
+ def create_build(stage, stage_idx, name)
+ create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name)
+ end
end
describe 'GET stages.json' do
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index a4b4392d7cc..2294d5df581 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -36,7 +36,7 @@ describe Projects::ProjectMembersController do
before { project.team << [user, :master] }
it 'adds user to members' do
- expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(true)
+ expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(status: :success)
post :create, namespace_id: project.namespace,
project_id: project,
@@ -48,14 +48,14 @@ describe Projects::ProjectMembersController do
end
it 'adds no user to members' do
- expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(false)
+ expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(status: :failure, message: 'Message')
post :create, namespace_id: project.namespace,
project_id: project,
user_ids: '',
access_level: Gitlab::Access::GUEST
- expect(response).to set_flash.to 'No users specified.'
+ expect(response).to set_flash.to 'Message'
expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project))
end
end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 2d892f4a2b7..23b463c0082 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -3,7 +3,9 @@ require 'spec_helper'
describe Projects::ServicesController do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
- let(:service) { create(:service, project: project) }
+ let(:service) { create(:hipchat_service, project: project) }
+ let(:hipchat_client) { { '#room' => double(send: true) } }
+ let(:service_params) { { token: 'hipchat_token_p', room: '#room' } }
before do
sign_in(user)
@@ -13,97 +15,81 @@ describe Projects::ServicesController do
controller.instance_variable_set(:@service, service)
end
- shared_examples_for 'services controller' do |referrer|
- before do
- request.env["HTTP_REFERER"] = referrer
- end
-
- describe "#test" do
- context 'when can_test? returns false' do
- it 'renders 404' do
- allow_any_instance_of(Service).to receive(:can_test?).and_return(false)
+ describe '#test' do
+ context 'when can_test? returns false' do
+ it 'renders 404' do
+ allow_any_instance_of(Service).to receive(:can_test?).and_return(false)
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ put :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id
- expect(response).to have_http_status(404)
- end
+ expect(response).to have_http_status(404)
end
+ end
- context 'success' do
- context 'with empty project' do
- let(:project) { create(:empty_project) }
-
- context 'with chat notification service' do
- let(:service) { project.create_microsoft_teams_service(webhook: 'http://webhook.com') }
-
- it 'redirects and show success message' do
- allow_any_instance_of(MicrosoftTeams::Notifier).to receive(:ping).and_return(true)
-
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ context 'success' do
+ context 'with empty project' do
+ let(:project) { create(:empty_project) }
- expect(response).to redirect_to(root_path)
- expect(flash[:notice]).to eq('We sent a request to the provided URL')
- end
- end
+ context 'with chat notification service' do
+ let(:service) { project.create_microsoft_teams_service(webhook: 'http://webhook.com') }
- it 'redirects and show success message' do
- expect(service).to receive(:test).and_return(success: true, result: 'done')
+ it 'returns success' do
+ allow_any_instance_of(MicrosoftTeams::Notifier).to receive(:ping).and_return(true)
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ put :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id
- expect(response).to redirect_to(root_path)
- expect(flash[:notice]).to eq('We sent a request to the provided URL')
+ expect(response.status).to eq(200)
end
end
- it "redirects and show success message" do
- expect(service).to receive(:test).and_return(success: true, result: 'done')
+ it 'returns success' do
+ expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_return(hipchat_client)
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ put :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: service_params
- expect(response).to redirect_to(root_path)
- expect(flash[:notice]).to eq('We sent a request to the provided URL')
+ expect(response.status).to eq(200)
end
end
- context 'failure' do
- it "redirects and show failure message" do
- expect(service).to receive(:test).and_return(success: false, result: 'Bad test')
+ it 'returns success' do
+ expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_return(hipchat_client)
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ put :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: service_params
- expect(response).to redirect_to(root_path)
- expect(flash[:alert]).to eq('We tried to send a request to the provided URL but an error occurred: Bad test')
- end
+ expect(response.status).to eq(200)
end
end
- end
- describe 'referrer defined' do
- it_should_behave_like 'services controller' do
- let!(:referrer) { "/" }
- end
- end
+ context 'failure' do
+ it 'returns success status code and the error message' do
+ expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_raise('Bad test')
- describe 'referrer undefined' do
- it_should_behave_like 'services controller' do
- let!(:referrer) { nil }
+ put :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: service_params
+
+ expect(response.status).to eq(200)
+ expect(JSON.parse(response.body)).
+ to eq('error' => true, 'message' => 'Test failed.', 'service_response' => 'Bad test')
+ end
end
end
describe 'PUT #update' do
- context 'on successful update' do
- it 'sets the flash' do
- expect(service).to receive(:to_param).and_return('hipchat')
- expect(service).to receive(:event_names).and_return(HipchatService.event_names)
+ context 'when param `active` is set to true' do
+ it 'activates the service and redirects to integrations paths' do
+ put :update,
+ namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: { active: true }
+
+ expect(response).to redirect_to(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(flash[:notice]).to eq 'HipChat activated.'
+ end
+ end
+ context 'when param `active` is set to false' do
+ it 'does not activate the service but saves the settings' do
put :update,
- namespace_id: project.namespace.id,
- project_id: project.id,
- id: service.id,
- service: { active: false }
+ namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: { active: false }
- expect(flash[:notice]).to eq 'Successfully updated.'
+ expect(flash[:notice]).to eq 'HipChat settings saved, but not activated.'
end
end
end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 24a59caff4e..8c23c46798e 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -78,8 +78,18 @@ describe Projects::SnippetsController do
post :create, {
namespace_id: project.namespace.to_param,
project_id: project,
- project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
+ project_snippet: { title: 'Title', content: 'Content', description: 'Description' }.merge(snippet_params)
}.merge(additional_params)
+
+ Snippet.last
+ end
+
+ it 'creates the snippet correctly' do
+ snippet = create_snippet(project, visibility_level: Snippet::PRIVATE)
+
+ expect(snippet.title).to eq('Title')
+ expect(snippet.content).to eq('Content')
+ expect(snippet.description).to eq('Description')
end
context 'when the snippet is spam' do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index a8be6768a47..4f6fc6691be 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -226,6 +226,50 @@ describe ProjectsController do
end
end
+ describe '#transfer' do
+ render_views
+
+ let(:project) { create(:project) }
+ let(:admin) { create(:admin) }
+ let(:new_namespace) { create(:namespace) }
+
+ it 'updates namespace' do
+ sign_in(admin)
+
+ put :transfer,
+ namespace_id: project.namespace.path,
+ new_namespace_id: new_namespace.id,
+ id: project.path,
+ format: :js
+
+ project.reload
+
+ expect(project.namespace).to eq(new_namespace)
+ expect(response).to have_http_status(200)
+ end
+
+ context 'when new namespace is empty' do
+ it 'project namespace is not changed' do
+ controller.instance_variable_set(:@project, project)
+ sign_in(admin)
+
+ old_namespace = project.namespace
+
+ put :transfer,
+ namespace_id: old_namespace.path,
+ new_namespace_id: nil,
+ id: project.path,
+ format: :js
+
+ project.reload
+
+ expect(project.namespace).to eq(old_namespace)
+ expect(response).to have_http_status(200)
+ expect(flash[:alert]).to eq 'Please select a new namespace for your project.'
+ end
+ end
+ end
+
describe "#destroy" do
let(:admin) { create(:admin) }
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index 71dd9ef3eb4..634563fc290 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -77,7 +77,7 @@ describe RegistrationsController do
end
it 'schedules the user for destruction' do
- expect(DeleteUserWorker).to receive(:perform_async).with(user.id, user.id)
+ expect(DeleteUserWorker).to receive(:perform_async).with(user.id, user.id, {})
post(:destroy)
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 038132cffe0..e87e24a33a1 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -1,6 +1,37 @@
require 'spec_helper'
describe SessionsController do
+ describe '#new' do
+ before do
+ @request.env['devise.mapping'] = Devise.mappings[:user]
+ end
+
+ context 'when auto sign-in is enabled' do
+ before do
+ stub_omniauth_setting(auto_sign_in_with_provider: :saml)
+ allow(controller).to receive(:omniauth_authorize_path).with(:user, :saml).
+ and_return('/saml')
+ end
+
+ context 'and no auto_sign_in param is passed' do
+ it 'redirects to :omniauth_authorize_path' do
+ get(:new)
+
+ expect(response).to have_http_status(302)
+ expect(response).to redirect_to('/saml')
+ end
+ end
+
+ context 'and auto_sign_in=false param is passed' do
+ it 'responds with 200' do
+ get(:new, auto_sign_in: 'false')
+
+ expect(response).to have_http_status(200)
+ end
+ end
+ end
+ end
+
describe '#create' do
before do
@request.env['devise.mapping'] = Devise.mappings[:user]
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 930415a4778..9073c39f562 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -171,12 +171,50 @@ describe SnippetsController do
sign_in(user)
post :create, {
- personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
+ personal_snippet: { title: 'Title', content: 'Content', description: 'Description' }.merge(snippet_params)
}.merge(additional_params)
Snippet.last
end
+ it 'creates the snippet correctly' do
+ snippet = create_snippet(visibility_level: Snippet::PRIVATE)
+
+ expect(snippet.title).to eq('Title')
+ expect(snippet.content).to eq('Content')
+ expect(snippet.description).to eq('Description')
+ end
+
+ context 'when the snippet description contains a file' do
+ let(:picture_file) { '/temp/secret56/picture.jpg' }
+ let(:text_file) { '/temp/secret78/text.txt' }
+ let(:description) do
+ "Description with picture: ![picture](/uploads#{picture_file}) and "\
+ "text: [text.txt](/uploads#{text_file})"
+ end
+
+ before do
+ allow(FileUtils).to receive(:mkdir_p)
+ allow(FileUtils).to receive(:move)
+ end
+
+ subject { create_snippet({ description: description }, { files: [picture_file, text_file] }) }
+
+ it 'creates the snippet' do
+ expect { subject }.to change { Snippet.count }.by(1)
+ end
+
+ it 'stores the snippet description correctly' do
+ snippet = subject
+
+ expected_description = "Description with picture: "\
+ "![picture](/uploads/personal_snippet/#{snippet.id}/secret56/picture.jpg) and "\
+ "text: [text.txt](/uploads/personal_snippet/#{snippet.id}/secret78/text.txt)"
+
+ expect(snippet.description).to eq(expected_description)
+ end
+ end
+
context 'when the snippet is spam' do
before do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 8000c9dec61..01a0659479b 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -92,6 +92,40 @@ describe UploadsController do
end
end
end
+
+ context 'temporal with valid image' do
+ subject do
+ post :create, model: 'personal_snippet', file: jpg, format: :json
+ end
+
+ it 'returns a content with original filename, new link, and correct type.' do
+ subject
+
+ expect(response.body).to match '\"alt\":\"rails_sample\"'
+ expect(response.body).to match "\"url\":\"/uploads/temp"
+ end
+
+ it 'does not create an Upload record' do
+ expect { subject }.not_to change { Upload.count }
+ end
+ end
+
+ context 'temporal with valid non-image file' do
+ subject do
+ post :create, model: 'personal_snippet', file: txt, format: :json
+ end
+
+ it 'returns a content with original filename, new link, and correct type.' do
+ subject
+
+ expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
+ expect(response.body).to match "\"url\":\"/uploads/temp"
+ end
+
+ it 'does not create an Upload record' do
+ expect { subject }.not_to change { Upload.count }
+ end
+ end
end
end
diff --git a/spec/db/production/settings.rb b/spec/db/production/settings.rb
deleted file mode 100644
index 007b35bbb77..00000000000
--- a/spec/db/production/settings.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-require 'rainbow/ext/string'
-
-describe 'seed production settings', lib: true do
- include StubENV
-
- context 'GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN is set in the environment' do
- before do
- stub_env('GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN', '013456789')
- end
-
- it 'writes the token to the database' do
- load(File.join(__dir__, '../../../db/fixtures/production/010_settings.rb'))
- expect(ApplicationSetting.current.runners_registration_token).to eq('013456789')
- end
- end
-end
diff --git a/spec/db/production/settings_spec.rb b/spec/db/production/settings_spec.rb
new file mode 100644
index 00000000000..a9d015e0666
--- /dev/null
+++ b/spec/db/production/settings_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+require 'rainbow/ext/string'
+
+describe 'seed production settings', lib: true do
+ include StubENV
+ let(:settings_file) { Rails.root.join('db/fixtures/production/010_settings.rb') }
+ let(:settings) { Gitlab::CurrentSettings.current_application_settings }
+
+ context 'GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN is set in the environment' do
+ before do
+ stub_env('GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN', '013456789')
+ end
+
+ it 'writes the token to the database' do
+ load(settings_file)
+
+ expect(settings.runners_registration_token).to eq('013456789')
+ end
+ end
+
+ context 'GITLAB_PROMETHEUS_METRICS_ENABLED is set in the environment' do
+ context 'GITLAB_PROMETHEUS_METRICS_ENABLED is true' do
+ before do
+ stub_env('GITLAB_PROMETHEUS_METRICS_ENABLED', 'true')
+ end
+
+ it 'prometheus_metrics_enabled is set to true ' do
+ load(settings_file)
+
+ expect(settings.prometheus_metrics_enabled).to eq(true)
+ end
+ end
+
+ context 'GITLAB_PROMETHEUS_METRICS_ENABLED is false' do
+ before do
+ stub_env('GITLAB_PROMETHEUS_METRICS_ENABLED', 'false')
+ end
+
+ it 'prometheus_metrics_enabled is set to false' do
+ load(settings_file)
+
+ expect(settings.prometheus_metrics_enabled).to eq(false)
+ end
+ end
+
+ context 'GITLAB_PROMETHEUS_METRICS_ENABLED is false' do
+ before do
+ stub_env('GITLAB_PROMETHEUS_METRICS_ENABLED', '')
+ end
+
+ it 'prometheus_metrics_enabled is set to false' do
+ load(settings_file)
+
+ expect(settings.prometheus_metrics_enabled).to eq(false)
+ end
+ end
+ end
+end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 78ddd8d5584..0bb5a86d9b9 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -64,7 +64,8 @@ FactoryGirl.define do
trait :teardown_environment do
environment 'staging'
options environment: { name: 'staging',
- action: 'stop' }
+ action: 'stop',
+ url: 'http://staging.example.com/$CI_JOB_NAME' }
end
trait :allowed_to_fail do
@@ -128,6 +129,16 @@ FactoryGirl.define do
end
end
+ trait :unicode_trace do
+ after(:create) do |build, evaluator|
+ trace = File.binread(
+ File.expand_path(
+ Rails.root.join('spec/fixtures/trace/ansi-sequence-and-unicode')))
+
+ build.trace.set(trace)
+ end
+ end
+
trait :erased do
erased_at Time.now
erased_by factory: :user
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index 561fbc8e247..35803f0c37f 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -1,5 +1,6 @@
FactoryGirl.define do
factory :ci_empty_pipeline, class: Ci::Pipeline do
+ source :push
ref 'master'
sha '97de212e80737a608d939f648d959671fb0a0142'
status 'pending'
@@ -8,33 +9,39 @@ FactoryGirl.define do
factory :ci_pipeline_without_jobs do
after(:build) do |pipeline|
- allow(pipeline).to receive(:ci_yaml_file) { YAML.dump({}) }
+ pipeline.instance_variable_set(:@ci_yaml_file, YAML.dump({}))
end
end
factory :ci_pipeline_with_one_job do
after(:build) do |pipeline|
allow(pipeline).to receive(:ci_yaml_file) do
- YAML.dump({ rspec: { script: "ls" } })
+ pipeline.instance_variable_set(:@ci_yaml_file, YAML.dump({ rspec: { script: "ls" } }))
end
end
end
+ # Persist merge request head_pipeline_id
+ # on pipeline factories to avoid circular references
+ transient { head_pipeline_of nil }
+
+ after(:create) do |pipeline, evaluator|
+ merge_request = evaluator.head_pipeline_of
+ merge_request&.update(head_pipeline: pipeline)
+ end
+
factory :ci_pipeline do
transient { config nil }
after(:build) do |pipeline, evaluator|
- allow(pipeline).to receive(:ci_yaml_file) do
- if evaluator.config
- YAML.dump(evaluator.config)
- else
- File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
- end
- end
+ if evaluator.config
+ pipeline.instance_variable_set(:@ci_yaml_file, YAML.dump(evaluator.config))
- # Populates pipeline with errors
- #
- pipeline.config_processor if evaluator.config
+ # Populates pipeline with errors
+ pipeline.config_processor if evaluator.config
+ else
+ pipeline.instance_variable_set(:@ci_yaml_file, File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')))
+ end
end
trait :invalid do
diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb
index 7f557b25ccb..d3c8bf9d54f 100644
--- a/spec/factories/ci/stages.rb
+++ b/spec/factories/ci/stages.rb
@@ -1,5 +1,7 @@
FactoryGirl.define do
- factory :ci_stage, class: Ci::Stage do
+ factory :ci_stage, class: Ci::LegacyStage do
+ skip_create
+
transient do
name 'test'
status nil
@@ -8,7 +10,9 @@ FactoryGirl.define do
end
initialize_with do
- Ci::Stage.new(pipeline, name: name, status: status, warnings: warnings)
+ Ci::LegacyStage.new(pipeline, name: name,
+ status: status,
+ warnings: warnings)
end
end
end
diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb
index b8d8fab0e0b..10e0ab4fd3c 100644
--- a/spec/factories/ci/trigger_requests.rb
+++ b/spec/factories/ci/trigger_requests.rb
@@ -1,8 +1,8 @@
FactoryGirl.define do
factory :ci_trigger_request, class: Ci::TriggerRequest do
- factory :ci_trigger_request_with_variables do
- trigger factory: :ci_trigger
+ trigger factory: :ci_trigger
+ factory :ci_trigger_request_with_variables do
variables do
{
TRIGGER_KEY_1: 'TRIGGER_VALUE_1',
diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb
index 6653f0bb5c3..f83366136fd 100644
--- a/spec/factories/ci/variables.rb
+++ b/spec/factories/ci/variables.rb
@@ -2,5 +2,11 @@ FactoryGirl.define do
factory :ci_variable, class: Ci::Variable do
sequence(:key) { |n| "VARIABLE_#{n}" }
value 'VARIABLE_VALUE'
+
+ trait(:protected) do
+ protected true
+ end
+
+ project factory: :empty_project
end
end
diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb
index 89e260cf65b..36b9645438a 100644
--- a/spec/factories/commits.rb
+++ b/spec/factories/commits.rb
@@ -4,19 +4,14 @@ FactoryGirl.define do
factory :commit do
git_commit RepoHelpers.sample_commit
project factory: :empty_project
+ author { build(:author) }
initialize_with do
new(git_commit, project)
end
- after(:build) do |commit|
- allow(commit).to receive(:author).and_return build(:author)
- end
-
trait :without_author do
- after(:build) do |commit|
- allow(commit).to receive(:author).and_return nil
- end
+ author nil
end
end
end
diff --git a/spec/factories/conversational_development_index_metrics.rb b/spec/factories/conversational_development_index_metrics.rb
new file mode 100644
index 00000000000..a5412629195
--- /dev/null
+++ b/spec/factories/conversational_development_index_metrics.rb
@@ -0,0 +1,33 @@
+FactoryGirl.define do
+ factory :conversational_development_index_metric, class: ConversationalDevelopmentIndex::Metric do
+ leader_issues 9.256
+ instance_issues 1.234
+
+ leader_notes 30.33333
+ instance_notes 28.123
+
+ leader_milestones 16.2456
+ instance_milestones 1.234
+
+ leader_boards 5.2123
+ instance_boards 3.254
+
+ leader_merge_requests 1.2
+ instance_merge_requests 0.6
+
+ leader_ci_pipelines 12.1234
+ instance_ci_pipelines 2.344
+
+ leader_environments 3.3333
+ instance_environments 2.2222
+
+ leader_deployments 1.200
+ instance_deployments 0.771
+
+ leader_projects_prometheus_active 0.111
+ instance_projects_prometheus_active 0.109
+
+ leader_service_desk_issues 15.891
+ instance_service_desk_issues 13.345
+ end
+end
diff --git a/spec/factories/file_uploader.rb b/spec/factories/file_uploaders.rb
index bc74aeecc3b..d397dd705a5 100644
--- a/spec/factories/file_uploader.rb
+++ b/spec/factories/file_uploaders.rb
@@ -1,5 +1,7 @@
FactoryGirl.define do
factory :file_uploader do
+ skip_create
+
project factory: :empty_project
secret nil
diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb
index b16c1272e68..66b0f248959 100644
--- a/spec/factories/forked_project_links.rb
+++ b/spec/factories/forked_project_links.rb
@@ -7,5 +7,9 @@ FactoryGirl.define do
link.forked_from_project.reload
link.forked_to_project.reload
end
+
+ trait :forked_to_empty_project do
+ association :forked_to_project, factory: :empty_project
+ end
end
end
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index 4e140102492..a13b6e3596e 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -1,27 +1,18 @@
+require_relative '../support/helpers/key_generator_helper'
+
FactoryGirl.define do
factory :key do
title
- key do
- 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= dummy@gitlab.com'
- end
+ key { Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate + ' dummy@gitlab.com' }
- factory :deploy_key, class: 'DeployKey' do
- key do
- 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O96x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5/jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaCrzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy05qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz'
- end
- end
+ factory :deploy_key, class: 'DeployKey'
factory :personal_key do
user
end
factory :another_key do
- key do
- 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ'
- end
-
- factory :another_deploy_key, class: 'DeployKey' do
- end
+ factory :another_deploy_key, class: 'DeployKey'
end
factory :write_access_key, class: 'DeployKey' do
diff --git a/spec/factories/lists.rb b/spec/factories/lists.rb
index f6a78811cbe..48142d3c49b 100644
--- a/spec/factories/lists.rb
+++ b/spec/factories/lists.rb
@@ -6,6 +6,12 @@ FactoryGirl.define do
sequence(:position)
end
+ factory :backlog_list, parent: :list do
+ list_type :backlog
+ label nil
+ position nil
+ end
+
factory :closed_list, parent: :list do
list_type :closed
label nil
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index 0210e871a63..cd754ea235f 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -14,7 +14,7 @@ FactoryGirl.define do
issues_events true
confidential_issues_events true
note_events true
- build_events true
+ job_events true
pipeline_events true
wiki_page_events true
end
diff --git a/spec/factories/project_statistics.rb b/spec/factories/project_statistics.rb
index 72d43096216..6c2ed7c6581 100644
--- a/spec/factories/project_statistics.rb
+++ b/spec/factories/project_statistics.rb
@@ -1,6 +1,10 @@
FactoryGirl.define do
factory :project_statistics do
- project { create :project }
- namespace { project.namespace }
+ project
+
+ initialize_with do
+ # statistics are automatically created when a project is created
+ project&.statistics || new
+ end
end
end
diff --git a/spec/factories/project_wikis.rb b/spec/factories/project_wikis.rb
index a3403fd76ae..ae222d5e69a 100644
--- a/spec/factories/project_wikis.rb
+++ b/spec/factories/project_wikis.rb
@@ -1,5 +1,7 @@
FactoryGirl.define do
factory :project_wiki do
+ skip_create
+
project factory: :empty_project
user factory: :user
initialize_with { new(project, user) }
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 574b52e760d..e17e50db143 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -1,3 +1,5 @@
+require_relative '../support/test_env'
+
FactoryGirl.define do
# Project without repository
#
@@ -24,6 +26,22 @@ FactoryGirl.define do
visibility_level Gitlab::VisibilityLevel::PRIVATE
end
+ trait :import_scheduled do
+ import_status :scheduled
+ end
+
+ trait :import_started do
+ import_status :started
+ end
+
+ trait :import_finished do
+ import_status :finished
+ end
+
+ trait :import_failed do
+ import_status :failed
+ end
+
trait :archived do
archived true
end
@@ -60,7 +78,9 @@ FactoryGirl.define do
trait :test_repo do
after :create do |project|
- TestEnv.copy_repo(project)
+ TestEnv.copy_repo(project,
+ bare_repo: TestEnv.factory_repo_path_bare,
+ refs: TestEnv::BRANCH_SHA)
end
end
@@ -151,7 +171,9 @@ FactoryGirl.define do
end
after :create do |project, evaluator|
- TestEnv.copy_repo(project)
+ TestEnv.copy_repo(project,
+ bare_repo: TestEnv.factory_repo_path_bare,
+ refs: TestEnv::BRANCH_SHA)
if evaluator.create_template
args = evaluator.create_template
@@ -184,7 +206,9 @@ FactoryGirl.define do
path { 'forked-gitlabhq' }
after :create do |project|
- TestEnv.copy_forked_repo_with_submodules(project)
+ TestEnv.copy_repo(project,
+ bare_repo: TestEnv.forked_repo_path_bare,
+ refs: TestEnv::FORKED_BRANCH_SHA)
end
end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index 62aa71ae8d8..e7366a7fd1c 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -20,9 +20,8 @@ FactoryGirl.define do
project factory: :empty_project
active true
properties({
- namespace: 'somepath',
api_url: 'https://kubernetes.example.com',
- token: 'a' * 40,
+ token: 'a' * 40
})
end
@@ -34,4 +33,10 @@ FactoryGirl.define do
project_key: 'jira-key'
)
end
+
+ factory :hipchat_service do
+ project factory: :empty_project
+ type 'HipchatService'
+ token 'test_token'
+ end
end
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index 18cb0f5de26..388f662e6e5 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -3,6 +3,7 @@ FactoryGirl.define do
author
title { generate(:title) }
content { generate(:title) }
+ description { generate(:title) }
file_name { generate(:filename) }
trait :public do
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 33fa80772ff..e60fe713bc3 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -8,6 +8,10 @@ FactoryGirl.define do
confirmation_token { nil }
can_create_group true
+ before(:create) do |user|
+ user.ensure_rss_token
+ end
+
trait :admin do
admin true
end
diff --git a/spec/factories/web_hook_log.rb b/spec/factories/web_hook_log.rb
new file mode 100644
index 00000000000..230b3f6b26e
--- /dev/null
+++ b/spec/factories/web_hook_log.rb
@@ -0,0 +1,14 @@
+FactoryGirl.define do
+ factory :web_hook_log do
+ web_hook factory: :project_hook
+ trigger 'push_hooks'
+ url { generate(:url) }
+ request_headers {}
+ request_data {}
+ response_headers {}
+ response_body ''
+ response_status '200'
+ execution_duration 2.0
+ internal_error_message nil
+ end
+end
diff --git a/spec/factories/wiki_directories.rb b/spec/factories/wiki_directories.rb
index 3f3c864ac2b..3b4cfc380b8 100644
--- a/spec/factories/wiki_directories.rb
+++ b/spec/factories/wiki_directories.rb
@@ -1,5 +1,7 @@
FactoryGirl.define do
factory :wiki_directory do
+ skip_create
+
slug '/path_up_to/dir'
initialize_with { new(slug) }
end
diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb
index 786e1456f5f..09b3c0b0994 100644
--- a/spec/factories_spec.rb
+++ b/spec/factories_spec.rb
@@ -3,14 +3,20 @@ require 'spec_helper'
describe 'factories' do
FactoryGirl.factories.each do |factory|
describe "#{factory.name} factory" do
- let(:entity) { build(factory.name) }
+ it 'does not raise error when built' do
+ expect { build(factory.name) }.not_to raise_error
+ end
it 'does not raise error when created' do
- expect { entity }.not_to raise_error
+ expect { create(factory.name) }.not_to raise_error
end
- it 'is valid', if: factory.build_class < ActiveRecord::Base do
- expect(entity).to be_valid
+ factory.definition.defined_traits.map(&:name).each do |trait_name|
+ describe "linting #{trait_name} trait" do
+ skip 'does not raise error when created' do
+ expect { create(factory.name, trait_name) }.not_to raise_error
+ end
+ end
end
end
end
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index 9d5ce876c29..999ce3611b5 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -16,7 +16,7 @@ describe 'Admin Builds' do
create(:ci_build, pipeline: pipeline, status: :success)
create(:ci_build, pipeline: pipeline, status: :failed)
- visit admin_builds_path
+ visit admin_jobs_path
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_selector('.row-content-block', text: 'All jobs')
@@ -27,7 +27,7 @@ describe 'Admin Builds' do
context 'when have no jobs' do
it 'shows a message' do
- visit admin_builds_path
+ visit admin_jobs_path
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_content 'No jobs to show'
@@ -44,7 +44,7 @@ describe 'Admin Builds' do
build3 = create(:ci_build, pipeline: pipeline, status: :success)
build4 = create(:ci_build, pipeline: pipeline, status: :failed)
- visit admin_builds_path(scope: :pending)
+ visit admin_jobs_path(scope: :pending)
expect(page).to have_selector('.nav-links li.active', text: 'Pending')
expect(page.find('.build-link')).to have_content(build1.id)
@@ -59,7 +59,7 @@ describe 'Admin Builds' do
it 'shows a message' do
create(:ci_build, pipeline: pipeline, status: :success)
- visit admin_builds_path(scope: :pending)
+ visit admin_jobs_path(scope: :pending)
expect(page).to have_selector('.nav-links li.active', text: 'Pending')
expect(page).to have_content 'No jobs to show'
@@ -76,7 +76,7 @@ describe 'Admin Builds' do
build3 = create(:ci_build, pipeline: pipeline, status: :failed)
build4 = create(:ci_build, pipeline: pipeline, status: :pending)
- visit admin_builds_path(scope: :running)
+ visit admin_jobs_path(scope: :running)
expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page.find('.build-link')).to have_content(build1.id)
@@ -91,7 +91,7 @@ describe 'Admin Builds' do
it 'shows a message' do
create(:ci_build, pipeline: pipeline, status: :success)
- visit admin_builds_path(scope: :running)
+ visit admin_jobs_path(scope: :running)
expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page).to have_content 'No jobs to show'
@@ -107,7 +107,7 @@ describe 'Admin Builds' do
build2 = create(:ci_build, pipeline: pipeline, status: :running)
build3 = create(:ci_build, pipeline: pipeline, status: :success)
- visit admin_builds_path(scope: :finished)
+ visit admin_jobs_path(scope: :finished)
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page.find('.build-link')).not_to have_content(build1.id)
@@ -121,7 +121,7 @@ describe 'Admin Builds' do
it 'shows a message' do
create(:ci_build, pipeline: pipeline, status: :running)
- visit admin_builds_path(scope: :finished)
+ visit admin_jobs_path(scope: :finished)
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page).to have_content 'No jobs to show'
diff --git a/spec/features/admin/admin_conversational_development_index_spec.rb b/spec/features/admin/admin_conversational_development_index_spec.rb
new file mode 100644
index 00000000000..739ab907a29
--- /dev/null
+++ b/spec/features/admin/admin_conversational_development_index_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe 'Admin Conversational Development Index' do
+ before do
+ login_as :admin
+ end
+
+ context 'when usage ping is disabled' do
+ it 'shows empty state' do
+ stub_application_setting(usage_ping_enabled: false)
+
+ visit admin_conversational_development_index_path
+
+ expect(page).to have_content('Usage ping is not enabled')
+ end
+ end
+
+ context 'when there is no data to display' do
+ it 'shows empty state' do
+ stub_application_setting(usage_ping_enabled: true)
+
+ visit admin_conversational_development_index_path
+
+ expect(page).to have_content('Data is still calculating')
+ end
+ end
+
+ context 'when there is data to display' do
+ it 'shows numbers for each metric' do
+ stub_application_setting(usage_ping_enabled: true)
+ create(:conversational_development_index_metric)
+
+ visit admin_conversational_development_index_path
+
+ expect(page).to have_content(
+ 'Issues created per active user 1.2 You 9.3 Lead 13.3%'
+ )
+ end
+ end
+end
diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb
index c0b6995a84a..5f5fa4e932a 100644
--- a/spec/features/admin/admin_deploy_keys_spec.rb
+++ b/spec/features/admin/admin_deploy_keys_spec.rb
@@ -11,40 +11,67 @@ RSpec.describe 'admin deploy keys', type: :feature do
it 'show all public deploy keys' do
visit admin_deploy_keys_path
- expect(page).to have_content(deploy_key.title)
- expect(page).to have_content(another_deploy_key.title)
+ page.within(find('.deploy-keys-list', match: :first)) do
+ expect(page).to have_content(deploy_key.title)
+ expect(page).to have_content(another_deploy_key.title)
+ end
end
- describe 'create new deploy key' do
+ describe 'create a new deploy key' do
+ let(:new_ssh_key) { attributes_for(:key)[:key] }
+
before do
visit admin_deploy_keys_path
click_link 'New deploy key'
end
- it 'creates new deploy key' do
- fill_deploy_key
+ 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_renders_new_key
- end
+ expect(current_path).to eq admin_deploy_keys_path
- it 'creates new deploy key with write access' do
- fill_deploy_key
- check "deploy_key_can_push"
- click_button "Create"
+ page.within(find('.deploy-keys-list', match: :first)) do
+ expect(page).to have_content('laptop')
+ expect(page).to have_content('Yes')
+ end
+ end
+ end
- expect_renders_new_key
- expect(page).to have_content('Yes')
+ describe 'update an existing deploy key' do
+ before do
+ visit admin_deploy_keys_path
+ find('tr', text: deploy_key.title).click_link('Edit')
end
- def expect_renders_new_key
+ 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
- expect(page).to have_content('laptop')
+
+ 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
- def fill_deploy_key
- fill_in 'deploy_key_title', with: 'laptop'
- fill_in 'deploy_key_key', with: 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop'
+ describe 'remove an existing deploy key' do
+ before do
+ visit admin_deploy_keys_path
+ end
+
+ it 'removes an existing deploy key' do
+ find('tr', text: deploy_key.title).click_link('Remove')
+
+ expect(current_path).to eq admin_deploy_keys_path
+ page.within(find('.deploy-keys-list', match: :first)) do
+ expect(page).not_to have_content(deploy_key.title)
+ end
end
end
end
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index 273cacd82cd..e8e080ce3e2 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -32,7 +32,7 @@ feature 'Admin disables Git access protocol', feature: true do
scenario 'shows only HTTP url' do
visit_project
- expect(page).to have_content("git clone #{project.http_url_to_repo(admin)}")
+ expect(page).to have_content("git clone #{project.http_url_to_repo}")
expect(page).not_to have_selector('#clone-dropdown')
end
end
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index d5f595894d6..cf9d7bca255 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -24,7 +24,9 @@ feature 'Admin Groups', feature: true do
it 'creates new group' do
visit admin_groups_path
- click_link "New group"
+ page.within '#content-body' do
+ click_link "New group"
+ end
path_component = 'gitlab'
group_name = 'GitLab group name'
group_description = 'Description of group for GitLab'
diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb
new file mode 100644
index 00000000000..5b67f4de6ac
--- /dev/null
+++ b/spec/features/admin/admin_hook_logs_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+feature 'Admin::HookLogs', feature: true do
+ let(:project) { create(:project) }
+ let(:system_hook) { create(:system_hook) }
+ let(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') }
+
+ before do
+ login_as :admin
+ end
+
+ scenario 'show list of hook logs' do
+ hook_log
+ visit edit_admin_hook_path(system_hook)
+
+ expect(page).to have_content('Recent Deliveries')
+ expect(page).to have_content(hook_log.url)
+ end
+
+ scenario 'show hook log details' do
+ hook_log
+ visit edit_admin_hook_path(system_hook)
+ click_link 'View details'
+
+ expect(page).to have_content("POST #{hook_log.url}")
+ expect(page).to have_content(hook_log.internal_error_message)
+ expect(page).to have_content('Resend Request')
+ end
+
+ scenario 'retry hook log' do
+ WebMock.stub_request(:post, system_hook.url)
+
+ hook_log
+ visit edit_admin_hook_path(system_hook)
+ click_link 'View details'
+ click_link 'Resend Request'
+
+ expect(current_path).to eq(edit_admin_hook_path(system_hook))
+ end
+end
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index c5f24d412d7..80f7ec43c06 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -58,10 +58,19 @@ describe 'Admin::Hooks', feature: true do
end
describe 'Remove existing hook' do
- it 'remove existing hook' do
- visit admin_hooks_path
+ context 'removes existing hook' do
+ it 'from hooks list page' do
+ visit admin_hooks_path
+
+ expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1)
+ end
- expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1)
+ it 'from hook edit page' do
+ visit admin_hooks_path
+ click_link 'Edit'
+
+ expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1)
+ end
end
end
diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb
index fa3d9ee25c0..a9251db13e5 100644
--- a/spec/features/admin/admin_labels_spec.rb
+++ b/spec/features/admin/admin_labels_spec.rb
@@ -34,11 +34,11 @@ RSpec.describe 'admin issues labels' do
page.within '.labels' do
page.all('.btn-remove').each do |remove|
remove.click
- wait_for_ajax
+ wait_for_requests
end
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content("There are no labels yet")
expect(page).not_to have_content('bug')
diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb
index 1df972843e2..15482347886 100644
--- a/spec/features/admin/admin_system_info_spec.rb
+++ b/spec/features/admin/admin_system_info_spec.rb
@@ -20,6 +20,7 @@ describe 'Admin System Info' do
expect(page).to have_content 'CPU 2 cores'
expect(page).to have_content 'Memory 4 GB / 16 GB'
expect(page).to have_content 'Disks'
+ expect(page).to have_content 'Uptime'
end
end
@@ -34,6 +35,7 @@ describe 'Admin System Info' do
expect(page).to have_content 'CPU Unable to collect CPU info'
expect(page).to have_content 'Memory 4 GB / 16 GB'
expect(page).to have_content 'Disks'
+ expect(page).to have_content 'Uptime'
end
end
@@ -48,6 +50,7 @@ describe 'Admin System Info' do
expect(page).to have_content 'CPU 2 cores'
expect(page).to have_content 'Memory Unable to collect memory info'
expect(page).to have_content 'Disks'
+ expect(page).to have_content 'Uptime'
end
end
end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index c5b1ef1295c..301a47169a4 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -21,6 +21,9 @@ describe "Admin::Users", feature: true do
expect(page).to have_content(current_user.name)
expect(page).to have_content(user.email)
expect(page).to have_content(user.name)
+ expect(page).to have_link('Block', href: block_admin_user_path(user))
+ expect(page).to have_link('Remove user', href: admin_user_path(user))
+ expect(page).to have_link('Remove user and contributions', href: admin_user_path(user, hard_delete: true))
end
describe 'Two-factor Authentication filters' do
@@ -114,6 +117,9 @@ describe "Admin::Users", feature: true do
expect(page).to have_content(user.email)
expect(page).to have_content(user.name)
+ expect(page).to have_link('Block user', href: block_admin_user_path(user))
+ expect(page).to have_link('Remove user', href: admin_user_path(user))
+ expect(page).to have_link('Remove user and contributions', href: admin_user_path(user, hard_delete: true))
end
describe 'Impersonation' do
@@ -277,7 +283,7 @@ describe "Admin::Users", feature: true do
page.within(first('.group_member')) do
find('.btn-remove').click
end
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_selector('.group_member')
end
diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb
index 855247de2ea..ab5c42365fe 100644
--- a/spec/features/admin/admin_uses_repository_checks_spec.rb
+++ b/spec/features/admin/admin_uses_repository_checks_spec.rb
@@ -23,7 +23,7 @@ feature 'Admin uses repository checks', feature: true do
project = create(:empty_project)
project.update_columns(
last_repository_check_failed: true,
- last_repository_check_at: Time.now,
+ last_repository_check_at: Time.now
)
visit_admin_project_page(project)
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index 9ea325ab41b..711c8a710f3 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -20,13 +20,20 @@ describe "Dashboard Issues Feed", feature: true do
expect(body).to have_selector('title', text: "#{user.name} issues")
end
+ it "renders atom feed via RSS token" do
+ visit issues_dashboard_path(:atom, rss_token: user.rss_token)
+
+ expect(response_headers['Content-Type']).to have_content('application/atom+xml')
+ expect(body).to have_selector('title', text: "#{user.name} issues")
+ end
+
it "renders atom feed with url parameters" do
- visit issues_dashboard_path(:atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
+ visit issues_dashboard_path(:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query)
- expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
@@ -35,7 +42,7 @@ describe "Dashboard Issues Feed", feature: true do
let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') }
it "renders issue fields" do
- visit issues_dashboard_path(:atom, private_token: user.private_token)
+ visit issues_dashboard_path(:atom, rss_token: user.rss_token)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]")
@@ -58,7 +65,7 @@ describe "Dashboard Issues Feed", feature: true do
end
it "renders issue label and milestone info" do
- visit issues_dashboard_path(:atom, private_token: user.private_token)
+ visit issues_dashboard_path(:atom, rss_token: user.rss_token)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]")
diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index 746df36bb25..1df058b023c 100644
--- a/spec/features/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
@@ -11,6 +11,13 @@ describe "Dashboard Feed", feature: true do
end
end
+ context "projects atom feed via RSS token" do
+ it "renders projects atom feed" do
+ visit dashboard_projects_path(:atom, rss_token: user.rss_token)
+ expect(body).to have_selector('feed title')
+ end
+ end
+
context 'feed content' do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project, author: user, description: '') }
@@ -20,7 +27,7 @@ describe "Dashboard Feed", feature: true do
project.team << [user, :master]
issue_event(issue, user)
note_event(note, user)
- visit dashboard_projects_path(:atom, private_token: user.private_token)
+ visit dashboard_projects_path(:atom, rss_token: user.rss_token)
end
it "has issue opened event" do
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index 4f6754ad541..a61231ea254 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -43,25 +43,40 @@ describe 'Issues Feed', feature: true do
end
end
+ context 'when authenticated via RSS token' do
+ it 'renders atom feed' do
+ visit namespace_project_issues_path(project.namespace, project, :atom,
+ rss_token: user.rss_token)
+
+ expect(response_headers['Content-Type']).
+ to have_content('application/atom+xml')
+ expect(body).to have_selector('title', text: "#{project.name} issues")
+ expect(body).to have_selector('author email', text: issue.author_public_email)
+ expect(body).to have_selector('assignees assignee email', text: issue.assignees.first.public_email)
+ expect(body).to have_selector('assignee email', text: issue.assignees.first.public_email)
+ expect(body).to have_selector('entry summary', text: issue.title)
+ end
+ end
+
it "renders atom feed with url parameters for project issues" do
visit namespace_project_issues_path(project.namespace, project,
- :atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
+ :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query)
- expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
it "renders atom feed with url parameters for group issues" do
- visit issues_group_path(group, :atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
+ visit issues_group_path(group, :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query)
- expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 7a2987e815d..fae5aaa52bd 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -11,6 +11,13 @@ describe "User Feed", feature: true do
end
end
+ context 'user atom feed via RSS token' do
+ it "renders user atom feed" do
+ visit user_path(user, :atom, rss_token: user.rss_token)
+ expect(body).to have_selector('feed title')
+ end
+ end
+
context 'feed content' do
let(:project) { create(:project) }
let(:issue) do
@@ -40,7 +47,7 @@ describe "User Feed", feature: true do
issue_event(issue, user)
note_event(note, user)
merge_request_event(merge_request, user)
- visit user_path(user, :atom, private_token: user.private_token)
+ visit user_path(user, :atom, rss_token: user.rss_token)
end
it 'has issue opened event' do
diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb
index 67b0f006854..1cf7396bbac 100644
--- a/spec/features/auto_deploy_spec.rb
+++ b/spec/features/auto_deploy_spec.rb
@@ -5,14 +5,7 @@ describe 'Auto deploy' do
let(:project) { create(:project, :repository) }
before do
- project.create_kubernetes_service(
- active: true,
- properties: {
- namespace: project.path,
- api_url: 'https://kubernetes.example.com',
- token: 'a' * 40,
- }
- )
+ create :kubernetes_service, project: project
project.team << [user, :master]
login_as user
end
@@ -53,7 +46,7 @@ describe 'Auto deploy' do
within '.gitlab-ci-yml-selector' do
click_on 'OpenShift'
end
- wait_for_ajax
+ wait_for_requests
click_button 'Commit changes'
expect(page).to have_content('New Merge Request From auto-deploy into master')
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index 505e0b5c355..2b8edac4f10 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Issue Boards add issue modal', :feature, :js do
- include WaitForVueResource
-
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
@@ -19,13 +17,13 @@ describe 'Issue Boards add issue modal', :feature, :js do
login_as(user)
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
it 'resets filtered search state' do
visit namespace_project_board_path(project.namespace, project, board, search: 'testing')
- wait_for_vue_resource
+ wait_for_requests
click_button('Add issues')
@@ -74,7 +72,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
before do
click_button('Add issues')
- wait_for_vue_resource
+ wait_for_requests
end
it 'loads issues' do
@@ -107,7 +105,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
click_button('Add issues')
- wait_for_vue_resource
+ wait_for_requests
page.within('.add-issues-modal') do
expect(find('.add-issues-footer')).not_to have_button(planning.title)
@@ -122,7 +120,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
find('.form-control').native.send_keys(issue.title)
find('.form-control').native.send_keys(:enter)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 1)
end
@@ -133,7 +131,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
find('.form-control').native.send_keys('testing search')
find('.form-control').native.send_keys(:enter)
- wait_for_vue_resource
+ wait_for_requests
expect(page).not_to have_selector('.card')
expect(page).not_to have_content("You haven't added any issues to your project yet")
@@ -233,7 +231,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
click_button 'Add 1 issue'
end
- page.within(first('.board')) do
+ page.within(find('.board:nth-child(2)')) do
expect(page).to have_selector('.card')
end
end
@@ -249,7 +247,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
click_button 'Add 1 issue'
end
- page.within(find('.board:nth-child(2)')) do
+ page.within(find('.board:nth-child(3)')) do
expect(page).to have_selector('.card')
end
end
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 18585488e26..c80453b8227 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -1,7 +1,6 @@
require 'rails_helper'
describe 'Issue Boards', feature: true, js: true do
- include WaitForVueResource
include DragTo
let(:project) { create(:empty_project, :public) }
@@ -19,8 +18,8 @@ describe 'Issue Boards', feature: true, js: true do
context 'no lists' do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
- expect(page).to have_selector('.board', count: 2)
+ wait_for_requests
+ expect(page).to have_selector('.board', count: 3)
end
it 'shows blank state' do
@@ -37,18 +36,18 @@ describe 'Issue Boards', feature: true, js: true do
page.within(find('.board-blank-state')) do
click_button("Nevermind, I'll use my own")
end
- expect(page).to have_selector('.board', count: 1)
+ expect(page).to have_selector('.board', count: 2)
end
it 'creates default lists' do
- lists = ['To Do', 'Doing', 'Closed']
+ lists = ['Backlog', 'To Do', 'Doing', 'Closed']
page.within(find('.board-blank-state')) do
click_button('Add default lists')
end
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.board', count: 3)
+ expect(page).to have_selector('.board', count: 4)
page.all('.board').each_with_index do |list, i|
expect(list.find('.board-title')).to have_content(lists[i])
@@ -84,31 +83,27 @@ describe 'Issue Boards', feature: true, js: true do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.board', count: 3)
- expect(find('.board:nth-child(1)')).to have_selector('.card')
+ expect(page).to have_selector('.board', count: 4)
expect(find('.board:nth-child(2)')).to have_selector('.card')
expect(find('.board:nth-child(3)')).to have_selector('.card')
- end
-
- it 'shows lists' do
- expect(page).to have_selector('.board', count: 3)
+ expect(find('.board:nth-child(4)')).to have_selector('.card')
end
it 'shows description tooltip on list title' do
- page.within('.board:nth-child(1)') do
+ page.within('.board:nth-child(2)') do
expect(find('.board-title span.has-tooltip')[:title]).to eq('Test')
end
end
it 'shows issues in lists' do
- wait_for_board_cards(1, 8)
- wait_for_board_cards(2, 2)
+ wait_for_board_cards(2, 8)
+ wait_for_board_cards(3, 2)
end
it 'shows confidential issues with icon' do
- page.within(find('.board', match: :first)) do
+ page.within(find('.board:nth-child(2)')) do
expect(page).to have_selector('.confidential-icon', count: 1)
end
end
@@ -117,45 +112,45 @@ describe 'Issue Boards', feature: true, js: true do
find('.filtered-search').set(issue8.title)
find('.filtered-search').native.send_keys(:enter)
- wait_for_vue_resource
+ wait_for_requests
- expect(find('.board:nth-child(1)')).to have_selector('.card', count: 0)
expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0)
- expect(find('.board:nth-child(3)')).to have_selector('.card', count: 1)
+ expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0)
+ expect(find('.board:nth-child(4)')).to have_selector('.card', count: 1)
end
it 'search list' do
find('.filtered-search').set(issue5.title)
find('.filtered-search').native.send_keys(:enter)
- wait_for_vue_resource
+ wait_for_requests
- expect(find('.board:nth-child(1)')).to have_selector('.card', count: 1)
- expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0)
+ expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1)
expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0)
+ expect(find('.board:nth-child(4)')).to have_selector('.card', count: 0)
end
it 'allows user to delete board' do
- page.within(find('.board:nth-child(1)')) do
+ page.within(find('.board:nth-child(2)')) do
find('.board-delete').click
end
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.board', count: 2)
+ expect(page).to have_selector('.board', count: 3)
end
it 'removes checkmark in new list dropdown after deleting' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
- page.within(find('.board:nth-child(1)')) do
+ page.within(find('.board:nth-child(2)')) do
find('.board-delete').click
end
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.board', count: 2)
+ expect(page).to have_selector('.board', count: 3)
end
it 'infinite scrolls list' do
@@ -164,21 +159,21 @@ describe 'Issue Boards', feature: true, js: true do
end
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
- page.within(find('.board', match: :first)) do
+ page.within(find('.board:nth-child(2)')) do
expect(page.find('.board-header')).to have_content('58')
expect(page).to have_selector('.card', count: 20)
expect(page).to have_content('Showing 20 of 58 issues')
- evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
- wait_for_vue_resource
+ evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight")
+ wait_for_requests
expect(page).to have_selector('.card', count: 40)
expect(page).to have_content('Showing 40 of 58 issues')
- evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
- wait_for_vue_resource
+ evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight")
+ wait_for_requests
expect(page).to have_selector('.card', count: 58)
expect(page).to have_content('Showing all issues')
@@ -187,83 +182,83 @@ describe 'Issue Boards', feature: true, js: true do
context 'closed' do
it 'shows list of closed issues' do
- wait_for_board_cards(3, 1)
- wait_for_ajax
+ wait_for_board_cards(4, 1)
+ wait_for_requests
end
it 'moves issue to closed' do
- drag(list_from_index: 0, list_to_index: 2)
+ drag(list_from_index: 1, list_to_index: 3)
- wait_for_board_cards(1, 7)
- wait_for_board_cards(2, 2)
+ wait_for_board_cards(2, 7)
wait_for_board_cards(3, 2)
+ wait_for_board_cards(4, 2)
- expect(find('.board:nth-child(1)')).not_to have_content(issue9.title)
- expect(find('.board:nth-child(3)')).to have_selector('.card', count: 2)
- expect(find('.board:nth-child(3)')).to have_content(issue9.title)
- expect(find('.board:nth-child(3)')).not_to have_content(planning.title)
+ expect(find('.board:nth-child(2)')).not_to have_content(issue9.title)
+ expect(find('.board:nth-child(4)')).to have_selector('.card', count: 2)
+ expect(find('.board:nth-child(4)')).to have_content(issue9.title)
+ expect(find('.board:nth-child(4)')).not_to have_content(planning.title)
end
it 'removes all of the same issue to closed' do
- drag(list_from_index: 0, list_to_index: 2)
+ drag(list_from_index: 1, list_to_index: 3)
- wait_for_board_cards(1, 7)
- wait_for_board_cards(2, 2)
+ wait_for_board_cards(2, 7)
wait_for_board_cards(3, 2)
+ wait_for_board_cards(4, 2)
- expect(find('.board:nth-child(1)')).not_to have_content(issue9.title)
- expect(find('.board:nth-child(3)')).to have_content(issue9.title)
- expect(find('.board:nth-child(3)')).not_to have_content(planning.title)
+ expect(find('.board:nth-child(2)')).not_to have_content(issue9.title)
+ expect(find('.board:nth-child(4)')).to have_content(issue9.title)
+ expect(find('.board:nth-child(4)')).not_to have_content(planning.title)
end
end
context 'lists' do
it 'changes position of list' do
- drag(list_from_index: 1, list_to_index: 0, selector: '.board-header')
+ drag(list_from_index: 2, list_to_index: 1, selector: '.board-header')
- wait_for_board_cards(1, 2)
- wait_for_board_cards(2, 8)
- wait_for_board_cards(3, 1)
+ wait_for_board_cards(2, 2)
+ wait_for_board_cards(3, 8)
+ wait_for_board_cards(4, 1)
- expect(find('.board:nth-child(1)')).to have_content(development.title)
- expect(find('.board:nth-child(1)')).to have_content(planning.title)
+ expect(find('.board:nth-child(2)')).to have_content(development.title)
+ expect(find('.board:nth-child(2)')).to have_content(planning.title)
end
it 'issue moves between lists' do
- drag(list_from_index: 0, from_index: 1, list_to_index: 1)
+ drag(list_from_index: 1, from_index: 1, list_to_index: 2)
- wait_for_board_cards(1, 7)
- wait_for_board_cards(2, 2)
- wait_for_board_cards(3, 1)
+ wait_for_board_cards(2, 7)
+ wait_for_board_cards(3, 2)
+ wait_for_board_cards(4, 1)
- expect(find('.board:nth-child(2)')).to have_content(issue6.title)
- expect(find('.board:nth-child(2)').all('.card').last).not_to have_content(development.title)
+ expect(find('.board:nth-child(3)')).to have_content(issue6.title)
+ expect(find('.board:nth-child(3)').all('.card').last).not_to have_content(development.title)
end
it 'issue moves between lists' do
- drag(list_from_index: 1, list_to_index: 0)
+ drag(list_from_index: 2, list_to_index: 1)
- wait_for_board_cards(1, 9)
- wait_for_board_cards(2, 1)
+ wait_for_board_cards(2, 9)
wait_for_board_cards(3, 1)
+ wait_for_board_cards(4, 1)
- expect(find('.board:nth-child(1)')).to have_content(issue7.title)
- expect(find('.board:nth-child(1)').all('.card').first).not_to have_content(planning.title)
+ expect(find('.board:nth-child(2)')).to have_content(issue7.title)
+ expect(find('.board:nth-child(2)').all('.card').first).not_to have_content(planning.title)
end
it 'issue moves from closed' do
- drag(list_from_index: 2, list_to_index: 1)
+ drag(list_from_index: 3, list_to_index: 2)
- expect(find('.board:nth-child(2)')).to have_content(issue8.title)
+ expect(find('.board:nth-child(3)')).to have_content(issue8.title)
- wait_for_board_cards(1, 8)
- wait_for_board_cards(2, 3)
- wait_for_board_cards(3, 0)
+ wait_for_board_cards(2, 8)
+ wait_for_board_cards(3, 3)
+ wait_for_board_cards(4, 0)
end
context 'issue card' do
it 'shows assignee' do
- page.within(find('.board', match: :first)) do
+ page.within(find('.board:nth-child(2)')) do
expect(page).to have_selector('.avatar', count: 1)
end
end
@@ -272,7 +267,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'new list' do
it 'shows all labels in new list dropdown' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-issues-board-new') do
expect(page).to have_content(planning.title)
@@ -283,52 +278,52 @@ describe 'Issue Boards', feature: true, js: true do
it 'creates new list for label' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-issues-board-new') do
click_link testing.title
end
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.board', count: 4)
+ expect(page).to have_selector('.board', count: 5)
end
it 'creates new list for Backlog label' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-issues-board-new') do
click_link backlog.title
end
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.board', count: 4)
+ expect(page).to have_selector('.board', count: 5)
end
it 'creates new list for Closed label' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-issues-board-new') do
click_link closed.title
end
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.board', count: 4)
+ expect(page).to have_selector('.board', count: 5)
end
it 'keeps dropdown open after adding new list' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-issues-board-new') do
click_link closed.title
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_css('#js-add-list.open')
end
@@ -336,7 +331,7 @@ describe 'Issue Boards', feature: true, js: true do
it 'creates new list from a new label' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
click_link 'Create new label'
@@ -346,10 +341,10 @@ describe 'Issue Boards', feature: true, js: true do
click_button 'Create'
- wait_for_ajax
- wait_for_vue_resource
+ wait_for_requests
+ wait_for_requests
- expect(page).to have_selector('.board', count: 4)
+ expect(page).to have_selector('.board', count: 5)
end
end
end
@@ -360,9 +355,9 @@ describe 'Issue Boards', feature: true, js: true do
click_filter_link(user2.username)
submit_filter
- wait_for_vue_resource
- wait_for_board_cards(1, 1)
- wait_for_empty_boards((2..3))
+ wait_for_requests
+ wait_for_board_cards(2, 1)
+ wait_for_empty_boards((3..4))
end
it 'filters by assignee' do
@@ -370,10 +365,10 @@ describe 'Issue Boards', feature: true, js: true do
click_filter_link(user.username)
submit_filter
- wait_for_vue_resource
+ wait_for_requests
- wait_for_board_cards(1, 1)
- wait_for_empty_boards((2..3))
+ wait_for_board_cards(2, 1)
+ wait_for_empty_boards((3..4))
end
it 'filters by milestone' do
@@ -381,10 +376,10 @@ describe 'Issue Boards', feature: true, js: true do
click_filter_link(milestone.title)
submit_filter
- wait_for_vue_resource
- wait_for_board_cards(1, 1)
- wait_for_board_cards(2, 0)
+ wait_for_requests
+ wait_for_board_cards(2, 1)
wait_for_board_cards(3, 0)
+ wait_for_board_cards(4, 0)
end
it 'filters by label' do
@@ -392,9 +387,9 @@ describe 'Issue Boards', feature: true, js: true do
click_filter_link(testing.title)
submit_filter
- wait_for_vue_resource
- wait_for_board_cards(1, 1)
- wait_for_empty_boards((2..3))
+ wait_for_requests
+ wait_for_board_cards(2, 1)
+ wait_for_empty_boards((3..4))
end
it 'filters by label with space after reload' do
@@ -404,17 +399,17 @@ describe 'Issue Boards', feature: true, js: true do
# Test after reload
page.evaluate_script 'window.location.reload()'
- wait_for_board_cards(1, 1)
- wait_for_empty_boards((2..3))
+ wait_for_board_cards(2, 1)
+ wait_for_empty_boards((3..4))
- wait_for_vue_resource
+ wait_for_requests
- page.within(find('.board', match: :first)) do
+ page.within(find('.board:nth-child(2)')) do
expect(page.find('.board-header')).to have_content('1')
expect(page).to have_selector('.card', count: 1)
end
- page.within(find('.board:nth-child(2)')) do
+ page.within(find('.board:nth-child(3)')) do
expect(page.find('.board-header')).to have_content('0')
expect(page).to have_selector('.card', count: 0)
end
@@ -425,12 +420,12 @@ describe 'Issue Boards', feature: true, js: true do
click_filter_link(testing.title)
submit_filter
- wait_for_board_cards(1, 1)
+ wait_for_board_cards(2, 1)
find('.clear-search').click
submit_filter
- wait_for_board_cards(1, 8)
+ wait_for_board_cards(2, 8)
end
it 'infinite scrolls list with label filter' do
@@ -442,19 +437,19 @@ describe 'Issue Boards', feature: true, js: true do
click_filter_link(testing.title)
submit_filter
- wait_for_vue_resource
+ wait_for_requests
- page.within(find('.board', match: :first)) do
+ page.within(find('.board:nth-child(2)')) do
expect(page.find('.board-header')).to have_content('51')
expect(page).to have_selector('.card', count: 20)
expect(page).to have_content('Showing 20 of 51 issues')
- evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
+ evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight")
expect(page).to have_selector('.card', count: 40)
expect(page).to have_content('Showing 40 of 51 issues')
- evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
+ evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight")
expect(page).to have_selector('.card', count: 51)
expect(page).to have_content('Showing all issues')
@@ -470,42 +465,42 @@ describe 'Issue Boards', feature: true, js: true do
submit_filter
- wait_for_vue_resource
+ wait_for_requests
- wait_for_board_cards(1, 1)
- wait_for_empty_boards((2..3))
+ wait_for_board_cards(2, 1)
+ wait_for_empty_boards((3..4))
end
it 'filters by clicking label button on issue' do
- page.within(find('.board', match: :first)) do
+ page.within(find('.board:nth-child(2)')) do
expect(page).to have_selector('.card', count: 8)
expect(find('.card', match: :first)).to have_content(bug.title)
click_button(bug.title)
- wait_for_vue_resource
+ wait_for_requests
end
page.within('.tokens-container') do
expect(page).to have_content(bug.title)
end
- wait_for_vue_resource
+ wait_for_requests
- wait_for_board_cards(1, 1)
- wait_for_empty_boards((2..3))
+ wait_for_board_cards(2, 1)
+ wait_for_empty_boards((3..4))
end
it 'removes label filter by clicking label button on issue' do
- page.within(find('.board', match: :first)) do
+ page.within(find('.board:nth-child(2)')) do
page.within(find('.card', match: :first)) do
click_button(bug.title)
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 1)
end
- wait_for_vue_resource
+ wait_for_requests
end
end
end
@@ -513,7 +508,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'keyboard shortcuts' do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
it 'allows user to use keyboard shortcuts' do
@@ -526,7 +521,7 @@ describe 'Issue Boards', feature: true, js: true do
before do
logout
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
it 'displays lists' do
@@ -550,7 +545,7 @@ describe 'Issue Boards', feature: true, js: true do
logout
login_as(user_guest)
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
it 'does not show create new list' do
diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb
index bfa2a72a256..1c289993e28 100644
--- a/spec/features/boards/issue_ordering_spec.rb
+++ b/spec/features/boards/issue_ordering_spec.rb
@@ -1,7 +1,6 @@
require 'rails_helper'
describe 'Issue Boards', :feature, :js do
- include WaitForVueResource
include DragTo
let(:project) { create(:empty_project, :public) }
@@ -24,13 +23,13 @@ describe 'Issue Boards', :feature, :js do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.board', count: 2)
+ expect(page).to have_selector('.board', count: 3)
end
it 'has un-ordered issue as last issue' do
- page.within(first('.board')) do
+ page.within(find('.board:nth-child(2)')) do
expect(all('.card').last).to have_content(issue4.title)
end
end
@@ -38,9 +37,9 @@ describe 'Issue Boards', :feature, :js do
it 'moves un-ordered issue to top of list' do
drag(from_index: 3, to_index: 0)
- wait_for_vue_resource
+ wait_for_requests
- page.within(first('.board')) do
+ page.within(find('.board:nth-child(2)')) do
expect(first('.card')).to have_content(issue4.title)
end
end
@@ -49,15 +48,15 @@ describe 'Issue Boards', :feature, :js do
context 'ordering in list' do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.board', count: 2)
+ expect(page).to have_selector('.board', count: 3)
end
it 'moves from middle to top' do
drag(from_index: 1, to_index: 0)
- wait_for_vue_resource
+ wait_for_requests
expect(first('.card')).to have_content(issue2.title)
end
@@ -65,7 +64,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves from middle to bottom' do
drag(from_index: 1, to_index: 2)
- wait_for_vue_resource
+ wait_for_requests
expect(all('.card').last).to have_content(issue2.title)
end
@@ -73,7 +72,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves from top to bottom' do
drag(from_index: 0, to_index: 2)
- wait_for_vue_resource
+ wait_for_requests
expect(all('.card').last).to have_content(issue3.title)
end
@@ -81,7 +80,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves from bottom to top' do
drag(from_index: 2, to_index: 0)
- wait_for_vue_resource
+ wait_for_requests
expect(first('.card')).to have_content(issue1.title)
end
@@ -89,7 +88,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves from top to middle' do
drag(from_index: 0, to_index: 1)
- wait_for_vue_resource
+ wait_for_requests
expect(first('.card')).to have_content(issue2.title)
end
@@ -97,7 +96,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves from bottom to middle' do
drag(from_index: 2, to_index: 1)
- wait_for_vue_resource
+ wait_for_requests
expect(all('.card').last).to have_content(issue2.title)
end
@@ -112,52 +111,52 @@ describe 'Issue Boards', :feature, :js do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.board', count: 3)
+ expect(page).to have_selector('.board', count: 4)
end
it 'moves to top of another list' do
- drag(list_from_index: 0, list_to_index: 1)
+ drag(list_from_index: 1, list_to_index: 2)
- wait_for_vue_resource
+ wait_for_requests
- expect(first('.board')).to have_selector('.card', count: 2)
- expect(all('.board')[1]).to have_selector('.card', count: 4)
+ expect(find('.board:nth-child(2)')).to have_selector('.card', count: 2)
+ expect(all('.board')[2]).to have_selector('.card', count: 4)
- page.within(all('.board')[1]) do
+ page.within(all('.board')[2]) do
expect(first('.card')).to have_content(issue3.title)
end
end
it 'moves to bottom of another list' do
- drag(list_from_index: 0, list_to_index: 1, to_index: 2)
+ drag(list_from_index: 1, list_to_index: 2, to_index: 2)
- wait_for_vue_resource
+ wait_for_requests
- expect(first('.board')).to have_selector('.card', count: 2)
- expect(all('.board')[1]).to have_selector('.card', count: 4)
+ expect(find('.board:nth-child(2)')).to have_selector('.card', count: 2)
+ expect(all('.board')[2]).to have_selector('.card', count: 4)
- page.within(all('.board')[1]) do
+ page.within(all('.board')[2]) do
expect(all('.card').last).to have_content(issue3.title)
end
end
it 'moves to index of another list' do
- drag(list_from_index: 0, list_to_index: 1, to_index: 1)
+ drag(list_from_index: 1, list_to_index: 2, to_index: 1)
- wait_for_vue_resource
+ wait_for_requests
- expect(first('.board')).to have_selector('.card', count: 2)
- expect(all('.board')[1]).to have_selector('.card', count: 4)
+ expect(find('.board:nth-child(2)')).to have_selector('.card', count: 2)
+ expect(all('.board')[2]).to have_selector('.card', count: 4)
- page.within(all('.board')[1]) do
+ page.within(all('.board')[2]) do
expect(all('.card')[1]).to have_content(issue3.title)
end
end
end
- def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0)
+ def drag(selector: '.board-list', list_from_index: 1, from_index: 0, to_index: 0, list_to_index: 1)
drag_to(selector: selector,
scrollable: '#board-app',
list_from_index: list_from_index,
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
index a9cc6c49f8e..c2167ba12cd 100644
--- a/spec/features/boards/keyboard_shortcut_spec.rb
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Issue Boards shortcut', feature: true, js: true do
- include WaitForVueResource
-
let(:project) { create(:empty_project) }
before do
@@ -17,6 +15,6 @@ describe 'Issue Boards shortcut', feature: true, js: true do
find('body').native.send_keys('gb')
expect(page).to have_selector('.boards-list')
- wait_for_vue_resource
+ wait_for_requests
end
end
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index e1367c675e5..b6de6143354 100644
--- a/spec/features/boards/modal_filter_spec.rb
+++ b/spec/features/boards/modal_filter_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Issue Boards add issue modal filtering', :feature, :js do
- include WaitForVueResource
-
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
let(:planning) { create(:label, project: project, name: 'Planning') }
@@ -24,7 +22,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
find('.form-control').native.send_keys('testing empty state')
find('.form-control').native.send_keys(:enter)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_content('There are no issues to show.')
end
@@ -38,7 +36,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 0)
@@ -48,7 +46,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
click_button('Add issues')
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 1)
end
@@ -62,13 +60,13 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 0)
find('.clear-search').click
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 1)
end
@@ -89,9 +87,9 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.js-visual-token', text: user2.username)
+ expect(page).to have_selector('.js-visual-token', text: user2.name)
expect(page).to have_selector('.card', count: 1)
end
end
@@ -112,7 +110,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.js-visual-token', text: 'none')
expect(page).to have_selector('.card', count: 1)
@@ -125,9 +123,9 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.js-visual-token', text: user2.username)
+ expect(page).to have_selector('.js-visual-token', text: user2.name)
expect(page).to have_selector('.card', count: 1)
end
end
@@ -147,7 +145,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.js-visual-token', text: 'upcoming')
expect(page).to have_selector('.card', count: 0)
@@ -160,7 +158,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.js-visual-token', text: milestone.name)
expect(page).to have_selector('.card', count: 1)
@@ -182,7 +180,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.js-visual-token', text: 'none')
expect(page).to have_selector('.card', count: 1)
@@ -195,7 +193,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.js-visual-token', text: label.title)
expect(page).to have_selector('.card', count: 1)
@@ -205,7 +203,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
def visit_board
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
click_button('Add issues')
end
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb
index f04a1a89e96..056224dc436 100644
--- a/spec/features/boards/new_issue_spec.rb
+++ b/spec/features/boards/new_issue_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Issue Boards new issue', feature: true, js: true do
- include WaitForVueResource
-
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
let!(:list) { create(:list, board: board, position: 0) }
@@ -15,17 +13,17 @@ describe 'Issue Boards new issue', feature: true, js: true do
login_as(user)
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
- expect(page).to have_selector('.board', count: 2)
+ expect(page).to have_selector('.board', count: 3)
end
it 'displays new issue button' do
- expect(page).to have_selector('.board-issue-count-holder .btn', count: 1)
+ expect(first('.board')).to have_selector('.board-issue-count-holder .btn', count: 1)
end
it 'does not display new issue button in closed list' do
- page.within('.board:nth-child(2)') do
+ page.within('.board:nth-child(3)') do
expect(page).not_to have_selector('.board-issue-count-holder .btn')
end
end
@@ -60,7 +58,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
click_button 'Submit issue'
end
- wait_for_vue_resource
+ wait_for_requests
page.within(first('.board .board-issue-count')) do
expect(page).to have_content('1')
@@ -77,7 +75,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
click_button 'Submit issue'
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.issue-boards-sidebar')
end
@@ -86,7 +84,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
context 'unauthorized user' do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
it 'does not display new issue button' do
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index a5ef280a60f..235e4899707 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Issue Boards', feature: true, js: true do
- include WaitForVueResource
-
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:project) { create(:empty_project, :public) }
@@ -15,7 +13,7 @@ describe 'Issue Boards', feature: true, js: true do
let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) }
let(:board) { create(:board, project: project) }
let!(:list) { create(:list, board: board, label: development, position: 0) }
- let(:card) { first('.board').first('.card') }
+ let(:card) { find('.board:nth-child(2)').first('.card') }
before do
Timecop.freeze
@@ -25,7 +23,7 @@ describe 'Issue Boards', feature: true, js: true do
login_as(user)
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
after do
@@ -74,9 +72,9 @@ describe 'Issue Boards', feature: true, js: true do
click_button 'Remove from board'
end
- wait_for_vue_resource
+ wait_for_requests
- page.within(first('.board')) do
+ page.within(find('.board:nth-child(2)')) do
expect(page).to have_selector('.card', count: 1)
end
end
@@ -88,12 +86,12 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.assignee') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-user') do
click_link user.name
- wait_for_vue_resource
+ wait_for_requests
end
expect(page).to have_content(user.name)
@@ -103,19 +101,19 @@ describe 'Issue Boards', feature: true, js: true do
end
it 'removes the assignee' do
- card_two = first('.board').find('.card:nth-child(2)')
+ card_two = find('.board:nth-child(2)').find('.card:nth-child(2)')
click_card(card_two)
page.within('.assignee') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-user') do
click_link 'Unassigned'
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_content('No assignee')
end
@@ -131,7 +129,7 @@ describe 'Issue Boards', feature: true, js: true do
click_button 'assign yourself'
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_content(user.name)
end
@@ -145,25 +143,25 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.assignee') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-user') do
click_link user.name
- wait_for_vue_resource
+ wait_for_requests
end
expect(page).to have_content(user.name)
end
- page.within(first('.board')) do
- find('.card:nth-child(2)').click
+ page.within(find('.board:nth-child(2)')) do
+ find('.card:nth-child(2)').trigger('click')
end
page.within('.assignee') do
click_link 'Edit'
- expect(page).to have_selector('.is-active')
+ expect(find('.dropdown-menu')).to have_selector('.is-active')
end
end
end
@@ -175,11 +173,11 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.milestone') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
click_link milestone.title
- wait_for_vue_resource
+ wait_for_requests
page.within('.value') do
expect(page).to have_content(milestone.title)
@@ -193,11 +191,11 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.milestone') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
click_link "No Milestone"
- wait_for_vue_resource
+ wait_for_requests
page.within('.value') do
expect(page).not_to have_content(milestone.title)
@@ -215,7 +213,7 @@ describe 'Issue Boards', feature: true, js: true do
click_button Date.today.day
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_content(Date.today.to_s(:medium))
end
@@ -229,11 +227,11 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.labels') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
click_link bug.title
- wait_for_vue_resource
+ wait_for_requests
find('.dropdown-menu-close-icon').click
@@ -253,12 +251,12 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.labels') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
click_link bug.title
click_link regression.title
- wait_for_vue_resource
+ wait_for_requests
find('.dropdown-menu-close-icon').click
@@ -280,11 +278,11 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.labels') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
click_link stretch.title
- wait_for_vue_resource
+ wait_for_requests
find('.dropdown-menu-close-icon').click
@@ -305,7 +303,7 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.subscription') do
click_button 'Subscribe'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content("Unsubscribe")
end
end
diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb
index 6cd7fddd288..4cd05010a93 100644
--- a/spec/features/boards/sub_group_project_spec.rb
+++ b/spec/features/boards/sub_group_project_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Sub-group project issue boards', :feature, :js do
- include WaitForVueResource
-
let(:group) { create(:group) }
let(:nested_group_1) { create(:group, parent: group) }
let(:project) { create(:empty_project, group: nested_group_1) }
@@ -18,7 +16,7 @@ describe 'Sub-group project issue boards', :feature, :js do
login_as(user)
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
it 'creates new label from sidebar' do
@@ -35,7 +33,7 @@ describe 'Sub-group project issue boards', :feature, :js do
click_button 'Create'
- wait_for_ajax
+ wait_for_requests
end
page.within '.labels' do
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index 496faf87a16..1b6d8439f92 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -74,7 +74,7 @@ feature 'Contributions Calendar', :feature, :js do
describe 'calendar day selection' do
before do
visit user.username
- wait_for_ajax
+ wait_for_requests
end
it 'displays calendar' do
@@ -86,7 +86,7 @@ feature 'Contributions Calendar', :feature, :js do
before do
cells[0].click
- wait_for_ajax
+ wait_for_requests
@first_day_activities = selected_day_activities
end
@@ -97,7 +97,7 @@ feature 'Contributions Calendar', :feature, :js do
describe 'select another calendar day' do
before do
cells[1].click
- wait_for_ajax
+ wait_for_requests
end
it 'displays different calendar day activities' do
@@ -108,7 +108,7 @@ feature 'Contributions Calendar', :feature, :js do
describe 'deselect calendar day' do
before do
cells[0].click
- wait_for_ajax
+ wait_for_requests
end
it 'hides calendar day activities' do
@@ -122,7 +122,7 @@ feature 'Contributions Calendar', :feature, :js do
shared_context 'visit user page' do
before do
visit user.username
- wait_for_ajax
+ wait_for_requests
end
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index e6c4ab24de5..2772f05982a 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -76,7 +76,7 @@ describe 'Commits' do
end
end
- describe 'Commit builds' do
+ describe 'Commit builds', :feature, :js do
before do
visit ci_status_path(pipeline)
end
@@ -85,7 +85,6 @@ describe 'Commits' do
expect(page).to have_content pipeline.sha[0..7]
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.user.name
- expect(page).to have_content pipeline.created_at.strftime('%b %d, %Y')
end
end
@@ -102,7 +101,7 @@ describe 'Commits' do
end
describe 'Cancel all builds' do
- it 'cancels commit' do
+ it 'cancels commit', :js do
visit ci_status_path(pipeline)
click_on 'Cancel running'
expect(page).to have_content 'canceled'
@@ -110,9 +109,9 @@ describe 'Commits' do
end
describe 'Cancel build' do
- it 'cancels build' do
+ it 'cancels build', :js do
visit ci_status_path(pipeline)
- find('a.btn[title="Cancel"]').click
+ find('.js-btn-cancel-pipeline').click
expect(page).to have_content 'canceled'
end
end
@@ -152,17 +151,20 @@ describe 'Commits' do
visit ci_status_path(pipeline)
end
- it do
+ it 'Renders header', :feature, :js do
expect(page).to have_content pipeline.sha[0..7]
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.user.name
- expect(page).to have_link('Download artifacts')
expect(page).not_to have_link('Cancel running')
expect(page).not_to have_link('Retry')
end
+
+ it do
+ expect(page).to have_link('Download artifacts')
+ end
end
- context 'when accessing internal project with disallowed access' do
+ context 'when accessing internal project with disallowed access', :feature, :js do
before do
project.update(
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
@@ -175,7 +177,7 @@ describe 'Commits' do
expect(page).to have_content pipeline.sha[0..7]
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.user.name
- expect(page).not_to have_link('Download artifacts')
+
expect(page).not_to have_link('Cancel running')
expect(page).not_to have_link('Retry')
end
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index b86609e07c5..fa7adbe71ea 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -19,7 +19,7 @@ describe "Container Registry" do
scenario 'user visits container registry main page' do
visit_container_registry
- expect(page).to have_content 'No container image repositories'
+ expect(page).to have_content 'No container images'
end
end
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index f197fb44608..740f60c05cc 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -51,7 +51,6 @@ describe 'Copy as GFM', feature: true, js: true do
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
-
- Manage Git repositories with fine grained access controls that keep your code secure
- Perform code reviews and enhance collaboration with merge requests
@@ -66,6 +65,38 @@ describe 'Copy as GFM', feature: true, js: true do
GFM
)
+ aggregate_failures('an accidentally selected empty element') do
+ gfm = '# Heading1'
+
+ html = <<-HTML.strip_heredoc
+ <h1>Heading1</h1>
+
+ <h2></h2>
+ HTML
+
+ output_gfm = html_to_gfm(html)
+ expect(output_gfm.strip).to eq(gfm.strip)
+ end
+
+ aggregate_failures('an accidentally selected other element') do
+ gfm = 'Test comment with **Markdown!**'
+
+ html = <<-HTML.strip_heredoc
+ <li class="note">
+ <div class="md">
+ <p>
+ Test comment with <strong>Markdown!</strong>
+ </p>
+ </div>
+ </li>
+
+ <li class="note"></li>
+ HTML
+
+ output_gfm = html_to_gfm(html)
+ expect(output_gfm.strip).to eq(gfm.strip)
+ end
+
verify(
'InlineDiffFilter',
@@ -96,7 +127,7 @@ describe 'Copy as GFM', feature: true, js: true do
# issue link
"[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue)})",
# issue link with note anchor
- "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})",
+ "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})"
)
verify(
@@ -352,7 +383,6 @@ describe 'Copy as GFM', feature: true, js: true do
<<-GFM.strip_heredoc,
- Nested
-
- Lists
GFM
@@ -375,7 +405,6 @@ describe 'Copy as GFM', feature: true, js: true do
<<-GFM.strip_heredoc,
1. Nested
-
1. Numbered lists
GFM
@@ -479,7 +508,7 @@ describe 'Copy as GFM', feature: true, js: true do
context 'from a blob' do
before do
visit namespace_project_blob_path(project.namespace, project, File.join('master', 'files/ruby/popen.rb'))
- wait_for_ajax
+ wait_for_requests
end
context 'selecting one word of text' do
@@ -521,7 +550,7 @@ describe 'Copy as GFM', feature: true, js: true do
context 'from a GFM code block' do
before do
visit namespace_project_blob_path(project.namespace, project, File.join('markdown', 'doc/api/users.md'))
- wait_for_ajax
+ wait_for_requests
end
context 'selecting one word of text' do
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index 7c9d522273b..b416bbd3c79 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -6,16 +6,18 @@ feature 'Cycle Analytics', feature: true, js: true do
let(:project) { create(:project, :repository) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
let(:milestone) { create(:milestone, project: project) }
- let(:mr) { create_merge_request_closing_issue(issue) }
- let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha) }
+ let(:mr) { create_merge_request_closing_issue(issue, commit_message: "References #{issue.to_reference}") }
+ let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
context 'as an allowed user' do
context 'when project is new' do
before do
- project.team << [user, :master]
+ project.add_master(user)
+
login_as(user)
+
visit namespace_project_cycle_analytics_path(project.namespace, project)
- wait_for_ajax
+ wait_for_requests
end
it 'shows introductory message' do
@@ -30,9 +32,9 @@ feature 'Cycle Analytics', feature: true, js: true do
context "when there's cycle analytics data" do
before do
- project.team << [user, :master]
-
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
+ project.add_master(user)
+
create_cycle
deploy_master
@@ -70,7 +72,7 @@ feature 'Cycle Analytics', feature: true, js: true do
project.team << [user, :master]
login_as(user)
visit namespace_project_cycle_analytics_path(project.namespace, project)
- wait_for_ajax
+ wait_for_requests
end
it 'shows the content in Spanish' do
@@ -85,7 +87,7 @@ feature 'Cycle Analytics', feature: true, js: true do
context "as a guest" do
before do
- project.team << [guest, :guest]
+ project.add_guest(guest)
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
create_cycle
@@ -93,7 +95,7 @@ feature 'Cycle Analytics', feature: true, js: true do
login_as(guest)
visit namespace_project_cycle_analytics_path(project.namespace, project)
- wait_for_ajax
+ wait_for_requests
end
it 'needs permissions to see restricted stages' do
@@ -137,6 +139,6 @@ feature 'Cycle Analytics', feature: true, js: true do
def click_stage(stage_name)
find('.stage-nav li', text: stage_name).click
- wait_for_ajax
+ wait_for_requests
end
end
diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb
index c977f266296..0764044260e 100644
--- a/spec/features/dashboard/activity_spec.rb
+++ b/spec/features/dashboard/activity_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'Dashboard Activity', feature: true do
login_as(create :user)
visit activity_dashboard_path
end
-
- it_behaves_like "it has an RSS button with current_user's private token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+
+ 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
diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb
index 0e9e3f78be2..1793e323588 100644
--- a/spec/features/dashboard/datetime_on_tooltips_spec.rb
+++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb
@@ -15,7 +15,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do
login_as user
visit user_path(user)
- wait_for_ajax()
+ wait_for_requests()
page.find('.js-timeago').hover
end
@@ -32,7 +32,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do
login_as user
visit user_snippets_path(user)
- wait_for_ajax()
+ wait_for_requests()
page.find('.js-timeago.snippet-created-ago').hover
end
diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb
index 1d4b86ed4b4..8e20fdec8ad 100644
--- a/spec/features/dashboard/group_spec.rb
+++ b/spec/features/dashboard/group_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe 'Dashboard Group', feature: true do
it 'creates new group', js: true do
visit dashboard_groups_path
- click_link 'New group'
+ find('.btn-new').trigger('click')
new_path = 'Samurai'
new_description = 'Tokugawa Shogunate'
diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb
index 52b4d82e856..b0e2953dda2 100644
--- a/spec/features/dashboard/groups_list_spec.rb
+++ b/spec/features/dashboard/groups_list_spec.rb
@@ -23,7 +23,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
it 'filters groups' do
fill_in 'filter_groups', with: group.name
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content(group.full_name)
expect(page).not_to have_content(nested_group.full_name)
@@ -32,10 +32,10 @@ describe 'Dashboard Groups page', js: true, feature: true do
it 'resets search when user cleans the input' do
fill_in 'filter_groups', with: group.name
- wait_for_ajax
+ wait_for_requests
fill_in 'filter_groups', with: ""
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content(group.full_name)
expect(page).to have_content(nested_group.full_name)
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index 7a132dba1e9..2cea6b1563e 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -2,66 +2,75 @@ require 'spec_helper'
RSpec.describe 'Dashboard Issues', feature: true do
let(:current_user) { create :user }
- let(:public_project) { create(:empty_project, :public) }
- let(:project) do
- create(:empty_project) do |project|
- project.team << [current_user, :master]
- end
- end
-
+ let!(:public_project) { create(:empty_project, :public) }
+ let(:project) { create(:empty_project) }
+ let(:project_with_issues_disabled) { create(:empty_project, :issues_disabled) }
let!(:authored_issue) { create :issue, author: current_user, project: project }
let!(:authored_issue_on_public_project) { create :issue, author: current_user, project: public_project }
let!(:assigned_issue) { create :issue, assignees: [current_user], project: project }
let!(:other_issue) { create :issue, project: project }
before do
+ [project, project_with_issues_disabled].each { |project| project.team << [current_user, :master] }
login_as(current_user)
-
visit issues_dashboard_path(assignee_id: current_user.id)
end
- it 'shows issues assigned to current user' do
- expect(page).to have_content(assigned_issue.title)
- expect(page).not_to have_content(authored_issue.title)
- expect(page).not_to have_content(other_issue.title)
- end
+ describe 'issues' do
+ it 'shows issues assigned to current user' do
+ expect(page).to have_content(assigned_issue.title)
+ expect(page).not_to have_content(authored_issue.title)
+ expect(page).not_to have_content(other_issue.title)
+ end
- it 'shows checkmark when unassigned is selected for assignee', js: true do
- find('.js-assignee-search').click
- find('li', text: 'Unassigned').click
- find('.js-assignee-search').click
+ it 'shows checkmark when unassigned is selected for assignee', js: true do
+ find('.js-assignee-search').click
+ find('li', text: 'Unassigned').click
+ find('.js-assignee-search').click
- expect(find('li[data-user-id="0"] a.is-active')).to be_visible
- end
+ expect(find('li[data-user-id="0"] a.is-active')).to be_visible
+ end
+
+ it 'shows issues when current user is author', js: true do
+ find('#assignee_id', visible: false).set('')
+ find('.js-author-search', match: :first).click
- it 'shows issues when current user is author', js: true do
- find('#assignee_id', visible: false).set('')
- find('.js-author-search', match: :first).click
+ expect(find('li[data-user-id="null"] a.is-active')).to be_visible
- expect(find('li[data-user-id="null"] a.is-active')).to be_visible
+ find('.dropdown-menu-author li a', match: :first, text: current_user.to_reference).click
+ find('.js-author-search', match: :first).click
- find('.dropdown-menu-author li a', match: :first, text: current_user.to_reference).click
- find('.js-author-search', match: :first).click
+ page.within '.dropdown-menu-user' do
+ expect(find('.dropdown-menu-author li a.is-active', match: :first, text: current_user.to_reference)).to be_visible
+ end
- page.within '.dropdown-menu-user' do
- expect(find('.dropdown-menu-author li a.is-active', match: :first, text: current_user.to_reference)).to be_visible
+ expect(page).to have_content(authored_issue.title)
+ expect(page).to have_content(authored_issue_on_public_project.title)
+ expect(page).not_to have_content(assigned_issue.title)
+ expect(page).not_to have_content(other_issue.title)
end
- expect(page).to have_content(authored_issue.title)
- expect(page).to have_content(authored_issue_on_public_project.title)
- expect(page).not_to have_content(assigned_issue.title)
- expect(page).not_to have_content(other_issue.title)
- end
+ it 'shows all issues' do
+ click_link('Reset filters')
- it 'shows all issues' do
- click_link('Reset filters')
+ expect(page).to have_content(authored_issue.title)
+ expect(page).to have_content(authored_issue_on_public_project.title)
+ expect(page).to have_content(assigned_issue.title)
+ expect(page).to have_content(other_issue.title)
+ end
- expect(page).to have_content(authored_issue.title)
- expect(page).to have_content(authored_issue_on_public_project.title)
- expect(page).to have_content(assigned_issue.title)
- expect(page).to have_content(other_issue.title)
+ 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 private token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ describe 'new issue dropdown' do
+ it 'shows projects only with issues feature enabled', js: true do
+ find('.new-project-item-select-button').trigger('click')
+
+ page.within('.select2-results') do
+ expect(page).to have_content(project.name_with_namespace)
+ expect(page).not_to have_content(project_with_issues_disabled.name_with_namespace)
+ end
+ end
+ end
end
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 508ca38d7e5..9cebe52c444 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -2,16 +2,28 @@ require 'spec_helper'
describe 'Dashboard Merge Requests' do
let(:current_user) { create :user }
- let(:project) do
- create(:empty_project) do |project|
- project.add_master(current_user)
- end
- end
+ let(:project) { create(:empty_project) }
+ let(:project_with_merge_requests_disabled) { create(:empty_project, :merge_requests_disabled) }
before do
+ [project, project_with_merge_requests_disabled].each { |project| project.team << [current_user, :master] }
+
login_as(current_user)
end
+ describe 'new merge request dropdown' do
+ before { visit merge_requests_dashboard_path }
+
+ it 'shows projects only with merge requests feature enabled', js: true do
+ find('.new-project-item-select-button').trigger('click')
+
+ page.within('.select2-results') do
+ expect(page).to have_content(project.name_with_namespace)
+ expect(page).not_to have_content(project_with_merge_requests_disabled.name_with_namespace)
+ end
+ end
+ end
+
it 'should show an empty state' do
visit merge_requests_dashboard_path(assignee_id: current_user.id)
diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb
new file mode 100644
index 00000000000..b5b92c36895
--- /dev/null
+++ b/spec/features/dashboard/milestone_filter_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe 'Dashboard > milestone filter', :feature, :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, name: 'test', namespace: user.namespace) }
+ let(:milestone) { create(:milestone, title: "v1.0", project: project) }
+ let(:milestone2) { create(:milestone, title: "v2.0", project: project) }
+ let!(:issue) { create :issue, author: user, project: project, milestone: milestone }
+ let!(:issue2) { create :issue, author: user, project: project, milestone: milestone2 }
+
+ before do
+ login_as(user)
+ visit issues_dashboard_path(author_id: user.id)
+ end
+
+ context 'default state' do
+ it 'shows issues with Any Milestone' do
+ page.all('.issue-info').each do |issue_info|
+ expect(issue_info.text).to match(/v\d.0/)
+ end
+ end
+ end
+
+ context 'filtering by milestone' do
+ milestone_select = '.js-milestone-select'
+
+ before do
+ find(milestone_select).click
+ wait_for_requests
+
+ page.within('.dropdown-content') do
+ click_link 'v1.0'
+ end
+
+ find(milestone_select).click
+ wait_for_requests
+ end
+
+ it 'shows issues with Milestone v1.0' do
+ expect(find('.issues-list')).to have_selector('.issue', count: 1)
+ expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1)
+ end
+
+ it 'should not change active Milestone unless clicked' do
+ expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1)
+
+ # open & close dropdown
+ find('.dropdown-menu-close').click
+
+ expect(find('.milestone-filter')).not_to have_selector('.dropdown.open')
+
+ find(milestone_select).click
+
+ expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1)
+ expect(find('.dropdown-content a.is-active')).to have_content('v1.0')
+ end
+ end
+end
diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb
index 16c214ae060..cdf919af9b5 100644
--- a/spec/features/dashboard/project_member_activity_index_spec.rb
+++ b/spec/features/dashboard/project_member_activity_index_spec.rb
@@ -11,7 +11,7 @@ feature 'Project member activity', feature: true, js: true do
def visit_activities_and_wait_with_event(event_type)
Event.create(project: project, author_id: user.id, action: event_type)
visit activity_namespace_project_path(project.namespace, project)
- wait_for_ajax
+ wait_for_requests
end
subject { page.find(".event-title").text }
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index f1789fc9d43..3568954a548 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -3,10 +3,11 @@ require 'spec_helper'
RSpec.describe 'Dashboard Projects', feature: true do
let(:user) { create(:user) }
let(:project) { create(:project, name: "awesome stuff") }
+ let(:project2) { create(:project, :public, name: 'Community project') }
before do
project.team << [user, :developer]
- login_as user
+ login_as(user)
end
it 'shows the project the user in a member of in the list' do
@@ -14,6 +15,26 @@ RSpec.describe 'Dashboard Projects', feature: true do
expect(page).to have_content('awesome stuff')
end
+ it 'shows the last_activity_at attribute as the update date' do
+ now = Time.now
+ project.update_column(:last_activity_at, now)
+
+ visit dashboard_projects_path
+
+ expect(page).to have_xpath("//time[@datetime='#{now.getutc.iso8601}']")
+ end
+
+ context 'when on Starred projects tab' do
+ it 'shows only starred projects' do
+ user.toggle_star(project2)
+
+ visit(starred_dashboard_projects_path)
+
+ expect(page).not_to have_content(project.name)
+ expect(page).to have_content(project2.name)
+ end
+ end
+
describe "with a pipeline", redis: true do
let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) }
@@ -31,5 +52,5 @@ RSpec.describe 'Dashboard Projects', feature: true do
end
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index 4c9adcabe34..349b948eaee 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Dashboard shortcuts', feature: true, js: true do
+feature 'Dashboard shortcuts', :feature, :js do
context 'logged in' do
before do
login_as :user
@@ -8,21 +8,21 @@ feature 'Dashboard shortcuts', feature: true, js: true do
end
scenario 'Navigate to tabs' do
- find('body').native.send_keys([:shift, 'P'])
-
- check_page_title('Projects')
-
- find('body').native.send_key([:shift, 'I'])
+ find('body').send_keys([:shift, 'I'])
check_page_title('Issues')
- find('body').native.send_key([:shift, 'M'])
+ find('body').send_keys([:shift, 'M'])
check_page_title('Merge Requests')
- find('body').native.send_keys([:shift, 'T'])
+ find('body').send_keys([:shift, 'T'])
check_page_title('Todos')
+
+ find('body').send_keys([:shift, 'P'])
+
+ check_page_title('Projects')
end
end
@@ -32,17 +32,20 @@ feature 'Dashboard shortcuts', feature: true, js: true do
end
scenario 'Navigate to tabs' do
- find('body').native.send_keys([:shift, 'P'])
-
- expect(page).to have_content('No projects found')
-
- find('body').native.send_keys([:shift, 'G'])
+ find('body').send_keys([:shift, 'G'])
+ find('.nothing-here-block')
expect(page).to have_content('No public groups')
- find('body').native.send_keys([:shift, 'S'])
+ find('body').send_keys([:shift, 'S'])
+ find('.nothing-here-block')
expect(page).to have_selector('.snippets-list-holder')
+
+ find('body').send_keys([:shift, 'P'])
+
+ find('.nothing-here-block')
+ expect(page).to have_content('No projects found')
end
end
diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb
index ad60fb2c74f..1c53f6dff06 100644
--- a/spec/features/dashboard_issues_spec.rb
+++ b/spec/features/dashboard_issues_spec.rb
@@ -53,10 +53,10 @@ describe "Dashboard Issues filtering", feature: true, js: true do
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
- expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('milestone_title' => [''])
expect(params).to include('assignee_id' => [user.id.to_s])
- expect(auto_discovery_params).to include('private_token' => [user.private_token])
+ expect(auto_discovery_params).to include('rss_token' => [user.rss_token])
expect(auto_discovery_params).to include('milestone_title' => [''])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index 76c77e0bc5f..c4d5077e5e1 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -5,6 +5,11 @@ feature 'Expand and collapse diffs', js: true, feature: true do
let(:project) { create(:project, :repository) }
before do
+ # Set the limits to those when these specs were written, to avoid having to
+ # update the test repo every time we change them.
+ allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(100.kilobytes)
+ allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(10.kilobytes)
+
login_as :admin
# Ensure that undiffable.md is in .gitattributes
@@ -36,7 +41,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: "#{large_diff[:id]}_0_1")
execute_script('window.location.reload()')
- wait_for_ajax
+ wait_for_requests
expect(large_diff).to have_selector('.code')
expect(large_diff).not_to have_selector('.nothing-here-block')
@@ -50,7 +55,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: large_diff[:id])
execute_script('window.location.reload()')
- wait_for_ajax
+ wait_for_requests
expect(large_diff).to have_selector('.code')
expect(large_diff).not_to have_selector('.nothing-here-block')
@@ -62,18 +67,6 @@ feature 'Expand and collapse diffs', js: true, feature: true do
expect(small_diff).not_to have_selector('.nothing-here-block')
end
- it 'collapses large diffs by default' do
- expect(large_diff).not_to have_selector('.code')
- expect(large_diff).to have_selector('.nothing-here-block')
- end
-
- it 'collapses large diffs for renamed files by default' do
- expect(large_diff_renamed).not_to have_selector('.code')
- expect(large_diff_renamed).to have_selector('.nothing-here-block')
- expect(large_diff_renamed).to have_selector('.js-file-title .deletion')
- expect(large_diff_renamed).to have_selector('.js-file-title .addition')
- end
-
it 'shows non-renderable diffs as such immediately, regardless of their size' do
expect(undiffable).not_to have_selector('.code')
expect(undiffable).to have_selector('.nothing-here-block')
@@ -94,7 +87,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 'expanding a diff for a renamed file' do
before do
large_diff_renamed.find('.click-to-expand').click
- wait_for_ajax
+ wait_for_requests
end
it 'shows the old content' do
@@ -116,7 +109,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
find('.js-file-title', match: :first)
# Click `large_diff.md` title
all('.diff-toggle-caret')[1].click
- wait_for_ajax
+ wait_for_requests
end
it 'makes a request to get the content' do
@@ -139,7 +132,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
large_diff.find('.add-diff-note').click
large_diff.find('.note-textarea').send_keys comment_text
large_diff.find_button('Comment').click
- wait_for_ajax
+ wait_for_requests
end
it 'adds the comment' do
@@ -160,7 +153,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
find('.js-file-title', match: :first)
# Click `large_diff.md` title
all('.diff-toggle-caret')[1].click
- wait_for_ajax
+ wait_for_requests
end
it 'shows the diff content' do
@@ -216,7 +209,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
expect(page).to have_no_content('No longer a symlink')
find('.click-to-expand').click
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('No longer a symlink')
end
@@ -273,7 +266,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
expect(page).to have_content('too_large_image.jpg')
find('.note-textarea')
- wait_for_ajax
+ wait_for_requests
execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
end
diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb
index 9828cb179a7..d4284ed099b 100644
--- a/spec/features/explore/groups_list_spec.rb
+++ b/spec/features/explore/groups_list_spec.rb
@@ -23,7 +23,7 @@ describe 'Explore Groups page', :js, :feature do
it 'filters groups' do
fill_in 'filter_groups', with: group.name
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content(group.full_name)
expect(page).not_to have_content(public_group.full_name)
@@ -32,10 +32,10 @@ describe 'Explore Groups page', :js, :feature do
it 'resets search when user cleans the input' do
fill_in 'filter_groups', with: group.name
- wait_for_ajax
+ wait_for_requests
fill_in 'filter_groups', with: ""
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content(group.full_name)
expect(page).to have_content(public_group.full_name)
diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb
new file mode 100644
index 00000000000..15a6354211b
--- /dev/null
+++ b/spec/features/explore/new_menu_spec.rb
@@ -0,0 +1,172 @@
+require 'spec_helper'
+
+feature 'Top Plus Menu', feature: true, js: true do
+ let(:user) { create :user }
+ let(:guest_user) { create :user}
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
+ let(:public_project) { create(:project, :public) }
+
+ before do
+ group.add_owner(user)
+ group.add_guest(guest_user)
+
+ project.add_guest(guest_user)
+ end
+
+ context 'used by full user' do
+ before do
+ login_as(user)
+ end
+
+ scenario 'click on New project shows new project page' do
+ visit root_dashboard_path
+
+ click_topmenuitem("New project")
+
+ expect(page).to have_content('Project path')
+ expect(page).to have_content('Project name')
+ end
+
+ scenario 'click on New group shows new group page' do
+ visit root_dashboard_path
+
+ click_topmenuitem("New group")
+
+ expect(page).to have_content('Group path')
+ expect(page).to have_content('Group name')
+ end
+
+ scenario 'click on New snippet shows new snippet page' do
+ visit root_dashboard_path
+
+ click_topmenuitem("New snippet")
+
+ expect(page).to have_content('New Snippet')
+ expect(page).to have_content('Title')
+ end
+
+ scenario 'click on New issue shows new issue page' do
+ visit namespace_project_path(project.namespace, project)
+
+ click_topmenuitem("New issue")
+
+ expect(page).to have_content('New Issue')
+ expect(page).to have_content('Title')
+ end
+
+ scenario 'click on New merge request shows new merge request page' do
+ visit namespace_project_path(project.namespace, project)
+
+ click_topmenuitem("New merge request")
+
+ expect(page).to have_content('New Merge Request')
+ expect(page).to have_content('Source branch')
+ expect(page).to have_content('Target branch')
+ end
+
+ scenario 'click on New project snippet shows new snippet page' do
+ visit namespace_project_path(project.namespace, project)
+
+ page.within '.header-content' do
+ find('.header-new-dropdown-toggle').trigger('click')
+ expect(page).to have_selector('.header-new.dropdown.open', count: 1)
+ find('.header-new-project-snippet a').trigger('click')
+ end
+
+ expect(page).to have_content('New Snippet')
+ expect(page).to have_content('Title')
+ end
+
+ scenario 'Click on New subgroup shows new group page' do
+ visit group_path(group)
+
+ click_topmenuitem("New subgroup")
+
+ expect(page).to have_content('Group path')
+ expect(page).to have_content('Group name')
+ end
+
+ scenario 'Click on New project in group shows new project page' do
+ visit group_path(group)
+
+ page.within '.header-content' do
+ find('.header-new-dropdown-toggle').trigger('click')
+ expect(page).to have_selector('.header-new.dropdown.open', count: 1)
+ find('.header-new-group-project a').trigger('click')
+ end
+
+ expect(page).to have_content('Project path')
+ expect(page).to have_content('Project name')
+ end
+ end
+
+ context 'used by guest user' do
+ before do
+ login_as(guest_user)
+ end
+
+ scenario 'click on New issue shows new issue page' do
+ visit namespace_project_path(project.namespace, project)
+
+ click_topmenuitem("New issue")
+
+ expect(page).to have_content('New Issue')
+ expect(page).to have_content('Title')
+ end
+
+ scenario 'has no New merge request menu item' do
+ visit namespace_project_path(project.namespace, project)
+
+ hasnot_topmenuitem("New merge request")
+ end
+
+ scenario 'has no New project snippet menu item' do
+ visit namespace_project_path(project.namespace, project)
+
+ expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet')
+ end
+
+ scenario 'public project has no New Issue Button' do
+ visit namespace_project_path(public_project.namespace, public_project)
+
+ hasnot_topmenuitem("New issue")
+ end
+
+ scenario 'public project has no New merge request menu item' do
+ visit namespace_project_path(public_project.namespace, public_project)
+
+ hasnot_topmenuitem("New merge request")
+ end
+
+ scenario 'public project has no New project snippet menu item' do
+ visit namespace_project_path(public_project.namespace, public_project)
+
+ expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet')
+ end
+
+ scenario 'has no New subgroup menu item' do
+ visit group_path(group)
+
+ hasnot_topmenuitem("New subgroup")
+ end
+
+ scenario 'has no New project for group menu item' do
+ visit group_path(group)
+
+ expect(find('.header-new.dropdown')).not_to have_selector('.header-new-group-project')
+ end
+ end
+
+ def click_topmenuitem(item_name)
+ page.within '.header-content' do
+ find('.header-new-dropdown-toggle').trigger('click')
+ expect(page).to have_selector('.header-new.dropdown.open', count: 1)
+ click_link item_name
+ end
+ end
+
+ def hasnot_topmenuitem(item_name)
+ expect(find('.header-new.dropdown')).not_to have_content(item_name)
+ end
+end
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 005a029a393..55092412340 100644
--- a/spec/features/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -49,8 +49,6 @@ describe "GitLab Flavored Markdown", feature: true do
end
describe "for issues", feature: true, js: true do
- include WaitForVueResource
-
before do
@other_issue = create(:issue,
author: @user,
diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb
index 3b481cba424..81f9c103e95 100644
--- a/spec/features/groups/activity_spec.rb
+++ b/spec/features/groups/activity_spec.rb
@@ -11,8 +11,8 @@ feature 'Group activity page', feature: true do
visit path
end
- it_behaves_like "it has an RSS button with current_user's private token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ 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
@@ -20,7 +20,7 @@ feature 'Group activity page', feature: true do
visit path
end
- it_behaves_like "it has an RSS button without a private token"
- it_behaves_like "an autodiscoverable RSS feed without a private 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
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 45f57845c74..d6b88542ef7 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -12,15 +12,15 @@ feature 'Group issues page', feature: true do
context 'when signed in' do
let(:user) { user_in_group }
- it_behaves_like "it has an RSS button with current_user's private token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ 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 }
- it_behaves_like "it has an RSS button without a private token"
- it_behaves_like "an autodiscoverable RSS feed without a private 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
@@ -33,7 +33,7 @@ feature 'Group issues page', feature: true do
it 'filters by only group users' do
click_button('Assignee')
- wait_for_ajax
+ wait_for_requests
expect(find('.dropdown-menu-assignee')).to have_link(user.name)
expect(find('.dropdown-menu-assignee')).not_to have_link(user2.name)
diff --git a/spec/features/groups/members/sorting_spec.rb b/spec/features/groups/members/sorting_spec.rb
index 608aedd3471..902d3f789ff 100644
--- a/spec/features/groups/members/sorting_spec.rb
+++ b/spec/features/groups/members/sorting_spec.rb
@@ -68,7 +68,7 @@ feature 'Groups > Members > Sorting', feature: true do
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
end
- scenario 'sorts by recent sign in' do
+ scenario 'sorts by recent sign in', :redis do
visit_members_list(sort: :recent_sign_in)
expect(first_member).to include(owner.name)
@@ -76,7 +76,7 @@ feature 'Groups > Members > Sorting', feature: true do
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
end
- scenario 'sorts by oldest sign in' do
+ scenario 'sorts by oldest sign in', :redis do
visit_members_list(sort: :oldest_sign_in)
expect(first_member).to include(developer.name)
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index fb39693e8ca..d3c49c37374 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -11,7 +11,7 @@ feature 'Group show page', feature: true do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
context 'when signed out' do
@@ -19,6 +19,6 @@ feature 'Group show page', feature: true do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed without a private token"
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index f3ec80bb149..414838fa22e 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -52,6 +52,9 @@ describe 'issuable list', feature: true do
create(:issue, project: project, author: user)
else
create(:merge_request, source_project: project, source_branch: generate(:branch))
+ source_branch = FFaker::Name.name
+ pipeline = create(:ci_empty_pipeline, project: project, ref: source_branch, status: %w(running failed success).sample, sha: 'any')
+ create(:merge_request, title: FFaker::Lorem.sentence, source_project: project, source_branch: source_branch, head_pipeline: pipeline)
end
2.times do
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index 853632614c4..81ae54c7a10 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Awards Emoji', feature: true do
- include WaitForVueResource
-
let!(:project) { create(:project, :public) }
let!(:user) { create(:user) }
let(:issue) do
@@ -22,7 +20,7 @@ describe 'Awards Emoji', feature: true do
# The `heart_tip` emoji is not valid anymore so we need to skip validation
issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false)
visit namespace_project_issue_path(project.namespace, project, issue)
- wait_for_vue_resource
+ wait_for_requests
end
# Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529
@@ -36,19 +34,19 @@ describe 'Awards Emoji', feature: true do
before do
visit namespace_project_issue_path(project.namespace, project, issue)
- wait_for_vue_resource
+ wait_for_requests
end
it 'increments the thumbsdown emoji', js: true do
find('[data-name="thumbsdown"]').click
- wait_for_ajax
+ wait_for_requests
expect(thumbsdown_emoji).to have_text("1")
end
context 'click the thumbsup emoji' do
it 'increments the thumbsup emoji', js: true do
find('[data-name="thumbsup"]').click
- wait_for_ajax
+ wait_for_requests
expect(thumbsup_emoji).to have_text("1")
end
@@ -60,7 +58,7 @@ describe 'Awards Emoji', feature: true do
context 'click the thumbsdown emoji' do
it 'increments the thumbsdown emoji', js: true do
find('[data-name="thumbsdown"]').click
- wait_for_ajax
+ wait_for_requests
expect(thumbsdown_emoji).to have_text("1")
end
@@ -113,7 +111,7 @@ describe 'Awards Emoji', feature: true do
click_button 'Comment'
end
- wait_for_ajax
+ wait_for_requests
end
def thumbsup_emoji
@@ -143,6 +141,6 @@ describe 'Awards Emoji', feature: true do
find('[data-name="smiley"]').click
end
- wait_for_ajax
+ wait_for_requests
end
end
diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb
index 08e3f99e29f..fcf22dd5033 100644
--- a/spec/features/issues/award_spec.rb
+++ b/spec/features/issues/award_spec.rb
@@ -6,12 +6,10 @@ feature 'Issue awards', js: true, feature: true do
let(:issue) { create(:issue, project: project) }
describe 'logged in' do
- include WaitForVueResource
-
before do
login_as(user)
visit namespace_project_issue_path(project.namespace, project, issue)
- wait_for_vue_resource
+ wait_for_requests
end
it 'adds award to issue' do
@@ -41,11 +39,9 @@ feature 'Issue awards', js: true, feature: true do
end
describe 'logged out' do
- include WaitForVueResource
-
before do
visit namespace_project_issue_path(project.namespace, project, issue)
- wait_for_vue_resource
+ wait_for_requests
end
it 'does not see award menu button' do
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 1de50d6d77e..95b4930cd32 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -18,13 +18,13 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'can bulk assign' do
before do
- visit namespace_project_issues_path(project.namespace, project)
+ enable_bulk_update
end
context 'a label' do
context 'to all issues' do
before do
- check 'check_all_issues'
+ check 'check-all-issues'
open_labels_dropdown ['bug']
update_issues
end
@@ -52,7 +52,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'multiple labels' do
context 'to all issues' do
before do
- check 'check_all_issues'
+ check 'check-all-issues'
open_labels_dropdown %w(bug feature)
update_issues
end
@@ -86,9 +86,10 @@ feature 'Issues > Labels bulk assignment', feature: true do
before do
issue2.labels << bug
issue2.labels << feature
- visit namespace_project_issues_path(project.namespace, project)
- check 'check_all_issues'
+ enable_bulk_update
+ check 'check-all-issues'
+
open_labels_dropdown ['bug']
update_issues
end
@@ -107,9 +108,8 @@ feature 'Issues > Labels bulk assignment', feature: true do
issue2.labels << bug
issue2.labels << feature
- visit namespace_project_issues_path(project.namespace, project)
-
- check 'check_all_issues'
+ enable_bulk_update
+ check 'check-all-issues'
unmark_labels_in_dropdown %w(bug feature)
update_issues
end
@@ -127,8 +127,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
issue1.labels << bug
issue2.labels << feature
- visit namespace_project_issues_path(project.namespace, project)
-
+ enable_bulk_update
check_issue issue1
unmark_labels_in_dropdown ['bug']
update_issues
@@ -147,8 +146,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
issue2.labels << bug
issue2.labels << feature
- visit namespace_project_issues_path(project.namespace, project)
-
+ enable_bulk_update
check_issue issue1
check_issue issue2
unmark_labels_in_dropdown ['bug']
@@ -171,14 +169,15 @@ feature 'Issues > Labels bulk assignment', feature: true do
before do
issue1.labels << bug
issue2.labels << feature
- visit namespace_project_issues_path(project.namespace, project)
+ enable_bulk_update
end
it 'keeps labels' do
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
expect(find("#issue_#{issue2.id}")).to have_content 'feature'
- check 'check_all_issues'
+ check 'check-all-issues'
+
open_milestone_dropdown(['First Release'])
update_issues
@@ -192,14 +191,13 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'setting a milestone and adding another label' do
before do
issue1.labels << bug
-
- visit namespace_project_issues_path(project.namespace, project)
+ enable_bulk_update
end
it 'keeps existing label and new label is present' do
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
- check 'check_all_issues'
+ check 'check-all-issues'
open_milestone_dropdown ['First Release']
open_labels_dropdown ['feature']
update_issues
@@ -218,7 +216,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
issue1.labels << feature
issue2.labels << feature
- visit namespace_project_issues_path(project.namespace, project)
+ enable_bulk_update
end
it 'keeps existing label and new label is present' do
@@ -226,7 +224,8 @@ feature 'Issues > Labels bulk assignment', feature: true do
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
expect(find("#issue_#{issue2.id}")).to have_content 'feature'
- check 'check_all_issues'
+ check 'check-all-issues'
+
open_milestone_dropdown ['First Release']
unmark_labels_in_dropdown ['feature']
update_issues
@@ -248,7 +247,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
issue1.labels << bug
issue2.labels << feature
- visit namespace_project_issues_path(project.namespace, project)
+ enable_bulk_update
end
it 'keeps labels' do
@@ -257,7 +256,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
expect(find("#issue_#{issue2.id}")).to have_content 'feature'
expect(find("#issue_#{issue2.id}")).to have_content 'First Release'
- check 'check_all_issues'
+ check 'check-all-issues'
open_milestone_dropdown(['No Milestone'])
update_issues
@@ -272,8 +271,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'toggling checked issues' do
before do
issue1.labels << bug
-
- visit namespace_project_issues_path(project.namespace, project)
+ enable_bulk_update
end
it do
@@ -298,15 +296,15 @@ feature 'Issues > Labels bulk assignment', feature: true do
issue1.labels << feature
issue2.labels << bug
- visit namespace_project_issues_path(project.namespace, project)
+ enable_bulk_update
end
it 'applies label from filtered results' do
- check 'check_all_issues'
+ check 'check-all-issues'
- page.within('.issues_bulk_update') do
- click_button 'Labels'
- wait_for_ajax
+ page.within('.issues-bulk-update') do
+ click_button 'Select labels'
+ wait_for_requests
expect(find('.dropdown-menu-labels li', text: 'bug')).to have_css('.is-active')
expect(find('.dropdown-menu-labels li', text: 'feature')).to have_css('.is-indeterminate')
@@ -340,16 +338,17 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'cannot bulk assign labels' do
it do
- expect(page).not_to have_css '.check_all_issues'
+ expect(page).not_to have_button 'Edit Issues'
+ expect(page).not_to have_css '.check-all-issues'
expect(page).not_to have_css '.issue-check'
end
end
end
def open_milestone_dropdown(items = [])
- page.within('.issues_bulk_update') do
- click_button 'Milestone'
- wait_for_ajax
+ page.within('.issues-bulk-update') do
+ click_button 'Select milestone'
+ wait_for_requests
items.map do |item|
click_link item
end
@@ -357,9 +356,9 @@ feature 'Issues > Labels bulk assignment', feature: true do
end
def open_labels_dropdown(items = [], unmark = false)
- page.within('.issues_bulk_update') do
- click_button 'Labels'
- wait_for_ajax
+ page.within('.issues-bulk-update') do
+ click_button 'Select labels'
+ wait_for_requests
items.map do |item|
click_link item
end
@@ -391,7 +390,12 @@ feature 'Issues > Labels bulk assignment', feature: true do
end
def update_issues
- click_button 'Update issues'
- wait_for_ajax
+ click_button 'Update all'
+ wait_for_requests
+ end
+
+ def enable_bulk_update
+ visit namespace_project_issues_path(project.namespace, project)
+ click_button 'Edit Issues'
end
end
diff --git a/spec/features/issues/create_branch_merge_request_spec.rb b/spec/features/issues/create_branch_merge_request_spec.rb
index 44c19275ae5..1d7d8d291b2 100644
--- a/spec/features/issues/create_branch_merge_request_spec.rb
+++ b/spec/features/issues/create_branch_merge_request_spec.rb
@@ -16,7 +16,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
select_dropdown_option('create-mr')
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content("created branch 1-cherry-coloured-funk")
expect(page).to have_content("mentioned in merge request !1")
@@ -32,7 +32,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
select_dropdown_option('create-branch')
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.dropdown-toggle-text ', text: '1-cherry-coloured-funk')
expect(current_path).to eq namespace_project_tree_path(project.namespace, project, '1-cherry-coloured-funk')
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 4d38df05928..44353d880c2 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -157,6 +157,25 @@ describe 'Dropdown assignee', :feature, :js do
end
end
+ describe 'selecting from dropdown without Ajax call' do
+ before do
+ Gitlab::Testing::RequestBlockerMiddleware.block_requests!
+ filtered_search.set('assignee:')
+ end
+
+ after do
+ Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
+ end
+
+ it 'selects current user' do
+ find('#js-dropdown-assignee .filter-dropdown-item', text: user.username).click
+
+ expect(page).to have_css(js_dropdown_assignee, visible: false)
+ expect_tokens([{ name: 'assignee', value: user.username }])
+ expect_filtered_search_input_empty
+ end
+ end
+
describe 'input has existing content' do
it 'opens assignee dropdown with existing search term' do
filtered_search.set('searchTerm assignee:')
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 8a43512fa3f..6b707c4be4a 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -16,7 +16,7 @@ describe 'Dropdown author', js: true, feature: true do
end
sleep 0.5
- wait_for_ajax
+ wait_for_requests
end
def dropdown_author_size
@@ -135,6 +135,25 @@ describe 'Dropdown author', js: true, feature: true do
end
end
+ describe 'selecting from dropdown without Ajax call' do
+ before do
+ Gitlab::Testing::RequestBlockerMiddleware.block_requests!
+ filtered_search.set('author:')
+ end
+
+ after do
+ Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
+ end
+
+ it 'selects current user' do
+ find('#js-dropdown-author .filter-dropdown-item', text: user.username).click
+
+ expect(page).to have_css(js_dropdown_author, visible: false)
+ expect_tokens([{ name: 'author', value: user.username }])
+ expect_filtered_search_input_empty
+ end
+ end
+
describe 'input has existing content' do
it 'opens author dropdown with existing search term' do
filtered_search.set('searchTerm author:')
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index a8f4e2d7e10..863f8f75cd8 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -6,7 +6,7 @@ describe 'Filter issues', js: true, feature: true do
let!(:group) { create(:group) }
let!(:project) { create(:project, group: group) }
- let!(:user) { create(:user, username: 'joe') }
+ let!(:user) { create(:user, username: 'joe', name: 'Joe') }
let!(:user2) { create(:user, username: 'jane') }
let!(:label) { create(:label, project: project) }
let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
@@ -761,7 +761,7 @@ describe 'Filter issues', js: true, feature: true do
sort_toggle.click
find('.filtered-search-wrapper .dropdown-menu li a', text: 'Oldest updated').click
- wait_for_ajax
+ wait_for_requests
expect(find('.issues-list .issue:first-of-type .issue-title-text a')).to have_content(old_issue.title)
end
@@ -777,26 +777,26 @@ describe 'Filter issues', js: true, feature: true do
end
it 'open state' do
- find('.issues-state-filters a', text: 'Closed').click
- wait_for_ajax
+ find('.issues-state-filters [data-state="closed"]').click
+ wait_for_requests
- find('.issues-state-filters a', text: 'Open').click
- wait_for_ajax
+ find('.issues-state-filters [data-state="opened"]').click
+ wait_for_requests
expect(page).to have_selector('.issues-list .issue', count: 4)
end
it 'closed state' do
- find('.issues-state-filters a', text: 'Closed').click
- wait_for_ajax
+ find('.issues-state-filters [data-state="closed"]').click
+ wait_for_requests
expect(page).to have_selector('.issues-list .issue', count: 1)
expect(find('.issues-list .issue:first-of-type .issue-title-text a')).to have_content(closed_issue.title)
end
it 'all state' do
- find('.issues-state-filters a', text: 'All').click
- wait_for_ajax
+ find('.issues-state-filters [data-state="all"]').click
+ wait_for_requests
expect(page).to have_selector('.issues-list .issue', count: 5)
end
@@ -810,10 +810,10 @@ describe 'Filter issues', js: true, feature: true do
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
- expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('milestone_title' => [milestone.title])
expect(params).to include('assignee_id' => [user.id.to_s])
- expect(auto_discovery_params).to include('private_token' => [user.private_token])
+ expect(auto_discovery_params).to include('rss_token' => [user.rss_token])
expect(auto_discovery_params).to include('milestone_title' => [milestone.title])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
@@ -825,10 +825,10 @@ describe 'Filter issues', js: true, feature: true do
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
- expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('milestone_title' => [milestone.title])
expect(params).to include('assignee_id' => [user.id.to_s])
- expect(auto_discovery_params).to include('private_token' => [user.private_token])
+ expect(auto_discovery_params).to include('rss_token' => [user.rss_token])
expect(auto_discovery_params).to include('milestone_title' => [milestone.title])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb
index 08fe3b4553b..09f228bcf49 100644
--- a/spec/features/issues/filtered_search/recent_searches_spec.rb
+++ b/spec/features/issues/filtered_search/recent_searches_spec.rb
@@ -3,17 +3,17 @@ require 'spec_helper'
describe 'Recent searches', js: true, feature: true do
include FilteredSearchHelpers
- let!(:group) { create(:group) }
- let!(:project) { create(:project, group: group) }
- let!(:user) { create(:user) }
+ let(:project_1) { create(:empty_project, :public) }
+ let(:project_2) { create(:empty_project, :public) }
+ let(:project_1_local_storage_key) { "#{project_1.full_path}-issue-recent-searches" }
before do
Capybara.ignore_hidden_elements = false
- project.add_master(user)
- group.add_developer(user)
- create(:issue, project: project)
- login_as(user)
+ create(:issue, project: project_1)
+ create(:issue, project: project_2)
+ # Visit any fast-loading page so we can clear local storage without a DOM exception
+ visit '/404'
remove_recent_searches
end
@@ -22,7 +22,7 @@ describe 'Recent searches', js: true, feature: true do
end
it 'searching adds to recent searches' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit namespace_project_issues_path(project_1.namespace, project_1)
input_filtered_search('foo', submit: true)
input_filtered_search('bar', submit: true)
@@ -35,8 +35,8 @@ describe 'Recent searches', js: true, feature: true do
end
it 'visiting URL with search params adds to recent searches' do
- visit namespace_project_issues_path(project.namespace, project, label_name: 'foo', search: 'bar')
- visit namespace_project_issues_path(project.namespace, project, label_name: 'qux', search: 'garply')
+ visit namespace_project_issues_path(project_1.namespace, project_1, label_name: 'foo', search: 'bar')
+ visit namespace_project_issues_path(project_1.namespace, project_1, label_name: 'qux', search: 'garply')
items = all('.filtered-search-history-dropdown-item', visible: false)
@@ -46,9 +46,9 @@ describe 'Recent searches', js: true, feature: true do
end
it 'saved recent searches are restored last on the list' do
- set_recent_searches('["saved1", "saved2"]')
+ set_recent_searches(project_1_local_storage_key, '["saved1", "saved2"]')
- visit namespace_project_issues_path(project.namespace, project, search: 'foo')
+ visit namespace_project_issues_path(project_1.namespace, project_1, search: 'foo')
items = all('.filtered-search-history-dropdown-item', visible: false)
@@ -58,9 +58,27 @@ describe 'Recent searches', js: true, feature: true do
expect(items[2].text).to eq('saved2')
end
+ it 'searches are scoped to projects' do
+ visit namespace_project_issues_path(project_1.namespace, project_1)
+
+ input_filtered_search('foo', submit: true)
+ input_filtered_search('bar', submit: true)
+
+ visit namespace_project_issues_path(project_2.namespace, project_2)
+
+ input_filtered_search('more', submit: true)
+ input_filtered_search('things', submit: true)
+
+ items = all('.filtered-search-history-dropdown-item', visible: false)
+
+ expect(items.count).to eq(2)
+ expect(items[0].text).to eq('things')
+ expect(items[1].text).to eq('more')
+ end
+
it 'clicking item fills search input' do
- set_recent_searches('["foo", "bar"]')
- visit namespace_project_issues_path(project.namespace, project)
+ set_recent_searches(project_1_local_storage_key, '["foo", "bar"]')
+ visit namespace_project_issues_path(project_1.namespace, project_1)
all('.filtered-search-history-dropdown-item', visible: false)[0].trigger('click')
wait_for_filtered_search('foo')
@@ -69,8 +87,8 @@ describe 'Recent searches', js: true, feature: true do
end
it 'clear recent searches button, clears recent searches' do
- set_recent_searches('["foo"]')
- visit namespace_project_issues_path(project.namespace, project)
+ set_recent_searches(project_1_local_storage_key, '["foo"]')
+ visit namespace_project_issues_path(project_1.namespace, project_1)
items_before = all('.filtered-search-history-dropdown-item', visible: false)
@@ -83,8 +101,8 @@ describe 'Recent searches', js: true, feature: true do
end
it 'shows flash error when failed to parse saved history' do
- set_recent_searches('fail')
- visit namespace_project_issues_path(project.namespace, project)
+ set_recent_searches(project_1_local_storage_key, 'fail')
+ visit namespace_project_issues_path(project_1.namespace, project_1)
expect(find('.flash-alert')).to have_text('An error occured while parsing recent searches')
end
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index 96e87c82d2c..ff32b0c7d11 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -2,6 +2,7 @@ require 'rails_helper'
describe 'Visual tokens', js: true, feature: true do
include FilteredSearchHelpers
+ include WaitForRequests
let!(:project) { create(:empty_project) }
let!(:user) { create(:user, name: 'administrator', username: 'root') }
@@ -33,7 +34,7 @@ describe 'Visual tokens', js: true, feature: true do
describe 'editing author token' do
before do
input_filtered_search('author:@root assignee:none', submit: false)
- first('.tokens-container .filtered-search-token').double_click
+ first('.tokens-container .filtered-search-token').click
end
it 'opens author dropdown' do
@@ -70,7 +71,8 @@ describe 'Visual tokens', js: true, feature: true do
end
it 'changes value in visual token' do
- expect(first('.tokens-container .filtered-search-token .value').text).to eq("@#{user_rock.username}")
+ wait_for_requests
+ expect(first('.tokens-container .filtered-search-token .value').text).to eq("#{user_rock.name}")
end
it 'moves input to the right' do
@@ -329,7 +331,7 @@ describe 'Visual tokens', js: true, feature: true do
it 'does not tokenize incomplete token' do
filtered_search.send_keys('author:')
- find('#content-body').click
+ find('body').click
token = page.all('.tokens-container .js-visual-token')[1]
expect_filtered_search_input_empty
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 677a725f107..96d37e33f3d 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -1,8 +1,9 @@
require 'rails_helper'
-describe 'New/edit issue', feature: true, js: true do
+describe 'New/edit issue', :feature, :js do
include GitlabRoutingHelper
include ActionView::Helpers::JavaScriptHelper
+ include FormHelper
let!(:project) { create(:project) }
let!(:user) { create(:user)}
@@ -23,11 +24,51 @@ describe 'New/edit issue', feature: true, js: true do
visit new_namespace_project_issue_path(project.namespace, project)
end
- describe 'single assignee' do
+ describe 'shorten users API pagination limit (CE)' do
+ before do
+ # Using `allow_any_instance_of`/`and_wrap_original`, `original` would
+ # somehow refer to the very block we defined to _wrap_ that method, instead of
+ # the original method, resulting in infinite recurison when called.
+ # This is likely a bug with helper modules included into dynamically generated view classes.
+ # To work around this, we have to hold on to and call to the original implementation manually.
+ original_issue_dropdown_options = FormHelper.instance_method(:issue_dropdown_options)
+ allow_any_instance_of(FormHelper).to receive(:issue_dropdown_options).and_wrap_original do |original, *args|
+ options = original_issue_dropdown_options.bind(original.receiver).call(*args)
+ options[:data][:per_page] = 2
+
+ options
+ end
+
+ visit new_namespace_project_issue_path(project.namespace, project)
+
+ click_button 'Unassigned'
+
+ wait_for_requests
+ end
+
+ it 'should display selected users even if they are not part of the original API call' do
+ find('.dropdown-input-field').native.send_keys user2.name
+
+ page.within '.dropdown-menu-user' do
+ expect(page).to have_content user2.name
+ click_link user2.name
+ end
+
+ find('.js-assignee-search').click
+ find('.js-dropdown-input-clear').click
+
+ page.within '.dropdown-menu-user' do
+ expect(page).to have_content user.name
+ expect(find('.dropdown-menu-user a.is-active').first(:xpath, '..')['data-user-id']).to eq(user2.id.to_s)
+ end
+ end
+ end
+
+ describe 'single assignee (CE)' do
before do
click_button 'Unassigned'
- wait_for_ajax
+ wait_for_requests
end
it 'unselects other assignees when unassigned is selected' do
@@ -67,6 +108,9 @@ describe 'New/edit issue', feature: true, js: true do
expect(find('a', text: 'Assign to me')).to be_visible
click_button 'Unassigned'
+
+ wait_for_requests
+
page.within '.dropdown-menu-user' do
click_link user2.name
end
@@ -151,7 +195,7 @@ describe 'New/edit issue', feature: true, js: true do
it 'correctly updates the selected user when changing assignee' do
click_button 'Unassigned'
- wait_for_ajax
+ wait_for_requests
page.within '.dropdown-menu-user' do
click_link user.name
@@ -216,6 +260,37 @@ describe 'New/edit issue', feature: true, js: true do
end
end
+ describe 'sub-group project' do
+ let(:group) { create(:group) }
+ let(:nested_group_1) { create(:group, parent: group) }
+ let(:sub_group_project) { create(:empty_project, group: nested_group_1) }
+
+ before do
+ sub_group_project.add_master(user)
+
+ visit new_namespace_project_issue_path(sub_group_project.namespace, sub_group_project)
+ end
+
+ it 'creates new label from dropdown' do
+ click_button 'Labels'
+
+ click_link 'Create new label'
+
+ page.within '.dropdown-new-label' do
+ fill_in 'new_label_name', with: 'test label'
+ first('.suggest-colors-dropdown a').click
+
+ click_button 'Create'
+
+ wait_for_requests
+ end
+
+ page.within '.dropdown-menu-labels' do
+ expect(page).to have_link 'test label'
+ end
+ end
+ end
+
def before_for_selector(selector)
js = <<-JS.strip_heredoc
(function(selector) {
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index ad29911248f..350473437a8 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -11,7 +11,7 @@ feature 'GFM autocomplete', feature: true, js: true do
login_as(user)
visit namespace_project_issue_path(project.namespace, project, issue)
- wait_for_ajax
+ wait_for_requests
end
it 'opens autocomplete menu when field starts with text' do
@@ -40,7 +40,7 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).to have_selector('.atwho-container')
- wait_for_ajax
+ wait_for_requests
expect(find('#at-view-58')).not_to have_selector('.cur:first-of-type')
end
@@ -80,7 +80,7 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).to have_selector('.atwho-container')
- wait_for_ajax
+ wait_for_requests
expect(find('#at-view-64')).to have_selector('.cur:first-of-type')
end
@@ -93,7 +93,7 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).to have_selector('.atwho-container')
- wait_for_ajax
+ wait_for_requests
expect(find('#at-view-64')).to have_content(user.name)
end
@@ -106,7 +106,7 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).to have_selector('.atwho-container')
- wait_for_ajax
+ wait_for_requests
expect(find('#at-view-58')).to have_selector('.cur:first-of-type')
end
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 0de0f93089a..96c24750250 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -23,7 +23,7 @@ feature 'Issue Sidebar', feature: true do
find('.block.assignee .edit-link').click
- wait_for_ajax
+ wait_for_requests
end
it 'shows author in assignee dropdown' do
@@ -37,7 +37,7 @@ feature 'Issue Sidebar', feature: true do
find('.dropdown-input-field').native.send_keys user2.name
sleep 1 # Required to wait for end of input delay
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content(user2.name)
end
@@ -48,7 +48,7 @@ feature 'Issue Sidebar', feature: true do
click_button 'assign yourself'
- wait_for_ajax
+ wait_for_requests
find('.block.assignee .edit-link').click
@@ -57,6 +57,23 @@ feature 'Issue Sidebar', feature: true do
expect(page.find('.dropdown-menu-user-link.is-active')).to have_content(user.name)
end
end
+
+ it 'keeps your filtered term after filtering and dismissing the dropdown' do
+ find('.dropdown-input-field').native.send_keys user2.name
+
+ wait_for_requests
+
+ page.within '.dropdown-menu-user' do
+ expect(page).not_to have_content 'Unassigned'
+ click_link user2.name
+ end
+
+ find('.js-right-sidebar').click
+ find('.block.assignee .edit-link').click
+
+ expect(page.all('.dropdown-menu-user li').length).to eq(1)
+ expect(find('.dropdown-input-field').value).to eq(user2.name)
+ end
end
context 'as a allowed user' do
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 6c09903a2f6..e75bf059218 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -38,9 +38,11 @@ feature 'issue move to another project' do
end
scenario 'moving issue to another project', js: true do
- find('#move_to_project_id', visible: false).set(new_project.id)
+ find('#issuable-move', visible: false).set(new_project.id)
click_button('Save changes')
+ wait_for_requests
+
expect(current_url).to include project_path(new_project)
expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}")
@@ -51,7 +53,7 @@ feature 'issue move to another project' do
scenario 'searching project dropdown', js: true do
new_project_search.team << [user, :reporter]
- page.within '.js-move-dropdown' do
+ page.within '.detail-page-description' do
first('.select2-choice').click
end
@@ -69,7 +71,7 @@ feature 'issue move to another project' do
background { another_project.team << [user, :guest] }
scenario 'browsing projects in projects select' do
- click_link 'Select project'
+ click_link 'Move to a different project'
page.within '.select2-results' do
expect(page).to have_content 'No project'
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
index 80f57906506..2c0a6ffd3cb 100644
--- a/spec/features/issues/note_polling_spec.rb
+++ b/spec/features/issues/note_polling_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
feature 'Issue notes polling', :feature, :js do
+ include NoteInteractionHelpers
+
let(:project) { create(:empty_project, :public) }
let(:issue) { create(:issue, project: project) }
@@ -48,7 +50,7 @@ feature 'Issue notes polling', :feature, :js do
end
it 'when editing but have not changed anything, and an update comes in, show the updated content in the textarea' do
- find("#note_#{existing_note.id} .js-note-edit").click
+ click_edit_action(existing_note)
expect(page).to have_field("note[note]", with: note_text)
@@ -58,19 +60,18 @@ feature 'Issue notes polling', :feature, :js do
end
it 'when editing but you changed some things, and an update comes in, show a warning' do
- find("#note_#{existing_note.id} .js-note-edit").click
+ click_edit_action(existing_note)
expect(page).to have_field("note[note]", with: note_text)
find("#note_#{existing_note.id} .js-note-text").set('something random')
-
update_note(existing_note, updated_text)
expect(page).to have_selector(".alert")
end
it 'when editing but you changed some things, an update comes in, and you press cancel, show the updated content' do
- find("#note_#{existing_note.id} .js-note-edit").click
+ click_edit_action(existing_note)
expect(page).to have_field("note[note]", with: note_text)
@@ -128,4 +129,12 @@ feature 'Issue notes polling', :feature, :js do
note.update(note: new_text)
page.execute_script('notes.refresh();')
end
+
+ def click_edit_action(note)
+ note_element = find("#note_#{note.id}")
+
+ open_more_actions_dropdown(note)
+
+ note_element.find('.js-note-edit').click
+ end
end
diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb
index a4035324d2b..15c817cabac 100644
--- a/spec/features/issues/notes_on_issues_spec.rb
+++ b/spec/features/issues/notes_on_issues_spec.rb
@@ -15,7 +15,7 @@ describe 'Create notes on issues', :js, :feature do
fill_in 'note[note]', with: note_text
click_button 'Comment'
- wait_for_ajax
+ wait_for_requests
end
it 'creates a note with reference and cross references the issue' do
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index b250fa2ed3c..8595847d313 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -14,7 +14,8 @@ feature 'Multiple issue updating from issues#index', feature: true do
it 'sets to closed' do
visit namespace_project_issues_path(project.namespace, project)
- find('#check_all_issues').click
+ click_button 'Edit Issues'
+ find('#check-all-issues').click
find('.js-issue-status').click
find('.dropdown-menu-status a', text: 'Closed').click
@@ -26,7 +27,8 @@ feature 'Multiple issue updating from issues#index', feature: true do
create_closed
visit namespace_project_issues_path(project.namespace, project, state: 'closed')
- find('#check_all_issues').click
+ click_button 'Edit Issues'
+ find('#check-all-issues').click
find('.js-issue-status').click
find('.dropdown-menu-status a', text: 'Open').click
@@ -39,7 +41,8 @@ feature 'Multiple issue updating from issues#index', feature: true do
it 'updates to current user' do
visit namespace_project_issues_path(project.namespace, project)
- find('#check_all_issues').click
+ click_button 'Edit Issues'
+ find('#check-all-issues').click
click_update_assignee_button
find('.dropdown-menu-user-link', text: user.username).click
@@ -54,7 +57,8 @@ feature 'Multiple issue updating from issues#index', feature: true do
create_assigned
visit namespace_project_issues_path(project.namespace, project)
- find('#check_all_issues').click
+ click_button 'Edit Issues'
+ find('#check-all-issues').click
click_update_assignee_button
click_link 'Unassigned'
@@ -69,8 +73,9 @@ feature 'Multiple issue updating from issues#index', feature: true do
it 'updates milestone' do
visit namespace_project_issues_path(project.namespace, project)
- find('#check_all_issues').click
- find('.issues_bulk_update .js-milestone-select').click
+ click_button 'Edit Issues'
+ find('#check-all-issues').click
+ find('.issues-bulk-update .js-milestone-select').click
find('.dropdown-menu-milestone a', text: milestone.title).click
click_update_issues_button
@@ -84,8 +89,9 @@ feature 'Multiple issue updating from issues#index', feature: true do
expect(first('.issue')).to have_content milestone.title
- find('#check_all_issues').click
- find('.issues_bulk_update .js-milestone-select').click
+ click_button 'Edit Issues'
+ find('#check-all-issues').click
+ find('.issues-bulk-update .js-milestone-select').click
find('.dropdown-menu-milestone a', text: "No Milestone").click
click_update_issues_button
@@ -108,11 +114,11 @@ feature 'Multiple issue updating from issues#index', feature: true do
def click_update_assignee_button
find('.js-update-assignee').click
- wait_for_ajax
+ wait_for_requests
end
def click_update_issues_button
- find('.update_selected_issues').click
- wait_for_ajax
+ find('.update-selected-issues').click
+ wait_for_requests
end
end
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index 4cd6c1171ac..d14c319707c 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -18,7 +18,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
end
after do
- wait_for_ajax
+ wait_for_requests
end
describe 'adding a due date from note' do
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 396a923082d..eecc565d2bd 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -30,13 +30,6 @@ describe 'Issues', feature: true do
it 'opens new issue popup' do
expect(page).to have_content("Issue ##{issue.iid}")
end
-
- describe 'fill in' do
- before do
- fill_in 'issue_title', with: 'bug 345'
- fill_in 'issue_description', with: 'bug description'
- end
- end
end
describe 'Editing issue assignee' do
@@ -384,7 +377,7 @@ describe 'Issues', feature: true do
previous_token = find('input#issue_email').value
find('.incoming-email-token-reset').trigger('click')
- wait_for_ajax
+ wait_for_requests
expect(page).to have_no_field('issue_email', with: previous_token)
new_token = project1.new_issue_address(@user.reload)
@@ -430,7 +423,7 @@ describe 'Issues', feature: true do
expect(page).to have_content 'No assignee'
end
- # wait_for_ajax does not work with vue-resource at the moment
+ # wait_for_requests does not work with vue-resource at the moment
sleep 1
expect(issue.reload.assignees).to be_empty
@@ -557,18 +550,11 @@ describe 'Issues', feature: true do
expect(page).to have_content milestone.title
end
end
-
- describe 'removing assignee' do
- let(:user2) { create(:user) }
-
- before do
- issue.assignees << user2
- issue.save
- end
- end
end
describe 'new issue' do
+ let!(:issue) { create(:issue, project: project) }
+
context 'by unauthenticated user' do
before do
logout
@@ -675,7 +661,7 @@ describe 'Issues', feature: true do
click_button date.day
end
- wait_for_ajax
+ wait_for_requests
expect(find('.value').text).to have_content date.strftime('%b %-d, %Y')
end
@@ -691,7 +677,7 @@ describe 'Issues', feature: true do
click_button date.day
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_no_content 'No due date'
@@ -703,8 +689,6 @@ describe 'Issues', feature: true do
end
describe 'title issue#show', js: true do
- include WaitForVueResource
-
it 'updates the title', js: true do
issue = create(:issue, author: @user, assignees: [@user], project: project, title: 'new title')
@@ -714,7 +698,7 @@ describe 'Issues', feature: true do
issue.update(title: "updated title")
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_text("updated title")
end
end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 11d417c253d..c82e8c03343 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -41,7 +41,7 @@ feature 'Login', feature: true do
expect(page).to have_content('Your account has been blocked.')
end
- it 'does not update Devise trackable attributes' do
+ it 'does not update Devise trackable attributes', :redis do
user = create(:user, :blocked)
expect { login_with(user) }.not_to change { user.reload.sign_in_count }
@@ -55,7 +55,7 @@ feature 'Login', feature: true do
expect(page).to have_content('Invalid Login or password.')
end
- it 'does not update Devise trackable attributes' do
+ it 'does not update Devise trackable attributes', :redis do
expect { login_with(User.ghost) }.not_to change { User.ghost.reload.sign_in_count }
end
end
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb
index ee0880a1e2f..e627618042a 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_requests/closes_issues_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
feature 'Merge Request closing issues message', feature: true, js: true do
- include WaitForAjax
-
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue_1) { create(:issue, project: project)}
@@ -25,7 +23,7 @@ feature 'Merge Request closing issues message', feature: true, js: true do
login_as user
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
- wait_for_ajax
+ wait_for_requests
end
context 'not closing or mentioning any issue' do
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb
index 43977ad2fc5..27e2d5d16f3 100644
--- a/spec/features/merge_requests/conflicts_spec.rb
+++ b/spec/features/merge_requests/conflicts_spec.rb
@@ -23,13 +23,13 @@ feature 'Merge request conflict resolution', js: true, feature: true do
end
click_button 'Commit conflict resolution'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('All merge conflicts were resolved')
merge_request.reload_diff
click_on 'Changes'
- wait_for_ajax
+ wait_for_requests
within find('.diff-file', text: 'files/ruby/popen.rb') do
expect(page).to have_selector('.line_content.new', text: "vars = { 'PWD' => path }")
@@ -53,23 +53,23 @@ feature 'Merge request conflict resolution', js: true, feature: true do
within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do
click_button 'Edit inline'
- wait_for_ajax
+ wait_for_requests
execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("One morning");')
end
within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do
click_button 'Edit inline'
- wait_for_ajax
+ wait_for_requests
execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");')
end
click_button 'Commit conflict resolution'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('All merge conflicts were resolved')
merge_request.reload_diff
click_on 'Changes'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('One morning')
expect(page).to have_content('Gregor Samsa woke from troubled dreams')
@@ -100,7 +100,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do
context 'in Parallel view mode' do
before do
- click_link('conflicts', href: /\/conflicts\Z/)
+ click_link('conflicts', href: /\/conflicts\Z/)
click_button 'Side-by-side'
end
@@ -126,21 +126,21 @@ feature 'Merge request conflict resolution', js: true, feature: true do
it 'conflicts are resolved in Edit inline mode' do
within find('.files-wrapper .diff-file', text: 'files/markdown/ruby-style-guide.md') do
- wait_for_ajax
+ wait_for_requests
execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("Gregor Samsa woke from troubled dreams");')
end
click_button 'Commit conflict resolution'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('All merge conflicts were resolved')
merge_request.reload_diff
click_on 'Changes'
- wait_for_ajax
+ wait_for_requests
click_link 'Expand all'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('Gregor Samsa woke from troubled dreams')
end
@@ -151,7 +151,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do
'conflict-too-large' => 'when the conflicts contain a large file',
'conflict-binary-file' => 'when the conflicts contain a binary file',
'conflict-missing-side' => 'when the conflicts contain a file edited in one branch and deleted in another',
- 'conflict-non-utf8' => 'when the conflicts contain a non-UTF-8 file',
+ 'conflict-non-utf8' => 'when the conflicts contain a non-UTF-8 file'
}.freeze
UNRESOLVABLE_CONFLICTS.each do |source_branch, description|
@@ -171,7 +171,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do
it 'shows an error if the conflicts page is visited directly' do
visit current_url + '/conflicts'
- wait_for_ajax
+ wait_for_requests
expect(find('#conflicts')).to have_content('Please try to resolve them locally.')
end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index f1b3e7f158c..82987c768d1 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
feature 'Create New Merge Request', feature: true, js: true do
- include WaitForVueResource
-
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
@@ -146,7 +144,7 @@ feature 'Create New Merge Request', feature: true, js: true do
page.within('.merge-request') do
click_link 'Pipelines'
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_content "##{pipeline.id}"
end
diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_requests/deleted_source_branch_spec.rb
index 01e5e4f3a05..1723fb7d365 100644
--- a/spec/features/merge_requests/deleted_source_branch_spec.rb
+++ b/spec/features/merge_requests/deleted_source_branch_spec.rb
@@ -32,7 +32,7 @@ describe 'Deleted source branch', feature: true, js: true do
end
click_on 'Changes'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.diffs.tab-pane .nothing-here-block')
expect(page).to have_content('Source branch does not exist.')
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb
index b2e170513c4..e23dc2cd940 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
feature 'Diff note avatars', feature: true, js: true do
+ include NoteInteractionHelpers
+
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") }
@@ -60,7 +62,7 @@ feature 'Diff note avatars', feature: true, js: true do
click_button 'Comment'
- wait_for_ajax
+ wait_for_requests
end
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
@@ -76,7 +78,7 @@ feature 'Diff note avatars', feature: true, js: true do
before do
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: view)
- wait_for_ajax
+ wait_for_requests
end
it 'shows note avatar' do
@@ -91,7 +93,7 @@ feature 'Diff note avatars', feature: true, js: true do
page.within find("[id='#{position.line_code(project.repository)}']") do
find('.diff-notes-collapse').click
- expect(first('img.js-diff-comment-avatar')["title"]).to eq("#{note.author.name}: #{note.note.truncate(17)}")
+ expect(first('img.js-diff-comment-avatar')["data-original-title"]).to eq("#{note.author.name}: #{note.note.truncate(17)}")
end
end
@@ -110,11 +112,13 @@ feature 'Diff note avatars', feature: true, js: true do
end
it 'removes avatar when note is deleted' do
+ open_more_actions_dropdown(note)
+
page.within find(".note-row-#{note.id}") do
find('.js-note-delete').click
end
- wait_for_ajax
+ wait_for_requests
page.within find("[id='#{position.line_code(project.repository)}']") do
expect(page).not_to have_selector('img.js-diff-comment-avatar')
@@ -129,7 +133,7 @@ feature 'Diff note avatars', feature: true, js: true do
click_button 'Comment'
- wait_for_ajax
+ wait_for_requests
end
page.within find("[id='#{position.line_code(project.repository)}']") do
@@ -148,7 +152,7 @@ feature 'Diff note avatars', feature: true, js: true do
find('.js-comment-button').trigger 'click'
- wait_for_ajax
+ wait_for_requests
end
end
@@ -166,7 +170,7 @@ feature 'Diff note avatars', feature: true, js: true do
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: view)
- wait_for_ajax
+ wait_for_requests
end
it 'shows extra comment count' do
diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb
index 0e23c3a8849..4d549f3bdbb 100644
--- a/spec/features/merge_requests/diff_notes_resolve_spec.rb
+++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb
@@ -275,7 +275,7 @@ feature 'Diff notes resolve', feature: true, js: true do
end
page.within '.line-resolve-all-container' do
- page.find('.discussion-next-btn').click
+ page.find('.discussion-next-btn').trigger('click')
end
expect(page.evaluate_script("$('body').scrollTop()")).to be > 0
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb
index 4860a2a7498..44013df3ea0 100644
--- a/spec/features/merge_requests/diffs_spec.rb
+++ b/spec/features/merge_requests/diffs_spec.rb
@@ -68,9 +68,14 @@ feature 'Diffs URL', js: true, feature: true do
let(:merge_request) { create(:merge_request_with_diffs, source_project: forked_project, target_project: project, author: author_user) }
let(:changelog_id) { Digest::SHA1.hexdigest("CHANGELOG") }
+ before do
+ forked_project.repository.after_import
+ end
+
context 'as author' do
it 'shows direct edit link' do
login_as(author_user)
+
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
@@ -81,6 +86,7 @@ feature 'Diffs URL', js: true, feature: true do
context 'as user who needs to fork' do
it 'shows fork/cancel confirmation' do
login_as(user)
+
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
diff --git a/spec/features/merge_requests/discussion_spec.rb b/spec/features/merge_requests/discussion_spec.rb
index f59d0faa274..9db235f35ba 100644
--- a/spec/features/merge_requests/discussion_spec.rb
+++ b/spec/features/merge_requests/discussion_spec.rb
@@ -5,7 +5,7 @@ feature 'Merge Request Discussions', feature: true do
login_as :admin
end
- context "Diff discussions" do
+ 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) }
@@ -43,9 +43,48 @@ feature 'Merge Request Discussions', feature: true do
it 'shows a link to the outdated diff' do
within(".discussion[data-discussion-id='#{outdated_discussion.id}']") do
path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: old_merge_request_diff.id, anchor: outdated_discussion.line_code)
- expect(page).to have_link('an outdated diff', href: path)
+ expect(page).to have_link('an old version of the diff', href: path)
end
end
end
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) }
+
+ it 'is displayed' do
+ expect(page).to have_css(".discussion[data-discussion-id='#{discussion_id}']")
+ end
+
+ it 'can be replied to' do
+ within(".discussion[data-discussion-id='#{discussion_id}']") do
+ click_button 'Reply...'
+ fill_in 'note[note]', with: 'Test!'
+ click_button 'Comment'
+
+ expect(page).to have_css('.note', count: 2)
+ end
+ end
+ end
+
+ before(:each) do
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ context 'a regular commit comment' do
+ let(:note) { create(:note_on_commit, project: project) }
+
+ it_behaves_like 'a functional discussion'
+ end
+
+ context 'a commit diff comment' do
+ let(:note) { create(:diff_note_on_commit, project: project) }
+
+ it_behaves_like 'a functional discussion'
+ end
+ end
end
diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb
index ec87a99b3ab..c77a5c68bc6 100644
--- a/spec/features/merge_requests/edit_mr_spec.rb
+++ b/spec/features/merge_requests/edit_mr_spec.rb
@@ -29,6 +29,19 @@ feature 'Edit Merge Request', feature: true do
expect(page).to have_content 'Someone edited the merge request the same time you did'
end
+ it 'allows to unselect "Remove source branch"', js: true 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_namespace_project_merge_request_path(project.namespace, 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: true 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.
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index 2da60e9f4ad..d086be70d69 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -40,13 +40,13 @@ describe 'Filter merge requests', feature: true do
end
it 'does not change when closed link is clicked' do
- find('.issues-state-filters a', text: "Closed").click
+ 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 a', text: "All").click
+ find('.issues-state-filters [data-state="all"]').click
expect_assignee_visual_tokens()
end
@@ -73,13 +73,13 @@ describe 'Filter merge requests', feature: true do
end
it 'does not change when closed link is clicked' do
- find('.issues-state-filters a', text: "Closed").click
+ 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 a', text: "All").click
+ find('.issues-state-filters [data-state="all"]').click
expect_milestone_visual_tokens()
end
@@ -142,11 +142,9 @@ describe 'Filter merge requests', feature: true do
expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
expect_filtered_search_input_empty
- input_filtered_search_keys("label:~#{label.title} ")
+ input_filtered_search_keys("label:~#{label.title}")
expect_mr_list_count(1)
-
- find("#state-opened[href=\"#{URI.parse(current_url).path}?assignee_username=#{user.username}&label_name%5B%5D=#{label.title}&scope=all&state=opened\"]")
end
context 'assignee and label', js: true do
@@ -163,13 +161,13 @@ describe 'Filter merge requests', feature: true do
end
it 'does not change when closed link is clicked' do
- find('.issues-state-filters a', text: "Closed").click
+ 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 a', text: "All").click
+ find('.issues-state-filters [data-state="all"]').click
expect_assignee_label_visual_tokens()
end
@@ -289,7 +287,7 @@ describe 'Filter merge requests', feature: true do
page.within '.dropdown-menu-sort' do
click_link 'Oldest created'
end
- wait_for_ajax
+ wait_for_requests
page.within '.mr-list' do
expect(page).to have_content('Frontend')
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
index f8518f450dc..00ef1ffdddc 100644
--- a/spec/features/merge_requests/form_spec.rb
+++ b/spec/features/merge_requests/form_spec.rb
@@ -90,7 +90,7 @@ describe 'New/edit merge request', feature: true, js: true do
page.within '.issuable-meta' do
merge_request = MergeRequest.find_by(source_branch: 'fix')
- expect(page).to have_text("Merge Request #{merge_request.to_reference}")
+ expect(page).to have_text("Merge request #{merge_request.to_reference}")
# compare paths because the host differ in test
expect(find_link(merge_request.to_reference)[:href])
.to end_with(merge_request_path(merge_request))
diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
index b79667a1a4c..c1d4d508e57 100644
--- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
+++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
@@ -4,16 +4,18 @@ feature 'Merge immediately', :feature, :js do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
- let(:merge_request) do
+ let!(:merge_request) do
create(:merge_request_with_diffs, source_project: project,
author: user,
- title: 'Bug NS-04')
+ title: 'Bug NS-04',
+ head_pipeline: pipeline,
+ source_branch: pipeline.ref)
end
let(:pipeline) do
create(:ci_pipeline, project: project,
- sha: merge_request.diff_head_sha,
- ref: merge_request.source_branch)
+ ref: 'master',
+ sha: project.repository.commit('master').id)
end
before { project.team << [user, :master] }
@@ -32,11 +34,13 @@ feature 'Merge immediately', :feature, :js do
page.within '.mr-widget-body' do
find('.dropdown-toggle').click
- click_link 'Merge immediately'
+ Sidekiq::Testing.fake! do
+ click_link 'Merge immediately'
- expect(find('.accept-merge-request.btn-info')).to have_content('Merge in progress')
+ expect(find('.accept-merge-request.btn-info')).to have_content('Merge in progress')
- wait_for_ajax
+ wait_for_requests
+ end
end
end
end
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
index b33d7f90a31..67c608da59d 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
@@ -7,16 +7,20 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
let(:merge_request) do
create(:merge_request_with_diffs, source_project: project,
author: user,
- title: 'Bug NS-04')
+ 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,
- ref: merge_request.source_branch)
+ ref: merge_request.source_branch,
+ head_pipeline_of: merge_request)
end
- before { project.team << [user, :master] }
+ before do
+ project.add_master(user)
+ end
context 'when there is active pipeline for merge request' do
background do
@@ -38,7 +42,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
click_button "Merge when pipeline succeeds"
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 be removed."
+ 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
expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i
@@ -79,7 +83,8 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
source_project: project,
title: 'Bug NS-04',
author: user,
- merge_user: user)
+ merge_user: user,
+ merge_params: { force_remove_source_branch: '1' })
end
before do
diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
index 449a60c1d05..3a11ea3c8b2 100644
--- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
feature 'Mini Pipeline Graph', :js, :feature do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
- let(:merge_request) { create(:merge_request, source_project: project) }
+ 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') }
@@ -12,13 +12,39 @@ feature 'Mini Pipeline Graph', :js, :feature do
build.run
login_as(user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit_merge_request
+ end
+
+ def visit_merge_request(format = :html)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request, format: format)
end
it 'should display 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') }
+
+ before do
+ create(:ci_build, pipeline: pipeline, artifacts_file: artifacts_file1)
+ create(:ci_build, pipeline: pipeline, when: 'manual')
+ end
+
+ it 'avoids repeated database queries' do
+ before = ActiveRecord::QueryRecorder.new { visit_merge_request(:json) }
+
+ create(:ci_build, pipeline: pipeline, artifacts_file: artifacts_file2)
+ create(:ci_build, pipeline: pipeline, when: 'manual')
+
+ after = ActiveRecord::QueryRecorder.new { visit_merge_request(:json) }
+
+ expect(before.count).to eq(after.count)
+ expect(before.cached_count).to eq(after.cached_count)
+ end
+ end
+
describe 'build list toggle' do
let(:toggle) do
find('.mini-pipeline-graph-dropdown-toggle')
@@ -56,7 +82,7 @@ feature 'Mini Pipeline Graph', :js, :feature do
before do
toggle.click
- wait_for_ajax
+ wait_for_requests
end
it 'should open when toggle is clicked' do
@@ -85,7 +111,7 @@ feature 'Mini Pipeline Graph', :js, :feature do
build_item.click
find('.build-page')
- expect(current_path).to eql(namespace_project_build_path(project.namespace, project, build))
+ expect(current_path).to eql(namespace_project_job_path(project.namespace, project, build))
end
it 'should show tooltip when hovered' do
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
index 187e927dac4..b1dc81a606a 100644
--- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
feature 'Only allow merge requests to be merged if the pipeline succeeds', feature: true, js: true do
- include WaitForVueResource
-
let(:merge_request) { create(:merge_request_with_diffs) }
let(:project) { merge_request.target_project }
@@ -16,7 +14,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'allows MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge'
end
@@ -28,7 +26,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
project: project,
sha: merge_request.diff_head_sha,
ref: merge_request.source_branch,
- status: status)
+ status: status, head_pipeline_of: merge_request)
end
context 'when merge requests can only be merged if the pipeline succeeds' do
@@ -42,7 +40,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'does not allow to merge immediately' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge when pipeline succeeds'
expect(page).not_to have_button 'Select merge moment'
@@ -55,7 +53,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'does not allow MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_css('button[disabled="disabled"]', text: 'Merge')
expect(page).to have_content('Please retry the job or push a new commit to fix the failure.')
@@ -68,7 +66,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'does not allow MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).not_to have_button 'Merge'
expect(page).to have_content('Please retry the job or push a new commit to fix the failure.')
@@ -81,7 +79,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'allows MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge'
end
@@ -93,7 +91,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'allows MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge'
end
@@ -111,7 +109,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'allows MR to be merged immediately' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge when pipeline succeeds'
@@ -126,7 +124,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'allows MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge'
end
@@ -138,7 +136,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'allows MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge'
end
diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_requests/pipelines_spec.rb
index 99e283ac181..4c76004cb93 100644
--- a/spec/features/merge_requests/pipelines_spec.rb
+++ b/spec/features/merge_requests/pipelines_spec.rb
@@ -26,7 +26,7 @@ feature 'Pipelines for Merge Requests', feature: true, js: true do
page.within('.merge-request-tabs') do
click_link('Pipelines')
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.pipeline-actions')
end
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb
index 9ecc998785b..bcdfdf78a44 100644
--- a/spec/features/merge_requests/update_merge_requests_spec.rb
+++ b/spec/features/merge_requests/update_merge_requests_spec.rb
@@ -98,16 +98,18 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
end
def change_status(text)
- find('#check_all_issues').click
+ click_button 'Edit Merge Requests'
+ find('#check-all-issues').click
find('.js-issue-status').click
find('.dropdown-menu-status a', text: text).click
click_update_merge_requests_button
end
def change_assignee(text)
- find('#check_all_issues').click
+ click_button 'Edit Merge Requests'
+ find('#check-all-issues').click
find('.js-update-assignee').click
- wait_for_ajax
+ wait_for_requests
page.within '.dropdown-menu-user' do
click_link text
@@ -117,14 +119,15 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
end
def change_milestone(text)
- find('#check_all_issues').click
- find('.issues_bulk_update .js-milestone-select').click
+ click_button 'Edit Merge Requests'
+ find('#check-all-issues').click
+ find('.issues-bulk-update .js-milestone-select').click
find('.dropdown-menu-milestone a', text: text).click
click_update_merge_requests_button
end
def click_update_merge_requests_button
- find('.update_selected_issues').click
- wait_for_ajax
+ find('.update-selected-issues').click
+ wait_for_requests
end
end
diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
index 7756202e3f5..14bc549c9f9 100644
--- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
@@ -73,7 +73,7 @@ feature 'Merge requests > User posts diff notes', :js do
context 'with an unfolded line' do
before(:each) do
find('.js-unfold', match: :first).click
- wait_for_ajax
+ wait_for_requests
end
# The first `.js-unfold` unfolds upwards, therefore the first
@@ -122,7 +122,7 @@ feature 'Merge requests > User posts diff notes', :js do
context 'with an unfolded line' do
before(:each) do
find('.js-unfold', match: :first).click
- wait_for_ajax
+ wait_for_requests
end
# The first `.js-unfold` unfolds upwards, therefore the first
@@ -213,7 +213,7 @@ feature 'Merge requests > User posts diff notes', :js do
write_comment_on_line(line_holder, diff_side)
click_button 'Comment'
- wait_for_ajax
+ wait_for_requests
assert_comment_persistence(line_holder, asset_form_reset: asset_form_reset)
end
diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb
index 7fc0e2ce6ec..22552529b9e 100644
--- a/spec/features/merge_requests/user_posts_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_notes_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'Merge requests > User posts notes', :js do
+ include NoteInteractionHelpers
+
let(:project) { create(:project) }
let(:merge_request) do
create(:merge_request, source_project: project, target_project: project)
@@ -73,6 +75,8 @@ describe 'Merge requests > User posts notes', :js do
describe 'editing the note' do
before do
find('.note').hover
+ open_more_actions_dropdown(note)
+
find('.js-note-edit').click
end
@@ -98,8 +102,10 @@ describe 'Merge requests > User posts notes', :js do
find('.btn-save').click
end
- wait_for_ajax
+ wait_for_requests
find('.note').hover
+ open_more_actions_dropdown(note)
+
find('.js-note-edit').click
page.within('.current-note-edit-form') do
@@ -126,6 +132,8 @@ describe 'Merge requests > User posts notes', :js do
describe 'deleting an attachment' do
before do
find('.note').hover
+ open_more_actions_dropdown(note)
+
find('.js-note-edit').click
end
@@ -139,7 +147,7 @@ describe 'Merge requests > User posts notes', :js do
find('.js-note-attachment-delete').click
is_expected.not_to have_css('.note-attachment')
is_expected.not_to have_css('.current-note-edit-form')
- wait_for_ajax
+ wait_for_requests
end
end
end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index f0ad57eb92f..0e64a3e1a4b 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -21,7 +21,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
end
after do
- wait_for_ajax
+ wait_for_requests
end
describe 'toggling the WIP prefix in the title from note' do
@@ -160,7 +160,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
it 'changes target branch from a note' do
write_note("message start \n/target_branch merge-test\n message end.")
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_content('/target_branch')
expect(page).to have_content('message start')
expect(page).to have_content('message end.')
diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_requests/versions_spec.rb
index 2b5b803946c..aad522ee26e 100644
--- a/spec/features/merge_requests/versions_spec.rb
+++ b/spec/features/merge_requests/versions_spec.rb
@@ -75,7 +75,7 @@ feature 'Merge Request versions', js: true, feature: true do
find(".js-comment-button").click
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content("Typo, please fix")
end
@@ -124,9 +124,11 @@ feature 'Merge Request versions', js: true, feature: true do
diff_refs: merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs
)
outdated_diff_note = create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position)
+ outdated_diff_note.position = outdated_diff_note.original_position
+ outdated_diff_note.save!
visit current_url
- wait_for_ajax
+ wait_for_requests
expect(page).to have_css(".diffs .notes[data-discussion-id='#{outdated_diff_note.discussion_id}']")
end
@@ -144,7 +146,7 @@ feature 'Merge Request versions', js: true, feature: true do
find(".js-comment-button").click
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content("Typo, please fix")
end
diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb
index 8370499f6ed..118ecd9cba5 100644
--- a/spec/features/merge_requests/widget_deployments_spec.rb
+++ b/spec/features/merge_requests/widget_deployments_spec.rb
@@ -18,7 +18,7 @@ feature 'Widget Deployments Header', feature: true, js: true do
end
scenario 'displays that the environment is deployed' do
- wait_for_ajax
+ 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))
@@ -34,7 +34,7 @@ feature 'Widget Deployments Header', feature: true, js: true do
end
background do
- wait_for_ajax
+ wait_for_requests
end
scenario 'does show stop button' do
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index 3fcdc9f2c61..4f3a5119915 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -27,7 +27,7 @@ describe 'Merge request', :feature, :js do
it 'shows widget status after creating new merge request' do
click_button 'Submit merge request'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.accept-merge-request')
expect(find('.accept-merge-request')['disabled']).not_to be(true)
@@ -48,7 +48,7 @@ describe 'Merge request', :feature, :js do
end
it 'shows environments link' do
- wait_for_ajax
+ wait_for_requests
page.within('.mr-widget-heading') do
expect(page).to have_content("Deployed to #{environment.name}")
@@ -58,7 +58,7 @@ describe 'Merge request', :feature, :js do
it 'shows green accept merge request button' do
# Wait for the `ci_status` and `merge_check` requests
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.accept-merge-request')
expect(find('.accept-merge-request')['disabled']).not_to be(true)
end
@@ -76,7 +76,7 @@ describe 'Merge request', :feature, :js do
it 'has danger button while waiting for external CI status' do
# Wait for the `ci_status` and `merge_check` requests
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.accept-merge-request.btn-danger')
end
end
@@ -88,7 +88,8 @@ describe 'Merge request', :feature, :js do
sha: merge_request.diff_head_sha,
ref: merge_request.source_branch,
status: 'failed',
- statuses: [commit_status])
+ statuses: [commit_status],
+ head_pipeline_of: merge_request)
create(:ci_build, :pending, pipeline: pipeline)
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
@@ -96,17 +97,20 @@ describe 'Merge request', :feature, :js do
it 'has danger button when not succeeded' do
# Wait for the `ci_status` and `merge_check` requests
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.accept-merge-request.btn-danger')
end
end
context 'when merge request is in the blocked pipeline state' do
before do
- create(:ci_pipeline, project: project,
- sha: merge_request.diff_head_sha,
- ref: merge_request.source_branch,
- status: :manual)
+ create(
+ :ci_pipeline,
+ project: project,
+ sha: merge_request.diff_head_sha,
+ ref: merge_request.source_branch,
+ status: :manual,
+ head_pipeline_of: merge_request)
visit namespace_project_merge_request_path(project.namespace,
project,
@@ -128,7 +132,8 @@ describe 'Merge request', :feature, :js do
sha: merge_request.diff_head_sha,
ref: merge_request.source_branch,
status: 'pending',
- statuses: [commit_status])
+ statuses: [commit_status],
+ head_pipeline_of: merge_request)
create(:ci_build, :pending, pipeline: pipeline)
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
@@ -136,7 +141,7 @@ describe 'Merge request', :feature, :js do
it 'has info button when MWBS button' do
# Wait for the `ci_status` and `merge_check` requests
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.accept-merge-request.btn-info')
end
end
@@ -154,7 +159,7 @@ describe 'Merge request', :feature, :js do
it 'shows information about the merge error' do
# Wait for the `ci_status` and `merge_check` requests
- wait_for_ajax
+ wait_for_requests
page.within('.mr-widget-body') do
expect(page).to have_content('Something went wrong')
@@ -175,7 +180,7 @@ describe 'Merge request', :feature, :js do
it 'shows information about the merge error' do
# Wait for the `ci_status` and `merge_check` requests
- wait_for_ajax
+ wait_for_requests
page.within('.mr-widget-body') do
expect(page).to have_content('Something went wrong')
@@ -197,4 +202,25 @@ describe 'Merge request', :feature, :js do
end
end
end
+
+ context 'user can merge into source project but cannot push to fork', js: true do
+ let(:fork_project) { create(:project, :public) }
+ let(:user2) { create(:user) }
+
+ before do
+ project.team << [user2, :master]
+ logout
+ login_as user2
+ merge_request.update(target_project: fork_project)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'user can merge into the source project' do
+ expect(page).to have_button('Merge', disabled: false)
+ end
+
+ it 'user cannot remove source branch' do
+ expect(page).to have_field('remove-source-branch-input', disabled: true)
+ end
+ end
end
diff --git a/spec/features/milestones/milestones_spec.rb b/spec/features/milestones/milestones_spec.rb
index 9eec3d7f270..b3dfd6d0e81 100644
--- a/spec/features/milestones/milestones_spec.rb
+++ b/spec/features/milestones/milestones_spec.rb
@@ -78,7 +78,7 @@ describe 'Milestone draggable', feature: true, js: true do
scroll_into_view('.milestone-content')
drag_to(selector: '.issues-sortable-list', list_to_index: 1)
- wait_for_ajax
+ wait_for_requests
end
def create_and_drag_merge_request(params = {})
@@ -87,12 +87,12 @@ describe 'Milestone draggable', feature: true, js: true do
visit namespace_project_milestone_path(project.namespace, project, milestone)
page.find("a[href='#tab-merge-requests']").click
- wait_for_ajax
+ wait_for_requests
scroll_into_view('.milestone-content')
drag_to(selector: '.merge_requests-sortable-list', list_to_index: 1)
- wait_for_ajax
+ wait_for_requests
end
def scroll_into_view(selector)
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index e63feb14b7e..7df628fd7a0 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -47,6 +47,21 @@ describe 'Profile account page', feature: true do
end
end
+ describe 'when I reset RSS token' do
+ before do
+ visit profile_account_path
+ end
+
+ it 'resets RSS token' do
+ previous_token = find("#rss-token").value
+
+ click_link('Reset RSS token')
+
+ expect(page).to have_content 'RSS token was successfully reset'
+ expect(find('#rss-token').value).not_to eq(previous_token)
+ end
+ end
+
describe 'when I reset incoming email token' do
before do
allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index 27a20e78a43..7e2e685df26 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -17,6 +17,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
def disallow_personal_access_token_saves!
allow_any_instance_of(PersonalAccessToken).to receive(:save).and_return(false)
+
errors = ActiveModel::Errors.new(PersonalAccessToken.new).tap { |e| e.add(:name, "cannot be nil") }
allow_any_instance_of(PersonalAccessToken).to receive(:errors).and_return(errors)
end
@@ -91,8 +92,11 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
context "when revocation fails" do
it "displays an error message" do
- disallow_personal_access_token_saves!
visit profile_personal_access_tokens_path
+ allow_any_instance_of(PersonalAccessToken).to receive(:update!).and_return(false)
+
+ errors = ActiveModel::Errors.new(PersonalAccessToken.new).tap { |e| e.add(:name, "cannot be nil") }
+ allow_any_instance_of(PersonalAccessToken).to receive(:errors).and_return(errors)
click_on "Revoke"
expect(active_personal_access_tokens).to have_text(personal_access_token.name)
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index 15c8677fcd3..d368bc4d753 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -44,7 +44,7 @@ describe 'Profile > Preferences', feature: true do
expect(page.current_path).to eq starred_dashboard_projects_path
end
- click_link 'Your projects'
+ find('.shortcuts-activity').trigger('click')
expect(page).not_to have_content("You don't have starred projects yet")
expect(page.current_path).to eq dashboard_projects_path
diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb
index b47c6d431eb..3c1de5c09b2 100644
--- a/spec/features/projects/activity/rss_spec.rb
+++ b/spec/features/projects/activity/rss_spec.rb
@@ -16,7 +16,7 @@ feature 'Project Activity RSS' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's private token"
+ it_behaves_like "it has an RSS button with current_user's RSS token"
end
context 'when signed out' do
@@ -24,6 +24,6 @@ feature 'Project Activity RSS' do
visit path
end
- it_behaves_like "it has an RSS button without a private token"
+ it_behaves_like "it has an RSS button without an RSS token"
end
end
diff --git a/spec/features/projects/artifacts/browse_spec.rb b/spec/features/projects/artifacts/browse_spec.rb
new file mode 100644
index 00000000000..68375956273
--- /dev/null
+++ b/spec/features/projects/artifacts/browse_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+feature 'Browse artifact', :js, feature: true do
+ let(:project) { create(:project, :public) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
+
+ def browse_path(path)
+ browse_namespace_project_job_artifacts_path(project.namespace, project, job, path)
+ end
+
+ context 'when visiting old URL' do
+ let(:browse_url) do
+ browse_path('other_artifacts_0.1.2')
+ end
+
+ before do
+ visit browse_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(browse_url)
+ end
+ end
+end
diff --git a/spec/features/projects/artifacts/download_spec.rb b/spec/features/projects/artifacts/download_spec.rb
new file mode 100644
index 00000000000..dd9454840ee
--- /dev/null
+++ b/spec/features/projects/artifacts/download_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+feature 'Download artifact', :js, feature: true do
+ let(:project) { create(:project, :public) }
+ let(:pipeline) { create(:ci_empty_pipeline, status: :success, project: project, sha: project.commit.sha, ref: 'master') }
+ let(:job) { create(:ci_build, :artifacts, :success, pipeline: pipeline) }
+
+ shared_examples 'downloading' do
+ it 'downloads the zip' do
+ expect(page.response_headers['Content-Disposition'])
+ .to eq(%Q{attachment; filename="#{job.artifacts_file.filename}"})
+
+ # Check the content does match, but don't print this as error message
+ expect(page.source.b == job.artifacts_file.file.read.b)
+ end
+ end
+
+ context 'when downloading' do
+ before do
+ visit download_url
+ end
+
+ context 'via job id' do
+ let(:download_url) do
+ download_namespace_project_job_artifacts_path(project.namespace, project, job)
+ end
+
+ it_behaves_like 'downloading'
+ end
+
+ context 'via branch name and job name' do
+ let(:download_url) do
+ latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{pipeline.ref}/download", job: job.name)
+ end
+
+ it_behaves_like 'downloading'
+ end
+ end
+
+ context 'when visiting old URL' do
+ before do
+ visit download_url.sub('/-/jobs', '/builds')
+ end
+
+ context 'via job id' do
+ let(:download_url) do
+ download_namespace_project_job_artifacts_path(project.namespace, project, job)
+ end
+
+ it_behaves_like 'downloading'
+ end
+
+ context 'via branch name and job name' do
+ let(:download_url) do
+ latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{pipeline.ref}/download", job: job.name)
+ end
+
+ it_behaves_like 'downloading'
+ end
+ end
+end
diff --git a/spec/features/projects/artifacts/file_spec.rb b/spec/features/projects/artifacts/file_spec.rb
index 74308a7e8dd..25c4f3c87a2 100644
--- a/spec/features/projects/artifacts/file_spec.rb
+++ b/spec/features/projects/artifacts/file_spec.rb
@@ -6,14 +6,18 @@ feature 'Artifact file', :js, feature: true do
let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
def visit_file(path)
- visit file_namespace_project_build_artifacts_path(project.namespace, project, build, path)
+ visit file_path(path)
+ end
+
+ def file_path(path)
+ file_namespace_project_job_artifacts_path(project.namespace, project, build, path)
end
context 'Text file' do
before do
visit_file('other_artifacts_0.1.2/doc_sample.txt')
- wait_for_ajax
+ wait_for_requests
end
it 'displays an error' do
@@ -37,7 +41,7 @@ feature 'Artifact file', :js, feature: true do
before do
visit_file('rails_sample.jpg')
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob' do
@@ -56,4 +60,18 @@ feature 'Artifact file', :js, feature: true do
end
end
end
+
+ context 'when visiting old URL' do
+ let(:file_url) do
+ file_path('other_artifacts_0.1.2/doc_sample.txt')
+ end
+
+ before do
+ visit file_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(file_url)
+ end
+ end
end
diff --git a/spec/features/projects/artifacts/raw_spec.rb b/spec/features/projects/artifacts/raw_spec.rb
new file mode 100644
index 00000000000..b589701729d
--- /dev/null
+++ b/spec/features/projects/artifacts/raw_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+feature 'Raw artifact', :js, feature: true do
+ let(:project) { create(:project, :public) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
+
+ def raw_path(path)
+ raw_namespace_project_job_artifacts_path(project.namespace, project, job, path)
+ end
+
+ context 'when visiting old URL' do
+ let(:raw_url) do
+ raw_path('other_artifacts_0.1.2/doc_sample.txt')
+ end
+
+ before do
+ visit raw_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(raw_url)
+ end
+ end
+end
diff --git a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
index d94204230f6..53c5a52ce3a 100644
--- a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
+++ b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
@@ -55,7 +55,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
end
end
- describe 'Click "Blame" button' do
+ describe 'Click "Annotate" button' do
it 'works with no initial line number fragment hash' do
visit_blob
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index 5955623f565..82cfbfda157 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -5,13 +5,13 @@ feature 'File blob', :js, feature: true do
def visit_blob(path, fragment = nil)
visit namespace_project_blob_path(project.namespace, project, File.join('master', path), anchor: fragment)
+
+ wait_for_requests
end
context 'Ruby file' do
before do
visit_blob('files/ruby/popen.rb')
-
- wait_for_ajax
end
it 'displays the blob' do
@@ -35,8 +35,6 @@ feature 'File blob', :js, feature: true do
context 'visiting directly' do
before do
visit_blob('files/markdown/ruby-style-guide.md')
-
- wait_for_ajax
end
it 'displays the blob using the rich viewer' do
@@ -63,7 +61,7 @@ feature 'File blob', :js, feature: true do
before do
find('.js-blob-viewer-switch-btn[data-viewer=simple]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the simple viewer' do
@@ -84,7 +82,7 @@ feature 'File blob', :js, feature: true do
before do
find('.js-blob-viewer-switch-btn[data-viewer=rich]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the rich viewer' do
@@ -104,8 +102,6 @@ feature 'File blob', :js, feature: true do
context 'visiting with a line number anchor' do
before do
visit_blob('files/markdown/ruby-style-guide.md', 'L1')
-
- wait_for_ajax
end
it 'displays the blob using the simple viewer' do
@@ -148,8 +144,6 @@ feature 'File blob', :js, feature: true do
project.update_attribute(:lfs_enabled, true)
visit_blob('files/lfs/file.md')
-
- wait_for_ajax
end
it 'displays an error' do
@@ -176,7 +170,7 @@ feature 'File blob', :js, feature: true do
before do
find('.js-blob-viewer-switcher .js-blob-viewer-switch-btn[data-viewer=simple]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays an error' do
@@ -198,8 +192,6 @@ feature 'File blob', :js, feature: true do
context 'when LFS is disabled on the project' do
before do
visit_blob('files/lfs/file.md')
-
- wait_for_ajax
end
it 'displays the blob' do
@@ -235,8 +227,6 @@ feature 'File blob', :js, feature: true do
).execute
visit_blob('files/test.pdf')
-
- wait_for_ajax
end
it 'displays the blob' do
@@ -263,8 +253,6 @@ feature 'File blob', :js, feature: true do
project.update_attribute(:lfs_enabled, true)
visit_blob('files/lfs/lfs_object.iso')
-
- wait_for_ajax
end
it 'displays the blob' do
@@ -287,8 +275,6 @@ feature 'File blob', :js, feature: true do
context 'when LFS is disabled on the project' do
before do
visit_blob('files/lfs/lfs_object.iso')
-
- wait_for_ajax
end
it 'displays the blob' do
@@ -312,8 +298,6 @@ feature 'File blob', :js, feature: true do
context 'ZIP file' do
before do
visit_blob('Gemfile.zip')
-
- wait_for_ajax
end
it 'displays the blob' do
@@ -348,8 +332,6 @@ feature 'File blob', :js, feature: true do
).execute
visit_blob('files/empty.md')
-
- wait_for_ajax
end
it 'displays an error' do
@@ -369,4 +351,116 @@ feature 'File blob', :js, feature: true do
end
end
end
+
+ context '.gitlab-ci.yml' do
+ before do
+ project.add_master(project.creator)
+
+ Files::CreateService.new(
+ project,
+ project.creator,
+ start_branch: 'master',
+ branch_name: 'master',
+ commit_message: "Add .gitlab-ci.yml",
+ file_path: '.gitlab-ci.yml',
+ file_content: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+ ).execute
+
+ visit_blob('.gitlab-ci.yml')
+ end
+
+ it 'displays an auxiliary viewer' do
+ aggregate_failures do
+ # shows that configuration is valid
+ expect(page).to have_content('This GitLab CI configuration is valid.')
+
+ # shows a learn more link
+ expect(page).to have_link('Learn more')
+ end
+ end
+ end
+
+ context '.gitlab/route-map.yml' do
+ before do
+ project.add_master(project.creator)
+
+ Files::CreateService.new(
+ project,
+ project.creator,
+ start_branch: 'master',
+ branch_name: 'master',
+ commit_message: "Add .gitlab/route-map.yml",
+ file_path: '.gitlab/route-map.yml',
+ file_content: <<-MAP.strip_heredoc
+ # Team data
+ - source: 'data/team.yml'
+ public: 'team/'
+ MAP
+ ).execute
+
+ visit_blob('.gitlab/route-map.yml')
+ end
+
+ it 'displays an auxiliary viewer' do
+ aggregate_failures do
+ # shows that map is valid
+ expect(page).to have_content('This Route Map is valid.')
+
+ # shows a learn more link
+ expect(page).to have_link('Learn more')
+ end
+ end
+ end
+
+ context 'LICENSE' do
+ before do
+ visit_blob('LICENSE')
+ end
+
+ it 'displays an auxiliary viewer' do
+ aggregate_failures do
+ # shows license
+ expect(page).to have_content('This project is licensed under the MIT License.')
+
+ # shows a learn more link
+ expect(page).to have_link('Learn more', 'http://choosealicense.com/licenses/mit/')
+ end
+ end
+ end
+
+ context '*.gemspec' do
+ before do
+ project.add_master(project.creator)
+
+ Files::CreateService.new(
+ project,
+ project.creator,
+ start_branch: 'master',
+ branch_name: 'master',
+ commit_message: "Add activerecord.gemspec",
+ file_path: 'activerecord.gemspec',
+ file_content: <<-SPEC.strip_heredoc
+ Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = "activerecord"
+ end
+ SPEC
+ ).execute
+
+ visit_blob('activerecord.gemspec')
+ end
+
+ it 'displays an auxiliary viewer' do
+ aggregate_failures do
+ # shows names of dependency manager and package
+ expect(page).to have_content('This project manages its dependencies using RubyGems and defines a gem named activerecord.')
+
+ # shows a link to the gem
+ expect(page).to have_link('activerecord', 'https://rubygems.org/gems/activerecord')
+
+ # shows a learn more link
+ expect(page).to have_link('Learn more', 'http://choosealicense.com/licenses/mit/')
+ end
+ end
+ end
end
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index cc5b1a7e734..1a38997450d 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -18,7 +18,7 @@ feature 'Editing file blob', feature: true, js: true do
end
def edit_and_commit
- wait_for_ajax
+ wait_for_requests
find('.js-edit-blob').click
execute_script('ace.edit("editor").setValue("class NextFeature\nend\n")')
click_button 'Commit changes'
diff --git a/spec/features/projects/blobs/user_create_spec.rb b/spec/features/projects/blobs/user_create_spec.rb
index d805450e095..4b6c55f5f44 100644
--- a/spec/features/projects/blobs/user_create_spec.rb
+++ b/spec/features/projects/blobs/user_create_spec.rb
@@ -15,7 +15,7 @@ feature 'New blob creation', feature: true, js: true do
end
def edit_file
- wait_for_ajax
+ wait_for_requests
fill_in 'file_name', with: 'feature.rb'
execute_script("ace.edit('editor').setValue('#{content}')")
end
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index 8e0306ce83b..7668ce5f8be 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -4,7 +4,13 @@ describe 'Branches', feature: true do
let(:project) { create(:project, :public) }
let(:repository) { project.repository }
- context 'logged in' do
+ def set_protected_branch_name(branch_name)
+ find(".js-protected-branch-select").click
+ find(".dropdown-input-field").set(branch_name)
+ click_on("Create wildcard #{branch_name}")
+ end
+
+ context 'logged in as developer' do
before do
login_as :user
project.team << [@user, :developer]
@@ -38,6 +44,83 @@ describe 'Branches', feature: true do
expect(find('.all-branches')).to have_selector('li', count: 1)
end
end
+
+ describe 'Delete unprotected branch' do
+ it 'removes branch after confirmation', js: true do
+ visit namespace_project_branches_path(project.namespace, project)
+
+ fill_in 'branch-search', with: 'fix'
+
+ find('#branch-search').native.send_keys(:enter)
+
+ expect(page).to have_content('fix')
+ expect(find('.all-branches')).to have_selector('li', count: 1)
+ find('.js-branch-fix .btn-remove').trigger(:click)
+
+ expect(page).not_to have_content('fix')
+ expect(find('.all-branches')).to have_selector('li', count: 0)
+ end
+ end
+
+ describe 'Delete protected branch' do
+ before do
+ project.add_user(@user, :master)
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('fix')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content('fix') }
+ expect(ProtectedBranch.count).to eq(1)
+ project.add_user(@user, :developer)
+ end
+
+ it 'does not allow devleoper to removes protected branch', js: true do
+ visit namespace_project_branches_path(project.namespace, project)
+
+ fill_in 'branch-search', with: 'fix'
+ find('#branch-search').native.send_keys(:enter)
+
+ expect(page).to have_css('.btn-remove.disabled')
+ end
+ end
+ end
+
+ context 'logged in as master' do
+ before do
+ login_as :user
+ project.team << [@user, :master]
+ end
+
+ describe 'Delete protected branch' do
+ before do
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('fix')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content('fix') }
+ expect(ProtectedBranch.count).to eq(1)
+ end
+
+ it 'removes branch after modal confirmation', js: true do
+ visit namespace_project_branches_path(project.namespace, project)
+
+ fill_in 'branch-search', with: 'fix'
+ find('#branch-search').native.send_keys(:enter)
+
+ expect(page).to have_content('fix')
+ expect(find('.all-branches')).to have_selector('li', count: 1)
+ page.find('[data-target="#modal-delete-branch"]').trigger(:click)
+
+ expect(page).to have_css('.js-delete-branch[disabled]')
+ fill_in 'delete_branch_input', with: 'fix'
+ click_link 'Delete protected branch'
+
+ fill_in 'branch-search', with: 'fix'
+ find('#branch-search').native.send_keys(:enter)
+
+ expect(page).to have_content('No branches to show')
+ end
+ end
end
context 'logged out' do
diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb
index fa67d390c47..bc7ca0ddd38 100644
--- a/spec/features/projects/commit/cherry_pick_spec.rb
+++ b/spec/features/projects/commit/cherry_pick_spec.rb
@@ -72,11 +72,11 @@ describe 'Cherry-pick Commits' do
click_button 'master'
end
- wait_for_ajax
+ wait_for_requests
page.within('#modal-cherry-pick-commit .dropdown-menu') do
find('.dropdown-input input').set('feature')
- wait_for_ajax
+ wait_for_requests
click_link "feature"
end
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
index 98c0f2c63b0..f2de195eb7f 100644
--- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb
+++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
@@ -32,7 +32,7 @@ feature 'Mini Pipeline Graph in Commit View', :js, :feature do
it 'should show the builds list when stage is clicked' do
first('.mini-pipeline-graph-dropdown-toggle').click
- wait_for_ajax
+ wait_for_requests
page.within '.js-builds-dropdown-list' do
expect(page).to have_selector('.ci-status-icon-running')
diff --git a/spec/features/projects/commit/rss_spec.rb b/spec/features/projects/commit/rss_spec.rb
index 6e0e1916f87..03b6d560c96 100644
--- a/spec/features/projects/commit/rss_spec.rb
+++ b/spec/features/projects/commit/rss_spec.rb
@@ -12,8 +12,8 @@ feature 'Project Commits RSS' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's private token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ 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
@@ -21,7 +21,7 @@ feature 'Project Commits RSS' do
visit path
end
- it_behaves_like "it has an RSS button without a private token"
- it_behaves_like "an autodiscoverable RSS feed without a private 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
diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb
index b2a3b111c9e..ee6985ad993 100644
--- a/spec/features/projects/compare_spec.rb
+++ b/spec/features/projects/compare_spec.rb
@@ -24,6 +24,7 @@ describe "Compare", js: true do
expect(find(".js-compare-to-dropdown .dropdown-toggle-text")).to have_content("binary-encoding")
click_button "Compare"
+
expect(page).to have_content "Commits"
end
@@ -52,8 +53,12 @@ describe "Compare", js: true do
def select_using_dropdown(dropdown_type, selection)
dropdown = find(".js-compare-#{dropdown_type}-dropdown")
dropdown.find(".compare-dropdown-toggle").click
+ # find input before using to wait for the inputs visiblity
+ dropdown.find('.dropdown-menu')
dropdown.fill_in("Filter by Git revision", with: selection)
- wait_for_ajax
- dropdown.find_all("a[data-ref=\"#{selection}\"]", visible: true).last.click
+ wait_for_requests
+ # find before all to wait for the items visiblity
+ dropdown.find("a[data-ref=\"#{selection}\"]", match: :first)
+ dropdown.all("a[data-ref=\"#{selection}\"]").last.click
end
end
diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
index 2352329d58c..0c51fe72ca4 100644
--- a/spec/features/projects/developer_views_empty_project_instructions_spec.rb
+++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
@@ -56,14 +56,8 @@ feature 'Developer views empty project instructions', feature: true do
end
def expect_instructions_for(protocol)
- url =
- case protocol
- when 'ssh'
- project.ssh_url_to_repo
- when 'http'
- project.http_url_to_repo(developer)
- end
-
- expect(page).to have_content("git clone #{url}")
+ msg = :"#{protocol.downcase}_url_to_repo"
+
+ expect(page).to have_content("git clone #{project.send(msg)}")
end
end
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 86ce50c976f..18b608c863e 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -12,6 +12,7 @@ feature 'Environment', :feature do
feature 'environment details page' do
given!(:environment) { create(:environment, project: project) }
+ given!(:permissions) { }
given!(:deployment) { }
given!(:action) { }
@@ -62,20 +63,31 @@ feature 'Environment', :feature do
name: 'deploy to production')
end
- given(:role) { :master }
+ context 'when user has ability to trigger deployment' do
+ given(:permissions) do
+ create(:protected_branch, :developers_can_merge,
+ name: action.ref, project: project)
+ end
- scenario 'does show a play button' do
- expect(page).to have_link(action.name.humanize)
- end
+ it 'does show a play button' do
+ expect(page).to have_link(action.name.humanize)
+ end
+
+ it 'does allow to play manual action' do
+ expect(action).to be_manual
- scenario 'does allow to play manual action' do
- expect(action).to be_manual
+ expect { click_link(action.name.humanize) }
+ .not_to change { Ci::Pipeline.count }
- expect { click_link(action.name.humanize) }
- .not_to change { Ci::Pipeline.count }
+ expect(page).to have_content(action.name)
+ expect(action.reload).to be_pending
+ end
+ end
- expect(page).to have_content(action.name)
- expect(action.reload).to be_pending
+ context 'when user has no ability to trigger a deployment' do
+ it 'does not show a play button' do
+ expect(page).not_to have_link(action.name.humanize)
+ end
end
context 'with external_url' do
@@ -134,12 +146,23 @@ feature 'Environment', :feature do
on_stop: 'close_app')
end
- given(:role) { :master }
+ context 'when user has ability to stop environment' do
+ given(:permissions) do
+ create(:protected_branch, :developers_can_merge,
+ name: action.ref, project: project)
+ end
- scenario 'does allow to stop environment' do
- click_link('Stop')
+ it 'allows to stop environment' do
+ click_link('Stop')
- expect(page).to have_content('close_app')
+ expect(page).to have_content('close_app')
+ end
+ end
+
+ context 'when user has no ability to stop environment' do
+ it 'does not allow to stop environment' do
+ expect(page).to have_no_link('Stop')
+ end
end
context 'for reporter' do
@@ -150,12 +173,6 @@ feature 'Environment', :feature do
end
end
end
-
- context 'without stop action' do
- scenario 'does allow to stop environment' do
- click_link('Stop')
- end
- end
end
context 'when environment is stopped' do
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index cf393afccbb..613b1edba36 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -31,7 +31,7 @@ feature 'Environments page', :feature, :js do
it 'should show one environment' do
visit namespace_project_environments_path(project.namespace, project, scope: 'available')
expect(page).to have_css('.environments-container')
- expect(page.all('tbody > tr').length).to eq(1)
+ expect(page.all('.environment-name').length).to eq(1)
end
end
@@ -59,7 +59,7 @@ feature 'Environments page', :feature, :js do
it 'should show one environment' do
visit namespace_project_environments_path(project.namespace, project, scope: 'stopped')
expect(page).to have_css('.environments-container')
- expect(page.all('tbody > tr').length).to eq(1)
+ expect(page.all('.environment-name').length).to eq(1)
end
end
end
@@ -239,7 +239,9 @@ feature 'Environments page', :feature, :js do
context 'when logged as developer' do
before do
- click_link 'New environment'
+ within(".top-area") do
+ click_link 'New environment'
+ end
end
context 'for valid name' do
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index e1781cf320a..c49648f54bd 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -21,17 +21,17 @@ describe 'Edit Project Settings', feature: true do
select 'Disabled', from: "project_project_feature_attributes_#{tool_name}_access_level"
click_button 'Save changes'
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_selector(".shortcuts-#{shortcut_name}")
select 'Everyone with access', from: "project_project_feature_attributes_#{tool_name}_access_level"
click_button 'Save changes'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector(".shortcuts-#{shortcut_name}")
select 'Only team members', from: "project_project_feature_attributes_#{tool_name}_access_level"
click_button 'Save changes'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector(".shortcuts-#{shortcut_name}")
sleep 0.1
@@ -74,7 +74,7 @@ describe 'Edit Project Settings', feature: true do
issues: namespace_project_issues_path(project.namespace, project),
wiki: namespace_project_wiki_path(project.namespace, project, :home),
snippets: namespace_project_snippets_path(project.namespace, project),
- merge_requests: namespace_project_merge_requests_path(project.namespace, project),
+ merge_requests: namespace_project_merge_requests_path(project.namespace, project)
}
end
@@ -169,7 +169,7 @@ describe 'Edit Project Settings', feature: true do
select "Disabled", from: "project_project_feature_attributes_wiki_access_level"
click_button "Save changes"
- wait_for_ajax
+ wait_for_requests
visit namespace_project_path(project.namespace, project)
@@ -182,7 +182,7 @@ describe 'Edit Project Settings', feature: true do
select "Disabled", from: "project_project_feature_attributes_wiki_access_level"
click_button "Save changes"
- wait_for_ajax
+ wait_for_requests
visit activity_namespace_project_path(project.namespace, project)
@@ -223,7 +223,7 @@ describe 'Edit Project Settings', feature: true do
def save_changes_and_check_activity_tab
click_button "Save changes"
- wait_for_ajax
+ wait_for_requests
visit activity_namespace_project_path(project.namespace, project)
diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb
index 70e96efd557..30a1eedbb48 100644
--- a/spec/features/projects/files/browse_files_spec.rb
+++ b/spec/features/projects/files/browse_files_spec.rb
@@ -12,7 +12,7 @@ feature 'user browses project', feature: true, js: true do
scenario "can see blame of '.gitignore'" do
click_link ".gitignore"
- click_link 'Blame'
+ click_link 'Annotate'
expect(page).to have_content "*.rb"
expect(page).to have_content "Dmitriy Zaporozhets"
@@ -24,11 +24,23 @@ feature 'user browses project', feature: true, js: true do
click_link 'files'
click_link 'lfs'
click_link 'lfs_object.iso'
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_content 'Download (1.5 MB)'
expect(page).to have_content 'version https://git-lfs.github.com/spec/v1'
expect(page).to have_content 'oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897'
expect(page).to have_content 'size 1575078'
end
+
+ scenario 'can see last commit for current directory' do
+ last_commit = project.repository.last_commit_for_path(project.default_branch, 'files')
+
+ click_link 'files'
+ wait_for_requests
+
+ page.within('.blob-commit-info') do
+ expect(page).to have_content last_commit.short_id
+ expect(page).to have_content last_commit.author_name
+ end
+ end
end
diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb
index 548131c7cd4..93909e91d05 100644
--- a/spec/features/projects/files/dockerfile_dropdown_spec.rb
+++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb
@@ -19,14 +19,14 @@ feature 'User wants to add a Dockerfile file', feature: true do
scenario 'user can pick a Dockerfile file from the dropdown', js: true do
find('.js-dockerfile-selector').click
- wait_for_ajax
+ wait_for_requests
within '.dockerfile-selector' do
find('.dropdown-input-field').set('HTTPd')
find('.dropdown-content li', text: 'HTTPd').click
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_css('.dockerfile-selector .dropdown-toggle-text', text: 'HTTPd')
expect(page).to have_content('COPY ./ /usr/local/apache2/htdocs/')
diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb
index e7a6749d8ac..ee42bcaec4b 100644
--- a/spec/features/projects/files/find_file_keyboard_spec.rb
+++ b/spec/features/projects/files/find_file_keyboard_spec.rb
@@ -10,7 +10,7 @@ feature 'Find file keyboard shortcuts', feature: true, js: true do
visit namespace_project_find_file_path(project.namespace, project, project.repository.root_ref)
- wait_for_ajax
+ wait_for_requests
end
it 'opens file when pressing enter key' do
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index e59428f8b24..e9f49453121 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -15,12 +15,12 @@ feature 'User wants to add a .gitignore file', feature: true do
scenario 'user can pick a .gitignore file from the dropdown', js: true do
find('.js-gitignore-selector').click
- wait_for_ajax
+ wait_for_requests
within '.gitignore-selector' do
find('.dropdown-input-field').set('rails')
find('.dropdown-content li', text: 'Rails').click
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_css('.gitignore-selector .dropdown-toggle-text', text: 'Rails')
expect(page).to have_content('/.bundle')
diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
index 85b66b93fba..031b89d0499 100644
--- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
+++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
@@ -15,12 +15,12 @@ feature 'User wants to add a .gitlab-ci.yml file', feature: true do
scenario 'user can pick a template from the dropdown', js: true do
find('.js-gitlab-ci-yml-selector').click
- wait_for_ajax
+ wait_for_requests
within '.gitlab-ci-yml-selector' do
find('.dropdown-input-field').set('Jekyll')
find('.dropdown-content li', text: 'Jekyll').click
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'Jekyll')
expect(page).to have_content('This file is a template, and might need editing before it works on your project')
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index 249830921ac..8d410cc3f2e 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -63,7 +63,7 @@ feature 'project owner creates a license file', feature: true, js: true do
page.within('.js-license-selector-wrap') do
click_button 'Apply a license template'
click_link template
- wait_for_ajax
+ wait_for_requests
end
end
end
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 70a41886985..8e197bccabf 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -40,7 +40,7 @@ feature 'project owner sees a link to create a license file in empty project', f
page.within('.js-license-selector-wrap') do
click_button 'Apply a license template'
click_link template
- wait_for_ajax
+ wait_for_requests
end
end
end
diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb
index cd3af0b7d29..de10eec0557 100644
--- a/spec/features/projects/files/undo_template_spec.rb
+++ b/spec/features/projects/files/undo_template_spec.rb
@@ -57,7 +57,7 @@ end
def select_file_template(template_selector_selector, template_name)
find(template_selector_selector).click
find('.dropdown-content li', text: template_name).click
- wait_for_ajax
+ wait_for_requests
end
def select_file_template_type(template_type)
diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb
index dd9622f16a0..67bc9142356 100644
--- a/spec/features/projects/gfm_autocomplete_load_spec.rb
+++ b/spec/features/projects/gfm_autocomplete_load_spec.rb
@@ -10,7 +10,7 @@ describe 'GFM autocomplete loading', feature: true, js: true do
end
it 'does not load on project#show' do
- expect(evaluate_script('gl.GfmAutoComplete.dataSources')).to eq({})
+ expect(evaluate_script('gl.GfmAutoComplete')).to eq(nil)
end
it 'loads on new issue page' do
diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb
index 726469daba4..b91c3eff478 100644
--- a/spec/features/projects/guest_navigation_menu_spec.rb
+++ b/spec/features/projects/guest_navigation_menu_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Guest navigation menu" do
+describe 'Guest navigation menu' do
let(:project) { create(:empty_project, :private, public_builds: false) }
let(:guest) { create(:user) }
@@ -10,10 +10,10 @@ describe "Guest navigation menu" do
login_as(guest)
end
- it "shows allowed tabs only" do
+ it 'shows allowed tabs only' do
visit namespace_project_path(project.namespace, project)
- within(".nav-links") do
+ within('.layout-nav') do
expect(page).to have_content 'Project'
expect(page).to have_content 'Issues'
expect(page).to have_content 'Wiki'
@@ -23,4 +23,60 @@ describe "Guest navigation menu" do
expect(page).not_to have_content 'Merge Requests'
end
end
+
+ it 'does not show fork button' do
+ visit namespace_project_path(project.namespace, project)
+
+ within('.count-buttons') do
+ expect(page).not_to have_link 'Fork'
+ end
+ end
+
+ it 'does not show clone path' do
+ visit namespace_project_path(project.namespace, project)
+
+ within('.project-repo-buttons') do
+ expect(page).not_to have_selector '.project-clone-holder'
+ end
+ end
+
+ describe 'project landing page' do
+ before do
+ project.project_feature.update!(
+ issues_access_level: ProjectFeature::DISABLED,
+ wiki_access_level: ProjectFeature::DISABLED
+ )
+ end
+
+ it 'does not show the project file list landing page' do
+ visit namespace_project_path(project.namespace, project)
+
+ expect(page).not_to have_selector '.project-stats'
+ expect(page).not_to have_selector '.project-last-commit'
+ expect(page).not_to have_selector '.project-show-files'
+ expect(page).to have_selector '.project-show-customize_workflow'
+ end
+
+ it 'shows the customize workflow when issues and wiki are disabled' do
+ visit namespace_project_path(project.namespace, project)
+
+ expect(page).to have_selector '.project-show-customize_workflow'
+ end
+
+ it 'shows the wiki when enabled' do
+ project.project_feature.update!(wiki_access_level: ProjectFeature::PRIVATE)
+
+ visit namespace_project_path(project.namespace, project)
+
+ expect(page).to have_selector '.project-show-wiki'
+ end
+
+ it 'shows the issues when enabled' do
+ project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+
+ visit namespace_project_path(project.namespace, project)
+
+ expect(page).to have_selector '.issues-list'
+ end
+ end
end
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index fa5e30075e3..3076c863dcb 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -34,14 +34,14 @@ feature 'issuable templates', feature: true, js: true do
scenario 'user selects "bug" template' do
select_template 'bug'
- wait_for_ajax
+ wait_for_requests
assert_template
save_changes
end
scenario 'user selects "bug" template and then "no template"' do
select_template 'bug'
- wait_for_ajax
+ wait_for_requests
select_option 'No template'
assert_template('')
save_changes('')
@@ -49,7 +49,7 @@ feature 'issuable templates', feature: true, js: true do
scenario 'user selects "bug" template, edits description and then selects "reset template"' do
select_template 'bug'
- wait_for_ajax
+ wait_for_requests
find_field('issue_description').send_keys(description_addition)
assert_template(template_content + description_addition)
select_option 'Reset template'
@@ -61,7 +61,7 @@ feature 'issuable templates', feature: true, js: true do
start_height = page.evaluate_script('$(".markdown-area").outerHeight()')
select_template 'test'
- wait_for_ajax
+ wait_for_requests
end_height = page.evaluate_script('$(".markdown-area").outerHeight()')
@@ -88,7 +88,7 @@ feature 'issuable templates', feature: true, js: true do
scenario 'user selects "bug" template' do
select_template 'bug'
- wait_for_ajax
+ wait_for_requests
assert_template("#{template_content}")
save_changes
end
@@ -111,7 +111,7 @@ feature 'issuable templates', feature: true, js: true do
scenario 'user selects "feature-proposal" template' do
select_template 'feature-proposal'
- wait_for_ajax
+ wait_for_requests
assert_template
save_changes
end
@@ -143,7 +143,7 @@ feature 'issuable templates', feature: true, js: true do
context 'template exists in target project' do
scenario 'user selects template' do
select_template 'feature-proposal'
- wait_for_ajax
+ wait_for_requests
assert_template
save_changes
end
diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb
index 71429f00095..f6852192aef 100644
--- a/spec/features/projects/issues/rss_spec.rb
+++ b/spec/features/projects/issues/rss_spec.rb
@@ -16,8 +16,8 @@ feature 'Project Issues RSS' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's private token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ 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
@@ -25,7 +25,7 @@ feature 'Project Issues RSS' do
visit path
end
- it_behaves_like "it has an RSS button without a private token"
- it_behaves_like "an autodiscoverable RSS feed without a private 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
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/jobs_spec.rb
index ab10434e10c..0eda46649db 100644
--- a/spec/features/projects/builds_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require 'tempfile'
-feature 'Builds', :feature do
+feature 'Jobs', :feature do
let(:user) { create(:user) }
let(:user_access_level) { :developer }
let(:project) { create(:project) }
@@ -19,12 +19,12 @@ feature 'Builds', :feature do
login_as(user)
end
- describe "GET /:project/builds" do
+ describe "GET /:project/jobs" do
let!(:build) { create(:ci_build, pipeline: pipeline) }
context "Pending scope" do
before do
- visit namespace_project_builds_path(project.namespace, project, scope: :pending)
+ visit namespace_project_jobs_path(project.namespace, project, scope: :pending)
end
it "shows Pending tab jobs" do
@@ -39,7 +39,7 @@ feature 'Builds', :feature do
context "Running scope" do
before do
build.run!
- visit namespace_project_builds_path(project.namespace, project, scope: :running)
+ visit namespace_project_jobs_path(project.namespace, project, scope: :running)
end
it "shows Running tab jobs" do
@@ -54,7 +54,7 @@ feature 'Builds', :feature do
context "Finished scope" do
before do
build.run!
- visit namespace_project_builds_path(project.namespace, project, scope: :finished)
+ visit namespace_project_jobs_path(project.namespace, project, scope: :finished)
end
it "shows Finished tab jobs" do
@@ -67,7 +67,7 @@ feature 'Builds', :feature do
context "All jobs" do
before do
project.builds.running_or_pending.each(&:success)
- visit namespace_project_builds_path(project.namespace, project)
+ visit namespace_project_jobs_path(project.namespace, project)
end
it "shows All tab jobs" do
@@ -78,12 +78,26 @@ feature 'Builds', :feature do
expect(page).not_to have_link 'Cancel running'
end
end
+
+ context "when visiting old URL" do
+ let(:jobs_url) do
+ namespace_project_jobs_path(project.namespace, project)
+ end
+
+ before do
+ visit jobs_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(jobs_url)
+ end
+ end
end
- describe "POST /:project/builds/:id/cancel_all" do
+ describe "POST /:project/jobs/:id/cancel_all" do
before do
build.run!
- visit namespace_project_builds_path(project.namespace, project)
+ visit namespace_project_jobs_path(project.namespace, project)
click_link "Cancel running"
end
@@ -97,10 +111,10 @@ feature 'Builds', :feature do
end
end
- describe "GET /:project/builds/:id" do
+ describe "GET /:project/jobs/:id" do
context "Job from project" do
before do
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
it 'shows commit`s data' do
@@ -117,7 +131,7 @@ feature 'Builds', :feature do
context "Job from other project" do
before do
- visit namespace_project_build_path(project.namespace, project, build2)
+ visit namespace_project_job_path(project.namespace, project, build2)
end
it { expect(page.status_code).to eq(404) }
@@ -126,7 +140,7 @@ feature 'Builds', :feature do
context "Download artifacts" do
before do
build.update_attributes(artifacts_file: artifacts_file)
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
it 'has button to download artifacts' do
@@ -139,7 +153,7 @@ feature 'Builds', :feature do
build.update_attributes(artifacts_file: artifacts_file,
artifacts_expire_at: expire_at)
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
context 'no expire date defined' do
@@ -183,14 +197,29 @@ feature 'Builds', :feature do
end
end
+ context "when visiting old URL" do
+ let(:job_url) do
+ namespace_project_job_path(project.namespace, project, build)
+ end
+
+ before do
+ visit job_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(job_url)
+ end
+ end
+
feature 'Raw trace' do
before do
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
+
+ visit namespace_project_job_path(project.namespace, project, build)
end
it do
- expect(page).to have_link 'Raw'
+ expect(page).to have_css('.js-raw-link')
end
end
@@ -198,7 +227,7 @@ feature 'Builds', :feature do
before do
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
context 'when job has an initial trace' do
@@ -222,7 +251,7 @@ feature 'Builds', :feature do
end
before do
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
it 'shows variable key and value after click', js: true do
@@ -247,17 +276,17 @@ feature 'Builds', :feature do
let(:build) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
it 'shows a link for the job' do
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
expect(page).to have_link environment.name
end
end
- context 'job is complete and not successfull' do
+ context 'job is complete and not successful' do
let(:build) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) }
it 'shows a link for the job' do
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
expect(page).to have_link environment.name
end
@@ -268,7 +297,7 @@ feature 'Builds', :feature do
let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
it 'shows a link to latest deployment' do
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
expect(page).to have_link('latest deployment')
end
@@ -276,11 +305,11 @@ feature 'Builds', :feature do
end
end
- describe "POST /:project/builds/:id/cancel" do
+ describe "POST /:project/jobs/:id/cancel" do
context "Job from project" do
before do
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
click_link "Cancel"
end
@@ -294,19 +323,19 @@ feature 'Builds', :feature do
context "Job from other project" do
before do
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
- page.driver.post(cancel_namespace_project_build_path(project.namespace, project, build2))
+ visit namespace_project_job_path(project.namespace, project, build)
+ page.driver.post(cancel_namespace_project_job_path(project.namespace, project, build2))
end
it { expect(page.status_code).to eq(404) }
end
end
- describe "POST /:project/builds/:id/retry" do
+ describe "POST /:project/jobs/:id/retry" do
context "Job from project" do
before do
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
click_link 'Cancel'
page.within('.build-header') do
click_link 'Retry job'
@@ -322,18 +351,18 @@ feature 'Builds', :feature do
end
end
- context "Build from other project" do
+ context "Job from other project" do
before do
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
click_link 'Cancel'
- page.driver.post(retry_namespace_project_build_path(project.namespace, project, build2))
+ page.driver.post(retry_namespace_project_job_path(project.namespace, project, build2))
end
it { expect(page).to have_http_status(404) }
end
- context "Build that current user is not allowed to retry" do
+ context "Job that current user is not allowed to retry" do
before do
build.run!
build.cancel!
@@ -341,7 +370,7 @@ feature 'Builds', :feature do
logout_direct
login_with(create(:user))
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
it 'does not show the Retry button' do
@@ -352,31 +381,31 @@ feature 'Builds', :feature do
end
end
- describe "GET /:project/builds/:id/download" do
+ describe "GET /:project/jobs/:id/download" do
before do
build.update_attributes(artifacts_file: artifacts_file)
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
click_link 'Download'
end
context "Build from other project" do
before do
build2.update_attributes(artifacts_file: artifacts_file)
- visit download_namespace_project_build_artifacts_path(project.namespace, project, build2)
+ visit download_namespace_project_job_artifacts_path(project.namespace, project, build2)
end
it { expect(page.status_code).to eq(404) }
end
end
- describe 'GET /:project/builds/:id/raw' do
+ describe 'GET /:project/jobs/:id/raw', :js do
context 'access source' do
- context 'build from project' do
+ context 'job from project' do
before do
- Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+ Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
build.run!
- visit namespace_project_build_path(project.namespace, project, build)
- page.within('.js-build-sidebar') { click_link 'Raw' }
+ visit namespace_project_job_path(project.namespace, project, build)
+ find('.js-raw-link-controller').click()
end
it 'sends the right headers' do
@@ -386,11 +415,11 @@ feature 'Builds', :feature do
end
end
- context 'build from other project' do
+ context 'job from other project' do
before do
- Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+ Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
build2.run!
- visit raw_namespace_project_build_path(project.namespace, project, build2)
+ visit raw_namespace_project_job_path(project.namespace, project, build2)
end
it 'sends the right headers' do
@@ -403,23 +432,23 @@ feature 'Builds', :feature do
let(:existing_file) { Tempfile.new('existing-trace-file').path }
before do
- Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+ Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
build.run!
allow_any_instance_of(Gitlab::Ci::Trace).to receive(:paths)
.and_return(paths)
- visit namespace_project_build_path(project.namespace, project, build)
+ visit namespace_project_job_path(project.namespace, project, build)
end
- context 'when build has trace in file' do
+ context 'when build has trace in file', :js do
let(:paths) do
[existing_file]
end
before do
- page.within('.js-build-sidebar') { click_link 'Raw' }
+ find('.js-raw-link-controller').click()
end
it 'sends the right headers' do
@@ -429,46 +458,60 @@ feature 'Builds', :feature do
end
end
- context 'when build has trace in DB' do
+ context 'when job has trace in DB' do
let(:paths) { [] }
it 'sends the right headers' do
- expect(page.status_code).not_to have_link('Raw')
+ expect(page.status_code).not_to have_selector('.js-raw-link-controller')
end
end
end
+
+ context "when visiting old URL" do
+ let(:raw_job_url) do
+ raw_namespace_project_job_path(project.namespace, project, build)
+ end
+
+ before do
+ visit raw_job_url.sub('/-/jobs', '/builds')
+ end
+
+ it "redirects to new URL" do
+ expect(page.current_path).to eq(raw_job_url)
+ end
+ end
end
- describe "GET /:project/builds/:id/trace.json" do
- context "Build from project" do
+ describe "GET /:project/jobs/:id/trace.json" do
+ context "Job from project" do
before do
- visit trace_namespace_project_build_path(project.namespace, project, build, format: :json)
+ visit trace_namespace_project_job_path(project.namespace, project, build, format: :json)
end
it { expect(page.status_code).to eq(200) }
end
- context "Build from other project" do
+ context "Job from other project" do
before do
- visit trace_namespace_project_build_path(project.namespace, project, build2, format: :json)
+ visit trace_namespace_project_job_path(project.namespace, project, build2, format: :json)
end
it { expect(page.status_code).to eq(404) }
end
end
- describe "GET /:project/builds/:id/status" do
- context "Build from project" do
+ describe "GET /:project/jobs/:id/status" do
+ context "Job from project" do
before do
- visit status_namespace_project_build_path(project.namespace, project, build)
+ visit status_namespace_project_job_path(project.namespace, project, build)
end
it { expect(page.status_code).to eq(200) }
end
- context "Build from other project" do
+ context "Job from other project" do
before do
- visit status_namespace_project_build_path(project.namespace, project, build2)
+ visit status_namespace_project_job_path(project.namespace, project, build2)
end
it { expect(page.status_code).to eq(404) }
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 836f81fb16d..34fafe072a3 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -24,7 +24,7 @@ feature 'Prioritize labels', feature: true do
page.within('.other-labels') do
all('.js-toggle-priority')[1].click
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_content('feature')
end
@@ -43,7 +43,7 @@ feature 'Prioritize labels', feature: true do
expect(page).to have_content('feature')
first('.js-toggle-priority').click
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_content('bug')
end
@@ -59,7 +59,7 @@ feature 'Prioritize labels', feature: true do
page.within('.other-labels') do
first('.js-toggle-priority').click
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_content('bug')
end
@@ -78,7 +78,7 @@ feature 'Prioritize labels', feature: true do
expect(page).to have_content('bug')
first('.js-toggle-priority').click
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_content('bug')
end
@@ -107,7 +107,7 @@ feature 'Prioritize labels', feature: true do
end
refresh
- wait_for_ajax
+ wait_for_requests
page.within('.prioritized-labels') do
expect(first('li')).to have_content('feature')
diff --git a/spec/features/projects/main/rss_spec.rb b/spec/features/projects/main/rss_spec.rb
index b1a3af612a1..53966229a2a 100644
--- a/spec/features/projects/main/rss_spec.rb
+++ b/spec/features/projects/main/rss_spec.rb
@@ -12,7 +12,7 @@ feature 'Project RSS' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
context 'when signed out' do
@@ -20,6 +20,6 @@ feature 'Project RSS' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed without a private token"
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end
diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb
index ab2b089db2e..3d253f01484 100644
--- a/spec/features/projects/members/group_links_spec.rb
+++ b/spec/features/projects/members/group_links_spec.rb
@@ -20,7 +20,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
click_link 'Guest'
end
- wait_for_ajax
+ wait_for_requests
visit namespace_project_settings_members_path(project.namespace, project)
@@ -31,7 +31,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
tomorrow = Date.today + 3
fill_in "member_expires_at_#{group.id}", with: tomorrow.strftime("%F")
- wait_for_ajax
+ wait_for_requests
page.within(find('li.group_member')) do
expect(page).to have_content('Expires in')
@@ -42,7 +42,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
page.within(first('.group_member')) do
find('.btn-remove').click
end
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_selector('.group_member')
end
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index 19d14ad9af4..1e6f15d8258 100644
--- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
@@ -38,7 +38,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
page.within "#project_member_#{new_member.project_members.first.id}" do
find('.js-access-expiration-date').set date.to_s(:medium)
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('Expires in 3 days')
end
end
diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb
index de6a750c932..d428f6fcf22 100644
--- a/spec/features/projects/members/sorting_spec.rb
+++ b/spec/features/projects/members/sorting_spec.rb
@@ -67,7 +67,7 @@ feature 'Projects > Members > Sorting', feature: true do
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
end
- scenario 'sorts by recent sign in' do
+ scenario 'sorts by recent sign in', :redis do
visit_members_list(sort: :recent_sign_in)
expect(first_member).to include(master.name)
@@ -75,7 +75,7 @@ feature 'Projects > Members > Sorting', feature: true do
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
end
- scenario 'sorts by oldest sign in' do
+ scenario 'sorts by oldest sign in', :redis do
visit_members_list(sort: :oldest_sign_in)
expect(first_member).to include(developer.name)
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index c66b9a34b86..b1f9eb15667 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -17,10 +17,10 @@ feature "New project", feature: true do
expect(find_field("project_visibility_level_#{level}")).to be_checked
end
- it 'saves visibility level on validation error' do
+ it "saves visibility level #{level} on validation error" do
visit new_project_path
- choose(key)
+ choose(s_(key))
click_button('Create project')
expect(find_field("project_visibility_level_#{level}")).to be_checked
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 1211b17b3d8..317949d6b56 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -2,10 +2,9 @@ require 'spec_helper'
feature 'Pipeline Schedules', :feature do
include PipelineSchedulesHelper
- include WaitForAjax
let!(:project) { create(:project) }
- let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project ) }
let!(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }
let(:scope) { nil }
let!(:user) { create(:user) }
@@ -32,6 +31,7 @@ feature 'Pipeline Schedules', :feature do
it 'displays the required information description' do
page.within('.pipeline-schedule-table-row') do
expect(page).to have_content('pipeline schedule')
+ expect(page).to have_content(pipeline_schedule.real_next_run.strftime('%b %d, %Y'))
expect(page).to have_link('master')
expect(page).to have_link("##{pipeline.id}")
end
@@ -65,6 +65,17 @@ feature 'Pipeline Schedules', :feature do
expect(page).not_to have_content('pipeline schedule')
end
end
+
+ context 'when ref is nil' do
+ before do
+ pipeline_schedule.update_attribute(:ref, nil)
+ visit_pipelines_schedules
+ end
+
+ it 'shows a list of the pipeline schedules with empty ref column' do
+ expect(first('.branch-name-cell').text).to eq('')
+ end
+ end
end
describe 'POST /projects/pipeline_schedules/new', js: true do
@@ -108,6 +119,19 @@ feature 'Pipeline Schedules', :feature do
expect(page).to have_content('my brand new description')
end
+
+ context 'when ref is nil' do
+ before do
+ pipeline_schedule.update_attribute(:ref, nil)
+ edit_pipeline_schedule
+ end
+
+ it 'shows the pipeline schedule with default ref' do
+ page.within('.git-revision-dropdown-toggle') do
+ expect(first('.dropdown-toggle-text').text).to eq('master')
+ end
+ end
+ end
end
def visit_new_pipeline_schedule
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index cfac54ef259..36a3ddca6ef 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -229,7 +229,6 @@ describe 'Pipeline', :feature, :js do
before { find('.js-retry-button').trigger('click') }
it { expect(page).not_to have_content('Retry') }
- it { expect(page).to have_selector('.retried') }
end
end
@@ -240,7 +239,6 @@ describe 'Pipeline', :feature, :js do
before { click_on 'Cancel running' }
it { expect(page).not_to have_content('Cancel running') }
- it { expect(page).to have_selector('.ci-canceled') }
end
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 8cc96c7b00f..05c2bf350f1 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
describe 'Pipelines', :feature, :js do
- include WaitForVueResource
-
let(:project) { create(:empty_project) }
context 'when user is logged in' do
@@ -22,7 +20,7 @@ describe 'Pipelines', :feature, :js do
project: project,
ref: 'master',
status: 'running',
- sha: project.commit.id,
+ sha: project.commit.id
)
end
@@ -54,7 +52,7 @@ describe 'Pipelines', :feature, :js do
context 'header tabs' do
before do
visit namespace_project_pipelines_path(project.namespace, project)
- wait_for_vue_resource
+ wait_for_requests
end
it 'shows a tab for All pipelines and count' do
@@ -106,7 +104,7 @@ describe 'Pipelines', :feature, :js do
context 'when canceling' do
before do
find('.js-pipelines-cancel-button').click
- wait_for_vue_resource
+ wait_for_requests
end
it 'indicated that pipelines was canceled' do
@@ -136,7 +134,7 @@ describe 'Pipelines', :feature, :js do
context 'when retrying' do
before do
find('.js-pipelines-retry-button').click
- wait_for_vue_resource
+ wait_for_requests
end
it 'shows running pipeline that is not retryable' do
@@ -356,14 +354,14 @@ describe 'Pipelines', :feature, :js do
it 'should render pagination' do
visit namespace_project_pipelines_path(project.namespace, project)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.gl-pagination')
end
it 'should render second page of pipelines' do
visit namespace_project_pipelines_path(project.namespace, project, page: '2')
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.gl-pagination .page', count: 2)
end
@@ -392,7 +390,7 @@ describe 'Pipelines', :feature, :js do
create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3)
visit namespace_project_pipeline_path(project.namespace, project, pipeline)
- wait_for_vue_resource
+ wait_for_requests
end
it 'shows a graph with grouped stages' do
@@ -444,6 +442,8 @@ describe 'Pipelines', :feature, :js do
it 'creates a new pipeline' do
expect { click_on 'Create pipeline' }
.to change { Ci::Pipeline.count }.by(1)
+
+ expect(Ci::Pipeline.last).to be_web
end
end
@@ -507,6 +507,6 @@ describe 'Pipelines', :feature, :js do
def visit_project_pipelines(**query)
visit namespace_project_pipelines_path(project.namespace, project, query)
- wait_for_vue_resource
+ wait_for_requests
end
end
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index 881ad7910dd..04414490571 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -12,12 +12,12 @@ feature 'Ref switcher', feature: true, js: true do
it 'allow user to change ref by enter key' do
click_button 'master'
- wait_for_ajax
+ wait_for_requests
page.within '.project-refs-form' do
input = find('input[type="search"]')
input.set 'binary'
- wait_for_ajax
+ wait_for_requests
expect(find('.dropdown-content ul')).to have_selector('li', count: 6)
@@ -31,7 +31,7 @@ feature 'Ref switcher', feature: true, js: true do
it "user selects ref with special characters" do
click_button 'master'
- wait_for_ajax
+ wait_for_requests
page.within '.project-refs-form' do
page.fill_in 'Search branches and tags', with: "'test'"
diff --git a/spec/features/projects/services/jira_service_spec.rb b/spec/features/projects/services/jira_service_spec.rb
new file mode 100644
index 00000000000..c96d87e5708
--- /dev/null
+++ b/spec/features/projects/services/jira_service_spec.rb
@@ -0,0 +1,92 @@
+require 'spec_helper'
+
+feature 'Setup Jira service', :feature, :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:service) { project.create_jira_service }
+
+ let(:url) { 'http://jira.example.com' }
+ let(:project_url) { 'http://username:password@jira.example.com/rest/api/2/project/GitLabProject' }
+
+ def fill_form(active = true)
+ check 'Active' if active
+
+ fill_in 'service_url', with: url
+ fill_in 'service_project_key', with: 'GitLabProject'
+ fill_in 'service_username', with: 'username'
+ fill_in 'service_password', with: 'password'
+ fill_in 'service_jira_issue_transition_id', with: '25'
+ end
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+
+ visit namespace_project_settings_integrations_path(project.namespace, project)
+ end
+
+ describe 'user sets and activates Jira Service' do
+ context 'when Jira connection test succeeds' do
+ before do
+ WebMock.stub_request(:get, project_url)
+ end
+
+ it 'activates the JIRA service' do
+ click_link('JIRA')
+ fill_form
+ click_button('Test settings and save changes')
+ wait_for_requests
+
+ expect(page).to have_content('JIRA activated.')
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ end
+ end
+
+ context 'when Jira connection test fails' do
+ before do
+ WebMock.stub_request(:get, project_url).to_return(status: 401)
+ end
+
+ it 'shows errors when some required fields are not filled in' do
+ click_link('JIRA')
+
+ check 'Active'
+ fill_in 'service_password', with: 'password'
+ click_button('Test settings and save changes')
+
+ page.within('.service-settings') do
+ expect(page).to have_content('This field is required.')
+ end
+ end
+
+ it 'activates the JIRA service' do
+ click_link('JIRA')
+ fill_form
+ click_button('Test settings and save changes')
+ wait_for_requests
+
+ expect(find('.flash-container-page')).to have_content 'Test failed.'
+ expect(find('.flash-container-page')).to have_content 'Save anyway'
+
+ find('.flash-alert .flash-action').trigger('click')
+ wait_for_requests
+
+ expect(page).to have_content('JIRA activated.')
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ end
+ end
+ end
+
+ describe 'user sets Jira Service but keeps it disabled' do
+ context 'when Jira connection test succeeds' do
+ it 'activates the JIRA service' do
+ click_link('JIRA')
+ fill_form(false)
+ click_button('Save changes')
+
+ expect(page).to have_content('JIRA settings saved, but not activated.')
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb
index dc3854262e7..1fe82222e59 100644
--- a/spec/features/projects/services/mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/mattermost_slash_command_spec.rb
@@ -24,15 +24,25 @@ feature 'Setup Mattermost slash commands', :feature, :js do
expect(token_placeholder).to eq('XXxxXXxxXXxxXXxxXXxxXXxx')
end
- it 'shows the token after saving' do
+ it 'redirects to the integrations page after saving but not activating' do
token = ('a'..'z').to_a.join
fill_in 'service_token', with: token
- click_on 'Save'
+ click_on 'Save changes'
- value = find_field('service_token').value
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(page).to have_content('Mattermost slash commands settings saved, but not activated.')
+ end
+
+ it 'redirects to the integrations page after activating' do
+ token = ('a'..'z').to_a.join
+
+ fill_in 'service_token', with: token
+ check 'service_active'
+ click_on 'Save changes'
- expect(value).to eq(token)
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(page).to have_content('Mattermost slash commands activated.')
end
it 'shows the add to mattermost button' do
diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb
index db903a0c8f0..f53b820c460 100644
--- a/spec/features/projects/services/slack_slash_command_spec.rb
+++ b/spec/features/projects/services/slack_slash_command_spec.rb
@@ -21,13 +21,21 @@ feature 'Slack slash commands', feature: true do
expect(page).to have_content('This service allows users to perform common')
end
- it 'shows the token after saving' do
+ it 'redirects to the integrations page after saving but not activating' do
fill_in 'service_token', with: 'token'
click_on 'Save'
- value = find_field('service_token').value
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(page).to have_content('Slack slash commands settings saved, but not activated.')
+ end
+
+ it 'redirects to the integrations page after activating' do
+ fill_in 'service_token', with: 'token'
+ check 'service_active'
+ click_on 'Save'
- expect(value).to eq('token')
+ expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(page).to have_content('Slack slash commands activated.')
end
it 'shows the correct trigger url' do
diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb
index 7909234556e..fbaea14a2be 100644
--- a/spec/features/projects/settings/integration_settings_spec.rb
+++ b/spec/features/projects/settings/integration_settings_spec.rb
@@ -52,6 +52,7 @@ feature 'Integration settings', feature: true do
fill_in 'hook_url', with: url
check 'Tag push events'
check 'Enable SSL verification'
+ check 'Job events'
click_button 'Add webhook'
@@ -59,6 +60,7 @@ feature 'Integration settings', feature: true do
expect(page).to have_content('SSL Verification: enabled')
expect(page).to have_content('Push Events')
expect(page).to have_content('Tag Push Events')
+ expect(page).to have_content('Job events')
end
scenario 'edit existing webhook' do
@@ -83,11 +85,55 @@ feature 'Integration settings', feature: true do
expect(current_path).to eq(integrations_path)
end
- scenario 'remove existing webhook' do
- hook
- visit integrations_path
+ context 'remove existing webhook' do
+ scenario 'from webhooks list page' do
+ hook
+ visit integrations_path
+
+ expect { click_link 'Remove' }.to change(ProjectHook, :count).by(-1)
+ end
+
+ scenario 'from webhook edit page' do
+ hook
+ visit integrations_path
+ click_link 'Edit'
+
+ expect { click_link 'Remove' }.to change(ProjectHook, :count).by(-1)
+ end
+ end
+ end
+
+ context 'Webhook logs' do
+ let(:hook) { create(:project_hook, project: project) }
+ let(:hook_log) { create(:web_hook_log, web_hook: hook, internal_error_message: 'some error') }
+
+ scenario 'show list of hook logs' do
+ hook_log
+ visit edit_namespace_project_hook_path(project.namespace, project, hook)
+
+ expect(page).to have_content('Recent Deliveries')
+ expect(page).to have_content(hook_log.url)
+ end
+
+ scenario 'show hook log details' do
+ hook_log
+ visit edit_namespace_project_hook_path(project.namespace, project, hook)
+ click_link 'View details'
+
+ expect(page).to have_content("POST #{hook_log.url}")
+ expect(page).to have_content(hook_log.internal_error_message)
+ expect(page).to have_content('Resend Request')
+ end
+
+ scenario 'retry hook log' do
+ WebMock.stub_request(:post, hook.url)
+
+ hook_log
+ visit edit_namespace_project_hook_path(project.namespace, project, hook)
+ click_link 'View details'
+ click_link 'Resend Request'
- expect { click_link 'Remove' }.to change(ProjectHook, :count).by(-1)
+ expect(current_path).to eq(edit_namespace_project_hook_path(project.namespace, project, hook))
end
end
end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
new file mode 100644
index 00000000000..4cc38c5286e
--- /dev/null
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -0,0 +1,78 @@
+require 'spec_helper'
+
+feature 'Repository settings', feature: true do
+ let(:project) { create(:project_empty_repo) }
+ let(:user) { create(:user) }
+ let(:role) { :developer }
+
+ background do
+ project.team << [user, role]
+ login_as(user)
+ end
+
+ context 'for developer' do
+ given(:role) { :developer }
+
+ scenario 'is not allowed to view' do
+ visit namespace_project_settings_repository_path(project.namespace, project)
+
+ expect(page.status_code).to eq(404)
+ end
+ end
+
+ context 'for master' do
+ given(:role) { :master }
+
+ context 'Deploy Keys', js: true do
+ let(:private_deploy_key) { create(:deploy_key, title: 'private_deploy_key', public: false) }
+ let(:public_deploy_key) { create(:another_deploy_key, title: 'public_deploy_key', public: true) }
+ let(:new_ssh_key) { attributes_for(:key)[:key] }
+
+ scenario 'get list of keys' do
+ project.deploy_keys << private_deploy_key
+ project.deploy_keys << public_deploy_key
+
+ visit namespace_project_settings_repository_path(project.namespace, project)
+
+ expect(page.status_code).to eq(200)
+ expect(page).to have_content('private_deploy_key')
+ expect(page).to have_content('public_deploy_key')
+ end
+
+ scenario 'add a new deploy key' do
+ visit namespace_project_settings_repository_path(project.namespace, project)
+
+ fill_in 'deploy_key_title', with: 'new_deploy_key'
+ fill_in 'deploy_key_key', with: new_ssh_key
+ check 'deploy_key_can_push'
+ click_button 'Add key'
+
+ expect(page).to have_content('new_deploy_key')
+ expect(page).to have_content('Write access allowed')
+ end
+
+ scenario 'edit an existing deploy key' do
+ project.deploy_keys << private_deploy_key
+ visit namespace_project_settings_repository_path(project.namespace, project)
+
+ 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
+ project.deploy_keys << private_deploy_key
+ visit namespace_project_settings_repository_path(project.namespace, project)
+
+ find('li', text: private_deploy_key.title).click_button('Remove')
+
+ expect(page).not_to have_content(private_deploy_key.title)
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb
index cef315ac9cd..fac4506bdf6 100644
--- a/spec/features/projects/settings/visibility_settings_spec.rb
+++ b/spec/features/projects/settings/visibility_settings_spec.rb
@@ -14,7 +14,7 @@ feature 'Visibility settings', feature: true, js: true do
visibility_select_container = find('.js-visibility-select')
expect(visibility_select_container.find('.visibility-select').value).to eq project.visibility_level.to_s
- expect(visibility_select_container).to have_content 'The project can be cloned without any authentication.'
+ expect(visibility_select_container).to have_content 'The project can be accessed without any authentication.'
end
scenario 'project visibility description updates on change' do
@@ -41,7 +41,7 @@ feature 'Visibility settings', feature: true, js: true do
expect(visibility_select_container).not_to have_select '.visibility-select'
expect(visibility_select_container).to have_content 'Public'
- expect(visibility_select_container).to have_content 'The project can be cloned without any authentication.'
+ expect(visibility_select_container).to have_content 'The project can be accessed without any authentication.'
end
end
end
diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb
new file mode 100644
index 00000000000..5ac1ca45c74
--- /dev/null
+++ b/spec/features/projects/snippets/create_snippet_spec.rb
@@ -0,0 +1,86 @@
+require 'rails_helper'
+
+feature 'Create Snippet', :js, feature: true do
+ include DropzoneHelper
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, :public) }
+
+ def fill_form
+ fill_in 'project_snippet_title', with: 'My Snippet Title'
+ fill_in 'project_snippet_description', with: 'My Snippet **Description**'
+ page.within('.file-editor') do
+ find('.ace_editor').native.send_keys('Hello World!')
+ end
+ end
+
+ context 'when a user is authenticated' do
+ before do
+ project.team << [user, :master]
+ login_as(user)
+
+ visit namespace_project_snippets_path(project.namespace, project)
+
+ click_on('New snippet')
+ end
+
+ it 'creates a new snippet' do
+ fill_form
+ click_button('Create snippet')
+ wait_for_requests
+
+ expect(page).to have_content('My Snippet Title')
+ expect(page).to have_content('Hello World!')
+ page.within('.snippet-header .description') do
+ expect(page).to have_content('My Snippet Description')
+ expect(page).to have_selector('strong')
+ end
+ end
+
+ it 'uploads a file when dragging into textarea' do
+ fill_form
+ dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
+
+ expect(page.find_field("project_snippet_description").value).to have_content('banana_sample')
+
+ click_button('Create snippet')
+ wait_for_requests
+
+ link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
+ expect(link).to match(%r{/#{Regexp.escape(project.full_path) }/uploads/\h{32}/banana_sample\.gif\z})
+ end
+
+ it 'creates a snippet when all reuiqred fields are filled in after validation failing' do
+ fill_in 'project_snippet_title', with: 'My Snippet Title'
+ click_button('Create snippet')
+
+ expect(page).to have_selector('#error_explanation')
+
+ fill_form
+ dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
+
+ click_button('Create snippet')
+ wait_for_requests
+
+ expect(page).to have_content('My Snippet Title')
+ expect(page).to have_content('Hello World!')
+ page.within('.snippet-header .description') do
+ expect(page).to have_content('My Snippet Description')
+ expect(page).to have_selector('strong')
+ end
+ link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
+ expect(link).to match(%r{/#{Regexp.escape(project.full_path) }/uploads/\h{32}/banana_sample\.gif\z})
+ end
+ end
+
+ context 'when a user is not authenticated' do
+ it 'shows a public snippet on the index page but not the New snippet button' do
+ snippet = create(:project_snippet, :public, project: project)
+
+ visit namespace_project_snippets_path(project.namespace, project)
+
+ expect(page).to have_content(snippet.title)
+ expect(page).not_to have_content('New snippet')
+ end
+ end
+end
diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb
index cedf3778c7e..b844e60e5d5 100644
--- a/spec/features/projects/snippets/show_spec.rb
+++ b/spec/features/projects/snippets/show_spec.rb
@@ -17,7 +17,7 @@ feature 'Project snippet', :js, feature: true do
before do
visit namespace_project_snippet_path(project.namespace, project, snippet)
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob' do
@@ -48,7 +48,7 @@ feature 'Project snippet', :js, feature: true do
before do
visit namespace_project_snippet_path(project.namespace, project, snippet)
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the rich viewer' do
@@ -78,7 +78,7 @@ feature 'Project snippet', :js, feature: true do
before do
find('.js-blob-viewer-switch-btn[data-viewer=simple]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the simple viewer' do
@@ -99,7 +99,7 @@ feature 'Project snippet', :js, feature: true do
before do
find('.js-blob-viewer-switch-btn[data-viewer=rich]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the rich viewer' do
@@ -120,7 +120,7 @@ feature 'Project snippet', :js, feature: true do
before do
visit namespace_project_snippet_path(project.namespace, project, snippet, anchor: 'L1')
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the simple viewer' do
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
new file mode 100644
index 00000000000..e88907b8016
--- /dev/null
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe 'Subgroup Issuables', :feature, :js, :nested_groups do
+ let!(:group) { create(:group, name: 'group') }
+ let!(:subgroup) { create(:group, parent: group, name: 'subgroup') }
+ let!(:project) { create(:empty_project, namespace: subgroup, name: 'project') }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ login_as user
+ end
+
+ it 'shows the full subgroup title when issues index page is empty' do
+ visit namespace_project_issues_path(project.namespace.to_param, project.to_param)
+
+ expect_to_have_full_subgroup_title
+ end
+
+ it 'shows the full subgroup title when merge requests index page is empty' do
+ visit namespace_project_merge_requests_path(project.namespace.to_param, project.to_param)
+
+ expect_to_have_full_subgroup_title
+ end
+
+ def expect_to_have_full_subgroup_title
+ title = find('.title-container')
+
+ expect(title).not_to have_selector '.initializing'
+ expect(title).to have_content 'group / subgroup / project'
+ end
+end
diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb
index 9ac51997d65..9bf59c4139c 100644
--- a/spec/features/projects/tree/rss_spec.rb
+++ b/spec/features/projects/tree/rss_spec.rb
@@ -12,7 +12,7 @@ feature 'Project Tree RSS' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
context 'when signed out' do
@@ -20,6 +20,6 @@ feature 'Project Tree RSS' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed without a private token"
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end
diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb
index b7a41ca54e6..640f1376548 100644
--- a/spec/features/projects/view_on_env_spec.rb
+++ b/spec/features/projects/view_on_env_spec.rb
@@ -54,7 +54,7 @@ describe 'View on environment', js: true do
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
- wait_for_ajax
+ wait_for_requests
end
it 'has a "View on env" button' do
@@ -70,7 +70,7 @@ describe 'View on environment', js: true do
visit namespace_project_compare_path(project.namespace, project, from: 'master', to: branch_name)
- wait_for_ajax
+ wait_for_requests
end
it 'has a "View on env" button' do
@@ -84,7 +84,7 @@ describe 'View on environment', js: true do
visit namespace_project_compare_path(project.namespace, project, from: 'master', to: sha)
- wait_for_ajax
+ wait_for_requests
end
it 'has a "View on env" button' do
@@ -98,7 +98,7 @@ describe 'View on environment', js: true do
visit namespace_project_blob_path(project.namespace, project, File.join(branch_name, file_path))
- wait_for_ajax
+ wait_for_requests
end
it 'has a "View on env" button' do
@@ -112,7 +112,7 @@ describe 'View on environment', js: true do
visit namespace_project_blob_path(project.namespace, project, File.join(sha, file_path))
- wait_for_ajax
+ wait_for_requests
end
it 'has a "View on env" button' do
@@ -126,7 +126,7 @@ describe 'View on environment', js: true do
visit namespace_project_commit_path(project.namespace, project, sha)
- wait_for_ajax
+ wait_for_requests
end
it 'has a "View on env" button' do
diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb
index 43d8b45669e..49d7ef09e64 100644
--- a/spec/features/projects/wiki/markdown_preview_spec.rb
+++ b/spec/features/projects/wiki/markdown_preview_spec.rb
@@ -17,14 +17,14 @@ feature 'Projects > Wiki > User previews markdown changes', feature: true, js: t
login_as(user)
visit namespace_project_path(project.namespace, project)
- click_link 'Wiki'
+ find('.shortcuts-wiki').trigger('click')
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
end
context "while creating a new wiki page" do
context "when there are no spaces or hyphens in the page name" do
it "rewrites relative links as expected" do
- click_link 'New page'
+ find('.add-new-wiki').trigger('click')
page.within '#modal-new-wiki' do
fill_in :new_wiki_path, with: 'a/b/c/d'
click_button 'Create page'
@@ -73,7 +73,7 @@ feature 'Projects > Wiki > User previews markdown changes', feature: true, js: t
fill_in :new_wiki_path, with: 'a-page/b-page/c-page/d-page'
click_button 'Create page'
end
-
+
page.within '.wiki-form' do
fill_in :wiki_content, with: wiki_content
click_on "Preview"
@@ -91,7 +91,7 @@ feature 'Projects > Wiki > User previews markdown changes', feature: true, js: t
context "while editing a wiki page" do
def create_wiki_page(path)
- click_link 'New page'
+ find('.add-new-wiki').trigger('click')
page.within '#modal-new-wiki' do
fill_in :new_wiki_path, with: path
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index 1ffac8cd542..8912d575878 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projects > Wiki > User creates wiki page', feature: true do
+feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
let(:user) { create(:user) }
background do
@@ -8,7 +8,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
login_as(user)
visit namespace_project_path(project.namespace, project)
- click_link 'Wiki'
+ find('.shortcuts-wiki').trigger('click')
end
context 'in the user namespace' do
@@ -28,6 +28,40 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
+
+ scenario 'creates ASCII wiki with LaTeX blocks' do
+ stub_application_setting(plantuml_url: 'http://localhost', plantuml_enabled: true)
+
+ ascii_content = <<~MD
+ :stem: latexmath
+
+ [stem]
+ ++++
+ \sqrt{4} = 2
+ ++++
+
+ another part
+
+ [latexmath]
+ ++++
+ \beta_x \gamma
+ ++++
+
+ stem:[2+2] is 4
+ MD
+
+ find('#wiki_format option[value=asciidoc]').select_option
+ fill_in :wiki_content, with: ascii_content
+
+ page.within '.wiki-form' do
+ click_button 'Create page'
+ end
+
+ page.within '.wiki' do
+ expect(page).to have_selector('.katex', count: 3)
+ expect(page).to have_content('2+2 is 4')
+ end
+ end
end
context 'when wiki is not empty' do
diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
index 6825b95c8aa..95826e7e5be 100644
--- a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
@@ -21,6 +21,6 @@ describe 'Projects > Wiki > User views Git access wiki page', :feature do
click_link 'Clone repository'
expect(page).to have_text("Clone repository #{project.wiki.path_with_namespace}")
- expect(page).to have_text(project.wiki.http_url_to_repo(user))
+ expect(page).to have_text(project.wiki.http_url_to_repo)
end
end
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index fc9b293c393..667895bffa5 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -1,7 +1,6 @@
require 'spec_helper'
-Dir["./spec/features/protected_branches/*.rb"].sort.each { |f| require f }
-feature 'Projected Branches', feature: true, js: true do
+feature 'Protected Branches', feature: true, js: true do
let(:user) { create(:user, :admin) }
let(:project) { create(:project, :repository) }
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index e68448467b0..66236dbc7fc 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -1,5 +1,4 @@
require 'spec_helper'
-Dir["./spec/features/protected_tags/*.rb"].sort.each { |f| require f }
feature 'Projected Tags', feature: true, js: true do
let(:user) { create(:user, :admin) }
diff --git a/spec/features/reportable_note/commit_spec.rb b/spec/features/reportable_note/commit_spec.rb
new file mode 100644
index 00000000000..39b1c4acf52
--- /dev/null
+++ b/spec/features/reportable_note/commit_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe 'Reportable note on commit', :feature, :js do
+ include RepoHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ project.add_master(user)
+ login_as user
+ end
+
+ context 'a normal note' do
+ let!(:note) { create(:note_on_commit, commit_id: sample_commit.id, project: project) }
+
+ before do
+ visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+ end
+
+ it_behaves_like 'reportable note'
+ end
+
+ context 'a diff note' do
+ let!(:note) { create(:diff_note_on_commit, commit_id: sample_commit.id, project: project) }
+
+ before do
+ visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+ end
+
+ it_behaves_like 'reportable note'
+ end
+end
diff --git a/spec/features/reportable_note/issue_spec.rb b/spec/features/reportable_note/issue_spec.rb
new file mode 100644
index 00000000000..5f526818994
--- /dev/null
+++ b/spec/features/reportable_note/issue_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe 'Reportable note on issue', :feature, :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:issue) { create(:issue, project: project) }
+ let!(:note) { create(:note_on_issue, noteable: issue, project: project) }
+
+ before do
+ project.add_master(user)
+ login_as user
+
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it_behaves_like 'reportable note'
+end
diff --git a/spec/features/reportable_note/merge_request_spec.rb b/spec/features/reportable_note/merge_request_spec.rb
new file mode 100644
index 00000000000..6d053d26626
--- /dev/null
+++ b/spec/features/reportable_note/merge_request_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe 'Reportable note on merge request', :feature, :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ before do
+ project.add_master(user)
+ login_as user
+
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ context 'a normal note' do
+ let!(:note) { create(:note_on_merge_request, noteable: merge_request, project: project) }
+
+ it_behaves_like 'reportable note'
+ end
+
+ context 'a diff note' do
+ let!(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
+
+ it_behaves_like 'reportable note'
+ end
+end
diff --git a/spec/features/reportable_note/snippets_spec.rb b/spec/features/reportable_note/snippets_spec.rb
new file mode 100644
index 00000000000..3f1e0cf9097
--- /dev/null
+++ b/spec/features/reportable_note/snippets_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe 'Reportable note on snippets', :feature, :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+
+ before do
+ project.add_master(user)
+ login_as user
+ end
+
+ describe 'on project snippet' do
+ let(:snippet) { create(:project_snippet, :public, project: project, author: user) }
+ let!(:note) { create(:note_on_project_snippet, noteable: snippet, project: project) }
+
+ before do
+ visit namespace_project_snippet_path(project.namespace, project, snippet)
+ end
+
+ it_behaves_like 'reportable note'
+ end
+
+ describe 'on personal snippet' do
+ let(:snippet) { create(:personal_snippet, :public, author: user) }
+ let!(:note) { create(:note_on_personal_snippet, noteable: snippet, author: user) }
+
+ before do
+ visit snippet_path(snippet)
+ end
+
+ it_behaves_like 'reportable note'
+ end
+end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 498a4a5cba0..7834807b1f1 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -20,14 +20,15 @@ describe "Search", feature: true do
context 'search filters', js: true do
let(:group) { create(:group) }
+ let!(:group_project) { create(:empty_project, group: group) }
before do
group.add_owner(user)
end
it 'shows group name after filtering' do
- find('.js-search-group-dropdown').click
- wait_for_ajax
+ find('.js-search-group-dropdown').trigger('click')
+ wait_for_requests
page.within '.search-holder' do
click_link group.name
@@ -36,10 +37,28 @@ describe "Search", feature: true do
expect(find('.js-search-group-dropdown')).to have_content(group.name)
end
+ it 'filters by group projects after filtering by group' do
+ find('.js-search-group-dropdown').trigger('click')
+ wait_for_requests
+
+ page.within '.search-holder' do
+ click_link group.name
+ end
+
+ expect(find('.js-search-group-dropdown')).to have_content(group.name)
+
+ page.within('.project-filter') do
+ find('.js-search-project-dropdown').trigger('click')
+ wait_for_requests
+
+ expect(page).to have_link(group_project.name_with_namespace)
+ end
+ end
+
it 'shows project name after filtering' do
page.within('.project-filter') do
- find('.js-search-project-dropdown').click
- wait_for_ajax
+ find('.js-search-project-dropdown').trigger('click')
+ wait_for_requests
click_link project.name_with_namespace
end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 78a76d9c112..2a2655bbdb5 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -334,7 +334,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/builds" do
- subject { namespace_project_builds_path(project.namespace, project) }
+ subject { namespace_project_jobs_path(project.namespace, project) }
context "when allowed for public and internal" do
before { project.update(public_builds: true) }
@@ -368,7 +368,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { namespace_project_build_path(project.namespace, project, build.id) }
+ subject { namespace_project_job_path(project.namespace, project, build.id) }
context "when allowed for public and internal" do
before { project.update(public_builds: true) }
@@ -402,7 +402,7 @@ describe "Internal Project Access", feature: true do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { trace_namespace_project_build_path(project.namespace, project, build.id) }
+ subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
context 'when allowed for public and internal' do
before do
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index a66f6e09055..b676c236758 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -330,7 +330,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/builds" do
- subject { namespace_project_builds_path(project.namespace, project) }
+ subject { namespace_project_jobs_path(project.namespace, project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -358,7 +358,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { namespace_project_build_path(project.namespace, project, build.id) }
+ subject { namespace_project_job_path(project.namespace, project, build.id) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -391,7 +391,7 @@ describe "Private Project Access", feature: true do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { trace_namespace_project_build_path(project.namespace, project, build.id) }
+ subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 5cd575500c3..35d5163941e 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -154,7 +154,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/builds" do
- subject { namespace_project_builds_path(project.namespace, project) }
+ subject { namespace_project_jobs_path(project.namespace, project) }
context "when allowed for public" do
before { project.update(public_builds: true) }
@@ -188,7 +188,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { namespace_project_build_path(project.namespace, project, build.id) }
+ subject { namespace_project_job_path(project.namespace, project, build.id) }
context "when allowed for public" do
before { project.update(public_builds: true) }
@@ -222,7 +222,7 @@ describe "Public Project Access", feature: true do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { trace_namespace_project_build_path(project.namespace, project, build.id) }
+ subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
context 'when allowed for public' do
before do
diff --git a/spec/features/snippets/create_snippet_spec.rb b/spec/features/snippets/create_snippet_spec.rb
index 9409c323288..ddd31ede064 100644
--- a/spec/features/snippets/create_snippet_spec.rb
+++ b/spec/features/snippets/create_snippet_spec.rb
@@ -1,24 +1,93 @@
require 'rails_helper'
feature 'Create Snippet', :js, feature: true do
+ include DropzoneHelper
+
before do
login_as :user
visit new_snippet_path
end
- scenario 'Authenticated user creates a snippet' do
+ def fill_form
fill_in 'personal_snippet_title', with: 'My Snippet Title'
+ fill_in 'personal_snippet_description', with: 'My Snippet **Description**'
page.within('.file-editor') do
find('.ace_editor').native.send_keys 'Hello World!'
end
+ end
- click_button 'Create snippet'
- wait_for_ajax
+ scenario 'Authenticated user creates a snippet' do
+ fill_form
+
+ click_button('Create snippet')
+ wait_for_requests
expect(page).to have_content('My Snippet Title')
+ page.within('.snippet-header .description') do
+ expect(page).to have_content('My Snippet Description')
+ expect(page).to have_selector('strong')
+ end
expect(page).to have_content('Hello World!')
end
+ scenario 'previews a snippet with file' do
+ fill_in 'personal_snippet_description', with: 'My Snippet'
+ dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
+ find('.js-md-preview-button').click
+
+ page.within('#new_personal_snippet .md-preview') do
+ expect(page).to have_content('My Snippet')
+
+ link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
+ expect(link).to match(%r{/uploads/temp/\h{32}/banana_sample\.gif\z})
+
+ visit(link)
+ expect(page.status_code).to eq(200)
+ end
+ end
+
+ scenario 'uploads a file when dragging into textarea' do
+ fill_form
+
+ dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
+
+ expect(page.find_field("personal_snippet_description").value).to have_content('banana_sample')
+
+ click_button('Create snippet')
+ wait_for_requests
+
+ link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
+ expect(link).to match(%r{/uploads/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
+
+ visit(link)
+ expect(page.status_code).to eq(200)
+ end
+
+ scenario 'validation fails for the first time' do
+ fill_in 'personal_snippet_title', with: 'My Snippet Title'
+ click_button('Create snippet')
+
+ expect(page).to have_selector('#error_explanation')
+
+ fill_form
+ dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
+
+ click_button('Create snippet')
+ wait_for_requests
+
+ expect(page).to have_content('My Snippet Title')
+ page.within('.snippet-header .description') do
+ expect(page).to have_content('My Snippet Description')
+ expect(page).to have_selector('strong')
+ end
+ expect(page).to have_content('Hello World!')
+ link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
+ expect(link).to match(%r{/uploads/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
+
+ visit(link)
+ expect(page.status_code).to eq(200)
+ end
+
scenario 'Authenticated user creates a snippet with + in filename' do
fill_in 'personal_snippet_title', with: 'My Snippet Title'
page.within('.file-editor') do
@@ -27,7 +96,7 @@ feature 'Create Snippet', :js, feature: true do
end
click_button 'Create snippet'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('My Snippet Title')
expect(page).to have_content('snippet+file+name')
diff --git a/spec/features/snippets/edit_snippet_spec.rb b/spec/features/snippets/edit_snippet_spec.rb
new file mode 100644
index 00000000000..89ae593db88
--- /dev/null
+++ b/spec/features/snippets/edit_snippet_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+feature 'Edit Snippet', :js, feature: true do
+ include DropzoneHelper
+
+ let(:file_name) { 'test.rb' }
+ let(:content) { 'puts "test"' }
+
+ let(:user) { create(:user) }
+ let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, author: user) }
+
+ before do
+ login_as(user)
+
+ visit edit_snippet_path(snippet)
+ wait_for_requests
+ end
+
+ it 'updates the snippet' do
+ fill_in 'personal_snippet_title', with: 'New Snippet Title'
+
+ click_button('Save changes')
+ wait_for_requests
+
+ expect(page).to have_content('New Snippet Title')
+ end
+
+ it 'updates the snippet with files attached' do
+ dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
+ expect(page.find_field("personal_snippet_description").value).to have_content('banana_sample')
+
+ click_button('Save changes')
+ wait_for_requests
+
+ link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
+ expect(link).to match(%r{/uploads/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z})
+ end
+end
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index 698eb46573f..44b0c89fac7 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'Comments on personal snippets', :js, feature: true do
+ include NoteInteractionHelpers
+
let!(:user) { create(:user) }
let!(:snippet) { create(:personal_snippet, :public) }
let!(:snippet_notes) do
@@ -22,6 +24,8 @@ describe 'Comments on personal snippets', :js, feature: true do
it 'contains notes for a snippet with correct action icons' do
expect(page).to have_selector('#notes-list li', count: 2)
+ open_more_actions_dropdown(snippet_notes[0])
+
# comment authored by current user
page.within("#notes-list li#note_#{snippet_notes[0].id}") do
expect(page).to have_content(snippet_notes[0].note)
@@ -29,6 +33,8 @@ describe 'Comments on personal snippets', :js, feature: true do
expect(page).to have_selector('.note-emoji-button')
end
+ open_more_actions_dropdown(snippet_notes[1])
+
page.within("#notes-list li#note_#{snippet_notes[1].id}") do
expect(page).to have_content(snippet_notes[1].note)
expect(page).not_to have_selector('.js-note-delete')
@@ -68,6 +74,8 @@ describe 'Comments on personal snippets', :js, feature: true do
context 'when editing a note' do
it 'changes the text' do
+ open_more_actions_dropdown(snippet_notes[0])
+
page.within("#notes-list li#note_#{snippet_notes[0].id}") do
click_on 'Edit comment'
end
@@ -89,11 +97,13 @@ describe 'Comments on personal snippets', :js, feature: true do
context 'when deleting a note' do
it 'removes the note from the snippet detail page' do
+ open_more_actions_dropdown(snippet_notes[0])
+
page.within("#notes-list li#note_#{snippet_notes[0].id}") do
- click_on 'Remove comment'
+ click_on 'Delete comment'
end
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_selector("#notes-list li#note_#{snippet_notes[0].id}")
end
diff --git a/spec/features/snippets/public_snippets_spec.rb b/spec/features/snippets/public_snippets_spec.rb
index 2df483818c3..afd945a8555 100644
--- a/spec/features/snippets/public_snippets_spec.rb
+++ b/spec/features/snippets/public_snippets_spec.rb
@@ -5,7 +5,7 @@ feature 'Public Snippets', :js, feature: true do
public_snippet = create(:personal_snippet, :public)
visit snippet_path(public_snippet)
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content(public_snippet.content)
end
diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb
index e36cf547f80..95fc1d2bb62 100644
--- a/spec/features/snippets/show_spec.rb
+++ b/spec/features/snippets/show_spec.rb
@@ -11,7 +11,7 @@ feature 'Snippet', :js, feature: true do
before do
visit snippet_path(snippet)
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob' do
@@ -42,7 +42,7 @@ feature 'Snippet', :js, feature: true do
before do
visit snippet_path(snippet)
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the rich viewer' do
@@ -72,7 +72,7 @@ feature 'Snippet', :js, feature: true do
before do
find('.js-blob-viewer-switch-btn[data-viewer=simple]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the simple viewer' do
@@ -93,7 +93,7 @@ feature 'Snippet', :js, feature: true do
before do
find('.js-blob-viewer-switch-btn[data-viewer=rich]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the rich viewer' do
@@ -114,7 +114,7 @@ feature 'Snippet', :js, feature: true do
before do
visit snippet_path(snippet, anchor: 'L1')
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the simple viewer' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 8bd13caf2b0..563e65d3cc5 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -64,13 +64,11 @@ feature 'Task Lists', feature: true do
describe 'for Issues', feature: true do
describe 'multiple tasks', js: true do
- include WaitForVueResource
-
let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
it 'renders' do
visit_issue(project, issue)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 6)
@@ -79,7 +77,7 @@ feature 'Task Lists', feature: true do
it 'contains the required selectors' do
visit_issue(project, issue)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector('a.btn-close')
@@ -87,14 +85,14 @@ feature 'Task Lists', feature: true do
it 'is only editable by author' do
visit_issue(project, issue)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
logout(:user)
login_as(user2)
visit current_path
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
end
@@ -106,13 +104,11 @@ feature 'Task Lists', feature: true do
end
describe 'single incomplete task', js: true do
- include WaitForVueResource
-
let!(:issue) { create(:issue, description: singleIncompleteMarkdown, author: user, project: project) }
it 'renders' do
visit_issue(project, issue)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
@@ -127,12 +123,11 @@ feature 'Task Lists', feature: true do
end
describe 'single complete task', js: true do
- include WaitForVueResource
let!(:issue) { create(:issue, description: singleCompleteMarkdown, author: user, project: project) }
it 'renders' do
visit_issue(project, issue)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb
index f32e70c2c3f..bbfa4e08379 100644
--- a/spec/features/todos/todos_filtering_spec.rb
+++ b/spec/features/todos/todos_filtering_spec.rb
@@ -28,7 +28,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
click_link project_1.name_with_namespace
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content project_1.name_with_namespace
expect(page).not_to have_content project_2.name_with_namespace
@@ -43,7 +43,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
click_link user_1.name
end
- wait_for_ajax
+ wait_for_requests
expect(find('.todos-list')).to have_content 'merge request'
expect(find('.todos-list')).not_to have_content 'issue'
@@ -90,7 +90,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
click_link 'Issue'
end
- wait_for_ajax
+ wait_for_requests
expect(find('.todos-list')).to have_content issue.to_reference
expect(find('.todos-list')).not_to have_content merge_request.to_reference
@@ -132,7 +132,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
click_link name
end
- wait_for_ajax
+ wait_for_requests
end
def expect_to_see_action(action_name)
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index be5b3af417f..bb4b2aed0e3 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -64,7 +64,7 @@ describe 'Dashboard Todos', feature: true do
before do
within first('.todo') do
click_link 'Done'
- wait_for_ajax
+ wait_for_requests
click_link 'Undo'
end
end
@@ -251,7 +251,7 @@ describe 'Dashboard Todos', feature: true do
describe 'mark all as done', js: true do
before do
visit dashboard_todos_path
- click_link 'Mark all as done'
+ find('.js-todos-mark-all').trigger('click')
end
it 'shows "All done" message!' do
@@ -308,10 +308,10 @@ describe 'Dashboard Todos', feature: true do
end
def mark_all_and_undo
- click_link 'Mark all as done'
- wait_for_ajax
- click_link 'Undo mark all as done'
- wait_for_ajax
+ find('.js-todos-mark-all').trigger('click')
+ wait_for_requests
+ find('.js-todos-undo-all').trigger('click')
+ wait_for_requests
end
end
end
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index 544d2dcb87f..2fed8067042 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -6,7 +6,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
def manage_two_factor_authentication
click_on 'Manage two-factor authentication'
expect(page).to have_content("Setup new U2F device")
- wait_for_ajax
+ wait_for_requests
end
def register_u2f_device(u2f_device = nil, name: 'My device')
diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb
index a23c4ca2b92..8509551ce4a 100644
--- a/spec/features/unsubscribe_links_spec.rb
+++ b/spec/features/unsubscribe_links_spec.rb
@@ -24,8 +24,8 @@ describe 'Unsubscribe links', feature: true do
visit body_link
expect(current_path).to eq unsubscribe_sent_notification_path(SentNotification.last)
- expect(page).to have_text(%(Unsubscribe from issue #{issue.title} (#{issue.to_reference})))
- expect(page).to have_text(%(Are you sure you want to unsubscribe from issue #{issue.title} (#{issue.to_reference})?))
+ expect(page).to have_text(%(Unsubscribe from issue))
+ expect(page).to have_text(%(Are you sure you want to unsubscribe from the issue: #{issue.title} (#{issue.to_reference})?))
expect(issue.subscribed?(recipient, project)).to be_truthy
click_link 'Unsubscribe'
diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb
index 0c160dd74b4..9332d3b88d2 100644
--- a/spec/features/uploads/user_uploads_file_to_note_spec.rb
+++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb
@@ -5,18 +5,78 @@ feature 'User uploads file to note', feature: true do
let(:user) { create(:user) }
let(:project) { create(:empty_project, creator: user, namespace: user.namespace) }
+ let(:issue) { create(:issue, project: project, author: user) }
- scenario 'they see the attached file', js: true do
- issue = create(:issue, project: project, author: user)
-
+ before do
login_as(user)
visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ context 'before uploading' do
+ it 'shows "Attach a file" button', js: true do
+ expect(page).to have_button('Attach a file')
+ expect(page).not_to have_selector('.uploading-progress-container', visible: true)
+ end
+ end
+
+ context 'uploading is in progress' do
+ it 'shows "Cancel" button on uploading', js: true do
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
+
+ expect(page).to have_button('Cancel')
+ end
+
+ it 'cancels uploading on clicking to "Cancel" button', js: true do
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
+
+ click_button 'Cancel'
+
+ expect(page).to have_button('Attach a file')
+ expect(page).not_to have_button('Cancel')
+ expect(page).not_to have_selector('.uploading-progress-container', visible: true)
+ end
+
+ it 'shows "Attaching a file" message on uploading 1 file', js: true do
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
+
+ expect(page).to have_selector('.attaching-file-message', visible: true, text: 'Attaching a file -')
+ end
+
+ it 'shows "Attaching 2 files" message on uploading 2 file', js: true do
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'video_sample.mp4'),
+ Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
+
+ expect(page).to have_selector('.attaching-file-message', visible: true, text: 'Attaching 2 files -')
+ end
+
+ it 'shows error message, "retry" and "attach a new file" link a if file is too big', js: true do
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'video_sample.mp4')], 0.01)
+
+ error_text = 'File is too big (0.06MiB). Max filesize: 0.01MiB.'
+
+ expect(page).to have_selector('.uploading-error-message', visible: true, text: error_text)
+ expect(page).to have_selector('.retry-uploading-link', visible: true, text: 'Try again')
+ expect(page).to have_selector('.attach-new-file', visible: true, text: 'attach a new file')
+ expect(page).not_to have_button('Attach a file')
+ end
+ end
+
+ context 'uploading is complete' do
+ it 'shows "Attach a file" button on uploading complete', js: true do
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')])
+ wait_for_requests
+
+ expect(page).to have_button('Attach a file')
+ expect(page).not_to have_selector('.uploading-progress-container', visible: true)
+ end
- dropzone_file(Rails.root.join('spec', 'fixtures', 'dk.png'))
- click_button 'Comment'
- wait_for_ajax
+ scenario 'they see the attached file', js: true do
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')])
+ click_button 'Comment'
+ wait_for_requests
- expect(find('a.no-attachment-icon img[alt="dk"]')['src'])
- .to match(%r{/#{project.full_path}/uploads/\h{32}/dk\.png$})
+ expect(find('a.no-attachment-icon img[alt="dk"]')['src'])
+ .to match(%r{/#{project.full_path}/uploads/\h{32}/dk\.png$})
+ end
end
end
diff --git a/spec/features/user_callout_spec.rb b/spec/features/user_callout_spec.rb
index 848af5e3a4d..b84f834ff1e 100644
--- a/spec/features/user_callout_spec.rb
+++ b/spec/features/user_callout_spec.rb
@@ -20,7 +20,7 @@ describe 'User Callouts', js: true do
visit dashboard_projects_path
within('.user-callout') do
- find('.close').click
+ find('.close').trigger('click')
end
visit dashboard_projects_path
diff --git a/spec/features/users/projects_spec.rb b/spec/features/users/projects_spec.rb
index 373b64808f8..67ce4b44464 100644
--- a/spec/features/users/projects_spec.rb
+++ b/spec/features/users/projects_spec.rb
@@ -16,7 +16,7 @@ describe 'Projects tab on a user profile', :feature, :js do
click_link('Personal projects')
end
- wait_for_ajax
+ wait_for_requests
end
it 'paginates results' do
diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb
index 14564abb16d..dbd5f66b55e 100644
--- a/spec/features/users/rss_spec.rb
+++ b/spec/features/users/rss_spec.rb
@@ -9,7 +9,7 @@ feature 'User RSS' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's private token"
+ it_behaves_like "it has an RSS button with current_user's RSS token"
end
context 'when signed out' do
@@ -17,6 +17,6 @@ feature 'User RSS' do
visit path
end
- it_behaves_like "it has an RSS button without a private token"
+ it_behaves_like "it has an RSS button without an RSS token"
end
end
diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb
index 4efbd672322..2e388115633 100644
--- a/spec/features/users/snippets_spec.rb
+++ b/spec/features/users/snippets_spec.rb
@@ -11,7 +11,7 @@ describe 'Snippets tab on a user profile', feature: true, js: true do
allow(Snippet).to receive(:default_per_page).and_return(1)
visit user_path(user)
page.within('.user-profile-nav') { click_link 'Snippets' }
- wait_for_ajax
+ wait_for_requests
end
it_behaves_like 'paginated snippets', remote: true
@@ -27,7 +27,7 @@ describe 'Snippets tab on a user profile', feature: true, js: true do
login_as(:user)
visit user_path(user)
page.within('.user-profile-nav') { click_link 'Snippets' }
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.snippet-row', count: 2)
@@ -38,7 +38,7 @@ describe 'Snippets tab on a user profile', feature: true, js: true do
it 'contains only public snippets of a user when a user is not logged in' do
visit user_path(user)
page.within('.user-profile-nav') { click_link 'Snippets' }
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.snippet-row', count: 1)
expect(page).to have_content(public_snippet.title)
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index c43feadc808..fbe078bd136 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -78,25 +78,25 @@ feature 'Users', feature: true, js: true do
scenario 'doesn\'t show an error border if the username is available' do
fill_in username_input, with: 'new-user'
- wait_for_ajax
+ wait_for_requests
expect(find('.username')).not_to have_css '.gl-field-error-outline'
end
scenario 'does not show an error border if the username contains dots (.)' do
fill_in username_input, with: 'new.user.username'
- wait_for_ajax
+ wait_for_requests
expect(find('.username')).not_to have_css '.gl-field-error-outline'
end
scenario 'shows an error border if the username already exists' do
fill_in username_input, with: user.username
- wait_for_ajax
+ wait_for_requests
expect(find('.username')).to have_css '.gl-field-error-outline'
end
scenario 'shows an error border if the username contains special characters' do
fill_in username_input, with: 'new$user!username'
- wait_for_ajax
+ wait_for_requests
expect(find('.username')).to have_css '.gl-field-error-outline'
end
end
diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb
index b83a230c1f8..d0c982919db 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -19,7 +19,7 @@ describe 'Project variables', js: true do
end
end
- it 'adds new variable' do
+ it 'adds new secret variable' do
fill_in('variable_key', with: 'key')
fill_in('variable_value', with: 'key value')
click_button('Add new variable')
@@ -27,6 +27,7 @@ describe 'Project variables', js: true do
expect(page).to have_content('Variables were successfully updated.')
page.within('.variables-table') do
expect(page).to have_content('key')
+ expect(page).to have_content('No')
end
end
@@ -41,6 +42,19 @@ describe 'Project variables', js: true do
end
end
+ it 'adds new protected variable' do
+ fill_in('variable_key', with: 'key')
+ fill_in('variable_value', with: 'value')
+ check('Protected')
+ click_button('Add new variable')
+
+ expect(page).to have_content('Variables were successfully updated.')
+ page.within('.variables-table') do
+ expect(page).to have_content('key')
+ expect(page).to have_content('Yes')
+ end
+ end
+
it 'reveals and hides new variable' do
fill_in('variable_key', with: 'key')
fill_in('variable_value', with: 'key value')
@@ -85,7 +99,7 @@ describe 'Project variables', js: true do
click_button('Save variable')
expect(page).to have_content('Variable was successfully updated.')
- expect(project.variables.first.value).to eq('key value')
+ expect(project.variables(true).first.value).to eq('key value')
end
it 'edits variable with empty value' do
@@ -98,6 +112,34 @@ describe 'Project variables', js: true do
click_button('Save variable')
expect(page).to have_content('Variable was successfully updated.')
- expect(project.variables.first.value).to eq('')
+ expect(project.variables(true).first.value).to eq('')
+ end
+
+ it 'edits variable to be protected' do
+ page.within('.variables-table') do
+ find('.btn-variable-edit').click
+ end
+
+ expect(page).to have_content('Update variable')
+ check('Protected')
+ click_button('Save variable')
+
+ expect(page).to have_content('Variable was successfully updated.')
+ expect(project.variables(true).first).to be_protected
+ end
+
+ it 'edits variable to be unprotected' do
+ project.variables.first.update(protected: true)
+
+ page.within('.variables-table') do
+ find('.btn-variable-edit').click
+ end
+
+ expect(page).to have_content('Update variable')
+ uncheck('Protected')
+ click_button('Save variable')
+
+ expect(page).to have_content('Variable was successfully updated.')
+ expect(project.variables(true).first).not_to be_protected
end
end
diff --git a/spec/finders/events_finder_spec.rb b/spec/finders/events_finder_spec.rb
new file mode 100644
index 00000000000..30a2bd14f10
--- /dev/null
+++ b/spec/finders/events_finder_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe EventsFinder do
+ let(:user) { create(:user) }
+ let(:other_user) { create(:user) }
+ let(:project1) { create(:empty_project, :private, creator_id: user.id, namespace: user.namespace) }
+ let(:project2) { create(:empty_project, :private, creator_id: user.id, namespace: user.namespace) }
+ let(:closed_issue) { create(:closed_issue, project: project1, author: user) }
+ let(:opened_merge_request) { create(:merge_request, source_project: project2, author: user) }
+ let!(:closed_issue_event) { create(:event, project: project1, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 12, 30)) }
+ let!(:opened_merge_request_event) { create(:event, project: project2, author: user, target: opened_merge_request, action: Event::CREATED, created_at: Date.new(2017, 1, 31)) }
+ let(:closed_issue2) { create(:closed_issue, project: project1, author: user) }
+ let(:opened_merge_request2) { create(:merge_request, source_project: project2, author: user) }
+ let!(:closed_issue_event2) { create(:event, project: project1, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 2, 2)) }
+ let!(:opened_merge_request_event2) { create(:event, project: project2, author: user, target: opened_merge_request, action: Event::CREATED, created_at: Date.new(2017, 2, 2)) }
+
+ context 'when targeting a user' do
+ it 'returns events between specified dates filtered on action and type' do
+ events = described_class.new(source: user, current_user: user, action: 'created', target_type: 'merge_request', after: Date.new(2017, 1, 1), before: Date.new(2017, 2, 1)).execute
+
+ expect(events).to eq([opened_merge_request_event])
+ end
+
+ it 'does not return events the current_user does not have access to' do
+ events = described_class.new(source: user, current_user: other_user).execute
+
+ expect(events).not_to include(opened_merge_request_event)
+ end
+ end
+
+ context 'when targeting a project' do
+ it 'returns project events between specified dates filtered on action and type' do
+ events = described_class.new(source: project1, current_user: user, action: 'closed', target_type: 'issue', after: Date.new(2016, 12, 1), before: Date.new(2017, 1, 1)).execute
+
+ expect(events).to eq([closed_issue_event])
+ end
+
+ it 'does not return events the current_user does not have access to' do
+ events = described_class.new(source: project2, current_user: other_user).execute
+
+ expect(events).to be_empty
+ end
+ end
+end
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 148adcffe3b..03d98459e8c 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -137,6 +137,13 @@ describe ProjectsFinder do
it { is_expected.to eq([public_project]) }
end
+ describe 'filter by owned' do
+ let(:params) { { owned: true } }
+ let!(:owned_project) { create(:empty_project, :private, namespace: current_user.namespace) }
+
+ it { is_expected.to eq([owned_project]) }
+ end
+
describe 'filter by non_public' do
let(:params) { { non_public: true } }
before do
@@ -146,13 +153,19 @@ describe ProjectsFinder do
it { is_expected.to eq([private_project]) }
end
- describe 'filter by viewable_starred_projects' do
+ describe 'filter by starred' do
let(:params) { { starred: true } }
before do
current_user.toggle_star(public_project)
end
it { is_expected.to eq([public_project]) }
+
+ it 'returns only projects the user has access to' do
+ current_user.toggle_star(private_project)
+
+ is_expected.to eq([public_project])
+ end
end
describe 'sorting' do
diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb
new file mode 100644
index 00000000000..780b309b45e
--- /dev/null
+++ b/spec/finders/users_finder_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe UsersFinder do
+ describe '#execute' do
+ let!(:user1) { create(:user, username: 'johndoe') }
+ let!(:user2) { create(:user, :blocked, username: 'notsorandom') }
+ let!(:external_user) { create(:user, :external) }
+ let!(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
+
+ context 'with a normal user' do
+ let(:user) { create(:user) }
+
+ it 'returns all users' do
+ users = described_class.new(user).execute
+
+ expect(users).to contain_exactly(user, user1, user2, omniauth_user)
+ end
+
+ it 'filters by username' do
+ users = described_class.new(user, username: 'johndoe').execute
+
+ expect(users).to contain_exactly(user1)
+ end
+
+ it 'filters by search' do
+ users = described_class.new(user, search: 'orando').execute
+
+ expect(users).to contain_exactly(user2)
+ end
+
+ it 'filters by blocked users' do
+ users = described_class.new(user, blocked: true).execute
+
+ expect(users).to contain_exactly(user2)
+ end
+
+ it 'filters by active users' do
+ users = described_class.new(user, active: true).execute
+
+ expect(users).to contain_exactly(user, user1, omniauth_user)
+ end
+
+ it 'returns no external users' do
+ users = described_class.new(user, external: true).execute
+
+ expect(users).to contain_exactly(user, user1, user2, omniauth_user)
+ end
+ end
+
+ context 'with an admin user' do
+ let(:admin) { create(:admin) }
+
+ it 'filters by external users' do
+ users = described_class.new(admin, external: true).execute
+
+ expect(users).to contain_exactly(external_user)
+ end
+
+ it 'returns all users' do
+ users = described_class.new(admin).execute
+
+ expect(users).to contain_exactly(admin, user1, user2, external_user, omniauth_user)
+ end
+ end
+ end
+end
diff --git a/spec/fixtures/api/schemas/entities/merge_request.json b/spec/fixtures/api/schemas/entities/merge_request.json
index 7dda62ca3e7..b6a59a6cc47 100644
--- a/spec/fixtures/api/schemas/entities/merge_request.json
+++ b/spec/fixtures/api/schemas/entities/merge_request.json
@@ -85,13 +85,15 @@
"email_patches_path": { "type": "string" },
"plain_diff_path": { "type": "string" },
"status_path": { "type": "string" },
+ "new_blob_path": { "type": "string" },
"merge_check_path": { "type": "string" },
"ci_environments_status_path": { "type": "string" },
"merge_commit_message_with_description": { "type": "string" },
"diverged_commits_count": { "type": "integer" },
"commit_change_content_path": { "type": "string" },
"remove_wip_path": { "type": "string" },
- "commits_count": { "type": "integer" }
+ "commits_count": { "type": "integer" },
+ "remove_source_branch": { "type": ["boolean", "null"] }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/list.json b/spec/fixtures/api/schemas/list.json
index 11a4caf6628..622a1e40d07 100644
--- a/spec/fixtures/api/schemas/list.json
+++ b/spec/fixtures/api/schemas/list.json
@@ -10,7 +10,7 @@
"id": { "type": "integer" },
"list_type": {
"type": "string",
- "enum": ["label", "closed"]
+ "enum": ["backlog", "label", "closed"]
},
"label": {
"type": ["object", "null"],
diff --git a/spec/fixtures/api/schemas/pipeline_schedule.json b/spec/fixtures/api/schemas/pipeline_schedule.json
new file mode 100644
index 00000000000..f6346bd0fb6
--- /dev/null
+++ b/spec/fixtures/api/schemas/pipeline_schedule.json
@@ -0,0 +1,41 @@
+{
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "description": { "type": "string" },
+ "ref": { "type": "string" },
+ "cron": { "type": "string" },
+ "cron_timezone": { "type": "string" },
+ "next_run_at": { "type": "date" },
+ "active": { "type": "boolean" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "last_pipeline": {
+ "type": ["object", "null"],
+ "properties": {
+ "id": { "type": "integer" },
+ "sha": { "type": "string" },
+ "ref": { "type": "string" },
+ "status": { "type": "string" }
+ },
+ "additionalProperties": false
+ },
+ "owner": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "id", "description", "ref", "cron", "cron_timezone", "next_run_at",
+ "active", "created_at", "updated_at", "owner"
+ ],
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/pipeline_schedules.json b/spec/fixtures/api/schemas/pipeline_schedules.json
new file mode 100644
index 00000000000..173a28d2505
--- /dev/null
+++ b/spec/fixtures/api/schemas/pipeline_schedules.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "pipeline_schedule.json" }
+}
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 1c4ea46f9cd..d3aebdecedd 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -4,6 +4,8 @@ require 'spec_helper'
describe ApplicationHelper do
include UploadHelpers
+ let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
+
describe 'current_controller?' do
it 'returns true when controller matches argument' do
stub_controller_name('foo')
@@ -57,8 +59,14 @@ describe ApplicationHelper do
describe 'project_icon' do
it 'returns an url for the avatar' do
project = create(:empty_project, avatar: File.open(uploaded_image_temp_path))
+ avatar_url = "/uploads/project/avatar/#{project.id}/banana_sample.gif"
+
+ expect(helper.project_icon(project.full_path).to_s).
+ to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
+
+ allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)
+ avatar_url = "#{gitlab_host}/uploads/project/avatar/#{project.id}/banana_sample.gif"
- avatar_url = "http://#{Gitlab.config.gitlab.host}/uploads/system/project/avatar/#{project.id}/banana_sample.gif"
expect(helper.project_icon(project.full_path).to_s).
to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
end
@@ -68,9 +76,8 @@ describe ApplicationHelper do
allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true)
- avatar_url = "http://#{Gitlab.config.gitlab.host}#{namespace_project_avatar_path(project.namespace, project)}"
- expect(helper.project_icon(project.full_path).to_s).to match(
- image_tag(avatar_url))
+ avatar_url = "#{gitlab_host}#{namespace_project_avatar_path(project.namespace, project)}"
+ expect(helper.project_icon(project.full_path).to_s).to match(image_tag(avatar_url))
end
end
@@ -78,8 +85,14 @@ describe ApplicationHelper do
it 'returns an url for the avatar' do
user = create(:user, avatar: File.open(uploaded_image_temp_path))
- expect(helper.avatar_icon(user.email).to_s).
- to match("/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+ avatar_url = "/uploads/user/avatar/#{user.id}/banana_sample.gif"
+
+ expect(helper.avatar_icon(user.email).to_s).to match(avatar_url)
+
+ allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)
+ avatar_url = "#{gitlab_host}/uploads/user/avatar/#{user.id}/banana_sample.gif"
+
+ expect(helper.avatar_icon(user.email).to_s).to match(avatar_url)
end
it 'returns an url for the avatar with relative url' do
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index 581726c1d0e..049475a5408 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -1,6 +1,8 @@
require 'rails_helper'
describe AvatarsHelper do
+ include ApplicationHelper
+
let(:user) { create(:user) }
describe '#user_avatar' do
@@ -15,7 +17,106 @@ describe AvatarsHelper do
end
it "contains the user's avatar image" do
- is_expected.to include(CGI.escapeHTML(user.avatar_url(16)))
+ is_expected.to include(CGI.escapeHTML(user.avatar_url(size: 16)))
+ end
+ end
+
+ describe '#user_avatar_without_link' do
+ let(:options) { { user: user } }
+ subject { helper.user_avatar_without_link(options) }
+
+ it 'displays user avatar' do
+ is_expected.to eq image_tag(
+ avatar_icon(user, 16),
+ class: 'avatar has-tooltip s16 ',
+ alt: "#{user.name}'s avatar",
+ title: user.name,
+ data: { container: 'body' }
+ )
+ end
+
+ context 'with css_class parameter' do
+ let(:options) { { user: user, css_class: '.cat-pics' } }
+
+ it 'uses provided css_class' do
+ is_expected.to eq image_tag(
+ avatar_icon(user, 16),
+ class: "avatar has-tooltip s16 #{options[:css_class]}",
+ alt: "#{user.name}'s avatar",
+ title: user.name,
+ data: { container: 'body' }
+ )
+ end
+ end
+
+ context 'with lazy parameter' do
+ let(:options) { { user: user, lazy: true } }
+
+ it 'uses data-src instead of src' do
+ is_expected.to eq image_tag(
+ '',
+ class: 'avatar has-tooltip s16 ',
+ alt: "#{user.name}'s avatar",
+ title: user.name,
+ data: { container: 'body', src: avatar_icon(user, 16) }
+ )
+ end
+ end
+
+ context 'with size parameter' do
+ let(:options) { { user: user, size: 99 } }
+
+ it 'uses provided size' do
+ is_expected.to eq image_tag(
+ avatar_icon(user, options[:size]),
+ class: "avatar has-tooltip s#{options[:size]} ",
+ alt: "#{user.name}'s avatar",
+ title: user.name,
+ data: { container: 'body' }
+ )
+ end
+ end
+
+ context 'with url parameter' do
+ let(:options) { { user: user, url: '/over/the/rainbow.png' } }
+
+ it 'uses provided url' do
+ is_expected.to eq image_tag(
+ options[:url],
+ class: 'avatar has-tooltip s16 ',
+ alt: "#{user.name}'s avatar",
+ title: user.name,
+ data: { container: 'body' }
+ )
+ end
+ end
+
+ context 'with user_name parameter' do
+ let(:options) { { user_name: 'Tinky Winky', user_email: 'no@f.un' } }
+
+ context 'with user parameter' do
+ let(:options) { { user: user, user_name: 'Tinky Winky' } }
+
+ it 'prefers user parameter' do
+ is_expected.to eq image_tag(
+ avatar_icon(user, 16),
+ class: 'avatar has-tooltip s16 ',
+ alt: "#{user.name}'s avatar",
+ title: user.name,
+ data: { container: 'body' }
+ )
+ end
+ end
+
+ it 'uses user_name and user_email parameter if user is not present' do
+ is_expected.to eq image_tag(
+ avatar_icon(options[:user_email], 16),
+ class: 'avatar has-tooltip s16 ',
+ alt: "#{options[:user_name]}'s avatar",
+ title: options[:user_name],
+ data: { container: 'body' }
+ )
+ end
end
end
end
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 1b4393e6167..bd3a3d24b84 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -116,10 +116,11 @@ describe BlobHelper do
let(:viewer_class) do
Class.new(BlobViewer::Base) do
- self.max_size = 1.megabyte
- self.absolute_max_size = 5.megabytes
+ include BlobViewer::ServerSide
+
+ self.collapse_limit = 1.megabyte
+ self.size_limit = 5.megabytes
self.type = :rich
- self.client_side = false
end
end
@@ -128,7 +129,7 @@ describe BlobHelper do
describe '#blob_render_error_reason' do
context 'for error :too_large' do
- context 'when the blob size is larger than the absolute max size' do
+ context 'when the blob size is larger than the absolute size limit' do
let(:blob) { fake_blob(size: 10.megabytes) }
it 'returns an error message' do
@@ -136,7 +137,7 @@ describe BlobHelper do
end
end
- context 'when the blob size is larger than the max size' do
+ context 'when the blob size is larger than the size limit' do
let(:blob) { fake_blob(size: 2.megabytes) }
it 'returns an error message' do
@@ -167,21 +168,19 @@ describe BlobHelper do
controller.params[:id] = File.join('master', blob.path)
end
- context 'for error :too_large' do
- context 'when the max size can be overridden' do
- let(:blob) { fake_blob(size: 2.megabytes) }
+ context 'for error :collapsed' do
+ let(:blob) { fake_blob(size: 2.megabytes) }
- it 'includes a "load it anyway" link' do
- expect(helper.blob_render_error_options(viewer)).to include(/load it anyway/)
- end
+ it 'includes a "load it anyway" link' do
+ expect(helper.blob_render_error_options(viewer)).to include(/load it anyway/)
end
+ end
- context 'when the max size cannot be overridden' do
- let(:blob) { fake_blob(size: 10.megabytes) }
+ context 'for error :too_large' do
+ let(:blob) { fake_blob(size: 10.megabytes) }
- it 'does not include a "load it anyway" link' do
- expect(helper.blob_render_error_options(viewer)).not_to include(/load it anyway/)
- end
+ it 'does not include a "load it anyway" link' do
+ expect(helper.blob_render_error_options(viewer)).not_to include(/load it anyway/)
end
context 'when the viewer is rich' do
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index eae097126ce..a74615e07f9 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -33,17 +33,17 @@ describe DiffHelper do
describe 'diff_options' do
it 'returns no collapse false' do
- expect(diff_options).to include(no_collapse: false)
+ expect(diff_options).to include(expanded: false)
end
- it 'returns no collapse true if expand_all_diffs' do
- allow(controller).to receive(:params) { { expand_all_diffs: true } }
- expect(diff_options).to include(no_collapse: true)
+ it 'returns no collapse true if expanded' do
+ allow(controller).to receive(:params) { { expanded: true } }
+ expect(diff_options).to include(expanded: true)
end
it 'returns no collapse true if action name diff_for_path' do
allow(controller).to receive(:action_name) { 'diff_for_path' }
- expect(diff_options).to include(no_collapse: true)
+ expect(diff_options).to include(expanded: true)
end
it 'returns paths if action name diff_for_path and param old path' do
@@ -122,13 +122,40 @@ describe DiffHelper do
it "returns strings with marked inline diffs" do
marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
- expect(marked_old_line).to eq("abc <span class='idiff left right deletion'>&#39;def&#39;</span>")
+ expect(marked_old_line).to eq(%q{abc <span class="idiff left right deletion">&#39;def&#39;</span>})
expect(marked_old_line).to be_html_safe
- expect(marked_new_line).to eq("abc <span class='idiff left right addition'>&quot;def&quot;</span>")
+ expect(marked_new_line).to eq(%q{abc <span class="idiff left right addition">&quot;def&quot;</span>})
expect(marked_new_line).to be_html_safe
end
end
+ describe '#parallel_diff_discussions' do
+ let(:discussion) { { 'abc_3_3' => 'comment' } }
+ let(:diff_file) { double(line_code: 'abc_3_3') }
+
+ before do
+ helper.instance_variable_set(:@grouped_diff_discussions, discussion)
+ end
+
+ it 'does not put comments on nonewline lines' do
+ left = Gitlab::Diff::Line.new('\\nonewline', 'old-nonewline', 3, 3, 3)
+ right = Gitlab::Diff::Line.new('\\nonewline', 'new-nonewline', 3, 3, 3)
+
+ result = helper.parallel_diff_discussions(left, right, diff_file)
+
+ expect(result).to eq([nil, nil])
+ end
+
+ it 'puts comments on added lines' do
+ left = Gitlab::Diff::Line.new('\\nonewline', 'old-nonewline', 3, 3, 3)
+ right = Gitlab::Diff::Line.new('new line', 'add', 3, 3, 3)
+
+ result = helper.parallel_diff_discussions(left, right, diff_file)
+
+ expect(result).to eq([nil, 'comment'])
+ end
+ end
+
describe "#diff_match_line" do
let(:old_pos) { 40 }
let(:new_pos) { 50 }
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index c1ecb46aece..8fcf7f5fa15 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -192,4 +192,22 @@ describe IssuablesHelper do
expect(helper.issuable_filter_present?).to be_falsey
end
end
+
+ 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_updated_at_by) do
+ {
+ updatedAt: edited_issuable.updated_at.to_time.iso8601,
+ updatedBy: {
+ name: user.name,
+ path: user_path(user)
+ }
+ }
+ end
+
+ it { expect(helper.updated_at_by(unedited_issuable)).to eq({}) }
+ it { expect(helper.updated_at_by(edited_issuable)).to eq(edited_updated_at_by) }
+ end
end
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index 099146678ae..cc861af8533 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -92,7 +92,13 @@ describe NotesHelper do
)
end
- let(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position).to_discussion }
+ let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position) }
+ let(:discussion) { diff_note.to_discussion }
+
+ before do
+ diff_note.position = diff_note.original_position
+ diff_note.save!
+ end
it 'returns the diff version comparison path with the line code' do
expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff3, start_sha: merge_request_diff1.head_commit_sha, anchor: discussion.line_code))
@@ -250,4 +256,14 @@ describe NotesHelper do
expect(helper.form_resources).to eq([@project.namespace, @project, @note])
end
end
+
+ describe '#noteable_note_url' do
+ let(:project) { create(:empty_project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:note) { create(:note_on_issue, noteable: issue, project: project) }
+
+ it 'returns the noteable url with an anchor to the note' do
+ expect(noteable_note_url(note)).to match("/#{project.namespace.path}/#{project.path}/issues/#{issue.iid}##{dom_id(note)}")
+ end
+ end
end
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
index 9d5f009ebe1..9ecaabc04ed 100644
--- a/spec/helpers/notifications_helper_spec.rb
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -12,5 +12,11 @@ describe NotificationsHelper do
describe 'notification_title' do
it { expect(notification_title(:watch)).to match('Watch') }
it { expect(notification_title(:mention)).to match('On mention') }
+ it { expect(notification_title(:global)).to match('Global') }
+ end
+
+ describe '#notification_event_name' do
+ it { expect(notification_event_name(:success_pipeline)).to match('Successful pipeline') }
+ it { expect(notification_event_name(:failed_pipeline)).to match('Failed pipeline') }
end
end
diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb
new file mode 100644
index 00000000000..b33b3f3a228
--- /dev/null
+++ b/spec/helpers/profiles_helper_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+describe ProfilesHelper do
+ describe '#email_provider_label' do
+ it "returns nil for users without external email" do
+ user = create(:user)
+ allow(helper).to receive(:current_user).and_return(user)
+
+ expect(helper.email_provider_label).to be_nil
+ end
+
+ it "returns omniauth provider label for users with external email" do
+ stub_cas_omniauth_provider
+ cas_user = create(:omniauth_user, provider: 'cas3', external_email: true, email_provider: 'cas3')
+ allow(helper).to receive(:current_user).and_return(cas_user)
+
+ expect(helper.email_provider_label).to eq('CAS')
+ end
+
+ it "returns 'LDAP' for users with external email but no email provider" do
+ ldap_user = create(:omniauth_user, external_email: true)
+ allow(helper).to receive(:current_user).and_return(ldap_user)
+
+ expect(helper.email_provider_label).to eq('LDAP')
+ end
+ end
+
+ def stub_cas_omniauth_provider
+ provider = OpenStruct.new(
+ 'name' => 'cas3',
+ 'label' => 'CAS'
+ )
+
+ stub_omniauth_setting(providers: [provider])
+ end
+end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index be97973c693..a695621b87a 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -66,8 +66,8 @@ describe ProjectsHelper do
describe "#project_list_cache_key", redis: true do
let(:project) { create(:project) }
- it "includes the namespace" do
- expect(helper.project_list_cache_key(project)).to include(project.namespace.cache_key)
+ it "includes the route" do
+ expect(helper.project_list_cache_key(project)).to include(project.route.cache_key)
end
it "includes the project" do
@@ -257,7 +257,7 @@ describe ProjectsHelper do
result = helper.project_feature_access_select(:issues_access_level)
expect(result).to include("Disabled")
expect(result).to include("Only team members")
- expect(result).not_to include("Everyone with access")
+ expect(result).to have_selector('option[disabled]', text: "Everyone with access")
end
end
@@ -272,7 +272,7 @@ describe ProjectsHelper do
expect(result).to include("Disabled")
expect(result).to include("Only team members")
- expect(result).not_to include("Everyone with access")
+ expect(result).to have_selector('option[disabled]', text: "Everyone with access")
expect(result).to have_selector('option[selected]', text: "Only team members")
end
end
diff --git a/spec/helpers/rss_helper_spec.rb b/spec/helpers/rss_helper_spec.rb
index f3f174f3d14..269e1057e8d 100644
--- a/spec/helpers/rss_helper_spec.rb
+++ b/spec/helpers/rss_helper_spec.rb
@@ -3,17 +3,17 @@ require 'spec_helper'
describe RssHelper do
describe '#rss_url_options' do
context 'when signed in' do
- it "includes the current_user's private_token" do
+ it "includes the current_user's rss_token" do
current_user = create(:user)
allow(helper).to receive(:current_user).and_return(current_user)
- expect(helper.rss_url_options).to include private_token: current_user.private_token
+ expect(helper.rss_url_options).to include rss_token: current_user.rss_token
end
end
context 'when signed out' do
- it "does not have a private_token" do
+ it "does not have an rss_token" do
allow(helper).to receive(:current_user).and_return(nil)
- expect(helper.rss_url_options[:private_token]).to be_nil
+ expect(helper.rss_url_options[:rss_token]).to be_nil
end
end
end
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index 9da33792659..cb727430117 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -52,6 +52,14 @@ describe SubmoduleHelper do
stub_url(['http://', config.host, '/gitlab/root/gitlab-org/gitlab-ce.git'].join(''))
expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
+
+ it 'works with subgroups' do
+ allow(Gitlab.config.gitlab).to receive(:port).and_return(80) # set this just to be sure
+ allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root')
+ allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
+ stub_url(['http://', config.host, '/gitlab/root/gitlab-org/sub/gitlab-ce.git'].join(''))
+ expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org/sub', 'gitlab-ce'), namespace_project_tree_path('gitlab-org/sub', 'gitlab-ce', 'hash')])
+ end
end
context 'submodule on github.com' do
@@ -81,6 +89,19 @@ describe SubmoduleHelper do
end
end
+ context 'in-repository submodule' do
+ let(:group) { create(:group, name: "Master Project", path: "master-project") }
+ let(:project) { create(:empty_project, group: group) }
+ before do
+ self.instance_variable_set(:@project, project)
+ end
+
+ it 'in-repository' do
+ stub_url('./')
+ expect(submodule_links(submodule_item)).to eq(["/master-project/#{project.path}", "/master-project/#{project.path}/tree/hash"])
+ end
+ end
+
context 'submodule on gitlab.com' do
it 'detects ssh' do
stub_url('git@gitlab.com:gitlab-org/gitlab-ce.git')
@@ -102,6 +123,11 @@ describe SubmoduleHelper do
expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
+ it 'handles urls with trailing whitespace' do
+ stub_url('http://gitlab.com/gitlab-org/gitlab-ce.git ')
+ expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ end
+
it 'returns original with non-standard url' do
stub_url('http://gitlab.com/another/gitlab-org/gitlab-ce.git')
expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index 8942b00b128..ad19cf9263d 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -37,7 +37,7 @@ describe VisibilityLevelHelper do
it "describes public projects" do
expect(project_visibility_level_description(Gitlab::VisibilityLevel::PUBLIC))
- .to eq "The project can be cloned without any authentication."
+ .to eq "The project can be accessed without any authentication."
end
end
diff --git a/spec/javascripts/abuse_reports_spec.js b/spec/javascripts/abuse_reports_spec.js
index 76b370b345b..069d857eab6 100644
--- a/spec/javascripts/abuse_reports_spec.js
+++ b/spec/javascripts/abuse_reports_spec.js
@@ -1,5 +1,5 @@
-require('~/lib/utils/text_utility');
-require('~/abuse_reports');
+import '~/lib/utils/text_utility';
+import '~/abuse_reports';
((global) => {
describe('Abuse Reports', () => {
diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js
index e6a6fc36ca1..e8c5f721423 100644
--- a/spec/javascripts/activities_spec.js
+++ b/spec/javascripts/activities_spec.js
@@ -1,8 +1,8 @@
/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow, max-len */
-require('vendor/jquery.endless-scroll.js');
-require('~/pager');
-require('~/activities');
+import 'vendor/jquery.endless-scroll';
+import '~/pager';
+import '~/activities';
(() => {
window.gon || (window.gon = {});
diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js
index a68bccb16f4..1518ae68b0d 100644
--- a/spec/javascripts/ajax_loading_spinner_spec.js
+++ b/spec/javascripts/ajax_loading_spinner_spec.js
@@ -1,7 +1,7 @@
-require('~/extensions/array');
-require('jquery');
-require('jquery-ujs');
-require('~/ajax_loading_spinner');
+import '~/extensions/array';
+import 'jquery';
+import 'jquery-ujs';
+import '~/ajax_loading_spinner';
describe('Ajax Loading Spinner', () => {
const fixtureTemplate = 'static/ajax_loading_spinner.html.raw';
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
new file mode 100644
index 00000000000..867322ce8ae
--- /dev/null
+++ b/spec/javascripts/api_spec.js
@@ -0,0 +1,281 @@
+import Api from '~/api';
+
+describe('Api', () => {
+ const dummyApiVersion = 'v3000';
+ const dummyUrlRoot = 'http://host.invalid';
+ const dummyGon = {
+ 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;
+
+ beforeEach(() => {
+ originalGon = window.gon;
+ window.gon = dummyGon;
+ });
+
+ afterEach(() => {
+ window.gon = originalGon;
+ });
+
+ describe('buildUrl', () => {
+ it('adds URL root and fills in API version', () => {
+ const input = '/api/:version/foo/bar';
+ const expectedOutput = `${dummyUrlRoot}/api/${dummyApiVersion}/foo/bar`;
+
+ const builtUrl = Api.buildUrl(input);
+
+ expect(builtUrl).toEqual(expectedOutput);
+ });
+ });
+
+ 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();
+ });
+
+ Api.group(groupId, (response) => {
+ expect(response).toBe(dummyResponse);
+ done();
+ });
+ });
+ });
+
+ describe('groups', () => {
+ it('fetches groups', (done) => {
+ 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();
+ });
+
+ Api.groups(query, options, (response) => {
+ expect(response).toBe(dummyResponse);
+ done();
+ });
+ });
+ });
+
+ describe('namespaces', () => {
+ 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();
+ });
+
+ Api.namespaces(query, (response) => {
+ expect(response).toBe(dummyResponse);
+ done();
+ });
+ });
+ });
+
+ describe('projects', () => {
+ it('fetches projects', (done) => {
+ const query = 'dummy query';
+ const options = { unused: 'option' };
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects.json?simple=true`;
+ const expectedData = Object.assign({
+ search: query,
+ per_page: 20,
+ membership: 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();
+ });
+
+ Api.projects(query, options, (response) => {
+ expect(response).toBe(dummyResponse);
+ done();
+ });
+ });
+ });
+
+ describe('newLabel', () => {
+ it('creates a new label', (done) => {
+ const namespace = 'some namespace';
+ const project = 'some project';
+ const labelData = { some: 'data' };
+ const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/labels`;
+ 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();
+ });
+
+ Api.newLabel(namespace, project, labelData, (response) => {
+ expect(response).toBe(dummyResponse);
+ done();
+ });
+ });
+ });
+
+ describe('groupProjects', () => {
+ it('fetches group projects', (done) => {
+ 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();
+ });
+
+ Api.groupProjects(groupId, query, (response) => {
+ expect(response).toBe(dummyResponse);
+ done();
+ });
+ });
+ });
+
+ describe('licenseText', () => {
+ it('fetches a license text', (done) => {
+ 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();
+ });
+
+ Api.licenseText(licenseKey, data, (response) => {
+ expect(response).toBe(dummyResponse);
+ done();
+ });
+ });
+ });
+
+ describe('gitignoreText', () => {
+ 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);
+ });
+
+ Api.gitignoreText(gitignoreKey, (response) => {
+ expect(response).toBe(dummyResponse);
+ done();
+ });
+ });
+ });
+
+ describe('gitlabCiYml', () => {
+ 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);
+ });
+
+ Api.gitlabCiYml(gitlabCiYmlKey, (response) => {
+ expect(response).toBe(dummyResponse);
+ done();
+ });
+ });
+ });
+
+ describe('dockerfileYml', () => {
+ 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);
+ });
+
+ Api.dockerfileYml(dockerfileYmlKey, (response) => {
+ expect(response).toBe(dummyResponse);
+ done();
+ });
+ });
+ });
+
+ describe('issueTemplate', () => {
+ it('fetches an issue template', (done) => {
+ const namespace = 'some namespace';
+ const project = 'some project';
+ 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();
+ });
+
+ Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => {
+ expect(error).toBe(null);
+ expect(response).toBe(dummyResponse);
+ done();
+ });
+ });
+ });
+
+ describe('users', () => {
+ it('fetches users', (done) => {
+ 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();
+ });
+
+ Api.users(query, options)
+ .then((response) => {
+ expect(response).toBe(dummyResponse);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 68ad5f66676..3fc03324d16 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -3,7 +3,7 @@
import Cookies from 'js-cookie';
import AwardsHandler from '~/awards_handler';
-require('~/lib/utils/common_utils');
+import '~/lib/utils/common_utils';
(function() {
var awardsHandler, lazyAssert, urlRoot, openAndWaitForEmojiMenu;
diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js
index 3deaf258cae..67afba19190 100644
--- a/spec/javascripts/behaviors/autosize_spec.js
+++ b/spec/javascripts/behaviors/autosize_spec.js
@@ -1,6 +1,6 @@
/* eslint-disable space-before-function-paren, no-var, comma-dangle, no-return-assign, max-len */
-require('~/behaviors/autosize');
+import '~/behaviors/autosize';
(function() {
describe('Autosize behavior', function() {
diff --git a/spec/javascripts/behaviors/bind_in_out_spec.js b/spec/javascripts/behaviors/bind_in_out_spec.js
index dd9ab33289f..5ff66167718 100644
--- a/spec/javascripts/behaviors/bind_in_out_spec.js
+++ b/spec/javascripts/behaviors/bind_in_out_spec.js
@@ -2,7 +2,7 @@ import BindInOut from '~/behaviors/bind_in_out';
import ClassSpecHelper from '../helpers/class_spec_helper';
describe('BindInOut', function () {
- describe('.constructor', function () {
+ describe('constructor', function () {
beforeEach(function () {
this.in = {};
this.out = {};
@@ -53,7 +53,7 @@ describe('BindInOut', function () {
});
});
- describe('.addEvents', function () {
+ describe('addEvents', function () {
beforeEach(function () {
this.in = jasmine.createSpyObj('in', ['addEventListener']);
@@ -79,7 +79,7 @@ describe('BindInOut', function () {
});
});
- describe('.updateOut', function () {
+ describe('updateOut', function () {
beforeEach(function () {
this.in = { value: 'the-value' };
this.out = { textContent: 'not-the-value' };
@@ -98,7 +98,7 @@ describe('BindInOut', function () {
});
});
- describe('.removeEvents', function () {
+ describe('removeEvents', function () {
beforeEach(function () {
this.in = jasmine.createSpyObj('in', ['removeEventListener']);
this.updateOut = () => {};
@@ -122,7 +122,7 @@ describe('BindInOut', function () {
});
});
- describe('.initAll', function () {
+ describe('initAll', function () {
beforeEach(function () {
this.ins = [0, 1, 2];
this.instances = [];
@@ -153,7 +153,7 @@ describe('BindInOut', function () {
});
});
- describe('.init', function () {
+ describe('init', function () {
beforeEach(function () {
spyOn(BindInOut.prototype, 'addEvents').and.callFake(function () { return this; });
spyOn(BindInOut.prototype, 'updateOut').and.callFake(function () { return this; });
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index 4820ce41ade..f56b99f8a16 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -1,6 +1,6 @@
/* eslint-disable space-before-function-paren, no-var, no-return-assign, comma-dangle, jasmine/no-spec-dupes, new-cap, max-len */
-require('~/behaviors/quick_submit');
+import '~/behaviors/quick_submit';
(function() {
describe('Quick Submit behavior', function() {
diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js
index 3a84013a2ed..f9fa814b801 100644
--- a/spec/javascripts/behaviors/requires_input_spec.js
+++ b/spec/javascripts/behaviors/requires_input_spec.js
@@ -1,6 +1,6 @@
/* eslint-disable space-before-function-paren, no-var */
-require('~/behaviors/requires_input');
+import '~/behaviors/requires_input';
(function() {
describe('requiresInput', function() {
diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js
new file mode 100644
index 00000000000..acd0aaf2a86
--- /dev/null
+++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js
@@ -0,0 +1,51 @@
+/* eslint-disable import/no-unresolved */
+
+import BalsamiqViewer from '~/blob/balsamiq/balsamiq_viewer';
+import bmprPath from '../../fixtures/blob/balsamiq/test.bmpr';
+
+describe('Balsamiq integration spec', () => {
+ let container;
+ let endpoint;
+ let balsamiqViewer;
+
+ preloadFixtures('static/balsamiq_viewer.html.raw');
+
+ beforeEach(() => {
+ loadFixtures('static/balsamiq_viewer.html.raw');
+
+ container = document.getElementById('js-balsamiq-viewer');
+ balsamiqViewer = new BalsamiqViewer(container);
+ });
+
+ describe('successful response', () => {
+ beforeEach((done) => {
+ endpoint = bmprPath;
+
+ balsamiqViewer.loadFile(endpoint).then(done).catch(done.fail);
+ });
+
+ it('does not show loading icon', () => {
+ expect(document.querySelector('.loading')).toBeNull();
+ });
+
+ it('renders the balsamiq previews', () => {
+ expect(document.querySelectorAll('.previews .preview').length).not.toEqual(0);
+ });
+ });
+
+ describe('error getting file', () => {
+ beforeEach((done) => {
+ endpoint = 'invalid/path/to/file.bmpr';
+
+ balsamiqViewer.loadFile(endpoint).then(done.fail, null).catch(done);
+ });
+
+ it('does not show loading icon', () => {
+ expect(document.querySelector('.loading')).toBeNull();
+ });
+
+ it('does not render the balsamiq previews', () => {
+ expect(document.querySelectorAll('.previews .preview').length).toEqual(0);
+ });
+ });
+});
diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js
index 85816ee1f11..aa87956109f 100644
--- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js
+++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js
@@ -4,17 +4,11 @@ import ClassSpecHelper from '../../helpers/class_spec_helper';
describe('BalsamiqViewer', () => {
let balsamiqViewer;
- let endpoint;
let viewer;
describe('class constructor', () => {
beforeEach(() => {
- endpoint = 'endpoint';
- viewer = {
- dataset: {
- endpoint,
- },
- };
+ viewer = {};
balsamiqViewer = new BalsamiqViewer(viewer);
});
@@ -22,25 +16,25 @@ describe('BalsamiqViewer', () => {
it('should set .viewer', () => {
expect(balsamiqViewer.viewer).toBe(viewer);
});
+ });
+
+ describe('fileLoaded', () => {
- it('should set .endpoint', () => {
- expect(balsamiqViewer.endpoint).toBe(endpoint);
- });
});
describe('loadFile', () => {
let xhr;
+ let loadFile;
+ const endpoint = 'endpoint';
beforeEach(() => {
- endpoint = 'endpoint';
xhr = jasmine.createSpyObj('xhr', ['open', 'send']);
balsamiqViewer = jasmine.createSpyObj('balsamiqViewer', ['renderFile']);
- balsamiqViewer.endpoint = endpoint;
spyOn(window, 'XMLHttpRequest').and.returnValue(xhr);
- BalsamiqViewer.prototype.loadFile.call(balsamiqViewer);
+ loadFile = BalsamiqViewer.prototype.loadFile.call(balsamiqViewer, endpoint);
});
it('should call .open', () => {
@@ -54,6 +48,10 @@ describe('BalsamiqViewer', () => {
it('should call .send', () => {
expect(xhr.send).toHaveBeenCalled();
});
+
+ it('should return a promise', () => {
+ expect(loadFile).toEqual(jasmine.any(Promise));
+ });
});
describe('renderFile', () => {
@@ -325,18 +323,4 @@ describe('BalsamiqViewer', () => {
expect(parseTitle).toBe('name');
});
});
-
- describe('onError', () => {
- beforeEach(() => {
- spyOn(window, 'Flash');
-
- BalsamiqViewer.onError();
- });
-
- ClassSpecHelper.itShouldBeAStaticMethod(BalsamiqViewer, 'onError');
-
- it('should instantiate Flash', () => {
- expect(window.Flash).toHaveBeenCalledWith('Balsamiq file could not be loaded.');
- });
- });
});
diff --git a/spec/javascripts/blob/create_branch_dropdown_spec.js b/spec/javascripts/blob/create_branch_dropdown_spec.js
index c1179e572ae..6dbaa47c544 100644
--- a/spec/javascripts/blob/create_branch_dropdown_spec.js
+++ b/spec/javascripts/blob/create_branch_dropdown_spec.js
@@ -1,7 +1,6 @@
-require('~/gl_dropdown');
-require('~/lib/utils/type_utility');
-require('~/blob/create_branch_dropdown');
-require('~/blob/target_branch_dropdown');
+import '~/gl_dropdown';
+import '~/blob/create_branch_dropdown';
+import '~/blob/target_branch_dropdown';
describe('CreateBranchDropdown', () => {
const fixtureTemplate = 'static/target_branch_dropdown.html.raw';
diff --git a/spec/javascripts/blob/target_branch_dropdown_spec.js b/spec/javascripts/blob/target_branch_dropdown_spec.js
index 4fb79663c51..99c9537d2ec 100644
--- a/spec/javascripts/blob/target_branch_dropdown_spec.js
+++ b/spec/javascripts/blob/target_branch_dropdown_spec.js
@@ -1,7 +1,6 @@
-require('~/gl_dropdown');
-require('~/lib/utils/type_utility');
-require('~/blob/create_branch_dropdown');
-require('~/blob/target_branch_dropdown');
+import '~/gl_dropdown';
+import '~/blob/create_branch_dropdown';
+import '~/blob/target_branch_dropdown';
describe('TargetBranchDropdown', () => {
const fixtureTemplate = 'static/target_branch_dropdown.html.raw';
@@ -63,7 +62,7 @@ describe('TargetBranchDropdown', () => {
expect('change.branch').toHaveBeenTriggeredOn(dropdown.$dropdown);
});
- describe('#dropdownData', () => {
+ describe('dropdownData', () => {
it('cache the refs', () => {
const refs = dropdown.cachedRefs;
dropdown.cachedRefs = null;
@@ -88,7 +87,7 @@ describe('TargetBranchDropdown', () => {
});
});
- describe('#setNewBranch', () => {
+ describe('setNewBranch', () => {
it('adds the new branch and select it', () => {
const branchName = 'new_branch';
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index 376e706d1db..447b244c71f 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -8,11 +8,11 @@
import Vue from 'vue';
import '~/boards/models/assignee';
-require('~/boards/models/list');
-require('~/boards/models/label');
-require('~/boards/stores/boards_store');
-const boardCard = require('~/boards/components/board_card').default;
-require('./mock_data');
+import '~/boards/models/list';
+import '~/boards/models/label';
+import '~/boards/stores/boards_store';
+import boardCard from '~/boards/components/board_card';
+import './mock_data';
describe('Issue card', () => {
let vm;
diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js
index 4999933c0c1..832877de71c 100644
--- a/spec/javascripts/boards/board_new_issue_spec.js
+++ b/spec/javascripts/boards/board_new_issue_spec.js
@@ -6,8 +6,8 @@
import Vue from 'vue';
import boardNewIssue from '~/boards/components/board_new_issue';
-require('~/boards/models/list');
-require('./mock_data');
+import '~/boards/models/list';
+import './mock_data';
describe('Issue boards new issue form', () => {
let vm;
@@ -19,6 +19,7 @@ describe('Issue boards new issue form', () => {
};
},
};
+
const submitIssue = () => {
vm.$el.querySelector('.btn-success').click();
};
@@ -107,7 +108,7 @@ describe('Issue boards new issue form', () => {
setTimeout(() => {
submitIssue();
- expect(vm.$el.querySelector('.btn-success').disbled).not.toBe(true);
+ expect(vm.$el.querySelector('.btn-success').disabled).toBe(false);
done();
}, 0);
});
@@ -115,36 +116,43 @@ describe('Issue boards new issue form', () => {
it('clears title after submit', (done) => {
vm.title = 'submit issue';
- setTimeout(() => {
+ Vue.nextTick(() => {
submitIssue();
- expect(vm.title).toBe('');
- done();
- }, 0);
+ setTimeout(() => {
+ expect(vm.title).toBe('');
+ done();
+ }, 0);
+ });
});
- it('adds new issue to list after submit', (done) => {
+ it('adds new issue to top of list after submit request', (done) => {
vm.title = 'submit issue';
setTimeout(() => {
submitIssue();
- expect(list.issues.length).toBe(2);
- expect(list.issues[1].title).toBe('submit issue');
- expect(list.issues[1].subscribed).toBe(true);
- done();
+ setTimeout(() => {
+ expect(list.issues.length).toBe(2);
+ expect(list.issues[0].title).toBe('submit issue');
+ expect(list.issues[0].subscribed).toBe(true);
+ done();
+ }, 0);
}, 0);
});
it('sets detail issue after submit', (done) => {
+ expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe(undefined);
vm.title = 'submit issue';
setTimeout(() => {
submitIssue();
- expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe('submit issue');
- done();
- });
+ setTimeout(() => {
+ expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe('submit issue');
+ done();
+ }, 0);
+ }, 0);
});
it('sets detail list after submit', (done) => {
@@ -153,8 +161,10 @@ describe('Issue boards new issue form', () => {
setTimeout(() => {
submitIssue();
- expect(gl.issueBoards.BoardsStore.detail.list.id).toBe(list.id);
- done();
+ setTimeout(() => {
+ expect(gl.issueBoards.BoardsStore.detail.list.id).toBe(list.id);
+ done();
+ }, 0);
}, 0);
});
});
@@ -169,13 +179,12 @@ describe('Issue boards new issue form', () => {
setTimeout(() => {
expect(list.issues.length).toBe(1);
done();
- }, 500);
+ }, 0);
}, 0);
});
it('shows error', (done) => {
vm.title = 'error';
- submitIssue();
setTimeout(() => {
submitIssue();
@@ -183,7 +192,7 @@ describe('Issue boards new issue form', () => {
setTimeout(() => {
expect(vm.error).toBe(true);
done();
- }, 500);
+ }, 0);
}, 0);
});
});
diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js
new file mode 100644
index 00000000000..c4e8966ad6c
--- /dev/null
+++ b/spec/javascripts/boards/components/board_spec.js
@@ -0,0 +1,112 @@
+import Vue from 'vue';
+import '~/boards/services/board_service';
+import '~/boards/components/board';
+import '~/boards/models/list';
+
+describe('Board component', () => {
+ let vm;
+ let el;
+
+ beforeEach((done) => {
+ loadFixtures('boards/show.html.raw');
+
+ el = document.createElement('div');
+ document.body.appendChild(el);
+
+ // eslint-disable-next-line no-undef
+ gl.boardService = new BoardService('/', '/', 1);
+
+ vm = new gl.issueBoards.Board({
+ propsData: {
+ boardId: '1',
+ disabled: false,
+ issueLinkBase: '/',
+ rootPath: '/',
+ // eslint-disable-next-line no-undef
+ list: new List({
+ id: 1,
+ position: 0,
+ title: 'test',
+ list_type: 'backlog',
+ }),
+ },
+ }).$mount(el);
+
+ Vue.nextTick(done);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+
+ // remove the component from the DOM
+ document.querySelector('.board').remove();
+
+ localStorage.removeItem(`boards.${vm.boardId}.${vm.list.type}.expanded`);
+ });
+
+ it('board is expandable when list type is backlog', () => {
+ expect(
+ vm.$el.classList.contains('is-expandable'),
+ ).toBe(true);
+ });
+
+ it('board is expandable when list type is closed', (done) => {
+ vm.list.type = 'closed';
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.classList.contains('is-expandable'),
+ ).toBe(true);
+
+ done();
+ });
+ });
+
+ it('board is not expandable when list type is label', (done) => {
+ vm.list.type = 'label';
+ vm.list.isExpandable = false;
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.classList.contains('is-expandable'),
+ ).toBe(false);
+
+ done();
+ });
+ });
+
+ it('collapses when clicking header', (done) => {
+ vm.$el.querySelector('.board-header').click();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.classList.contains('is-collapsed'),
+ ).toBe(true);
+
+ done();
+ });
+ });
+
+ it('created sets isExpanded to true from localStorage', (done) => {
+ vm.$el.querySelector('.board-header').click();
+
+ return Vue.nextTick()
+ .then(() => {
+ expect(
+ vm.$el.classList.contains('is-collapsed'),
+ ).toBe(true);
+
+ // call created manually
+ vm.$options.created[0].call(vm);
+
+ return Vue.nextTick();
+ })
+ .then(() => {
+ expect(
+ vm.$el.classList.contains('is-collapsed'),
+ ).toBe(true);
+
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index fddde799d01..bd9b4fbfdd3 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -129,7 +129,7 @@ describe('Issue card component', () => {
it('sets title', () => {
expect(
- component.$el.querySelector('.card-assignee a').getAttribute('title'),
+ component.$el.querySelector('.card-assignee img').getAttribute('data-original-title'),
).toContain(`Assigned to ${user.name}`);
});
diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js
index 8ec96bdb583..4c8a48580d7 100644
--- a/spec/javascripts/build_spec.js
+++ b/spec/javascripts/build_spec.js
@@ -8,13 +8,12 @@ import '~/breakpoints';
import 'vendor/jquery.nicescroll';
describe('Build', () => {
- const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1`;
+ const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`;
preloadFixtures('builds/build-with-artifacts.html.raw');
beforeEach(() => {
loadFixtures('builds/build-with-artifacts.html.raw');
- spyOn($, 'ajax');
});
describe('class constructor', () => {
@@ -33,7 +32,6 @@ describe('Build', () => {
it('copies build options', function () {
expect(this.build.pageUrl).toBe(BUILD_URL);
- expect(this.build.buildUrl).toBe(`${BUILD_URL}.json`);
expect(this.build.buildStatus).toBe('success');
expect(this.build.buildStage).toBe('test');
expect(this.build.state).toBe('');
@@ -60,32 +58,19 @@ describe('Build', () => {
it('displays the remove date correctly', () => {
const removeDateElement = document.querySelector('.js-artifacts-remove');
- expect(removeDateElement.innerText.trim()).toBe('1 year');
+ expect(removeDateElement.innerText.trim()).toBe('1 year remaining');
});
});
describe('running build', () => {
- beforeEach(function () {
- this.build = new 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(gl.utils, 'visitUrl');
- jasmine.clock().tick(4001);
-
- expect($.ajax.calls.count()).toBe(1);
-
- // We have to do it this way to prevent Webpack to fail to compile
- // when destructuring assignments and reusing
- // the same variables names inside the same scope
- let args = $.ajax.calls.argsFor(0)[0];
-
- expect(args.url).toBe(`${BUILD_URL}/trace.json`);
- expect(args.dataType).toBe('json');
- expect(args.success).toEqual(jasmine.any(Function));
-
- args.success.call($, {
+ deferred1.resolve({
html: '<span>Update<span>',
status: 'running',
state: 'newstate',
@@ -93,20 +78,9 @@ describe('Build', () => {
complete: false,
});
- expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
- expect(this.build.state).toBe('newstate');
-
- jasmine.clock().tick(4001);
-
- expect($.ajax.calls.count()).toBe(3);
-
- args = $.ajax.calls.argsFor(2)[0];
- expect(args.url).toBe(`${BUILD_URL}/trace.json`);
- expect(args.dataType).toBe('json');
- expect(args.data.state).toBe('newstate');
- expect(args.success).toEqual(jasmine.any(Function));
+ deferred2.resolve();
- args.success.call($, {
+ deferred3.resolve({
html: '<span>More</span>',
status: 'running',
state: 'finalstate',
@@ -114,150 +88,222 @@ describe('Build', () => {
complete: true,
});
+ this.build = new Build();
+
+ expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
+ expect(this.build.state).toBe('newstate');
+
+ jasmine.clock().tick(4001);
+
expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
expect(this.build.state).toBe('finalstate');
});
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(gl.utils, 'visitUrl');
- jasmine.clock().tick(4001);
- let args = $.ajax.calls.argsFor(0)[0];
- args.success.call($, {
- html: '<span>Update</span>',
+ deferred1.resolve({
+ html: '<span>Update<span>',
status: 'running',
append: false,
complete: false,
});
- expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
+ deferred2.resolve();
- jasmine.clock().tick(4001);
- args = $.ajax.calls.argsFor(2)[0];
- args.success.call($, {
+ deferred3.resolve({
html: '<span>Different</span>',
status: 'running',
append: false,
});
+ this.build = new Build();
+
+ expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
+
+ jasmine.clock().tick(4001);
+
expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
});
it('reloads the page when the build is done', () => {
spyOn(gl.utils, 'visitUrl');
+ const deferred = $.Deferred();
- jasmine.clock().tick(4001);
- const [{ success }] = $.ajax.calls.argsFor(0);
- success.call($, {
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+ deferred.resolve({
html: '<span>Final</span>',
status: 'passed',
append: true,
complete: true,
});
+ this.build = new Build();
+
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BUILD_URL);
});
+ });
- describe('truncated information', () => {
- describe('when size is less than total', () => {
- it('shows information about truncated log', () => {
- jasmine.clock().tick(4001);
- const [{ success }] = $.ajax.calls.argsFor(0);
-
- success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- });
-
- expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
+ describe('truncated information', () => {
+ describe('when size is less than total', () => {
+ it('shows information about truncated log', () => {
+ spyOn(gl.utils, 'visitUrl');
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+
+ deferred.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
});
- it('shows the size in KiB', () => {
- jasmine.clock().tick(4001);
- const [{ success }] = $.ajax.calls.argsFor(0);
- const size = 50;
-
- success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size,
- total: 100,
- });
-
- expect(
- document.querySelector('.js-truncated-info-size').textContent.trim(),
- ).toEqual(`${bytesToKiB(size)}`);
+ this.build = new Build();
+
+ expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
+ });
+
+ it('shows the size in KiB', () => {
+ const size = 50;
+ spyOn(gl.utils, 'visitUrl');
+ const deferred = $.Deferred();
+
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+ deferred.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size,
+ total: 100,
});
- it('shows incremented size', () => {
- jasmine.clock().tick(4001);
- let args = $.ajax.calls.argsFor(0)[0];
- args.success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- });
-
- expect(
- document.querySelector('.js-truncated-info-size').textContent.trim(),
- ).toEqual(`${bytesToKiB(50)}`);
-
- jasmine.clock().tick(4001);
- args = $.ajax.calls.argsFor(2)[0];
- args.success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: true,
- size: 10,
- total: 100,
- });
-
- expect(
- document.querySelector('.js-truncated-info-size').textContent.trim(),
- ).toEqual(`${bytesToKiB(60)}`);
+ this.build = new Build();
+
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${bytesToKiB(size)}`);
+ });
+
+ it('shows incremented size', () => {
+ const deferred1 = $.Deferred();
+ const deferred2 = $.Deferred();
+ const deferred3 = $.Deferred();
+
+ spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise());
+
+ spyOn(gl.utils, 'visitUrl');
+
+ deferred1.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
});
- it('renders the raw link', () => {
- jasmine.clock().tick(4001);
- const [{ success }] = $.ajax.calls.argsFor(0);
-
- success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- });
-
- expect(
- document.querySelector('.js-raw-link').textContent.trim(),
- ).toContain('Complete Raw');
+ deferred2.resolve();
+
+ this.build = new Build();
+
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${bytesToKiB(50)}`);
+
+ jasmine.clock().tick(4001);
+
+ deferred3.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: true,
+ size: 10,
+ total: 100,
});
+
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${bytesToKiB(60)}`);
});
- describe('when size is equal than total', () => {
- it('does not show the trunctated information', () => {
- jasmine.clock().tick(4001);
- const [{ success }] = $.ajax.calls.argsFor(0);
+ it('renders the raw link', () => {
+ const deferred = $.Deferred();
+ spyOn(gl.utils, 'visitUrl');
+
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+ deferred.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ });
- success.call($, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 100,
- total: 100,
- });
+ this.build = new Build();
- expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
+ expect(
+ document.querySelector('.js-raw-link').textContent.trim(),
+ ).toContain('Complete Raw');
+ });
+ });
+
+ describe('when size is equal than total', () => {
+ it('does not show the trunctated information', () => {
+ const deferred = $.Deferred();
+ spyOn(gl.utils, 'visitUrl');
+
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+ deferred.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 100,
+ total: 100,
});
+
+ this.build = new Build();
+
+ expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
+ });
+ });
+ });
+
+ describe('output trace', () => {
+ beforeEach(() => {
+ const deferred = $.Deferred();
+ spyOn(gl.utils, 'visitUrl');
+
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+ deferred.resolve({
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
});
+
+ this.build = new Build();
+ });
+
+ it('should render trace controls', () => {
+ const controllers = document.querySelector('.controllers');
+
+ expect(controllers.querySelector('.js-raw-link-controller')).toBeDefined();
+ expect(controllers.querySelector('.js-erase-link')).toBeDefined();
+ expect(controllers.querySelector('.js-scroll-up')).toBeDefined();
+ expect(controllers.querySelector('.js-scroll-down')).toBeDefined();
+ });
+
+ it('should render received output', () => {
+ expect(
+ document.querySelector('.js-build-output').innerHTML,
+ ).toEqual('<span>Update</span>');
});
});
});
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index ad31448f81c..ebfd60198b2 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -1,12 +1,17 @@
import Vue from 'vue';
import PipelinesTable from '~/commit/pipelines/pipelines_table';
-import pipeline from './mock_data';
describe('Pipelines table in Commits and Merge requests', () => {
+ const jsonFixtureName = 'pipelines/pipelines.json';
+ let pipeline;
+
preloadFixtures('static/pipelines_table.html.raw');
+ preloadFixtures(jsonFixtureName);
beforeEach(() => {
loadFixtures('static/pipelines_table.html.raw');
+ const pipelines = getJSONFixture(jsonFixtureName).pipelines;
+ pipeline = pipelines.find(p => p.id === 1);
});
describe('successful request', () => {
@@ -66,7 +71,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
it('should render a table with the received pipelines', (done) => {
setTimeout(() => {
- expect(this.component.$el.querySelectorAll('table > tbody > tr').length).toEqual(1);
+ expect(this.component.$el.querySelectorAll('.ci-table .commit').length).toEqual(1);
expect(this.component.$el.querySelector('.realtime-loading')).toBe(null);
expect(this.component.$el.querySelector('.empty-state')).toBe(null);
expect(this.component.$el.querySelector('.js-pipelines-error-state')).toBe(null);
@@ -103,7 +108,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
expect(this.component.$el.querySelector('.js-pipelines-error-state')).toBeDefined();
expect(this.component.$el.querySelector('.realtime-loading')).toBe(null);
expect(this.component.$el.querySelector('.js-empty-state')).toBe(null);
- expect(this.component.$el.querySelector('table')).toBe(null);
+ expect(this.component.$el.querySelector('.ci-table')).toBe(null);
done();
}, 0);
});
diff --git a/spec/javascripts/commits_spec.js b/spec/javascripts/commits_spec.js
index 05260760c43..44a4386b250 100644
--- a/spec/javascripts/commits_spec.js
+++ b/spec/javascripts/commits_spec.js
@@ -1,8 +1,8 @@
/* global CommitsList */
-require('vendor/jquery.endless-scroll');
-require('~/pager');
-require('~/commits');
+import 'vendor/jquery.endless-scroll';
+import '~/pager';
+import '~/commits';
(() => {
// TODO: remove this hack!
@@ -28,6 +28,32 @@ require('~/commits');
expect(CommitsList).toBeDefined();
});
+ describe('processCommits', () => {
+ it('should join commit headers', () => {
+ CommitsList.$contentList = $(`
+ <div>
+ <li class="commit-header" data-day="2016-09-20">
+ <span class="day">20 Sep, 2016</span>
+ <span class="commits-count">1 commit</span>
+ </li>
+ <li class="commit"></li>
+ </div>
+ `);
+
+ const data = `
+ <li class="commit-header" data-day="2016-09-20">
+ <span class="day">20 Sep, 2016</span>
+ <span class="commits-count">1 commit</span>
+ </li>
+ <li class="commit"></li>
+ `;
+
+ // The last commit header should be removed
+ // since the previous one has the same data-day value.
+ expect(CommitsList.processCommits(data).find('li.commit-header').length).toBe(0);
+ });
+ });
+
describe('on entering input', () => {
let ajaxSpy;
diff --git a/spec/javascripts/copy_as_gfm_spec.js b/spec/javascripts/copy_as_gfm_spec.js
new file mode 100644
index 00000000000..ded450749d3
--- /dev/null
+++ b/spec/javascripts/copy_as_gfm_spec.js
@@ -0,0 +1,49 @@
+import '~/copy_as_gfm';
+
+(() => {
+ describe('gl.CopyAsGFM', () => {
+ describe('gl.CopyAsGFM.pasteGFM', () => {
+ function callPasteGFM() {
+ const e = {
+ originalEvent: {
+ clipboardData: {
+ getData(mimeType) {
+ // When GFM code is copied, we put the regular plain text
+ // on the clipboard as `text/plain`, and the GFM as `text/x-gfm`.
+ // This emulates the behavior of `getData` with that data.
+ if (mimeType === 'text/plain') {
+ return 'code';
+ }
+ if (mimeType === 'text/x-gfm') {
+ return '`code`';
+ }
+ return null;
+ },
+ },
+ },
+ preventDefault() {},
+ };
+
+ window.gl.CopyAsGFM.pasteGFM(e);
+ }
+
+ it('wraps pasted code when not already in code tags', () => {
+ spyOn(window.gl.utils, 'insertText').and.callFake((el, textFunc) => {
+ const insertedText = textFunc('This is code: ', '');
+ expect(insertedText).toEqual('`code`');
+ });
+
+ callPasteGFM();
+ });
+
+ it('does not wrap pasted code when already in code tags', () => {
+ spyOn(window.gl.utils, 'insertText').and.callFake((el, textFunc) => {
+ const insertedText = textFunc('This is code: `', '`');
+ expect(insertedText).toEqual('code');
+ });
+
+ callPasteGFM();
+ });
+ });
+ });
+})();
diff --git a/spec/javascripts/datetime_utility_spec.js b/spec/javascripts/datetime_utility_spec.js
index d5eec10be42..c82ad0bea48 100644
--- a/spec/javascripts/datetime_utility_spec.js
+++ b/spec/javascripts/datetime_utility_spec.js
@@ -1,7 +1,27 @@
-require('~/lib/utils/datetime_utility');
+import '~/lib/utils/datetime_utility';
(() => {
describe('Date time utils', () => {
+ describe('timeFor', () => {
+ it('returns `past due` when in past', () => {
+ const date = new Date();
+ date.setFullYear(date.getFullYear() - 1);
+
+ expect(
+ gl.utils.timeFor(date),
+ ).toBe('Past due');
+ });
+
+ it('returns remaining time when in the future', () => {
+ const date = new Date();
+ date.setFullYear(date.getFullYear() + 1);
+
+ expect(
+ gl.utils.timeFor(date),
+ ).toBe('1 year remaining');
+ });
+ });
+
describe('get day name', () => {
it('should return Sunday', () => {
const day = gl.utils.getDayName(new Date('07/17/2016'));
diff --git a/spec/javascripts/deploy_keys/components/key_spec.js b/spec/javascripts/deploy_keys/components/key_spec.js
index 793ab8c451d..a4b98f6140d 100644
--- a/spec/javascripts/deploy_keys/components/key_spec.js
+++ b/spec/javascripts/deploy_keys/components/key_spec.js
@@ -39,9 +39,15 @@ describe('Deploy keys key', () => {
).toBe(`created ${gl.utils.getTimeago().format(deployKey.created_at)}`);
});
+ it('shows edit button', () => {
+ expect(
+ vm.$el.querySelectorAll('.btn')[0].textContent.trim(),
+ ).toBe('Edit');
+ });
+
it('shows remove button', () => {
expect(
- vm.$el.querySelector('.btn').textContent.trim(),
+ vm.$el.querySelectorAll('.btn')[1].textContent.trim(),
).toBe('Remove');
});
@@ -71,9 +77,15 @@ describe('Deploy keys key', () => {
setTimeout(done);
});
+ it('shows edit button', () => {
+ expect(
+ vm.$el.querySelectorAll('.btn')[0].textContent.trim(),
+ ).toBe('Edit');
+ });
+
it('shows enable button', () => {
expect(
- vm.$el.querySelector('.btn').textContent.trim(),
+ vm.$el.querySelectorAll('.btn')[1].textContent.trim(),
).toBe('Enable');
});
@@ -82,7 +94,7 @@ describe('Deploy keys key', () => {
Vue.nextTick(() => {
expect(
- vm.$el.querySelector('.btn').textContent.trim(),
+ vm.$el.querySelectorAll('.btn')[1].textContent.trim(),
).toBe('Disable');
done();
diff --git a/spec/javascripts/diff_comments_store_spec.js b/spec/javascripts/diff_comments_store_spec.js
index 66ece7e4f41..d6fc6b56b82 100644
--- a/spec/javascripts/diff_comments_store_spec.js
+++ b/spec/javascripts/diff_comments_store_spec.js
@@ -1,9 +1,9 @@
/* eslint-disable jasmine/no-global-setup, dot-notation, jasmine/no-expect-in-setup-teardown, max-len */
/* global CommentsStore */
-require('~/diff_notes/models/discussion');
-require('~/diff_notes/models/note');
-require('~/diff_notes/stores/comments');
+import '~/diff_notes/models/discussion';
+import '~/diff_notes/models/note';
+import '~/diff_notes/stores/comments';
function createDiscussion(noteId = 1, resolved = true) {
CommentsStore.create({
diff --git a/spec/javascripts/droplab/drop_down_spec.js b/spec/javascripts/droplab/drop_down_spec.js
index e7786e8cc2c..2bbcebeeac0 100644
--- a/spec/javascripts/droplab/drop_down_spec.js
+++ b/spec/javascripts/droplab/drop_down_spec.js
@@ -1,5 +1,3 @@
-/* eslint-disable */
-
import DropDown from '~/droplab/drop_down';
import utils from '~/droplab/utils';
import { SELECTED_CLASS, IGNORE_CLASS } from '~/droplab/constants';
@@ -17,7 +15,7 @@ describe('DropDown', function () {
it('sets the .hidden property to true', function () {
expect(this.dropdown.hidden).toBe(true);
- })
+ });
it('sets the .list property', function () {
expect(this.dropdown.list).toBe(this.list);
@@ -152,7 +150,7 @@ describe('DropDown', function () {
it('should call addSelectedClass', function () {
expect(this.dropdown.addSelectedClass).toHaveBeenCalledWith(this.closestElement);
- })
+ });
it('should call .preventDefault', function () {
expect(this.event.preventDefault).toHaveBeenCalled();
@@ -293,36 +291,6 @@ describe('DropDown', function () {
});
});
- describe('toggle', function () {
- beforeEach(function () {
- this.dropdown = { hidden: true, show: () => {}, hide: () => {} };
-
- spyOn(this.dropdown, 'show');
- spyOn(this.dropdown, 'hide');
-
- DropDown.prototype.toggle.call(this.dropdown);
- });
-
- it('should call .show if hidden is true', function () {
- expect(this.dropdown.show).toHaveBeenCalled();
- });
-
- describe('if hidden is false', function () {
- beforeEach(function () {
- this.dropdown = { hidden: false, show: () => {}, hide: () => {} };
-
- spyOn(this.dropdown, 'show');
- spyOn(this.dropdown, 'hide');
-
- DropDown.prototype.toggle.call(this.dropdown);
- });
-
- it('should call .show if hidden is true', function () {
- expect(this.dropdown.hide).toHaveBeenCalled();
- });
- });
- });
-
describe('setData', function () {
beforeEach(function () {
this.dropdown = { render: () => {} };
@@ -399,7 +367,7 @@ describe('DropDown', function () {
expect(this.data.map).toHaveBeenCalledWith(jasmine.any(Function));
});
- it('should call .renderChildren for each data item', function() {
+ it('should call .renderChildren for each data item', function () {
expect(this.dropdown.renderChildren.calls.count()).toBe(this.data.length);
});
@@ -407,7 +375,7 @@ describe('DropDown', function () {
expect(this.renderableList.innerHTML).toBe('01');
});
- describe('if no data argument is passed' , function () {
+ describe('if no data argument is passed', function () {
beforeEach(function () {
this.data.map.calls.reset();
this.dropdown.renderChildren.calls.reset();
@@ -446,14 +414,14 @@ describe('DropDown', function () {
describe('renderChildren', function () {
beforeEach(function () {
this.templateString = 'templateString';
- this.dropdown = { setImagesSrc: () => {}, templateString: this.templateString };
+ this.dropdown = { templateString: this.templateString };
this.data = { droplab_hidden: true };
this.html = 'html';
this.template = { firstChild: { outerHTML: 'outerHTML', style: {} } };
spyOn(utils, 'template').and.returnValue(this.html);
spyOn(document, 'createElement').and.returnValue(this.template);
- spyOn(this.dropdown, 'setImagesSrc');
+ spyOn(DropDown, 'setImagesSrc');
this.renderChildren = DropDown.prototype.renderChildren.call(this.dropdown, this.data);
});
@@ -471,7 +439,7 @@ describe('DropDown', function () {
});
it('should call .setImagesSrc with the template', function () {
- expect(this.dropdown.setImagesSrc).toHaveBeenCalledWith(this.template);
+ expect(DropDown.setImagesSrc).toHaveBeenCalledWith(this.template);
});
it('should set the template display to none', function () {
@@ -496,12 +464,11 @@ describe('DropDown', function () {
describe('setImagesSrc', function () {
beforeEach(function () {
- this.dropdown = {};
this.template = { querySelectorAll: () => {} };
spyOn(this.template, 'querySelectorAll').and.returnValue([]);
- DropDown.prototype.setImagesSrc.call(this.dropdown, this.template);
+ DropDown.setImagesSrc(this.template);
});
it('should call .querySelectorAll', function () {
@@ -562,7 +529,7 @@ describe('DropDown', function () {
describe('toggle', function () {
beforeEach(function () {
- this.hidden = true
+ this.hidden = true;
this.dropdown = { hidden: this.hidden, show: () => {}, hide: () => {} };
spyOn(this.dropdown, 'show');
@@ -577,7 +544,7 @@ describe('DropDown', function () {
describe('if .hidden is false', function () {
beforeEach(function () {
- this.hidden = false
+ this.hidden = false;
this.dropdown = { hidden: this.hidden, show: () => {}, hide: () => {} };
spyOn(this.dropdown, 'show');
diff --git a/spec/javascripts/droplab/hook_spec.js b/spec/javascripts/droplab/hook_spec.js
index 8ebdcdd1404..75bf5f3d611 100644
--- a/spec/javascripts/droplab/hook_spec.js
+++ b/spec/javascripts/droplab/hook_spec.js
@@ -1,5 +1,3 @@
-/* eslint-disable */
-
import Hook from '~/droplab/hook';
import * as dropdownSrc from '~/droplab/drop_down';
@@ -73,10 +71,4 @@ describe('Hook', function () {
});
});
});
-
- describe('addEvents', function () {
- it('should exist', function () {
- expect(Hook.prototype.hasOwnProperty('addEvents')).toBe(true);
- });
- });
});
diff --git a/spec/javascripts/droplab/plugins/ajax_filter_spec.js b/spec/javascripts/droplab/plugins/ajax_filter_spec.js
new file mode 100644
index 00000000000..8155d98b543
--- /dev/null
+++ b/spec/javascripts/droplab/plugins/ajax_filter_spec.js
@@ -0,0 +1,72 @@
+import AjaxCache from '~/lib/utils/ajax_cache';
+import AjaxFilter from '~/droplab/plugins/ajax_filter';
+
+describe('AjaxFilter', () => {
+ let dummyConfig;
+ const dummyData = 'dummy data';
+ let dummyList;
+
+ beforeEach(() => {
+ dummyConfig = {
+ endpoint: 'dummy endpoint',
+ searchKey: 'dummy search key',
+ };
+ dummyList = {
+ data: [],
+ list: document.createElement('div'),
+ };
+
+ AjaxFilter.hook = {
+ config: {
+ AjaxFilter: dummyConfig,
+ },
+ list: dummyList,
+ };
+ });
+
+ describe('trigger', () => {
+ let ajaxSpy;
+
+ beforeEach(() => {
+ spyOn(AjaxCache, 'retrieve').and.callFake(url => ajaxSpy(url));
+ spyOn(AjaxFilter, '_loadData');
+
+ dummyConfig.onLoadingFinished = jasmine.createSpy('spy');
+
+ const dynamicList = document.createElement('div');
+ dynamicList.dataset.dynamic = true;
+ dummyList.list.appendChild(dynamicList);
+ });
+
+ it('calls onLoadingFinished after loading data', (done) => {
+ ajaxSpy = (url) => {
+ expect(url).toBe('dummy endpoint?dummy search key=');
+ return Promise.resolve(dummyData);
+ };
+
+ AjaxFilter.trigger()
+ .then(() => {
+ expect(dummyConfig.onLoadingFinished.calls.count()).toBe(1);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not call onLoadingFinished if Ajax call fails', (done) => {
+ const dummyError = new Error('My dummy is sick! :-(');
+ ajaxSpy = (url) => {
+ expect(url).toBe('dummy endpoint?dummy search key=');
+ return Promise.reject(dummyError);
+ };
+
+ AjaxFilter.trigger()
+ .then(done.fail)
+ .catch((error) => {
+ expect(error).toBe(dummyError);
+ expect(dummyConfig.onLoadingFinished.calls.count()).toBe(0);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js
index 1c54cc3054c..6639a6b5e7b 100644
--- a/spec/javascripts/environments/environment_spec.js
+++ b/spec/javascripts/environments/environment_spec.js
@@ -41,7 +41,7 @@ describe('Environment', () => {
setTimeout(() => {
expect(
component.$el.querySelector('.js-new-environment-button').textContent,
- ).toContain('New Environment');
+ ).toContain('New environment');
expect(
component.$el.querySelector('.js-blank-state-title').textContent,
@@ -271,7 +271,7 @@ describe('Environment', () => {
// wait for next async request
setTimeout(() => {
expect(component.$el.querySelectorAll('.js-child-row').length).toEqual(1);
- expect(component.$el.querySelector('td.text-center > a.btn').textContent).toContain('Show all');
+ expect(component.$el.querySelector('.text-center > a.btn').textContent).toContain('Show all');
Vue.http.interceptors = _.without(Vue.http.interceptors, folderInterceptor);
done();
diff --git a/spec/javascripts/environments/environment_table_spec.js b/spec/javascripts/environments/environment_table_spec.js
index effbc6c3ee1..2862971bec4 100644
--- a/spec/javascripts/environments/environment_table_spec.js
+++ b/spec/javascripts/environments/environment_table_spec.js
@@ -29,6 +29,6 @@ describe('Environment item', () => {
},
}).$mount();
- expect(component.$el.tagName).toEqual('TABLE');
+ expect(component.$el.getAttribute('class')).toContain('ci-table');
});
});
diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js
index f617c4bdffe..6e855530b21 100644
--- a/spec/javascripts/environments/environments_store_spec.js
+++ b/spec/javascripts/environments/environments_store_spec.js
@@ -123,4 +123,13 @@ describe('Store', () => {
expect(store.state.paginationInformation).toEqual(expectedResult);
});
});
+
+ describe('getOpenFolders', () => {
+ it('should return open folder', () => {
+ store.storeEnvironments(serverData);
+
+ store.toggleFolder(store.state.environments[1]);
+ expect(store.getOpenFolders()[0]).toEqual(store.state.environments[1]);
+ });
+ });
});
diff --git a/spec/javascripts/extensions/array_spec.js b/spec/javascripts/extensions/array_spec.js
index 4b871fe967d..b1b81b4efc2 100644
--- a/spec/javascripts/extensions/array_spec.js
+++ b/spec/javascripts/extensions/array_spec.js
@@ -1,6 +1,6 @@
/* eslint-disable space-before-function-paren, no-var */
-require('~/extensions/array');
+import '~/extensions/array';
(function() {
describe('Array extensions', function() {
diff --git a/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js b/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js
index d0f09a561d5..79447787fc9 100644
--- a/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js
+++ b/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js
@@ -2,6 +2,8 @@ import Vue from 'vue';
import eventHub from '~/filtered_search/event_hub';
import RecentSearchesDropdownContent from '~/filtered_search/components/recent_searches_dropdown_content';
+import '~/filtered_search/filtered_search_token_keys';
+
const createComponent = (propsData) => {
const Component = Vue.extend(RecentSearchesDropdownContent);
@@ -17,12 +19,14 @@ const trimMarkupWhitespace = text => text.replace(/(\n|\s)+/gm, ' ').trim();
describe('RecentSearchesDropdownContent', () => {
const propsDataWithoutItems = {
items: [],
+ allowedKeys: gl.FilteredSearchTokenKeys.getKeys(),
};
const propsDataWithItems = {
items: [
'foo',
'author:@root label:~foo bar',
],
+ allowedKeys: gl.FilteredSearchTokenKeys.getKeys(),
};
let vm;
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js
index 3f92fe4701e..f7708301b6e 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
@@ -1,7 +1,7 @@
-require('~/filtered_search/dropdown_utils');
-require('~/filtered_search/filtered_search_tokenizer');
-require('~/filtered_search/filtered_search_dropdown');
-require('~/filtered_search/dropdown_user');
+import '~/filtered_search/dropdown_utils';
+import '~/filtered_search/filtered_search_tokenizer';
+import '~/filtered_search/filtered_search_dropdown';
+import '~/filtered_search/dropdown_user';
describe('Dropdown User', () => {
describe('getSearchInput', () => {
@@ -12,7 +12,7 @@ describe('Dropdown User', () => {
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
- dropdownUser = new gl.DropdownUser();
+ dropdownUser = new gl.DropdownUser(null, null, null, gl.FilteredSearchTokenKeys);
});
it('should not return the double quote found in value', () => {
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index c820c955172..f55726379f3 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
@@ -1,9 +1,13 @@
-require('~/extensions/array');
-require('~/filtered_search/dropdown_utils');
-require('~/filtered_search/filtered_search_tokenizer');
-require('~/filtered_search/filtered_search_dropdown_manager');
+import '~/extensions/array';
+import '~/filtered_search/dropdown_utils';
+import '~/filtered_search/filtered_search_tokenizer';
+import '~/filtered_search/filtered_search_dropdown_manager';
+import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
describe('Dropdown Utils', () => {
+ const issueListFixture = 'issues/issue_list.html.raw';
+ preloadFixtures(issueListFixture);
+
describe('getEscapedText', () => {
it('should return same word when it has no space', () => {
const escaped = gl.DropdownUtils.getEscapedText('textWithoutSpace');
@@ -122,6 +126,7 @@ describe('Dropdown Utils', () => {
describe('filterHint', () => {
let input;
+ let allowedKeys;
beforeEach(() => {
setFixtures(`
@@ -133,30 +138,38 @@ describe('Dropdown Utils', () => {
`);
input = document.getElementById('test');
+ allowedKeys = gl.FilteredSearchTokenKeys.getKeys();
});
+ function config() {
+ return {
+ input,
+ allowedKeys,
+ };
+ }
+
it('should filter', () => {
input.value = 'l';
- let updatedItem = gl.DropdownUtils.filterHint(input, {
+ let updatedItem = gl.DropdownUtils.filterHint(config(), {
hint: 'label',
});
expect(updatedItem.droplab_hidden).toBe(false);
input.value = 'o';
- updatedItem = gl.DropdownUtils.filterHint(input, {
+ updatedItem = gl.DropdownUtils.filterHint(config(), {
hint: 'label',
});
expect(updatedItem.droplab_hidden).toBe(true);
});
it('should return droplab_hidden false when item has no hint', () => {
- const updatedItem = gl.DropdownUtils.filterHint(input, {}, '');
+ const updatedItem = gl.DropdownUtils.filterHint(config(), {}, '');
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should allow multiple if item.type is array', () => {
input.value = 'label:~first la';
- const updatedItem = gl.DropdownUtils.filterHint(input, {
+ const updatedItem = gl.DropdownUtils.filterHint(config(), {
hint: 'label',
type: 'array',
});
@@ -165,12 +178,12 @@ describe('Dropdown Utils', () => {
it('should prevent multiple if item.type is not array', () => {
input.value = 'milestone:~first mile';
- let updatedItem = gl.DropdownUtils.filterHint(input, {
+ let updatedItem = gl.DropdownUtils.filterHint(config(), {
hint: 'milestone',
});
expect(updatedItem.droplab_hidden).toBe(true);
- updatedItem = gl.DropdownUtils.filterHint(input, {
+ updatedItem = gl.DropdownUtils.filterHint(config(), {
hint: 'milestone',
type: 'string',
});
@@ -305,4 +318,29 @@ describe('Dropdown Utils', () => {
});
});
});
+
+ describe('getSearchQuery', () => {
+ let authorToken;
+
+ beforeEach(() => {
+ loadFixtures(issueListFixture);
+
+ authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '@user');
+ const searchTermToken = FilteredSearchSpecHelper.createSearchVisualToken('search term');
+
+ const tokensContainer = document.querySelector('.tokens-container');
+ tokensContainer.appendChild(searchTermToken);
+ tokensContainer.appendChild(authorToken);
+ });
+
+ it('uses original value if present', () => {
+ const originalValue = 'original dance';
+ const valueContainer = authorToken.querySelector('.value-container');
+ valueContainer.dataset.originalValue = originalValue;
+
+ const searchQuery = gl.DropdownUtils.getSearchQuery();
+
+ expect(searchQuery).toBe(' search term author:original dance');
+ });
+ });
});
diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
index 17bf8932489..c92a147b937 100644
--- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
@@ -1,7 +1,7 @@
-require('~/extensions/array');
-require('~/filtered_search/filtered_search_visual_tokens');
-require('~/filtered_search/filtered_search_tokenizer');
-require('~/filtered_search/filtered_search_dropdown_manager');
+import '~/extensions/array';
+import '~/filtered_search/filtered_search_visual_tokens';
+import '~/filtered_search/filtered_search_tokenizer';
+import '~/filtered_search/filtered_search_dropdown_manager';
describe('Filtered Search Dropdown Manager', () => {
describe('addWordToInput', () => {
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index 063d547d00c..6d00d71f145 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
@@ -1,14 +1,13 @@
import * as recentSearchesStoreSrc from '~/filtered_search/stores/recent_searches_store';
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
-
-require('~/lib/utils/url_utility');
-require('~/lib/utils/common_utils');
-require('~/filtered_search/filtered_search_token_keys');
-require('~/filtered_search/filtered_search_tokenizer');
-require('~/filtered_search/filtered_search_dropdown_manager');
-require('~/filtered_search/filtered_search_manager');
-const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper');
+import '~/lib/utils/url_utility';
+import '~/lib/utils/common_utils';
+import '~/filtered_search/filtered_search_token_keys';
+import '~/filtered_search/filtered_search_tokenizer';
+import '~/filtered_search/filtered_search_dropdown_manager';
+import '~/filtered_search/filtered_search_manager';
+import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
describe('Filtered Search Manager', () => {
let input;
@@ -58,6 +57,7 @@ describe('Filtered Search Manager', () => {
input = document.querySelector('.filtered-search');
tokensContainer = document.querySelector('.tokens-container');
manager = new gl.FilteredSearchManager();
+ manager.setup();
});
afterEach(() => {
@@ -73,6 +73,7 @@ describe('Filtered Search Manager', () => {
spyOn(recentSearchesStoreSrc, 'default');
filteredSearchManager = new gl.FilteredSearchManager();
+ filteredSearchManager.setup();
return filteredSearchManager;
});
@@ -81,6 +82,7 @@ describe('Filtered Search Manager', () => {
expect(RecentSearchesService.isAvailable).toHaveBeenCalled();
expect(recentSearchesStoreSrc.default).toHaveBeenCalledWith({
isLocalStorageAvailable,
+ allowedKeys: gl.FilteredSearchTokenKeys.getKeys(),
});
});
@@ -89,11 +91,55 @@ describe('Filtered Search Manager', () => {
spyOn(window, 'Flash');
filteredSearchManager = new gl.FilteredSearchManager();
+ filteredSearchManager.setup();
expect(window.Flash).not.toHaveBeenCalled();
});
});
+ describe('searchState', () => {
+ beforeEach(() => {
+ spyOn(gl.FilteredSearchManager.prototype, 'search').and.callFake(() => {});
+ });
+
+ it('should blur button', () => {
+ const e = {
+ currentTarget: {
+ blur: () => {},
+ },
+ };
+ spyOn(e.currentTarget, 'blur').and.callThrough();
+ manager.searchState(e);
+
+ expect(e.currentTarget.blur).toHaveBeenCalled();
+ });
+
+ it('should not call search if there is no state', () => {
+ const e = {
+ currentTarget: {
+ blur: () => {},
+ },
+ };
+
+ manager.searchState(e);
+ expect(gl.FilteredSearchManager.prototype.search).not.toHaveBeenCalled();
+ });
+
+ it('should call search when there is state', () => {
+ const e = {
+ currentTarget: {
+ blur: () => {},
+ dataset: {
+ state: 'opened',
+ },
+ },
+ };
+
+ manager.searchState(e);
+ expect(gl.FilteredSearchManager.prototype.search).toHaveBeenCalledWith('opened');
+ });
+ });
+
describe('search', () => {
const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened';
@@ -313,42 +359,6 @@ describe('Filtered Search Manager', () => {
});
});
- describe('unselects token', () => {
- beforeEach(() => {
- tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
- ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug', true)}
- ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
- ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~awesome')}
- `);
- });
-
- it('unselects token when input is clicked', () => {
- const selectedToken = tokensContainer.querySelector('.js-visual-token .selected');
-
- expect(selectedToken.classList.contains('selected')).toEqual(true);
- expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled();
-
- // Click directly on input attached to document
- // so that the click event will propagate properly
- document.querySelector('.filtered-search').click();
-
- expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled();
- expect(selectedToken.classList.contains('selected')).toEqual(false);
- });
-
- it('unselects token when document.body is clicked', () => {
- const selectedToken = tokensContainer.querySelector('.js-visual-token .selected');
-
- expect(selectedToken.classList.contains('selected')).toEqual(true);
- expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled();
-
- document.body.click();
-
- expect(selectedToken.classList.contains('selected')).toEqual(false);
- expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled();
- });
- });
-
describe('toggleInputContainerFocus', () => {
it('toggles on focus', () => {
input.focus();
diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
index 6f9fa434c35..1a7631994b4 100644
--- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
@@ -1,5 +1,5 @@
-require('~/extensions/array');
-require('~/filtered_search/filtered_search_token_keys');
+import '~/extensions/array';
+import '~/filtered_search/filtered_search_token_keys';
describe('Filtered Search Token Keys', () => {
describe('get', () => {
diff --git a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
index 3e2e577f115..e4a15c83c23 100644
--- a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
@@ -1,11 +1,13 @@
-require('~/extensions/array');
-require('~/filtered_search/filtered_search_token_keys');
-require('~/filtered_search/filtered_search_tokenizer');
+import '~/extensions/array';
+import '~/filtered_search/filtered_search_token_keys';
+import '~/filtered_search/filtered_search_tokenizer';
describe('Filtered Search Tokenizer', () => {
+ const allowedKeys = gl.FilteredSearchTokenKeys.getKeys();
+
describe('processTokens', () => {
it('returns for input containing only search value', () => {
- const results = gl.FilteredSearchTokenizer.processTokens('searchTerm');
+ const results = gl.FilteredSearchTokenizer.processTokens('searchTerm', allowedKeys);
expect(results.searchToken).toBe('searchTerm');
expect(results.tokens.length).toBe(0);
expect(results.lastToken).toBe(results.searchToken);
@@ -13,7 +15,7 @@ describe('Filtered Search Tokenizer', () => {
it('returns for input containing only tokens', () => {
const results = gl.FilteredSearchTokenizer
- .processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none');
+ .processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none', allowedKeys);
expect(results.searchToken).toBe('');
expect(results.tokens.length).toBe(4);
expect(results.tokens[3]).toBe(results.lastToken);
@@ -37,7 +39,7 @@ describe('Filtered Search Tokenizer', () => {
it('returns for input starting with search value and ending with tokens', () => {
const results = gl.FilteredSearchTokenizer
- .processTokens('searchTerm anotherSearchTerm milestone:none');
+ .processTokens('searchTerm anotherSearchTerm milestone:none', allowedKeys);
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens.length).toBe(1);
expect(results.tokens[0]).toBe(results.lastToken);
@@ -48,7 +50,7 @@ describe('Filtered Search Tokenizer', () => {
it('returns for input starting with tokens and ending with search value', () => {
const results = gl.FilteredSearchTokenizer
- .processTokens('assignee:@user searchTerm');
+ .processTokens('assignee:@user searchTerm', allowedKeys);
expect(results.searchToken).toBe('searchTerm');
expect(results.tokens.length).toBe(1);
@@ -60,7 +62,7 @@ describe('Filtered Search Tokenizer', () => {
it('returns for input containing search value wrapped between tokens', () => {
const results = gl.FilteredSearchTokenizer
- .processTokens('author:@root label:~"Won\'t fix" searchTerm anotherSearchTerm milestone:none');
+ .processTokens('author:@root label:~"Won\'t fix" searchTerm anotherSearchTerm milestone:none', allowedKeys);
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens.length).toBe(3);
@@ -81,7 +83,7 @@ describe('Filtered Search Tokenizer', () => {
it('returns for input containing search value in between tokens', () => {
const results = gl.FilteredSearchTokenizer
- .processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing');
+ .processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing', allowedKeys);
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens.length).toBe(3);
expect(results.tokens[2]).toBe(results.lastToken);
@@ -100,14 +102,14 @@ describe('Filtered Search Tokenizer', () => {
});
it('returns search value for invalid tokens', () => {
- const results = gl.FilteredSearchTokenizer.processTokens('fake:token');
+ const results = gl.FilteredSearchTokenizer.processTokens('fake:token', allowedKeys);
expect(results.lastToken).toBe('fake:token');
expect(results.searchToken).toBe('fake:token');
expect(results.tokens.length).toEqual(0);
});
it('returns search value and token for mix of valid and invalid tokens', () => {
- const results = gl.FilteredSearchTokenizer.processTokens('label:real fake:token');
+ const results = gl.FilteredSearchTokenizer.processTokens('label:real fake:token', allowedKeys);
expect(results.tokens.length).toEqual(1);
expect(results.tokens[0].key).toBe('label');
expect(results.tokens[0].value).toBe('real');
@@ -117,13 +119,13 @@ describe('Filtered Search Tokenizer', () => {
});
it('returns search value for invalid symbols', () => {
- const results = gl.FilteredSearchTokenizer.processTokens('std::includes');
+ const results = gl.FilteredSearchTokenizer.processTokens('std::includes', allowedKeys);
expect(results.lastToken).toBe('std::includes');
expect(results.searchToken).toBe('std::includes');
});
it('removes duplicated values', () => {
- const results = gl.FilteredSearchTokenizer.processTokens('label:~foo label:~foo');
+ const results = gl.FilteredSearchTokenizer.processTokens('label:~foo label:~foo', allowedKeys);
expect(results.tokens.length).toBe(1);
expect(results.tokens[0].key).toBe('label');
expect(results.tokens[0].value).toBe('foo');
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 8b750561eb7..fa4343ffbc8 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -1,10 +1,22 @@
import AjaxCache from '~/lib/utils/ajax_cache';
+import UsersCache from '~/lib/utils/users_cache';
-require('~/filtered_search/filtered_search_visual_tokens');
-const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper');
+import '~/filtered_search/filtered_search_visual_tokens';
+import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
describe('Filtered Search Visual Tokens', () => {
+ const subject = gl.FilteredSearchVisualTokens;
+
+ const findElements = (tokenElement) => {
+ const tokenNameElement = tokenElement.querySelector('.name');
+ const tokenValueContainer = tokenElement.querySelector('.value-container');
+ const tokenValueElement = tokenValueContainer.querySelector('.value');
+ return { tokenNameElement, tokenValueContainer, tokenValueElement };
+ };
+
let tokensContainer;
+ let authorToken;
+ let bugLabelToken;
beforeEach(() => {
setFixtures(`
@@ -13,12 +25,15 @@ describe('Filtered Search Visual Tokens', () => {
</ul>
`);
tokensContainer = document.querySelector('.tokens-container');
+
+ authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '@user');
+ bugLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '~bug');
});
describe('getLastVisualTokenBeforeInput', () => {
it('returns when there are no visual tokens', () => {
const { lastVisualToken, isLastVisualTokenValid }
- = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ = subject.getLastVisualTokenBeforeInput();
expect(lastVisualToken).toEqual(null);
expect(isLastVisualTokenValid).toEqual(true);
@@ -27,11 +42,11 @@ describe('Filtered Search Visual Tokens', () => {
describe('input is the last item in tokensContainer', () => {
it('returns when there is one visual token', () => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
- FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'),
+ bugLabelToken.outerHTML,
);
const { lastVisualToken, isLastVisualTokenValid }
- = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ = subject.getLastVisualTokenBeforeInput();
expect(lastVisualToken).toEqual(document.querySelector('.filtered-search-token'));
expect(isLastVisualTokenValid).toEqual(true);
@@ -43,7 +58,7 @@ describe('Filtered Search Visual Tokens', () => {
);
const { lastVisualToken, isLastVisualTokenValid }
- = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ = subject.getLastVisualTokenBeforeInput();
expect(lastVisualToken).toEqual(document.querySelector('.filtered-search-token'));
expect(isLastVisualTokenValid).toEqual(false);
@@ -51,13 +66,13 @@ describe('Filtered Search Visual Tokens', () => {
it('returns when there are multiple visual tokens', () => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
- ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ ${bugLabelToken.outerHTML}
${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root')}
`);
const { lastVisualToken, isLastVisualTokenValid }
- = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ = subject.getLastVisualTokenBeforeInput();
const items = document.querySelectorAll('.tokens-container .js-visual-token');
expect(lastVisualToken.isEqualNode(items[items.length - 1])).toEqual(true);
@@ -66,13 +81,13 @@ describe('Filtered Search Visual Tokens', () => {
it('returns when there are multiple visual tokens and an incomplete visual token', () => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
- ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ ${bugLabelToken.outerHTML}
${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
${FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('assignee')}
`);
const { lastVisualToken, isLastVisualTokenValid }
- = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ = subject.getLastVisualTokenBeforeInput();
const items = document.querySelectorAll('.tokens-container .js-visual-token');
expect(lastVisualToken.isEqualNode(items[items.length - 1])).toEqual(true);
@@ -83,13 +98,13 @@ describe('Filtered Search Visual Tokens', () => {
describe('input is a middle item in tokensContainer', () => {
it('returns last token before input', () => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
- ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ ${bugLabelToken.outerHTML}
${FilteredSearchSpecHelper.createInputHTML()}
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '@root')}
`);
const { lastVisualToken, isLastVisualTokenValid }
- = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ = subject.getLastVisualTokenBeforeInput();
expect(lastVisualToken).toEqual(document.querySelector('.filtered-search-token'));
expect(isLastVisualTokenValid).toEqual(true);
@@ -103,7 +118,7 @@ describe('Filtered Search Visual Tokens', () => {
`);
const { lastVisualToken, isLastVisualTokenValid }
- = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ = subject.getLastVisualTokenBeforeInput();
expect(lastVisualToken).toEqual(document.querySelector('.filtered-search-token'));
expect(isLastVisualTokenValid).toEqual(false);
@@ -114,7 +129,7 @@ describe('Filtered Search Visual Tokens', () => {
describe('unselectTokens', () => {
it('does nothing when there are no tokens', () => {
const beforeHTML = tokensContainer.innerHTML;
- gl.FilteredSearchVisualTokens.unselectTokens();
+ subject.unselectTokens();
expect(tokensContainer.innerHTML).toEqual(beforeHTML);
});
@@ -128,7 +143,7 @@ describe('Filtered Search Visual Tokens', () => {
const selected = tokensContainer.querySelector('.js-visual-token .selected');
expect(selected.classList.contains('selected')).toEqual(true);
- gl.FilteredSearchVisualTokens.unselectTokens();
+ subject.unselectTokens();
expect(selected.classList.contains('selected')).toEqual(false);
});
@@ -137,7 +152,7 @@ describe('Filtered Search Visual Tokens', () => {
describe('selectToken', () => {
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
- ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ ${bugLabelToken.outerHTML}
${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~awesome')}
`);
@@ -147,7 +162,7 @@ describe('Filtered Search Visual Tokens', () => {
const firstTokenButton = tokensContainer.querySelector('.js-visual-token .selectable');
firstTokenButton.classList.add('selected');
- gl.FilteredSearchVisualTokens.selectToken(firstTokenButton);
+ subject.selectToken(firstTokenButton);
expect(firstTokenButton.classList.contains('selected')).toEqual(false);
});
@@ -156,7 +171,7 @@ describe('Filtered Search Visual Tokens', () => {
it('adds selected class', () => {
const firstTokenButton = tokensContainer.querySelector('.js-visual-token .selectable');
- gl.FilteredSearchVisualTokens.selectToken(firstTokenButton);
+ subject.selectToken(firstTokenButton);
expect(firstTokenButton.classList.contains('selected')).toEqual(true);
});
@@ -165,7 +180,7 @@ describe('Filtered Search Visual Tokens', () => {
const tokenButtons = tokensContainer.querySelectorAll('.js-visual-token .selectable');
tokenButtons[1].classList.add('selected');
- gl.FilteredSearchVisualTokens.selectToken(tokenButtons[0]);
+ subject.selectToken(tokenButtons[0]);
expect(tokenButtons[0].classList.contains('selected')).toEqual(true);
expect(tokenButtons[1].classList.contains('selected')).toEqual(false);
@@ -181,7 +196,7 @@ describe('Filtered Search Visual Tokens', () => {
expect(tokensContainer.querySelector('.js-visual-token .selectable')).not.toEqual(null);
- gl.FilteredSearchVisualTokens.removeSelectedToken();
+ subject.removeSelectedToken();
expect(tokensContainer.querySelector('.js-visual-token .selectable')).not.toEqual(null);
});
@@ -193,7 +208,7 @@ describe('Filtered Search Visual Tokens', () => {
expect(tokensContainer.querySelector('.js-visual-token .selectable')).not.toEqual(null);
- gl.FilteredSearchVisualTokens.removeSelectedToken();
+ subject.removeSelectedToken();
expect(tokensContainer.querySelector('.js-visual-token .selectable')).toEqual(null);
});
@@ -205,7 +220,7 @@ describe('Filtered Search Visual Tokens', () => {
beforeEach(() => {
setFixtures(`
<div class="test-area">
- ${gl.FilteredSearchVisualTokens.createVisualTokenElementHTML()}
+ ${subject.createVisualTokenElementHTML()}
</div>
`);
@@ -245,7 +260,7 @@ describe('Filtered Search Visual Tokens', () => {
describe('addVisualTokenElement', () => {
it('renders search visual tokens', () => {
- gl.FilteredSearchVisualTokens.addVisualTokenElement('search term', null, true);
+ subject.addVisualTokenElement('search term', null, true);
const token = tokensContainer.querySelector('.js-visual-token');
expect(token.classList.contains('filtered-search-term')).toEqual(true);
@@ -254,7 +269,7 @@ describe('Filtered Search Visual Tokens', () => {
});
it('renders filter visual token name', () => {
- gl.FilteredSearchVisualTokens.addVisualTokenElement('milestone');
+ subject.addVisualTokenElement('milestone');
const token = tokensContainer.querySelector('.js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
@@ -263,7 +278,7 @@ describe('Filtered Search Visual Tokens', () => {
});
it('renders filter visual token name and value', () => {
- gl.FilteredSearchVisualTokens.addVisualTokenElement('label', 'Frontend');
+ subject.addVisualTokenElement('label', 'Frontend');
const token = tokensContainer.querySelector('.js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
@@ -274,7 +289,7 @@ describe('Filtered Search Visual Tokens', () => {
it('inserts visual token before input', () => {
tokensContainer.appendChild(FilteredSearchSpecHelper.createFilterVisualToken('assignee', '@root'));
- gl.FilteredSearchVisualTokens.addVisualTokenElement('label', 'Frontend');
+ subject.addVisualTokenElement('label', 'Frontend');
const tokens = tokensContainer.querySelectorAll('.js-visual-token');
const labelToken = tokens[0];
const assigneeToken = tokens[1];
@@ -296,7 +311,7 @@ describe('Filtered Search Visual Tokens', () => {
);
const original = tokensContainer.innerHTML;
- gl.FilteredSearchVisualTokens.addValueToPreviousVisualTokenElement('value');
+ subject.addValueToPreviousVisualTokenElement('value');
expect(original).toEqual(tokensContainer.innerHTML);
});
@@ -308,7 +323,7 @@ describe('Filtered Search Visual Tokens', () => {
`);
const original = tokensContainer.innerHTML;
- gl.FilteredSearchVisualTokens.addValueToPreviousVisualTokenElement('value');
+ subject.addValueToPreviousVisualTokenElement('value');
expect(original).toEqual(tokensContainer.innerHTML);
});
@@ -319,7 +334,7 @@ describe('Filtered Search Visual Tokens', () => {
);
const original = tokensContainer.innerHTML;
- gl.FilteredSearchVisualTokens.addValueToPreviousVisualTokenElement('value');
+ subject.addValueToPreviousVisualTokenElement('value');
const updatedToken = tokensContainer.querySelector('.js-visual-token');
expect(updatedToken.querySelector('.name').innerText).toEqual('label');
@@ -330,7 +345,7 @@ describe('Filtered Search Visual Tokens', () => {
describe('addFilterVisualToken', () => {
it('creates visual token with just tokenName', () => {
- gl.FilteredSearchVisualTokens.addFilterVisualToken('milestone');
+ subject.addFilterVisualToken('milestone');
const token = tokensContainer.querySelector('.js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
@@ -339,8 +354,8 @@ describe('Filtered Search Visual Tokens', () => {
});
it('creates visual token with just tokenValue', () => {
- gl.FilteredSearchVisualTokens.addFilterVisualToken('milestone');
- gl.FilteredSearchVisualTokens.addFilterVisualToken('%8.17');
+ subject.addFilterVisualToken('milestone');
+ subject.addFilterVisualToken('%8.17');
const token = tokensContainer.querySelector('.js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
@@ -349,7 +364,7 @@ describe('Filtered Search Visual Tokens', () => {
});
it('creates full visual token', () => {
- gl.FilteredSearchVisualTokens.addFilterVisualToken('assignee', '@john');
+ subject.addFilterVisualToken('assignee', '@john');
const token = tokensContainer.querySelector('.js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
@@ -360,7 +375,7 @@ describe('Filtered Search Visual Tokens', () => {
describe('addSearchVisualToken', () => {
it('creates search visual token', () => {
- gl.FilteredSearchVisualTokens.addSearchVisualToken('search term');
+ subject.addSearchVisualToken('search term');
const token = tokensContainer.querySelector('.js-visual-token');
expect(token.classList.contains('filtered-search-term')).toEqual(true);
@@ -374,7 +389,7 @@ describe('Filtered Search Visual Tokens', () => {
${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
`);
- gl.FilteredSearchVisualTokens.addSearchVisualToken('append this');
+ subject.addSearchVisualToken('append this');
const token = tokensContainer.querySelector('.filtered-search-term');
expect(token.querySelector('.name').innerText).toEqual('search term append this');
@@ -386,10 +401,26 @@ describe('Filtered Search Visual Tokens', () => {
it('should get last token value', () => {
const value = '~bug';
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
- FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', value),
+ bugLabelToken.outerHTML,
+ );
+
+ expect(subject.getLastTokenPartial()).toEqual(value);
+ });
+
+ it('should get last token original value if available', () => {
+ const originalValue = '@user';
+ const valueContainer = authorToken.querySelector('.value-container');
+ valueContainer.dataset.originalValue = originalValue;
+ const avatar = document.createElement('img');
+ const valueElement = valueContainer.querySelector('.value');
+ valueElement.insertAdjacentElement('afterbegin', avatar);
+ tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
+ authorToken.outerHTML,
);
- expect(gl.FilteredSearchVisualTokens.getLastTokenPartial()).toEqual(value);
+ const lastTokenValue = subject.getLastTokenPartial();
+
+ expect(lastTokenValue).toEqual(originalValue);
});
it('should get last token name if there is no value', () => {
@@ -398,11 +429,11 @@ describe('Filtered Search Visual Tokens', () => {
FilteredSearchSpecHelper.createNameFilterVisualTokenHTML(name),
);
- expect(gl.FilteredSearchVisualTokens.getLastTokenPartial()).toEqual(name);
+ expect(subject.getLastTokenPartial()).toEqual(name);
});
it('should return empty when there are no tokens', () => {
- expect(gl.FilteredSearchVisualTokens.getLastTokenPartial()).toEqual('');
+ expect(subject.getLastTokenPartial()).toEqual('');
});
});
@@ -414,7 +445,7 @@ describe('Filtered Search Visual Tokens', () => {
expect(tokensContainer.querySelector('.js-visual-token .value')).not.toEqual(null);
- gl.FilteredSearchVisualTokens.removeLastTokenPartial();
+ subject.removeLastTokenPartial();
expect(tokensContainer.querySelector('.js-visual-token .value')).toEqual(null);
});
@@ -426,14 +457,14 @@ describe('Filtered Search Visual Tokens', () => {
expect(tokensContainer.querySelector('.js-visual-token .name')).not.toEqual(null);
- gl.FilteredSearchVisualTokens.removeLastTokenPartial();
+ subject.removeLastTokenPartial();
expect(tokensContainer.querySelector('.js-visual-token .name')).toEqual(null);
});
it('should not remove anything when there are no tokens', () => {
const html = tokensContainer.innerHTML;
- gl.FilteredSearchVisualTokens.removeLastTokenPartial();
+ subject.removeLastTokenPartial();
expect(tokensContainer.innerHTML).toEqual(html);
});
@@ -442,7 +473,7 @@ describe('Filtered Search Visual Tokens', () => {
describe('tokenizeInput', () => {
it('does not do anything if there is no input', () => {
const original = tokensContainer.innerHTML;
- gl.FilteredSearchVisualTokens.tokenizeInput();
+ subject.tokenizeInput();
expect(tokensContainer.innerHTML).toEqual(original);
});
@@ -454,7 +485,7 @@ describe('Filtered Search Visual Tokens', () => {
const input = document.querySelector('.filtered-search');
input.value = 'some value';
- gl.FilteredSearchVisualTokens.tokenizeInput();
+ subject.tokenizeInput();
const newToken = tokensContainer.querySelector('.filtered-search-term');
@@ -470,7 +501,7 @@ describe('Filtered Search Visual Tokens', () => {
const input = document.querySelector('.filtered-search');
input.value = '@john';
- gl.FilteredSearchVisualTokens.tokenizeInput();
+ subject.tokenizeInput();
const updatedToken = tokensContainer.querySelector('.filtered-search-token');
@@ -497,29 +528,39 @@ describe('Filtered Search Visual Tokens', () => {
it('tokenize\'s existing input', () => {
input.value = 'some text';
- spyOn(gl.FilteredSearchVisualTokens, 'tokenizeInput').and.callThrough();
+ spyOn(subject, 'tokenizeInput').and.callThrough();
- gl.FilteredSearchVisualTokens.editToken(token);
+ subject.editToken(token);
- expect(gl.FilteredSearchVisualTokens.tokenizeInput).toHaveBeenCalled();
+ expect(subject.tokenizeInput).toHaveBeenCalled();
expect(input.value).not.toEqual('some text');
});
it('moves input to the token position', () => {
expect(tokensContainer.children[3].querySelector('.filtered-search')).not.toEqual(null);
- gl.FilteredSearchVisualTokens.editToken(token);
+ subject.editToken(token);
expect(tokensContainer.children[1].querySelector('.filtered-search')).not.toEqual(null);
expect(tokensContainer.children[3].querySelector('.filtered-search')).toEqual(null);
});
it('input contains the visual token value', () => {
- gl.FilteredSearchVisualTokens.editToken(token);
+ subject.editToken(token);
expect(input.value).toEqual('none');
});
+ it('input contains the original value if present', () => {
+ const originalValue = '@user';
+ const valueContainer = token.querySelector('.value-container');
+ valueContainer.dataset.originalValue = originalValue;
+
+ subject.editToken(token);
+
+ expect(input.value).toEqual(originalValue);
+ });
+
describe('selected token is a search term token', () => {
beforeEach(() => {
token = document.querySelector('.filtered-search-term');
@@ -528,7 +569,7 @@ describe('Filtered Search Visual Tokens', () => {
it('token is removed', () => {
expect(tokensContainer.querySelector('.filtered-search-term')).not.toEqual(null);
- gl.FilteredSearchVisualTokens.editToken(token);
+ subject.editToken(token);
expect(tokensContainer.querySelector('.filtered-search-term')).toEqual(null);
});
@@ -536,7 +577,7 @@ describe('Filtered Search Visual Tokens', () => {
it('input has the same value as removed token', () => {
expect(input.value).toEqual('');
- gl.FilteredSearchVisualTokens.editToken(token);
+ subject.editToken(token);
expect(input.value).toEqual('search');
});
@@ -549,25 +590,25 @@ describe('Filtered Search Visual Tokens', () => {
FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none'),
);
- spyOn(gl.FilteredSearchVisualTokens, 'tokenizeInput').and.callFake(() => {});
- spyOn(gl.FilteredSearchVisualTokens, 'getLastVisualTokenBeforeInput').and.callThrough();
+ spyOn(subject, 'tokenizeInput').and.callFake(() => {});
+ spyOn(subject, 'getLastVisualTokenBeforeInput').and.callThrough();
- gl.FilteredSearchVisualTokens.moveInputToTheRight();
+ subject.moveInputToTheRight();
- expect(gl.FilteredSearchVisualTokens.tokenizeInput).toHaveBeenCalled();
- expect(gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput).not.toHaveBeenCalled();
+ expect(subject.tokenizeInput).toHaveBeenCalled();
+ expect(subject.getLastVisualTokenBeforeInput).not.toHaveBeenCalled();
});
it('tokenize\'s input', () => {
tokensContainer.innerHTML = `
${FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('label')}
${FilteredSearchSpecHelper.createInputHTML()}
- ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ ${bugLabelToken.outerHTML}
`;
document.querySelector('.filtered-search').value = 'none';
- gl.FilteredSearchVisualTokens.moveInputToTheRight();
+ subject.moveInputToTheRight();
const value = tokensContainer.querySelector('.js-visual-token .value');
expect(value.innerText).toEqual('none');
@@ -577,12 +618,12 @@ describe('Filtered Search Visual Tokens', () => {
tokensContainer.innerHTML = `
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none')}
${FilteredSearchSpecHelper.createInputHTML()}
- ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ ${bugLabelToken.outerHTML}
`;
document.querySelector('.filtered-search').value = 'test';
- gl.FilteredSearchVisualTokens.moveInputToTheRight();
+ subject.moveInputToTheRight();
const searchValue = tokensContainer.querySelector('.filtered-search-term .name');
expect(searchValue.innerText).toEqual('test');
@@ -592,10 +633,10 @@ describe('Filtered Search Visual Tokens', () => {
tokensContainer.innerHTML = `
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none')}
${FilteredSearchSpecHelper.createInputHTML()}
- ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
+ ${bugLabelToken.outerHTML}
`;
- gl.FilteredSearchVisualTokens.moveInputToTheRight();
+ subject.moveInputToTheRight();
expect(tokensContainer.children[2].querySelector('.filtered-search')).not.toEqual(null);
});
@@ -607,7 +648,7 @@ describe('Filtered Search Visual Tokens', () => {
${FilteredSearchSpecHelper.createInputHTML('', '~bug')}
`;
- gl.FilteredSearchVisualTokens.moveInputToTheRight();
+ subject.moveInputToTheRight();
const token = tokensContainer.children[1];
expect(token.querySelector('.value').innerText).toEqual('~bug');
@@ -615,42 +656,144 @@ describe('Filtered Search Visual Tokens', () => {
});
describe('renderVisualTokenValue', () => {
- let searchTokens;
+ const keywordToken = FilteredSearchSpecHelper.createFilterVisualToken('search');
+ const milestoneToken = FilteredSearchSpecHelper.createFilterVisualToken('milestone', 'upcoming');
+
+ let updateLabelTokenColorSpy;
+ let updateUserTokenAppearanceSpy;
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
- ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', 'none')}
- ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search')}
- ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'upcoming')}
+ ${authorToken.outerHTML}
+ ${bugLabelToken.outerHTML}
+ ${keywordToken.outerHTML}
+ ${milestoneToken.outerHTML}
`);
- searchTokens = document.querySelectorAll('.filtered-search-token');
+ spyOn(subject, 'updateLabelTokenColor');
+ updateLabelTokenColorSpy = subject.updateLabelTokenColor;
+
+ spyOn(subject, 'updateUserTokenAppearance');
+ updateUserTokenAppearanceSpy = subject.updateUserTokenAppearance;
});
- it('renders a token value element', () => {
- spyOn(gl.FilteredSearchVisualTokens, 'updateLabelTokenColor');
- const updateLabelTokenColorSpy = gl.FilteredSearchVisualTokens.updateLabelTokenColor;
+ it('renders a author token value element', () => {
+ const { tokenNameElement, tokenValueContainer, tokenValueElement } =
+ findElements(authorToken);
+ const tokenName = tokenNameElement.innerText;
+ const tokenValue = 'new value';
- expect(searchTokens.length).toBe(2);
- Array.prototype.forEach.call(searchTokens, (token) => {
- updateLabelTokenColorSpy.calls.reset();
+ subject.renderVisualTokenValue(authorToken, tokenName, tokenValue);
- const tokenName = token.querySelector('.name').innerText;
- const tokenValue = 'new value';
- gl.FilteredSearchVisualTokens.renderVisualTokenValue(token, tokenName, tokenValue);
+ expect(tokenValueElement.innerText).toBe(tokenValue);
+ expect(updateUserTokenAppearanceSpy.calls.count()).toBe(1);
+ const expectedArgs = [tokenValueContainer, tokenValueElement, tokenValue];
+ expect(updateUserTokenAppearanceSpy.calls.argsFor(0)).toEqual(expectedArgs);
+ expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
+ });
- const tokenValueElement = token.querySelector('.value');
- expect(tokenValueElement.innerText).toBe(tokenValue);
+ it('renders a label token value element', () => {
+ const { tokenNameElement, tokenValueContainer, tokenValueElement } =
+ findElements(bugLabelToken);
+ const tokenName = tokenNameElement.innerText;
+ const tokenValue = 'new value';
- if (tokenName.toLowerCase() === 'label') {
- const tokenValueContainer = token.querySelector('.value-container');
- expect(updateLabelTokenColorSpy.calls.count()).toBe(1);
- const expectedArgs = [tokenValueContainer, tokenValue];
- expect(updateLabelTokenColorSpy.calls.argsFor(0)).toEqual(expectedArgs);
- } else {
- expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
- }
- });
+ subject.renderVisualTokenValue(bugLabelToken, tokenName, tokenValue);
+
+ expect(tokenValueElement.innerText).toBe(tokenValue);
+ expect(updateLabelTokenColorSpy.calls.count()).toBe(1);
+ const expectedArgs = [tokenValueContainer, tokenValue];
+ expect(updateLabelTokenColorSpy.calls.argsFor(0)).toEqual(expectedArgs);
+ expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
+ });
+
+ it('renders a milestone token value element', () => {
+ const { tokenNameElement, tokenValueElement } = findElements(milestoneToken);
+ const tokenName = tokenNameElement.innerText;
+ const tokenValue = 'new value';
+
+ subject.renderVisualTokenValue(milestoneToken, tokenName, tokenValue);
+
+ expect(tokenValueElement.innerText).toBe(tokenValue);
+ expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
+ expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
+ });
+ });
+
+ describe('updateUserTokenAppearance', () => {
+ let usersCacheSpy;
+
+ beforeEach(() => {
+ spyOn(UsersCache, 'retrieve').and.callFake(username => usersCacheSpy(username));
+ });
+
+ it('ignores special value "none"', (done) => {
+ usersCacheSpy = (username) => {
+ expect(username).toBe('none');
+ done.fail('Should not resolve "none"!');
+ };
+ const { tokenValueContainer, tokenValueElement } = findElements(authorToken);
+
+ subject.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, 'none')
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('ignores error if UsersCache throws', (done) => {
+ spyOn(window, 'Flash');
+ const dummyError = new Error('Earth rotated backwards');
+ const { tokenValueContainer, tokenValueElement } = findElements(authorToken);
+ const tokenValue = tokenValueElement.innerText;
+ usersCacheSpy = (username) => {
+ expect(`@${username}`).toBe(tokenValue);
+ return Promise.reject(dummyError);
+ };
+
+ subject.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
+ .then(() => {
+ expect(window.Flash.calls.count()).toBe(0);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does nothing if user cannot be found', (done) => {
+ const { tokenValueContainer, tokenValueElement } = findElements(authorToken);
+ const tokenValue = tokenValueElement.innerText;
+ usersCacheSpy = (username) => {
+ expect(`@${username}`).toBe(tokenValue);
+ return Promise.resolve(undefined);
+ };
+
+ subject.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
+ .then(() => {
+ expect(tokenValueElement.innerText).toBe(tokenValue);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('replaces author token with avatar and display name', (done) => {
+ const dummyUser = {
+ name: 'Important Person',
+ avatar_url: 'https://host.invalid/mypics/avatar.png',
+ };
+ const { tokenValueContainer, tokenValueElement } = findElements(authorToken);
+ const tokenValue = tokenValueElement.innerText;
+ usersCacheSpy = (username) => {
+ expect(`@${username}`).toBe(tokenValue);
+ return Promise.resolve(dummyUser);
+ };
+
+ subject.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
+ .then(() => {
+ expect(tokenValueContainer.dataset.originalValue).toBe(tokenValue);
+ expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name);
+ const avatar = tokenValueElement.querySelector('img.avatar');
+ expect(avatar.src).toBe(dummyUser.avatar_url);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
@@ -659,21 +802,16 @@ describe('Filtered Search Visual Tokens', () => {
const dummyEndpoint = '/dummy/endpoint';
preloadFixtures(jsonFixtureName);
- const labelData = getJSONFixture(jsonFixtureName);
- const findLabel = tokenValue => labelData.find(
- label => tokenValue === `~${gl.DropdownUtils.getEscapedText(label.title)}`,
- );
- const bugLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '~bug');
+ let labelData;
+
+ beforeAll(() => {
+ labelData = getJSONFixture(jsonFixtureName);
+ });
+
const missingLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '~doesnotexist');
const spaceLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '~"some space"');
- const parseColor = (color) => {
- const dummyElement = document.createElement('div');
- dummyElement.style.color = color;
- return dummyElement.style.color;
- };
-
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
${bugLabelToken.outerHTML}
@@ -688,28 +826,60 @@ describe('Filtered Search Visual Tokens', () => {
AjaxCache.internalStorage[`${dummyEndpoint}/labels.json`] = labelData;
});
- const testCase = (token, done) => {
- const tokenValueContainer = token.querySelector('.value-container');
- const tokenValue = token.querySelector('.value').innerText;
- const label = findLabel(tokenValue);
+ const parseColor = (color) => {
+ const dummyElement = document.createElement('div');
+ dummyElement.style.color = color;
+ return dummyElement.style.color;
+ };
- gl.FilteredSearchVisualTokens.updateLabelTokenColor(tokenValueContainer, tokenValue)
- .then(() => {
- if (label) {
- expect(tokenValueContainer.getAttribute('style')).not.toBe(null);
- expect(tokenValueContainer.style.backgroundColor).toBe(parseColor(label.color));
- expect(tokenValueContainer.style.color).toBe(parseColor(label.text_color));
- } else {
- expect(token).toBe(missingLabelToken);
- expect(tokenValueContainer.getAttribute('style')).toBe(null);
- }
- })
- .then(done)
- .catch(fail);
+ const expectValueContainerStyle = (tokenValueContainer, label) => {
+ expect(tokenValueContainer.getAttribute('style')).not.toBe(null);
+ expect(tokenValueContainer.style.backgroundColor).toBe(parseColor(label.color));
+ expect(tokenValueContainer.style.color).toBe(parseColor(label.text_color));
};
- it('updates the color of a label token', done => testCase(bugLabelToken, done));
- it('updates the color of a label token with spaces', done => testCase(spaceLabelToken, done));
- it('does not change color of a missing label', done => testCase(missingLabelToken, done));
+ const findLabel = tokenValue => labelData.find(
+ label => tokenValue === `~${gl.DropdownUtils.getEscapedText(label.title)}`,
+ );
+
+ it('updates the color of a label token', (done) => {
+ const { tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
+ const tokenValue = tokenValueElement.innerText;
+ const matchingLabel = findLabel(tokenValue);
+
+ subject.updateLabelTokenColor(tokenValueContainer, tokenValue)
+ .then(() => {
+ expectValueContainerStyle(tokenValueContainer, matchingLabel);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('updates the color of a label token with spaces', (done) => {
+ const { tokenValueContainer, tokenValueElement } = findElements(spaceLabelToken);
+ const tokenValue = tokenValueElement.innerText;
+ const matchingLabel = findLabel(tokenValue);
+
+ subject.updateLabelTokenColor(tokenValueContainer, tokenValue)
+ .then(() => {
+ expectValueContainerStyle(tokenValueContainer, matchingLabel);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not change color of a missing label', (done) => {
+ const { tokenValueContainer, tokenValueElement } = findElements(missingLabelToken);
+ const tokenValue = tokenValueElement.innerText;
+ const matchingLabel = findLabel(tokenValue);
+ expect(matchingLabel).toBe(undefined);
+
+ subject.updateLabelTokenColor(tokenValueContainer, tokenValue)
+ .then(() => {
+ expect(tokenValueContainer.getAttribute('style')).toBe(null);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
});
diff --git a/spec/javascripts/filtered_search/services/recent_searches_service_spec.js b/spec/javascripts/filtered_search/services/recent_searches_service_spec.js
index 31fa478804a..c293c0afa97 100644
--- a/spec/javascripts/filtered_search/services/recent_searches_service_spec.js
+++ b/spec/javascripts/filtered_search/services/recent_searches_service_spec.js
@@ -1,6 +1,5 @@
-/* eslint-disable promise/catch-or-return */
-
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
+import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
import AccessorUtilities from '~/lib/utils/accessor';
describe('RecentSearchesService', () => {
@@ -22,11 +21,9 @@ describe('RecentSearchesService', () => {
fetchItemsPromise
.then((items) => {
expect(items).toEqual([]);
- done();
})
- .catch((err) => {
- done.fail('Shouldn\'t reject with empty localStorage key', err);
- });
+ .then(done)
+ .catch(done.fail);
});
it('should reject when unable to parse', (done) => {
@@ -34,19 +31,24 @@ describe('RecentSearchesService', () => {
const fetchItemsPromise = service.fetch();
fetchItemsPromise
+ .then(done.fail)
.catch((error) => {
expect(error).toEqual(jasmine.any(SyntaxError));
- done();
- });
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should reject when service is unavailable', (done) => {
RecentSearchesService.isAvailable.and.returnValue(false);
- service.fetch().catch((error) => {
- expect(error).toEqual(jasmine.any(Error));
- done();
- });
+ service.fetch()
+ .then(done.fail)
+ .catch((error) => {
+ expect(error).toEqual(jasmine.any(Error));
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should return items from localStorage', (done) => {
@@ -56,8 +58,9 @@ describe('RecentSearchesService', () => {
fetchItemsPromise
.then((items) => {
expect(items).toEqual(['foo', 'bar']);
- done();
- });
+ })
+ .then(done)
+ .catch(done.fail);
});
describe('if .isAvailable returns `false`', () => {
@@ -65,12 +68,17 @@ describe('RecentSearchesService', () => {
RecentSearchesService.isAvailable.and.returnValue(false);
spyOn(window.localStorage, 'getItem');
-
- RecentSearchesService.prototype.fetch();
});
- it('should not call .getItem', () => {
- expect(window.localStorage.getItem).not.toHaveBeenCalled();
+ it('should not call .getItem', (done) => {
+ RecentSearchesService.prototype.fetch()
+ .then(done.fail)
+ .catch((err) => {
+ expect(err).toEqual(new RecentSearchesServiceError());
+ expect(window.localStorage.getItem).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
@@ -105,11 +113,11 @@ describe('RecentSearchesService', () => {
RecentSearchesService.isAvailable.and.returnValue(true);
spyOn(JSON, 'stringify').and.returnValue(searchesString);
-
- RecentSearchesService.prototype.save.call(recentSearchesService);
});
it('should call .setItem', () => {
+ RecentSearchesService.prototype.save.call(recentSearchesService);
+
expect(window.localStorage.setItem).toHaveBeenCalledWith(localStorageKey, searchesString);
});
});
@@ -117,11 +125,11 @@ describe('RecentSearchesService', () => {
describe('if .isAvailable returns `false`', () => {
beforeEach(() => {
RecentSearchesService.isAvailable.and.returnValue(false);
-
- RecentSearchesService.prototype.save();
});
it('should not call .setItem', () => {
+ RecentSearchesService.prototype.save();
+
expect(window.localStorage.setItem).not.toHaveBeenCalled();
});
});
diff --git a/spec/javascripts/fixtures/balsamiq.rb b/spec/javascripts/fixtures/balsamiq.rb
new file mode 100644
index 00000000000..b5372821bf5
--- /dev/null
+++ b/spec/javascripts/fixtures/balsamiq.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe 'Balsamiq file', '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, namespace: namespace, path: 'balsamiq-project') }
+
+ before(:all) do
+ clean_frontend_fixtures('blob/balsamiq/')
+ end
+
+ it 'blob/balsamiq/test.bmpr' do |example|
+ blob = project.repository.blob_at('b89b56d79', 'files/images/balsamiq.bmpr')
+
+ store_frontend_fixture(blob.data.force_encoding('utf-8'), example.description)
+ end
+end
diff --git a/spec/javascripts/fixtures/balsamiq_viewer.html.haml b/spec/javascripts/fixtures/balsamiq_viewer.html.haml
new file mode 100644
index 00000000000..18166ba4901
--- /dev/null
+++ b/spec/javascripts/fixtures/balsamiq_viewer.html.haml
@@ -0,0 +1 @@
+.file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: '/test' } }
diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb
new file mode 100644
index 00000000000..d7c3dc0a235
--- /dev/null
+++ b/spec/javascripts/fixtures/boards.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'boards-project') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('boards/')
+ end
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'boards/show.html.raw' do |example|
+ get(:index,
+ namespace_id: project.namespace,
+ project_id: project)
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/fixtures/issuable_filter.html.haml b/spec/javascripts/fixtures/issuable_filter.html.haml
index ae745b292e6..84fa5395cb8 100644
--- a/spec/javascripts/fixtures/issuable_filter.html.haml
+++ b/spec/javascripts/fixtures/issuable_filter.html.haml
@@ -1,6 +1,6 @@
%form.js-filter-form{action: '/user/project/issues?scope=all&state=closed'}
%input{id: 'utf8', name: 'utf8', value: '✓'}
- %input{id: 'check_all_issues', name: 'check_all_issues'}
+ %input{id: 'check-all-issues', name: 'check-all-issues'}
%input{id: 'search', name: 'search'}
%input{id: 'author_id', name: 'author_id'}
%input{id: 'assignee_id', name: 'assignee_id'}
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
index 88e3f860809..1a30909977e 100644
--- a/spec/javascripts/fixtures/issues.rb
+++ b/spec/javascripts/fixtures/issues.rb
@@ -36,6 +36,17 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
render_issue(example.description, issue)
end
+ it 'issues/issue_list.html.raw' do |example|
+ create(:issue, project: project)
+
+ get :index,
+ namespace_id: project.namespace.to_param,
+ project_id: project
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+
private
def render_issue(fixture_file_name, issue)
diff --git a/spec/javascripts/fixtures/builds.rb b/spec/javascripts/fixtures/jobs.rb
index 320de791b08..dc7dde1138c 100644
--- a/spec/javascripts/fixtures/builds.rb
+++ b/spec/javascripts/fixtures/jobs.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::BuildsController, '(JavaScript fixtures)', type: :controller do
+describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index 47d904b865b..a746a776548 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -16,6 +16,16 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
sha: merge_request.diff_head_sha
)
end
+ let(:path) { "files/ruby/popen.rb" }
+ let(:position) do
+ Gitlab::Diff::Position.new(
+ old_path: path,
+ new_path: path,
+ old_line: nil,
+ new_line: 14,
+ diff_refs: merge_request.diff_refs
+ )
+ end
render_views
@@ -39,6 +49,12 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
render_merge_request(example.description, merged_merge_request)
end
+ it 'merge_requests/diff_comment.html.raw' do |example|
+ create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+ create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
+ render_merge_request(example.description, merge_request)
+ end
+
private
def render_merge_request(fixture_file_name, merge_request)
diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/javascripts/fixtures/pipelines.rb
new file mode 100644
index 00000000000..daafbac86db
--- /dev/null
+++ b/spec/javascripts/fixtures/pipelines.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'pipelines-project') }
+ let(:commit) { create(:commit, project: project) }
+ let(:commit_without_author) { RepoHelpers.another_sample_commit }
+ let!(:user) { create(:user, email: commit.author_email) }
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: commit.id, user: user) }
+ let!(:pipeline_without_author) { create(:ci_pipeline, project: project, sha: commit_without_author.id) }
+ let!(:pipeline_without_commit) { create(:ci_pipeline, project: project, sha: '0000') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('pipelines/')
+ end
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'pipelines/pipelines.json' do |example|
+ get :index,
+ namespace_id: namespace,
+ project_id: project,
+ format: :json
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/fixtures/raw.rb b/spec/javascripts/fixtures/raw.rb
index 1ce622fc836..17533443d76 100644
--- a/spec/javascripts/fixtures/raw.rb
+++ b/spec/javascripts/fixtures/raw.rb
@@ -21,4 +21,10 @@ describe 'Raw files', '(JavaScript fixtures)', type: :controller do
store_frontend_fixture(blob.data, example.description)
end
+
+ it 'blob/notebook/math.json' do |example|
+ blob = project.repository.blob_at('93ee732', 'files/ipython/math.ipynb')
+
+ store_frontend_fixture(blob.data, example.description)
+ end
end
diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb
new file mode 100644
index 00000000000..554451d1bbf
--- /dev/null
+++ b/spec/javascripts/fixtures/services.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Projects::ServicesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
+ let!(:service) { create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker') }
+
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('services/')
+ end
+
+ before(:each) do
+ sign_in(admin)
+ end
+
+ it 'services/edit_service.html.raw' do |example|
+ get :edit,
+ namespace_id: namespace,
+ project_id: project,
+ id: service.to_param
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js
index 5dfa4008fbd..ad0c7264616 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/javascripts/gfm_auto_complete_spec.js
@@ -1,13 +1,15 @@
/* eslint no-param-reassign: "off" */
-require('~/gfm_auto_complete');
-require('vendor/jquery.caret');
-require('vendor/jquery.atwho');
+import GfmAutoComplete from '~/gfm_auto_complete';
-const global = window.gl || (window.gl = {});
-const GfmAutoComplete = global.GfmAutoComplete;
+import 'vendor/jquery.caret';
+import 'vendor/jquery.atwho';
describe('GfmAutoComplete', function () {
+ const gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({
+ fetchData: () => {},
+ });
+
describe('DefaultOptions.sorter', function () {
describe('assets loading', function () {
beforeEach(function () {
@@ -16,7 +18,7 @@ describe('GfmAutoComplete', function () {
this.atwhoInstance = { setting: {} };
this.items = [];
- this.sorterValue = GfmAutoComplete.DefaultOptions.sorter
+ this.sorterValue = gfmAutoCompleteCallbacks.sorter
.call(this.atwhoInstance, '', this.items);
});
@@ -38,7 +40,7 @@ describe('GfmAutoComplete', function () {
it('should enable highlightFirst if alwaysHighlightFirst is set', function () {
const atwhoInstance = { setting: { alwaysHighlightFirst: true } };
- GfmAutoComplete.DefaultOptions.sorter.call(atwhoInstance);
+ gfmAutoCompleteCallbacks.sorter.call(atwhoInstance);
expect(atwhoInstance.setting.highlightFirst).toBe(true);
});
@@ -46,7 +48,7 @@ describe('GfmAutoComplete', function () {
it('should enable highlightFirst if a query is present', function () {
const atwhoInstance = { setting: {} };
- GfmAutoComplete.DefaultOptions.sorter.call(atwhoInstance, 'query');
+ gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, 'query');
expect(atwhoInstance.setting.highlightFirst).toBe(true);
});
@@ -58,7 +60,7 @@ describe('GfmAutoComplete', function () {
const items = [];
const searchKey = 'searchKey';
- GfmAutoComplete.DefaultOptions.sorter.call(atwhoInstance, query, items, searchKey);
+ gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, query, items, searchKey);
expect($.fn.atwho.default.callbacks.sorter).toHaveBeenCalledWith(query, items, searchKey);
});
@@ -67,7 +69,7 @@ describe('GfmAutoComplete', function () {
describe('DefaultOptions.matcher', function () {
const defaultMatcher = (context, flag, subtext) => (
- GfmAutoComplete.DefaultOptions.matcher.call(context, flag, subtext)
+ gfmAutoCompleteCallbacks.matcher.call(context, flag, subtext)
);
const flagsUseDefaultMatcher = ['@', '#', '!', '~', '%'];
diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js
index eb532dff5a1..3292590b9ed 100644
--- a/spec/javascripts/gl_dropdown_spec.js
+++ b/spec/javascripts/gl_dropdown_spec.js
@@ -1,9 +1,8 @@
/* eslint-disable comma-dangle, no-param-reassign, no-unused-expressions, max-len */
-require('~/gl_dropdown');
-require('~/lib/utils/common_utils');
-require('~/lib/utils/type_utility');
-require('~/lib/utils/url_utility');
+import '~/gl_dropdown';
+import '~/lib/utils/common_utils';
+import '~/lib/utils/url_utility';
(() => {
const NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link';
diff --git a/spec/javascripts/gl_emoji_spec.js b/spec/javascripts/gl_emoji_spec.js
index b2b46640e5b..a09e0072fa8 100644
--- a/spec/javascripts/gl_emoji_spec.js
+++ b/spec/javascripts/gl_emoji_spec.js
@@ -192,6 +192,9 @@ describe('gl_emoji', () => {
});
describe('isFlagEmoji', () => {
+ it('should gracefully handle empty string', () => {
+ expect(isFlagEmoji('')).toBeFalsy();
+ });
it('should detect flag_ac', () => {
expect(isFlagEmoji('🇦🇨')).toBeTruthy();
});
@@ -216,6 +219,9 @@ describe('gl_emoji', () => {
});
describe('isKeycapEmoji', () => {
+ it('should gracefully handle empty string', () => {
+ expect(isKeycapEmoji('')).toBeFalsy();
+ });
it('should detect one(keycap)', () => {
expect(isKeycapEmoji('1️⃣')).toBeTruthy();
});
@@ -231,6 +237,9 @@ describe('gl_emoji', () => {
});
describe('isSkinToneComboEmoji', () => {
+ it('should gracefully handle empty string', () => {
+ expect(isSkinToneComboEmoji('')).toBeFalsy();
+ });
it('should detect hand_splayed_tone5', () => {
expect(isSkinToneComboEmoji('🖐🏿')).toBeTruthy();
});
@@ -255,6 +264,9 @@ describe('gl_emoji', () => {
});
describe('isHorceRacingSkinToneComboEmoji', () => {
+ it('should gracefully handle empty string', () => {
+ expect(isHorceRacingSkinToneComboEmoji('')).toBeFalsy();
+ });
it('should detect horse_racing_tone2', () => {
expect(isHorceRacingSkinToneComboEmoji('🏇🏼')).toBeTruthy();
});
@@ -264,6 +276,9 @@ describe('gl_emoji', () => {
});
describe('isPersonZwjEmoji', () => {
+ it('should gracefully handle empty string', () => {
+ expect(isPersonZwjEmoji('')).toBeFalsy();
+ });
it('should detect couple_mm', () => {
expect(isPersonZwjEmoji('👨‍❤️‍👨')).toBeTruthy();
});
@@ -300,6 +315,22 @@ describe('gl_emoji', () => {
});
describe('isEmojiUnicodeSupported', () => {
+ it('should gracefully handle empty string with unicode support', () => {
+ const isSupported = isEmojiUnicodeSupported(
+ { '1.0': true },
+ '',
+ '1.0',
+ );
+ expect(isSupported).toBeTruthy();
+ });
+ it('should gracefully handle empty string without unicode support', () => {
+ const isSupported = isEmojiUnicodeSupported(
+ {},
+ '',
+ '1.0',
+ );
+ expect(isSupported).toBeFalsy();
+ });
it('bomb(6.0) with 6.0 support', () => {
const emojiKey = 'bomb';
const unicodeSupportMap = Object.assign({}, emptySupportMap, {
diff --git a/spec/javascripts/gl_field_errors_spec.js b/spec/javascripts/gl_field_errors_spec.js
index 733023481f5..fa24aa426b6 100644
--- a/spec/javascripts/gl_field_errors_spec.js
+++ b/spec/javascripts/gl_field_errors_spec.js
@@ -1,6 +1,6 @@
/* eslint-disable space-before-function-paren, arrow-body-style */
-require('~/gl_field_errors');
+import '~/gl_field_errors';
((global) => {
preloadFixtures('static/gl_field_errors.html.raw');
diff --git a/spec/javascripts/gl_form_spec.js b/spec/javascripts/gl_form_spec.js
index 71d6e2a7e22..837feacec1d 100644
--- a/spec/javascripts/gl_form_spec.js
+++ b/spec/javascripts/gl_form_spec.js
@@ -1,9 +1,9 @@
-/* global autosize */
+import autosize from 'vendor/autosize';
+import '~/gl_form';
+import '~/lib/utils/text_utility';
+import '~/lib/utils/common_utils';
-window.autosize = require('vendor/autosize');
-require('~/gl_form');
-require('~/lib/utils/text_utility');
-require('~/lib/utils/common_utils');
+window.autosize = autosize;
describe('GLForm', () => {
const global = window.gl || (window.gl = {});
@@ -27,12 +27,12 @@ describe('GLForm', () => {
$.prototype.off.calls.reset();
$.prototype.on.calls.reset();
$.prototype.css.calls.reset();
- autosize.calls.reset();
+ window.autosize.calls.reset();
done();
});
});
- describe('.setupAutosize', () => {
+ describe('setupAutosize', () => {
beforeEach((done) => {
this.glForm.setupAutosize();
setTimeout(() => {
@@ -51,7 +51,7 @@ describe('GLForm', () => {
});
it('should autosize the textarea', () => {
- expect(autosize).toHaveBeenCalledWith(jasmine.any(Object));
+ expect(window.autosize).toHaveBeenCalledWith(jasmine.any(Object));
});
it('should set the resize css property to vertical', () => {
@@ -59,7 +59,7 @@ describe('GLForm', () => {
});
});
- describe('.setHeightData', () => {
+ describe('setHeightData', () => {
beforeEach(() => {
spyOn($.prototype, 'data');
spyOn($.prototype, 'outerHeight').and.returnValue(200);
@@ -75,13 +75,13 @@ describe('GLForm', () => {
});
});
- describe('.destroyAutosize', () => {
+ describe('destroyAutosize', () => {
describe('when called', () => {
beforeEach(() => {
spyOn($.prototype, 'data');
spyOn($.prototype, 'outerHeight').and.returnValue(200);
spyOn(window, 'outerHeight').and.returnValue(400);
- spyOn(autosize, 'destroy');
+ spyOn(window.autosize, 'destroy');
this.glForm.destroyAutosize();
});
@@ -95,7 +95,7 @@ describe('GLForm', () => {
});
it('should call autosize destroy', () => {
- expect(autosize.destroy).toHaveBeenCalledWith(this.textarea);
+ expect(window.autosize.destroy).toHaveBeenCalledWith(this.textarea);
});
it('should set the data-height attribute', () => {
@@ -114,9 +114,9 @@ describe('GLForm', () => {
it('should return undefined if the data-height equals the outerHeight', () => {
spyOn($.prototype, 'outerHeight').and.returnValue(200);
spyOn($.prototype, 'data').and.returnValue(200);
- spyOn(autosize, 'destroy');
+ spyOn(window.autosize, 'destroy');
expect(this.glForm.destroyAutosize()).toBeUndefined();
- expect(autosize.destroy).not.toHaveBeenCalled();
+ expect(window.autosize.destroy).not.toHaveBeenCalled();
});
});
});
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index b5dde5525e5..0e01934d3a3 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -1,7 +1,7 @@
/* eslint-disable space-before-function-paren, no-var */
-require('~/header');
-require('~/lib/utils/text_utility');
+import '~/header';
+import '~/lib/utils/text_utility';
(function() {
describe('Header', function() {
diff --git a/spec/javascripts/helpers/class_spec_helper.js b/spec/javascripts/helpers/class_spec_helper.js
index 61db27a8fcc..7a60d33b471 100644
--- a/spec/javascripts/helpers/class_spec_helper.js
+++ b/spec/javascripts/helpers/class_spec_helper.js
@@ -1,4 +1,4 @@
-class ClassSpecHelper {
+export default class ClassSpecHelper {
static itShouldBeAStaticMethod(base, method) {
return it('should be a static method', () => {
expect(Object.prototype.hasOwnProperty.call(base, method)).toBeTruthy();
@@ -7,5 +7,3 @@ class ClassSpecHelper {
}
window.ClassSpecHelper = ClassSpecHelper;
-
-module.exports = ClassSpecHelper;
diff --git a/spec/javascripts/helpers/class_spec_helper_spec.js b/spec/javascripts/helpers/class_spec_helper_spec.js
index 0a61e561640..686b8eaed31 100644
--- a/spec/javascripts/helpers/class_spec_helper_spec.js
+++ b/spec/javascripts/helpers/class_spec_helper_spec.js
@@ -1,9 +1,9 @@
/* global ClassSpecHelper */
-require('./class_spec_helper');
+import './class_spec_helper';
describe('ClassSpecHelper', () => {
- describe('.itShouldBeAStaticMethod', function () {
+ describe('itShouldBeAStaticMethod', function () {
beforeEach(() => {
class TestClass {
instanceMethod() { this.prop = 'val'; }
diff --git a/spec/javascripts/helpers/filtered_search_spec_helper.js b/spec/javascripts/helpers/filtered_search_spec_helper.js
index b8d4a93b1ab..8933dd5def4 100644
--- a/spec/javascripts/helpers/filtered_search_spec_helper.js
+++ b/spec/javascripts/helpers/filtered_search_spec_helper.js
@@ -1,4 +1,4 @@
-class FilteredSearchSpecHelper {
+export default class FilteredSearchSpecHelper {
static createFilterVisualTokenHTML(name, value, isSelected) {
return FilteredSearchSpecHelper.createFilterVisualToken(name, value, isSelected).outerHTML;
}
@@ -30,12 +30,15 @@ class FilteredSearchSpecHelper {
`;
}
+ static createSearchVisualToken(name) {
+ const li = document.createElement('li');
+ li.classList.add('js-visual-token', 'filtered-search-term');
+ li.innerHTML = `<div class="name">${name}</div>`;
+ return li;
+ }
+
static createSearchVisualTokenHTML(name) {
- return `
- <li class="js-visual-token filtered-search-term">
- <div class="name">${name}</div>
- </li>
- `;
+ return FilteredSearchSpecHelper.createSearchVisualToken(name).outerHTML;
}
static createInputHTML(placeholder = '', value = '') {
@@ -53,5 +56,3 @@ class FilteredSearchSpecHelper {
`;
}
}
-
-module.exports = FilteredSearchSpecHelper;
diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js
new file mode 100644
index 00000000000..45909d4e70e
--- /dev/null
+++ b/spec/javascripts/integrations/integration_settings_form_spec.js
@@ -0,0 +1,199 @@
+import IntegrationSettingsForm from '~/integrations/integration_settings_form';
+
+describe('IntegrationSettingsForm', () => {
+ const FIXTURE = 'services/edit_service.html.raw';
+ preloadFixtures(FIXTURE);
+
+ beforeEach(() => {
+ loadFixtures(FIXTURE);
+ });
+
+ describe('contructor', () => {
+ let integrationSettingsForm;
+
+ beforeEach(() => {
+ integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ spyOn(integrationSettingsForm, 'init');
+ });
+
+ it('should initialize form element refs on class object', () => {
+ // Form Reference
+ expect(integrationSettingsForm.$form).toBeDefined();
+ expect(integrationSettingsForm.$form.prop('nodeName')).toEqual('FORM');
+
+ // Form Child Elements
+ expect(integrationSettingsForm.$serviceToggle).toBeDefined();
+ expect(integrationSettingsForm.$submitBtn).toBeDefined();
+ expect(integrationSettingsForm.$submitBtnLoader).toBeDefined();
+ expect(integrationSettingsForm.$submitBtnLabel).toBeDefined();
+ });
+
+ it('should initialize form metadata on class object', () => {
+ expect(integrationSettingsForm.testEndPoint).toBeDefined();
+ expect(integrationSettingsForm.canTestService).toBeDefined();
+ });
+ });
+
+ describe('toggleServiceState', () => {
+ let integrationSettingsForm;
+
+ beforeEach(() => {
+ integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ });
+
+ it('should remove `novalidate` attribute to form when called with `true`', () => {
+ integrationSettingsForm.toggleServiceState(true);
+
+ expect(integrationSettingsForm.$form.attr('novalidate')).not.toBeDefined();
+ });
+
+ it('should set `novalidate` attribute to form when called with `false`', () => {
+ integrationSettingsForm.toggleServiceState(false);
+
+ expect(integrationSettingsForm.$form.attr('novalidate')).toBeDefined();
+ });
+ });
+
+ describe('toggleSubmitBtnLabel', () => {
+ let integrationSettingsForm;
+
+ beforeEach(() => {
+ integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ });
+
+ it('should set Save button label to "Test settings and save changes" when serviceActive & canTestService are `true`', () => {
+ integrationSettingsForm.canTestService = true;
+
+ integrationSettingsForm.toggleSubmitBtnLabel(true);
+ expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Test settings and save changes');
+ });
+
+ it('should set Save button label to "Save changes" when either serviceActive or canTestService (or both) is `false`', () => {
+ integrationSettingsForm.canTestService = false;
+
+ integrationSettingsForm.toggleSubmitBtnLabel(false);
+ expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Save changes');
+
+ integrationSettingsForm.toggleSubmitBtnLabel(true);
+ expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Save changes');
+
+ integrationSettingsForm.canTestService = true;
+
+ integrationSettingsForm.toggleSubmitBtnLabel(false);
+ expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Save changes');
+ });
+ });
+
+ describe('toggleSubmitBtnState', () => {
+ let integrationSettingsForm;
+
+ beforeEach(() => {
+ integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ });
+
+ it('should disable Save button and show loader animation when called with `true`', () => {
+ integrationSettingsForm.toggleSubmitBtnState(true);
+
+ expect(integrationSettingsForm.$submitBtn.is(':disabled')).toBeTruthy();
+ expect(integrationSettingsForm.$submitBtnLoader.hasClass('hidden')).toBeFalsy();
+ });
+
+ it('should enable Save button and hide loader animation when called with `false`', () => {
+ integrationSettingsForm.toggleSubmitBtnState(false);
+
+ expect(integrationSettingsForm.$submitBtn.is(':disabled')).toBeFalsy();
+ expect(integrationSettingsForm.$submitBtnLoader.hasClass('hidden')).toBeTruthy();
+ });
+ });
+
+ describe('testSettings', () => {
+ let integrationSettingsForm;
+ let formData;
+
+ beforeEach(() => {
+ 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());
+
+ integrationSettingsForm.testSettings(formData);
+
+ expect($.ajax).toHaveBeenCalledWith({
+ type: 'PUT',
+ url: integrationSettingsForm.testEndPoint,
+ data: formData,
+ });
+ });
+
+ it('should show error Flash with `Save anyway` action if ajax request responds with error in test', () => {
+ const errorMessage = 'Test failed.';
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+
+ integrationSettingsForm.testSettings(formData);
+
+ deferred.resolve({ error: true, message: errorMessage });
+
+ const $flashContainer = $('.flash-container');
+ expect($flashContainer.find('.flash-text').text()).toEqual(errorMessage);
+ expect($flashContainer.find('.flash-action')).toBeDefined();
+ expect($flashContainer.find('.flash-action').text()).toEqual('Save anyway');
+ });
+
+ it('should submit form if ajax request responds without any error in test', () => {
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+
+ integrationSettingsForm.testSettings(formData);
+
+ spyOn(integrationSettingsForm.$form, 'submit');
+ deferred.resolve({ error: false });
+
+ expect(integrationSettingsForm.$form.submit).toHaveBeenCalled();
+ });
+
+ 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());
+
+ integrationSettingsForm.testSettings(formData);
+
+ deferred.resolve({ error: true, message: errorMessage });
+
+ const $flashAction = $('.flash-container .flash-action');
+ expect($flashAction).toBeDefined();
+
+ spyOn(integrationSettingsForm.$form, 'submit');
+ $flashAction.trigger('click');
+ expect(integrationSettingsForm.$form.submit).toHaveBeenCalled();
+ });
+
+ it('should show error Flash if ajax request failed', () => {
+ const errorMessage = 'Something went wrong on our end.';
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+
+ integrationSettingsForm.testSettings(formData);
+
+ deferred.reject();
+
+ expect($('.flash-container .flash-text').text()).toEqual(errorMessage);
+ });
+
+ it('should always call `toggleSubmitBtnState` with `false` once request is completed', () => {
+ const deferred = $.Deferred();
+ spyOn($, 'ajax').and.returnValue(deferred.promise());
+
+ integrationSettingsForm.testSettings(formData);
+
+ spyOn(integrationSettingsForm, 'toggleSubmitBtnState');
+ deferred.reject();
+
+ expect(integrationSettingsForm.toggleSubmitBtnState).toHaveBeenCalledWith(false);
+ });
+ });
+});
diff --git a/spec/javascripts/issuable_spec.js b/spec/javascripts/issuable_spec.js
index 26d87cc5931..45f55395d3a 100644
--- a/spec/javascripts/issuable_spec.js
+++ b/spec/javascripts/issuable_spec.js
@@ -1,7 +1,7 @@
-/* global Issuable */
+/* global IssuableIndex */
-require('~/lib/utils/url_utility');
-require('~/issuable');
+import '~/lib/utils/url_utility';
+import '~/issuable_index';
(() => {
const BASE_URL = '/user/project/issues?scope=all&state=closed';
@@ -24,11 +24,11 @@ require('~/issuable');
beforeEach(() => {
loadFixtures('static/issuable_filter.html.raw');
- Issuable.init();
+ IssuableIndex.init();
});
it('should be defined', () => {
- expect(window.Issuable).toBeDefined();
+ expect(window.IssuableIndex).toBeDefined();
});
describe('filtering', () => {
@@ -43,7 +43,7 @@ require('~/issuable');
it('should contain only the default parameters', () => {
spyOn(gl.utils, 'visitUrl');
- Issuable.filterResults($filtersForm);
+ IssuableIndex.filterResults($filtersForm);
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + DEFAULT_PARAMS);
});
@@ -52,7 +52,7 @@ require('~/issuable');
spyOn(gl.utils, 'visitUrl');
updateForm({ search: 'broken' }, $filtersForm);
- Issuable.filterResults($filtersForm);
+ IssuableIndex.filterResults($filtersForm);
const params = `${DEFAULT_PARAMS}&search=broken`;
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params);
@@ -64,14 +64,14 @@ require('~/issuable');
// initial filter
updateForm({ milestone_title: 'v1.0' }, $filtersForm);
- Issuable.filterResults($filtersForm);
+ IssuableIndex.filterResults($filtersForm);
let params = `${DEFAULT_PARAMS}&milestone_title=v1.0`;
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params);
// update filter
updateForm({ label_name: 'Frontend' }, $filtersForm);
- Issuable.filterResults($filtersForm);
+ IssuableIndex.filterResults($filtersForm);
params = `${DEFAULT_PARAMS}&milestone_title=v1.0&label_name=Frontend`;
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params);
});
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
new file mode 100644
index 00000000000..59c006aa0af
--- /dev/null
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -0,0 +1,377 @@
+import Vue from 'vue';
+import '~/render_math';
+import '~/render_gfm';
+import issuableApp from '~/issue_show/components/app.vue';
+import eventHub from '~/issue_show/event_hub';
+import issueShowData from '../mock_data';
+
+const issueShowInterceptor = data => (request, next) => {
+ next(request.respondWith(JSON.stringify(data), {
+ status: 200,
+ headers: {
+ 'POLL-INTERVAL': 1,
+ },
+ }));
+};
+
+function formatText(text) {
+ return text.trim().replace(/\s\s+/g, ' ');
+}
+
+describe('Issuable output', () => {
+ document.body.innerHTML = '<span id="task_status"></span>';
+
+ let vm;
+
+ beforeEach(() => {
+ const IssuableDescriptionComponent = Vue.extend(issuableApp);
+ Vue.http.interceptors.push(issueShowInterceptor(issueShowData.initialRequest));
+
+ spyOn(eventHub, '$emit');
+
+ vm = new IssuableDescriptionComponent({
+ propsData: {
+ canUpdate: true,
+ canDestroy: true,
+ canMove: true,
+ endpoint: '/gitlab-org/gitlab-shell/issues/9/realtime_changes',
+ issuableRef: '#1',
+ initialTitleHtml: '',
+ initialTitleText: '',
+ initialDescriptionHtml: '',
+ initialDescriptionText: '',
+ markdownPreviewUrl: '/',
+ markdownDocs: '/',
+ projectsAutocompleteUrl: '/',
+ isConfidential: false,
+ projectNamespace: '/',
+ projectPath: '/',
+ },
+ }).$mount();
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, issueShowInterceptor);
+ });
+
+ it('should render a title/description/edited and update title/description/edited on update', (done) => {
+ setTimeout(() => {
+ const editedText = vm.$el.querySelector('.edited-text');
+
+ expect(document.querySelector('title').innerText).toContain('this is a title (#1)');
+ expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>this is a title</p>');
+ expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>this is a description!</p>');
+ expect(vm.$el.querySelector('.js-task-list-field').value).toContain('this is a description');
+ expect(formatText(editedText.innerText)).toMatch(/Edited[\s\S]+?by Some User/);
+ expect(editedText.querySelector('.author_link').href).toMatch(/\/some_user$/);
+ expect(editedText.querySelector('time')).toBeTruthy();
+
+ Vue.http.interceptors.push(issueShowInterceptor(issueShowData.secondRequest));
+
+ setTimeout(() => {
+ expect(document.querySelector('title').innerText).toContain('2 (#1)');
+ expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>2</p>');
+ expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>42</p>');
+ expect(vm.$el.querySelector('.js-task-list-field').value).toContain('42');
+ expect(vm.$el.querySelector('.edited-text')).toBeTruthy();
+ expect(formatText(vm.$el.querySelector('.edited-text').innerText)).toMatch(/Edited[\s\S]+?by Other User/);
+ expect(editedText.querySelector('.author_link').href).toMatch(/\/other_user$/);
+ expect(editedText.querySelector('time')).toBeTruthy();
+
+ done();
+ });
+ });
+ });
+
+ it('shows actions if permissions are correct', (done) => {
+ vm.showForm = true;
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('does not show actions if permissions are incorrect', (done) => {
+ vm.showForm = true;
+ vm.canUpdate = false;
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn'),
+ ).toBeNull();
+
+ done();
+ });
+ });
+
+ it('does not update formState if form is already open', (done) => {
+ vm.openForm();
+
+ vm.state.titleText = 'testing 123';
+
+ vm.openForm();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.store.formState.title,
+ ).not.toBe('testing 123');
+
+ done();
+ });
+ });
+
+ describe('updateIssuable', () => {
+ it('fetches new data after update', (done) => {
+ spyOn(vm.service, 'getData');
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return {
+ confidential: false,
+ web_url: location.pathname,
+ };
+ },
+ });
+ }));
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ vm.service.getData,
+ ).toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('reloads the page if the confidential status has changed', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return {
+ confidential: true,
+ web_url: location.pathname,
+ };
+ },
+ });
+ }));
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ gl.utils.visitUrl,
+ ).toHaveBeenCalledWith(location.pathname);
+
+ done();
+ });
+ });
+
+ it('correctly updates issuable data', (done) => {
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve();
+ }));
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ vm.service.updateIssuable,
+ ).toHaveBeenCalledWith(vm.formState);
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('close.form');
+
+ done();
+ });
+ });
+
+ it('does not redirect if issue has not moved', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return {
+ web_url: location.pathname,
+ confidential: vm.isConfidential,
+ };
+ },
+ });
+ }));
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ gl.utils.visitUrl,
+ ).not.toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('redirects if issue is moved', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return {
+ web_url: '/testing-issue-move',
+ confidential: vm.isConfidential,
+ };
+ },
+ });
+ }));
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ gl.utils.visitUrl,
+ ).toHaveBeenCalledWith('/testing-issue-move');
+
+ done();
+ });
+ });
+
+ it('does not update issuable if project move confirm is false', (done) => {
+ spyOn(window, 'confirm').and.returnValue(false);
+ spyOn(vm.service, 'updateIssuable');
+
+ vm.store.formState.move_to_project_id = 1;
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ vm.service.updateIssuable,
+ ).not.toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('closes form on error', (done) => {
+ spyOn(window, 'Flash').and.callThrough();
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve, reject) => {
+ reject();
+ }));
+
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('close.form');
+ expect(
+ window.Flash,
+ ).toHaveBeenCalledWith('Error updating issue');
+
+ done();
+ });
+ });
+ });
+
+ describe('deleteIssuable', () => {
+ it('changes URL when deleted', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return { web_url: '/test' };
+ },
+ });
+ }));
+
+ vm.deleteIssuable();
+
+ setTimeout(() => {
+ expect(
+ gl.utils.visitUrl,
+ ).toHaveBeenCalledWith('/test');
+
+ done();
+ });
+ });
+
+ it('stops polling when deleting', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+ spyOn(vm.poll, 'stop');
+ spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return { web_url: '/test' };
+ },
+ });
+ }));
+
+ vm.deleteIssuable();
+
+ setTimeout(() => {
+ expect(
+ vm.poll.stop,
+ ).toHaveBeenCalledWith();
+
+ done();
+ });
+ });
+
+ it('closes form on error', (done) => {
+ spyOn(window, 'Flash').and.callThrough();
+ spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve, reject) => {
+ reject();
+ }));
+
+ vm.deleteIssuable();
+
+ setTimeout(() => {
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('close.form');
+ expect(
+ window.Flash,
+ ).toHaveBeenCalledWith('Error deleting issue');
+
+ done();
+ });
+ });
+ });
+
+ describe('open form', () => {
+ it('shows locked warning if form is open & data is different', (done) => {
+ Vue.http.interceptors.push(issueShowInterceptor(issueShowData.initialRequest));
+
+ Vue.nextTick()
+ .then(() => new Promise((resolve) => {
+ setTimeout(resolve);
+ }))
+ .then(() => {
+ vm.openForm();
+
+ Vue.http.interceptors.push(issueShowInterceptor(issueShowData.secondRequest));
+
+ return new Promise((resolve) => {
+ setTimeout(resolve);
+ });
+ })
+ .then(() => {
+ expect(
+ vm.formState.lockedWarningVisible,
+ ).toBeTruthy();
+
+ expect(
+ vm.$el.querySelector('.alert'),
+ ).not.toBeNull();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
new file mode 100644
index 00000000000..408349cc42d
--- /dev/null
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -0,0 +1,99 @@
+import Vue from 'vue';
+import descriptionComponent from '~/issue_show/components/description.vue';
+
+describe('Description component', () => {
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(descriptionComponent);
+
+ if (!document.querySelector('.issuable-meta')) {
+ const metaData = document.createElement('div');
+ metaData.classList.add('issuable-meta');
+ metaData.innerHTML = '<span id="task_status"></span><span id="task_status_short"></span>';
+
+ document.body.appendChild(metaData);
+ }
+
+ vm = new Component({
+ propsData: {
+ canUpdate: true,
+ descriptionHtml: 'test',
+ descriptionText: 'test',
+ updatedAt: new Date().toString(),
+ taskStatus: '',
+ },
+ }).$mount();
+ });
+
+ it('animates description changes', (done) => {
+ vm.descriptionHtml = 'changed';
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.wiki').classList.contains('issue-realtime-pre-pulse'),
+ ).toBeTruthy();
+
+ setTimeout(() => {
+ expect(
+ vm.$el.querySelector('.wiki').classList.contains('issue-realtime-trigger-pulse'),
+ ).toBeTruthy();
+
+ done();
+ });
+ });
+ });
+
+ it('re-inits the TaskList when description changed', (done) => {
+ spyOn(gl, 'TaskList');
+ vm.descriptionHtml = 'changed';
+
+ setTimeout(() => {
+ expect(
+ gl.TaskList,
+ ).toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('does not re-init the TaskList when canUpdate is false', (done) => {
+ spyOn(gl, 'TaskList');
+ vm.canUpdate = false;
+ vm.descriptionHtml = 'changed';
+
+ setTimeout(() => {
+ expect(
+ gl.TaskList,
+ ).not.toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ describe('taskStatus', () => {
+ it('adds full taskStatus', (done) => {
+ vm.taskStatus = '1 of 1';
+
+ setTimeout(() => {
+ expect(
+ document.querySelector('.issuable-meta #task_status').textContent.trim(),
+ ).toBe('1 of 1');
+
+ done();
+ });
+ });
+
+ it('adds short taskStatus', (done) => {
+ vm.taskStatus = '1 of 1';
+
+ setTimeout(() => {
+ expect(
+ document.querySelector('.issuable-meta #task_status_short').textContent.trim(),
+ ).toBe('1/1 task');
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/issue_show/components/edit_actions_spec.js b/spec/javascripts/issue_show/components/edit_actions_spec.js
new file mode 100644
index 00000000000..f6625b748b6
--- /dev/null
+++ b/spec/javascripts/issue_show/components/edit_actions_spec.js
@@ -0,0 +1,147 @@
+import Vue from 'vue';
+import editActions from '~/issue_show/components/edit_actions.vue';
+import eventHub from '~/issue_show/event_hub';
+import Store from '~/issue_show/stores';
+
+describe('Edit Actions components', () => {
+ let vm;
+
+ beforeEach((done) => {
+ const Component = Vue.extend(editActions);
+ const store = new Store({
+ titleHtml: '',
+ descriptionHtml: '',
+ issuableRef: '',
+ });
+ store.formState.title = 'test';
+
+ spyOn(eventHub, '$emit');
+
+ vm = new Component({
+ propsData: {
+ canDestroy: true,
+ formState: store.formState,
+ },
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('renders all buttons as enabled', () => {
+ expect(
+ vm.$el.querySelectorAll('.disabled').length,
+ ).toBe(0);
+
+ expect(
+ vm.$el.querySelectorAll('[disabled]').length,
+ ).toBe(0);
+ });
+
+ it('does not render delete button if canUpdate is false', (done) => {
+ vm.canDestroy = false;
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn-danger'),
+ ).toBeNull();
+
+ done();
+ });
+ });
+
+ it('disables submit button when title is blank', (done) => {
+ vm.formState.title = '';
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn-save').getAttribute('disabled'),
+ ).toBe('disabled');
+
+ done();
+ });
+ });
+
+ describe('updateIssuable', () => {
+ it('sends update.issauble event when clicking save button', () => {
+ vm.$el.querySelector('.btn-save').click();
+
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('update.issuable');
+ });
+
+ it('shows loading icon after clicking save button', (done) => {
+ vm.$el.querySelector('.btn-save').click();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn-save .fa'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('disabled button after clicking save button', (done) => {
+ vm.$el.querySelector('.btn-save').click();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn-save').getAttribute('disabled'),
+ ).toBe('disabled');
+
+ done();
+ });
+ });
+ });
+
+ describe('closeForm', () => {
+ it('emits close.form when clicking cancel', () => {
+ vm.$el.querySelector('.btn-default').click();
+
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('close.form');
+ });
+ });
+
+ describe('deleteIssuable', () => {
+ it('sends delete.issuable event when clicking save button', () => {
+ spyOn(window, 'confirm').and.returnValue(true);
+ vm.$el.querySelector('.btn-danger').click();
+
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('delete.issuable');
+ });
+
+ it('shows loading icon after clicking delete button', (done) => {
+ spyOn(window, 'confirm').and.returnValue(true);
+ vm.$el.querySelector('.btn-danger').click();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.btn-danger .fa'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('does no actions when confirm is false', (done) => {
+ spyOn(window, 'confirm').and.returnValue(false);
+ vm.$el.querySelector('.btn-danger').click();
+
+ Vue.nextTick(() => {
+ expect(
+ eventHub.$emit,
+ ).not.toHaveBeenCalledWith('delete.issuable');
+ expect(
+ vm.$el.querySelector('.btn-danger .fa'),
+ ).toBeNull();
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/issue_show/components/fields/description_spec.js b/spec/javascripts/issue_show/components/fields/description_spec.js
new file mode 100644
index 00000000000..f5b35b1e8b0
--- /dev/null
+++ b/spec/javascripts/issue_show/components/fields/description_spec.js
@@ -0,0 +1,56 @@
+import Vue from 'vue';
+import Store from '~/issue_show/stores';
+import descriptionField from '~/issue_show/components/fields/description.vue';
+
+describe('Description field component', () => {
+ let vm;
+ let store;
+
+ beforeEach((done) => {
+ const Component = Vue.extend(descriptionField);
+ const el = document.createElement('div');
+ store = new Store({
+ titleHtml: '',
+ descriptionHtml: '',
+ issuableRef: '',
+ });
+ store.formState.description = 'test';
+
+ document.body.appendChild(el);
+
+ vm = new Component({
+ el,
+ propsData: {
+ markdownPreviewUrl: '/',
+ markdownDocs: '/',
+ formState: store.formState,
+ },
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('renders markdown field with description', () => {
+ expect(
+ vm.$el.querySelector('.md-area textarea').value,
+ ).toBe('test');
+ });
+
+ it('renders markdown field with a markdown description', (done) => {
+ store.formState.description = '**test**';
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.md-area textarea').value,
+ ).toBe('**test**');
+
+ done();
+ });
+ });
+
+ it('focuses field when mounted', () => {
+ expect(
+ document.activeElement,
+ ).toBe(vm.$refs.textarea);
+ });
+});
diff --git a/spec/javascripts/issue_show/components/fields/description_template_spec.js b/spec/javascripts/issue_show/components/fields/description_template_spec.js
new file mode 100644
index 00000000000..2b7ee65094b
--- /dev/null
+++ b/spec/javascripts/issue_show/components/fields/description_template_spec.js
@@ -0,0 +1,49 @@
+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;
+ let formState;
+
+ beforeEach((done) => {
+ const Component = Vue.extend(descriptionTemplate);
+ formState = {
+ description: 'test',
+ };
+
+ vm = new Component({
+ propsData: {
+ formState,
+ issuableTemplates: [{ name: 'test' }],
+ projectPath: '/',
+ projectNamespace: '/',
+ },
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('renders templates as JSON array in data attribute', () => {
+ expect(
+ vm.$el.querySelector('.js-issuable-selector').getAttribute('data-data'),
+ ).toBe('[{"name":"test"}]');
+ });
+
+ it('updates formState when changing template', () => {
+ vm.issuableTemplate.editor.setValue('test new template');
+
+ expect(
+ formState.description,
+ ).toBe('test new template');
+ });
+
+ it('returns formState description with editor getValue', () => {
+ formState.description = 'testing new template';
+
+ expect(
+ vm.issuableTemplate.editor.getValue(),
+ ).toBe('testing new template');
+ });
+});
diff --git a/spec/javascripts/issue_show/components/fields/project_move_spec.js b/spec/javascripts/issue_show/components/fields/project_move_spec.js
new file mode 100644
index 00000000000..86d35c33ff4
--- /dev/null
+++ b/spec/javascripts/issue_show/components/fields/project_move_spec.js
@@ -0,0 +1,38 @@
+import Vue from 'vue';
+import projectMove from '~/issue_show/components/fields/project_move.vue';
+
+describe('Project move field component', () => {
+ let vm;
+ let formState;
+
+ beforeEach((done) => {
+ const Component = Vue.extend(projectMove);
+
+ formState = {
+ move_to_project_id: 0,
+ };
+
+ vm = new Component({
+ propsData: {
+ formState,
+ projectsAutocompleteUrl: '/autocomplete',
+ },
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('mounts select2 element', () => {
+ expect(
+ vm.$el.querySelector('.select2-container'),
+ ).not.toBeNull();
+ });
+
+ it('updates formState on change', () => {
+ $(vm.$refs['move-dropdown']).val(2).trigger('change');
+
+ expect(
+ formState.move_to_project_id,
+ ).toBe(2);
+ });
+});
diff --git a/spec/javascripts/issue_show/components/fields/title_spec.js b/spec/javascripts/issue_show/components/fields/title_spec.js
new file mode 100644
index 00000000000..53ae038a6a2
--- /dev/null
+++ b/spec/javascripts/issue_show/components/fields/title_spec.js
@@ -0,0 +1,30 @@
+import Vue from 'vue';
+import Store from '~/issue_show/stores';
+import titleField from '~/issue_show/components/fields/title.vue';
+
+describe('Title field component', () => {
+ let vm;
+ let store;
+
+ beforeEach(() => {
+ const Component = Vue.extend(titleField);
+ store = new Store({
+ titleHtml: '',
+ descriptionHtml: '',
+ issuableRef: '',
+ });
+ store.formState.title = 'test';
+
+ vm = new Component({
+ propsData: {
+ formState: store.formState,
+ },
+ }).$mount();
+ });
+
+ it('renders form control with formState title', () => {
+ expect(
+ vm.$el.querySelector('.form-control').value,
+ ).toBe('test');
+ });
+});
diff --git a/spec/javascripts/issue_show/components/form_spec.js b/spec/javascripts/issue_show/components/form_spec.js
new file mode 100644
index 00000000000..9a85223208c
--- /dev/null
+++ b/spec/javascripts/issue_show/components/form_spec.js
@@ -0,0 +1,68 @@
+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;
+
+ beforeEach((done) => {
+ const Component = Vue.extend(formComponent);
+
+ vm = new Component({
+ propsData: {
+ canDestroy: true,
+ canMove: true,
+ formState: {
+ title: 'b',
+ description: 'a',
+ lockedWarningVisible: false,
+ },
+ markdownPreviewUrl: '/',
+ markdownDocs: '/',
+ projectsAutocompleteUrl: '/',
+ projectPath: '/',
+ projectNamespace: '/',
+ },
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('does not render template selector if no templates exist', () => {
+ expect(
+ vm.$el.querySelector('.js-issuable-selector-wrap'),
+ ).toBeNull();
+ });
+
+ it('renders template selector when templates exists', (done) => {
+ spyOn(gl, 'IssuableTemplateSelectors');
+ vm.issuableTemplates = ['test'];
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.js-issuable-selector-wrap'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('hides locked warning by default', () => {
+ expect(
+ vm.$el.querySelector('.alert'),
+ ).toBeNull();
+ });
+
+ it('shows locked warning if formState is different', (done) => {
+ vm.formState.lockedWarningVisible = true;
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.alert'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/issue_show/components/title_spec.js b/spec/javascripts/issue_show/components/title_spec.js
new file mode 100644
index 00000000000..a2d90a9b9f5
--- /dev/null
+++ b/spec/javascripts/issue_show/components/title_spec.js
@@ -0,0 +1,75 @@
+import Vue from 'vue';
+import Store from '~/issue_show/stores';
+import titleComponent from '~/issue_show/components/title.vue';
+
+describe('Title component', () => {
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(titleComponent);
+ const store = new Store({
+ titleHtml: '',
+ descriptionHtml: '',
+ issuableRef: '',
+ });
+ vm = new Component({
+ propsData: {
+ issuableRef: '#1',
+ titleHtml: 'Testing <img />',
+ titleText: 'Testing',
+ showForm: false,
+ formState: store.formState,
+ },
+ }).$mount();
+ });
+
+ it('renders title HTML', () => {
+ expect(
+ vm.$el.innerHTML.trim(),
+ ).toBe('Testing <img>');
+ });
+
+ it('updates page title when changing titleHtml', (done) => {
+ spyOn(vm, 'setPageTitle');
+ vm.titleHtml = 'test';
+
+ Vue.nextTick(() => {
+ expect(
+ vm.setPageTitle,
+ ).toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('animates title changes', (done) => {
+ vm.titleHtml = 'test';
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.classList.contains('issue-realtime-pre-pulse'),
+ ).toBeTruthy();
+
+ setTimeout(() => {
+ expect(
+ vm.$el.classList.contains('issue-realtime-trigger-pulse'),
+ ).toBeTruthy();
+
+ done();
+ });
+ });
+ });
+
+ it('updates page title after changing title', (done) => {
+ vm.titleHtml = 'changed';
+ vm.titleText = 'changed';
+
+ Vue.nextTick(() => {
+ expect(
+ document.querySelector('title').textContent.trim(),
+ ).toContain('changed');
+
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/issue_show/mock_data.js b/spec/javascripts/issue_show/mock_data.js
index a4562449ff1..eb3111412a7 100644
--- a/spec/javascripts/issue_show/mock_data.js
+++ b/spec/javascripts/issue_show/mock_data.js
@@ -4,7 +4,6 @@ export default {
title_text: 'this is a title',
description: '<p>this is a description!</p>',
description_text: 'this is a description',
- issue_number: 1,
task_status: '2 of 4 completed',
updated_at: '2015-05-15T12:31:04.428Z',
updated_by_name: 'Some User',
@@ -15,7 +14,6 @@ export default {
title_text: '2',
description: '<p>42</p>',
description_text: '42',
- issue_number: 1,
task_status: '0 of 0 completed',
updated_at: '2016-05-15T12:31:04.428Z',
updated_by_name: 'Other User',
@@ -26,7 +24,6 @@ export default {
title_text: 'this is a title',
description: '<li class="task-list-item enabled"><input type="checkbox" class="task-list-item-checkbox">Task List Item</li>',
description_text: '- [ ] Task List Item',
- issue_number: 1,
task_status: '0 of 1 completed',
updated_at: '2017-05-15T12:31:04.428Z',
updated_by_name: 'Last User',
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 763f5ee9e50..df97a100b0d 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,7 +1,7 @@
/* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
import Issue from '~/issue';
-require('~/lib/utils/text_utility');
+import '~/lib/utils/text_utility';
describe('Issue', function() {
let $boxClosed, $boxOpen, $btnClose, $btnReopen;
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js
index 37e038c16da..c99f379b871 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js
+++ b/spec/javascripts/labels_issue_sidebar_spec.js
@@ -2,15 +2,14 @@
/* global IssuableContext */
/* global LabelsSelect */
-require('~/lib/utils/type_utility');
-require('~/gl_dropdown');
-require('select2');
-require('vendor/jquery.nicescroll');
-require('~/api');
-require('~/create_label');
-require('~/issuable_context');
-require('~/users_select');
-require('~/labels_select');
+import '~/gl_dropdown';
+import 'select2';
+import 'vendor/jquery.nicescroll';
+import '~/api';
+import '~/create_label';
+import '~/issuable_context';
+import '~/users_select';
+import '~/labels_select';
(() => {
let saveLabelCount = 0;
diff --git a/spec/javascripts/lib/utils/ajax_cache_spec.js b/spec/javascripts/lib/utils/ajax_cache_spec.js
index 7b466a11b92..2c946802dcd 100644
--- a/spec/javascripts/lib/utils/ajax_cache_spec.js
+++ b/spec/javascripts/lib/utils/ajax_cache_spec.js
@@ -5,19 +5,13 @@ describe('AjaxCache', () => {
const dummyResponse = {
important: 'dummy data',
};
- let ajaxSpy = (url) => {
- expect(url).toBe(dummyEndpoint);
- const deferred = $.Deferred();
- deferred.resolve(dummyResponse);
- return deferred.promise();
- };
beforeEach(() => {
AjaxCache.internalStorage = { };
- spyOn(jQuery, 'ajax').and.callFake(url => ajaxSpy(url));
+ AjaxCache.pendingRequests = { };
});
- describe('#get', () => {
+ describe('get', () => {
it('returns undefined if cache is empty', () => {
const data = AjaxCache.get(dummyEndpoint);
@@ -41,7 +35,7 @@ describe('AjaxCache', () => {
});
});
- describe('#hasData', () => {
+ describe('hasData', () => {
it('returns false if cache is empty', () => {
expect(AjaxCache.hasData(dummyEndpoint)).toBe(false);
});
@@ -59,9 +53,9 @@ describe('AjaxCache', () => {
});
});
- describe('#purge', () => {
+ describe('remove', () => {
it('does nothing if cache is empty', () => {
- AjaxCache.purge(dummyEndpoint);
+ AjaxCache.remove(dummyEndpoint);
expect(AjaxCache.internalStorage).toEqual({ });
});
@@ -69,7 +63,7 @@ describe('AjaxCache', () => {
it('does nothing if cache contains no matching data', () => {
AjaxCache.internalStorage['not matching'] = dummyResponse;
- AjaxCache.purge(dummyEndpoint);
+ AjaxCache.remove(dummyEndpoint);
expect(AjaxCache.internalStorage['not matching']).toBe(dummyResponse);
});
@@ -77,14 +71,27 @@ describe('AjaxCache', () => {
it('removes matching data', () => {
AjaxCache.internalStorage[dummyEndpoint] = dummyResponse;
- AjaxCache.purge(dummyEndpoint);
+ AjaxCache.remove(dummyEndpoint);
expect(AjaxCache.internalStorage).toEqual({ });
});
});
- describe('#retrieve', () => {
+ describe('retrieve', () => {
+ let ajaxSpy;
+
+ beforeEach(() => {
+ spyOn(jQuery, 'ajax').and.callFake(url => ajaxSpy(url));
+ });
+
it('stores and returns data from Ajax call if cache is empty', (done) => {
+ ajaxSpy = (url) => {
+ expect(url).toBe(dummyEndpoint);
+ const deferred = $.Deferred();
+ deferred.resolve(dummyResponse);
+ return deferred.promise();
+ };
+
AjaxCache.retrieve(dummyEndpoint)
.then((data) => {
expect(data).toBe(dummyResponse);
@@ -94,6 +101,28 @@ describe('AjaxCache', () => {
.catch(fail);
});
+ it('makes no Ajax call if request is pending', () => {
+ const responseDeferred = $.Deferred();
+
+ ajaxSpy = (url) => {
+ expect(url).toBe(dummyEndpoint);
+ // neither reject nor resolve to keep request pending
+ return responseDeferred.promise();
+ };
+
+ const unexpectedResponse = data => fail(`Did not expect response: ${data}`);
+
+ AjaxCache.retrieve(dummyEndpoint)
+ .then(unexpectedResponse)
+ .catch(fail);
+
+ AjaxCache.retrieve(dummyEndpoint)
+ .then(unexpectedResponse)
+ .catch(fail);
+
+ expect($.ajax.calls.count()).toBe(1);
+ });
+
it('returns undefined if Ajax call fails and cache is empty', (done) => {
const dummyStatusText = 'exploded';
const dummyErrorMessage = 'server exploded';
@@ -125,5 +154,36 @@ describe('AjaxCache', () => {
.then(done)
.catch(fail);
});
+
+ it('makes Ajax call even if matching data exists when forceRequest parameter is provided', (done) => {
+ const oldDummyResponse = {
+ important: 'old dummy data',
+ };
+
+ AjaxCache.internalStorage[dummyEndpoint] = oldDummyResponse;
+
+ ajaxSpy = (url) => {
+ expect(url).toBe(dummyEndpoint);
+ const deferred = $.Deferred();
+ deferred.resolve(dummyResponse);
+ return deferred.promise();
+ };
+
+ // Call without forceRetrieve param
+ AjaxCache.retrieve(dummyEndpoint)
+ .then((data) => {
+ expect(data).toBe(oldDummyResponse);
+ })
+ .then(done)
+ .catch(fail);
+
+ // Call with forceRetrieve param
+ AjaxCache.retrieve(dummyEndpoint, true)
+ .then((data) => {
+ expect(data).toBe(dummyResponse);
+ })
+ .then(done)
+ .catch(fail);
+ });
});
});
diff --git a/spec/javascripts/lib/utils/cache_spec.js b/spec/javascripts/lib/utils/cache_spec.js
new file mode 100644
index 00000000000..2fe02a7592c
--- /dev/null
+++ b/spec/javascripts/lib/utils/cache_spec.js
@@ -0,0 +1,65 @@
+import Cache from '~/lib/utils/cache';
+
+describe('Cache', () => {
+ const dummyKey = 'just some key';
+ const dummyValue = 'more than a value';
+ let cache;
+
+ beforeEach(() => {
+ cache = new Cache();
+ });
+
+ describe('get', () => {
+ it('return cached data', () => {
+ cache.internalStorage[dummyKey] = dummyValue;
+
+ expect(cache.get(dummyKey)).toBe(dummyValue);
+ });
+
+ it('returns undefined for missing data', () => {
+ expect(cache.internalStorage[dummyKey]).toBe(undefined);
+ expect(cache.get(dummyKey)).toBe(undefined);
+ });
+ });
+
+ describe('hasData', () => {
+ it('return true for cached data', () => {
+ cache.internalStorage[dummyKey] = dummyValue;
+
+ expect(cache.hasData(dummyKey)).toBe(true);
+ });
+
+ it('returns false for missing data', () => {
+ expect(cache.internalStorage[dummyKey]).toBe(undefined);
+ expect(cache.hasData(dummyKey)).toBe(false);
+ });
+ });
+
+ describe('remove', () => {
+ it('removes data from cache', () => {
+ cache.internalStorage[dummyKey] = dummyValue;
+
+ cache.remove(dummyKey);
+
+ expect(cache.internalStorage[dummyKey]).toBe(undefined);
+ });
+
+ it('does nothing for missing data', () => {
+ expect(cache.internalStorage[dummyKey]).toBe(undefined);
+
+ cache.remove(dummyKey);
+
+ expect(cache.internalStorage[dummyKey]).toBe(undefined);
+ });
+
+ it('does not remove wrong data', () => {
+ cache.internalStorage[dummyKey] = dummyValue;
+ cache.internalStorage[dummyKey + dummyKey] = dummyValue + dummyValue;
+
+ cache.remove(dummyKey);
+
+ expect(cache.internalStorage[dummyKey]).toBe(undefined);
+ expect(cache.internalStorage[dummyKey + dummyKey]).toBe(dummyValue + dummyValue);
+ });
+ });
+});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 5eb147ed888..e3938a77680 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -1,6 +1,6 @@
/* eslint-disable promise/catch-or-return */
-require('~/lib/utils/common_utils');
+import '~/lib/utils/common_utils';
(() => {
describe('common_utils', () => {
@@ -41,6 +41,16 @@ require('~/lib/utils/common_utils');
const paramsArray = gl.utils.getUrlParamsArray();
expect(paramsArray[0][0] !== '?').toBe(true);
});
+
+ it('should decode params', () => {
+ history.pushState('', '', '?label_name%5B%5D=test');
+
+ expect(
+ gl.utils.getUrlParamsArray()[0],
+ ).toBe('label_name[]=test');
+
+ history.pushState('', '', '?');
+ });
});
describe('gl.utils.handleLocationHash', () => {
@@ -346,7 +356,7 @@ require('~/lib/utils/common_utils');
describe('gl.utils.setCiStatusFavicon', () => {
it('should set page favicon to CI status favicon based on provided status', () => {
- const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`;
+ const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
const FAVICON_PATH = '//icon_status_success';
const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub();
const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub();
diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js
index 90b12c9f115..83c92deccdc 100644
--- a/spec/javascripts/lib/utils/number_utility_spec.js
+++ b/spec/javascripts/lib/utils/number_utility_spec.js
@@ -1,4 +1,4 @@
-import { formatRelevantDigits, bytesToKiB } from '~/lib/utils/number_utils';
+import { formatRelevantDigits, bytesToKiB, bytesToMiB } from '~/lib/utils/number_utils';
describe('Number Utils', () => {
describe('formatRelevantDigits', () => {
@@ -45,4 +45,11 @@ describe('Number Utils', () => {
expect(bytesToKiB(1000)).toEqual(0.9765625);
});
});
+
+ describe('bytesToMiB', () => {
+ it('calculates MiB for the given bytes', () => {
+ expect(bytesToMiB(1048576)).toEqual(1);
+ expect(bytesToMiB(1000000)).toEqual(0.95367431640625);
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/poll_spec.js b/spec/javascripts/lib/utils/poll_spec.js
index 918b6d32c43..22f30191ab9 100644
--- a/spec/javascripts/lib/utils/poll_spec.js
+++ b/spec/javascripts/lib/utils/poll_spec.js
@@ -1,9 +1,5 @@
-import Vue from 'vue';
-import VueResource from 'vue-resource';
import Poll from '~/lib/utils/poll';
-Vue.use(VueResource);
-
const waitForAllCallsToFinish = (service, waitForCount, successCallback) => {
const timer = () => {
setTimeout(() => {
@@ -12,45 +8,33 @@ const waitForAllCallsToFinish = (service, waitForCount, successCallback) => {
} else {
timer();
}
- }, 5);
+ }, 0);
};
timer();
};
-class ServiceMock {
- constructor(endpoint) {
- this.service = Vue.resource(endpoint);
- }
+function mockServiceCall(service, response, shouldFail = false) {
+ const action = shouldFail ? Promise.reject : Promise.resolve;
+ const responseObject = response;
+
+ if (!responseObject.headers) responseObject.headers = {};
- fetch() {
- return this.service.get();
- }
+ service.fetch.and.callFake(action.bind(Promise, responseObject));
}
describe('Poll', () => {
- let callbacks;
- let service;
+ const service = jasmine.createSpyObj('service', ['fetch']);
+ const callbacks = jasmine.createSpyObj('callbacks', ['success', 'error']);
- beforeEach(() => {
- callbacks = {
- success: () => {},
- error: () => {},
- };
-
- service = new ServiceMock('endpoint');
-
- spyOn(callbacks, 'success');
- spyOn(callbacks, 'error');
- spyOn(service, 'fetch').and.callThrough();
+ afterEach(() => {
+ callbacks.success.calls.reset();
+ callbacks.error.calls.reset();
+ service.fetch.calls.reset();
});
it('calls the success callback when no header for interval is provided', (done) => {
- const successInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), { status: 200 }));
- };
-
- Vue.http.interceptors.push(successInterceptor);
+ mockServiceCall(service, { status: 200 });
new Poll({
resource: service,
@@ -63,18 +47,12 @@ describe('Poll', () => {
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, successInterceptor);
-
done();
- }, 0);
+ });
});
it('calls the error callback whe the http request returns an error', (done) => {
- const errorInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), { status: 500 }));
- };
-
- Vue.http.interceptors.push(errorInterceptor);
+ mockServiceCall(service, { status: 500 }, true);
new Poll({
resource: service,
@@ -86,42 +64,29 @@ describe('Poll', () => {
waitForAllCallsToFinish(service, 1, () => {
expect(callbacks.success).not.toHaveBeenCalled();
expect(callbacks.error).toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, errorInterceptor);
done();
});
});
it('should call the success callback when the interval header is -1', (done) => {
- const intervalInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), { status: 200, headers: { 'poll-interval': -1 } }));
- };
-
- Vue.http.interceptors.push(intervalInterceptor);
+ mockServiceCall(service, { status: 200, headers: { 'poll-interval': -1 } });
new Poll({
resource: service,
method: 'fetch',
successCallback: callbacks.success,
errorCallback: callbacks.error,
- }).makeRequest();
-
- setTimeout(() => {
+ }).makeRequest().then(() => {
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, intervalInterceptor);
-
done();
- }, 0);
+ }).catch(done.fail);
});
it('starts polling when http status is 200 and interval header is provided', (done) => {
- const pollInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), { status: 200, headers: { 'poll-interval': 2 } }));
- };
-
- Vue.http.interceptors.push(pollInterceptor);
+ mockServiceCall(service, { status: 200, headers: { 'poll-interval': 1 } });
const Polling = new Poll({
resource: service,
@@ -141,19 +106,13 @@ describe('Poll', () => {
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
-
done();
});
});
describe('stop', () => {
it('stops polling when method is called', (done) => {
- const pollInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), { status: 200, headers: { 'poll-interval': 2 } }));
- };
-
- Vue.http.interceptors.push(pollInterceptor);
+ mockServiceCall(service, { status: 200, headers: { 'poll-interval': 1 } });
const Polling = new Poll({
resource: service,
@@ -174,8 +133,6 @@ describe('Poll', () => {
expect(service.fetch).toHaveBeenCalledWith({ page: 1 });
expect(Polling.stop).toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
-
done();
});
});
@@ -183,11 +140,7 @@ describe('Poll', () => {
describe('restart', () => {
it('should restart polling when its called', (done) => {
- const pollInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), { status: 200, headers: { 'poll-interval': 2 } }));
- };
-
- Vue.http.interceptors.push(pollInterceptor);
+ mockServiceCall(service, { status: 200, headers: { 'poll-interval': 1 } });
const Polling = new Poll({
resource: service,
@@ -215,8 +168,6 @@ describe('Poll', () => {
expect(Polling.stop).toHaveBeenCalled();
expect(Polling.restart).toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
-
done();
});
});
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index daef9b93fa5..ca1b1b7cc3c 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -1,4 +1,4 @@
-require('~/lib/utils/text_utility');
+import '~/lib/utils/text_utility';
describe('text_utility', () => {
describe('gl.text.getTextWidth', () => {
diff --git a/spec/javascripts/lib/utils/users_cache_spec.js b/spec/javascripts/lib/utils/users_cache_spec.js
new file mode 100644
index 00000000000..ec6ea35952b
--- /dev/null
+++ b/spec/javascripts/lib/utils/users_cache_spec.js
@@ -0,0 +1,136 @@
+import Api from '~/api';
+import UsersCache from '~/lib/utils/users_cache';
+
+describe('UsersCache', () => {
+ const dummyUsername = 'win';
+ const dummyUser = 'has a farm';
+
+ beforeEach(() => {
+ UsersCache.internalStorage = { };
+ });
+
+ describe('get', () => {
+ it('returns undefined for empty cache', () => {
+ expect(UsersCache.internalStorage).toEqual({ });
+
+ const user = UsersCache.get(dummyUsername);
+
+ expect(user).toBe(undefined);
+ });
+
+ it('returns undefined for missing user', () => {
+ UsersCache.internalStorage['no body'] = 'no data';
+
+ const user = UsersCache.get(dummyUsername);
+
+ expect(user).toBe(undefined);
+ });
+
+ it('returns matching user', () => {
+ UsersCache.internalStorage[dummyUsername] = dummyUser;
+
+ const user = UsersCache.get(dummyUsername);
+
+ expect(user).toBe(dummyUser);
+ });
+ });
+
+ describe('hasData', () => {
+ it('returns false for empty cache', () => {
+ expect(UsersCache.internalStorage).toEqual({ });
+
+ expect(UsersCache.hasData(dummyUsername)).toBe(false);
+ });
+
+ it('returns false for missing user', () => {
+ UsersCache.internalStorage['no body'] = 'no data';
+
+ expect(UsersCache.hasData(dummyUsername)).toBe(false);
+ });
+
+ it('returns true for matching user', () => {
+ UsersCache.internalStorage[dummyUsername] = dummyUser;
+
+ expect(UsersCache.hasData(dummyUsername)).toBe(true);
+ });
+ });
+
+ describe('remove', () => {
+ it('does nothing if cache is empty', () => {
+ expect(UsersCache.internalStorage).toEqual({ });
+
+ UsersCache.remove(dummyUsername);
+
+ expect(UsersCache.internalStorage).toEqual({ });
+ });
+
+ it('does nothing if cache contains no matching data', () => {
+ UsersCache.internalStorage['no body'] = 'no data';
+
+ UsersCache.remove(dummyUsername);
+
+ expect(UsersCache.internalStorage['no body']).toBe('no data');
+ });
+
+ it('removes matching data', () => {
+ UsersCache.internalStorage[dummyUsername] = dummyUser;
+
+ UsersCache.remove(dummyUsername);
+
+ expect(UsersCache.internalStorage).toEqual({ });
+ });
+ });
+
+ describe('retrieve', () => {
+ let apiSpy;
+
+ beforeEach(() => {
+ spyOn(Api, 'users').and.callFake((query, options) => apiSpy(query, options));
+ });
+
+ it('stores and returns data from API call if cache is empty', (done) => {
+ apiSpy = (query, options) => {
+ expect(query).toBe('');
+ expect(options).toEqual({ username: dummyUsername });
+ return Promise.resolve([dummyUser]);
+ };
+
+ UsersCache.retrieve(dummyUsername)
+ .then((user) => {
+ expect(user).toBe(dummyUser);
+ expect(UsersCache.internalStorage[dummyUsername]).toBe(dummyUser);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('returns undefined if Ajax call fails and cache is empty', (done) => {
+ const dummyError = new Error('server exploded');
+ apiSpy = (query, options) => {
+ expect(query).toBe('');
+ expect(options).toEqual({ username: dummyUsername });
+ return Promise.reject(dummyError);
+ };
+
+ UsersCache.retrieve(dummyUsername)
+ .then(user => fail(`Received unexpected user: ${JSON.stringify(user)}`))
+ .catch((error) => {
+ expect(error).toBe(dummyError);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('makes no Ajax call if matching data exists', (done) => {
+ UsersCache.internalStorage[dummyUsername] = dummyUser;
+ apiSpy = () => fail(new Error('expected no Ajax call!'));
+
+ UsersCache.retrieve(dummyUsername)
+ .then((user) => {
+ expect(user).toBe(dummyUser);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index a1fd2d38968..aee274641e8 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -1,7 +1,7 @@
/* eslint-disable space-before-function-paren, no-var, no-param-reassign, quotes, prefer-template, no-else-return, new-cap, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, jasmine/no-spec-dupes, no-underscore-dangle, max-len */
/* global LineHighlighter */
-require('~/line_highlighter');
+import '~/line_highlighter';
(function() {
describe('LineHighlighter', function() {
@@ -58,7 +58,7 @@ require('~/line_highlighter');
return expect(func).not.toThrow();
});
});
- describe('#clickHandler', function() {
+ describe('clickHandler', function() {
it('handles clicking on a child icon element', function() {
var spy;
spy = spyOn(this["class"], 'setHash').and.callThrough();
@@ -176,7 +176,7 @@ require('~/line_highlighter');
});
});
});
- describe('#hashToRange', function() {
+ describe('hashToRange', function() {
beforeEach(function() {
return this.subject = this["class"].hashToRange;
});
@@ -190,7 +190,7 @@ require('~/line_highlighter');
return expect(this.subject('#foo')).toEqual([null, null]);
});
});
- describe('#highlightLine', function() {
+ describe('highlightLine', function() {
beforeEach(function() {
return this.subject = this["class"].highlightLine;
});
@@ -203,7 +203,7 @@ require('~/line_highlighter');
return expect($('#LC13')).toHaveClass(this.css);
});
});
- return describe('#setHash', function() {
+ return describe('setHash', function() {
beforeEach(function() {
return this.subject = this["class"].setHash;
});
diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js
new file mode 100644
index 00000000000..e54acfa8e44
--- /dev/null
+++ b/spec/javascripts/merge_request_notes_spec.js
@@ -0,0 +1,61 @@
+/* global Notes */
+
+import 'vendor/autosize';
+import '~/gl_form';
+import '~/lib/utils/text_utility';
+import '~/render_gfm';
+import '~/render_math';
+import '~/notes';
+
+describe('Merge request notes', () => {
+ window.gon = window.gon || {};
+ window.gl = window.gl || {};
+ gl.utils = gl.utils || {};
+
+ const fixture = 'merge_requests/diff_comment.html.raw';
+ preloadFixtures(fixture);
+
+ beforeEach(() => {
+ loadFixtures(fixture);
+ gl.utils.disableButtonIfEmptyField = _.noop;
+ window.project_uploads_path = 'http://test.host/uploads';
+ $('body').data('page', 'projects:merge_requests:show');
+ window.gon.current_user_id = $('.note:last').data('author-id');
+
+ return new Notes('', []);
+ });
+
+ describe('up arrow', () => {
+ it('edits last comment when triggered in main form', () => {
+ const upArrowEvent = $.Event('keydown');
+ upArrowEvent.which = 38;
+
+ spyOnEvent('.note:last .js-note-edit', 'click');
+
+ $('.js-note-text').trigger(upArrowEvent);
+
+ expect('click').toHaveBeenTriggeredOn('.note:last .js-note-edit');
+ });
+
+ it('edits last comment in discussion when triggered in discussion form', (done) => {
+ const upArrowEvent = $.Event('keydown');
+ upArrowEvent.which = 38;
+
+ spyOnEvent('.note-discussion .js-note-edit', 'click');
+
+ $('.js-discussion-reply-button').click();
+
+ setTimeout(() => {
+ expect(
+ $('.note-discussion .js-note-text'),
+ ).toExist();
+
+ $('.note-discussion .js-note-text').trigger(upArrowEvent);
+
+ expect('click').toHaveBeenTriggeredOn('.note-discussion .js-note-edit');
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index fd97dced870..f444bcaf847 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -1,7 +1,7 @@
/* eslint-disable space-before-function-paren, no-return-assign */
/* global MergeRequest */
-require('~/merge_request');
+import '~/merge_request';
(function() {
describe('MergeRequest', function() {
@@ -13,7 +13,9 @@ require('~/merge_request');
});
it('modifies the Markdown field', function() {
spyOn(jQuery, 'ajax').and.stub();
- $('input[type=checkbox]').attr('checked', true).trigger('change');
+ const changeEvent = document.createEvent('HTMLEvents');
+ changeEvent.initEvent('change', true, true);
+ $('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() {
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index e437333d522..7b910282cc8 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -1,13 +1,15 @@
/* eslint-disable no-var, comma-dangle, object-shorthand */
-
-require('~/merge_request_tabs');
-require('~/commit/pipelines/pipelines_bundle.js');
-require('~/breakpoints');
-require('~/lib/utils/common_utils');
-require('~/diff');
-require('~/single_file_diff');
-require('~/files_comment_button');
-require('vendor/jquery.scrollTo');
+/* global Notes */
+
+import '~/merge_request_tabs';
+import '~/commit/pipelines/pipelines_bundle';
+import '~/breakpoints';
+import '~/lib/utils/common_utils';
+import '~/diff';
+import '~/single_file_diff';
+import '~/files_comment_button';
+import '~/notes';
+import 'vendor/jquery.scrollTo';
(function () {
// TODO: remove this hack!
@@ -29,7 +31,7 @@ require('vendor/jquery.scrollTo');
};
$.extend(stubLocation, defaults, stubs || {});
};
- preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+ preloadFixtures('merge_requests/merge_request_with_task_list.html.raw', 'merge_requests/diff_comment.html.raw');
beforeEach(function () {
this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation });
@@ -47,7 +49,7 @@ require('vendor/jquery.scrollTo');
this.class.destroyPipelinesView();
});
- describe('#activateTab', function () {
+ describe('activateTab', function () {
beforeEach(function () {
spyOn($, 'ajax').and.callFake(function () {});
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
@@ -71,7 +73,7 @@ require('vendor/jquery.scrollTo');
});
});
- describe('#opensInNewTab', function () {
+ describe('opensInNewTab', function () {
var tabUrl;
var windowTarget = '_blank';
@@ -152,7 +154,7 @@ require('vendor/jquery.scrollTo');
});
});
- describe('#setCurrentAction', function () {
+ describe('setCurrentAction', function () {
beforeEach(function () {
spyOn($, 'ajax').and.callFake(function () {});
this.subject = this.class.setCurrentAction;
@@ -221,7 +223,7 @@ require('vendor/jquery.scrollTo');
});
});
- describe('#tabShown', () => {
+ describe('tabShown', () => {
beforeEach(function () {
spyOn($, 'ajax').and.callFake(function (options) {
options.success({ html: '' });
@@ -281,13 +283,54 @@ require('vendor/jquery.scrollTo');
});
});
- describe('#loadDiff', function () {
+ describe('loadDiff', function () {
it('requires an absolute pathname', function () {
spyOn($, 'ajax').and.callFake(function (options) {
expect(options.url).toEqual('/foo/bar/merge_requests/1/diffs.json');
});
+
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
});
+
+ describe('with note fragment hash', () => {
+ beforeEach(() => {
+ loadFixtures('merge_requests/diff_comment.html.raw');
+ spyOn(window.gl.utils, 'getPagePath').and.returnValue('merge_requests');
+ window.notes = new Notes('', []);
+ spyOn(window.notes, 'toggleDiffNote').and.callThrough();
+ });
+
+ afterEach(() => {
+ delete window.notes;
+ });
+
+ it('should expand and scroll to linked fragment hash #note_xxx', function () {
+ const noteId = 'note_1';
+ spyOn(window.gl.utils, 'getLocationHash').and.returnValue(noteId);
+ spyOn($, 'ajax').and.callFake(function (options) {
+ options.success({ html: `<div id="${noteId}">foo</div>` });
+ });
+
+ this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+
+ expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({
+ target: jasmine.any(Object),
+ lineType: 'old',
+ forceShow: true,
+ });
+ });
+
+ it('should gracefully ignore non-existant fragment hash', function () {
+ spyOn(window.gl.utils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
+ spyOn($, 'ajax').and.callFake(function (options) {
+ options.success({ html: '' });
+ });
+
+ this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+
+ expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
+ });
+ });
});
});
}).call(window);
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index 90a429beeca..c57f44dae17 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -1,7 +1,7 @@
/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, max-len */
/* global NewBranchForm */
-require('~/new_branch_form');
+import '~/new_branch_form';
(function() {
describe('Branch', function() {
diff --git a/spec/javascripts/notebook/cells/markdown_spec.js b/spec/javascripts/notebook/cells/markdown_spec.js
index 38c976f38d8..a88e9ed3d99 100644
--- a/spec/javascripts/notebook/cells/markdown_spec.js
+++ b/spec/javascripts/notebook/cells/markdown_spec.js
@@ -1,8 +1,11 @@
import Vue from 'vue';
import MarkdownComponent from '~/notebook/cells/markdown.vue';
+import katex from 'vendor/katex';
const Component = Vue.extend(MarkdownComponent);
+window.katex = katex;
+
describe('Markdown component', () => {
let vm;
let cell;
@@ -38,4 +41,58 @@ describe('Markdown component', () => {
it('renders the markdown HTML', () => {
expect(vm.$el.querySelector('.markdown h1')).not.toBeNull();
});
+
+ describe('katex', () => {
+ beforeEach(() => {
+ json = getJSONFixture('blob/notebook/math.json');
+ });
+
+ it('renders multi-line katex', (done) => {
+ vm = new Component({
+ propsData: {
+ cell: json.cells[0],
+ },
+ }).$mount();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.katex'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('renders inline katex', (done) => {
+ vm = new Component({
+ propsData: {
+ cell: json.cells[1],
+ },
+ }).$mount();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('p:first-child .katex'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('renders multiple inline katex', (done) => {
+ vm = new Component({
+ propsData: {
+ cell: json.cells[1],
+ },
+ }).$mount();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelectorAll('p:nth-child(2) .katex').length,
+ ).toBe(4);
+
+ done();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 8fb2216d94b..77e68d578d9 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -13,7 +13,25 @@ import '~/notes';
window.gl = window.gl || {};
gl.utils = gl.utils || {};
+ const htmlEscape = (comment) => {
+ const escapedString = comment.replace(/["&'<>]/g, (a) => {
+ const escapedToken = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#x27;',
+ '`': '&#x60;'
+ }[a];
+
+ return escapedToken;
+ });
+
+ return escapedString;
+ };
+
describe('Notes', function() {
+ const FLASH_TYPE_ALERT = 'alert';
var commentsTemplate = 'issues/issue_with_comment.html.raw';
preloadFixtures(commentsTemplate);
@@ -33,7 +51,9 @@ import '~/notes';
});
it('modifies the Markdown field', function() {
- $('input[type=checkbox]').attr('checked', true).trigger('change');
+ const changeEvent = document.createEvent('HTMLEvents');
+ changeEvent.initEvent('change', true, true);
+ $('input[type=checkbox]').attr('checked', true)[0].dispatchEvent(changeEvent);
expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
});
@@ -127,7 +147,6 @@ import '~/notes';
beforeEach(() => {
note = {
id: 1,
- discussion_html: null,
valid: true,
note: 'heya',
html: '<div>heya</div>',
@@ -482,11 +501,17 @@ import '~/notes';
});
describe('getFormData', () => {
- it('should return form metadata object from form reference', () => {
+ let $form;
+ let sampleComment;
+
+ beforeEach(() => {
this.notes = new Notes('', []);
- const $form = $('form');
- const sampleComment = 'foobar';
+ $form = $('form');
+ sampleComment = 'foobar';
+ });
+
+ it('should return form metadata object from form reference', () => {
$form.find('textarea.js-note-text').val(sampleComment);
const { formData, formContent, formAction } = this.notes.getFormData($form);
@@ -494,6 +519,18 @@ import '~/notes';
expect(formContent).toEqual(sampleComment);
expect(formAction).toEqual($form.attr('action'));
});
+
+ it('should return form metadata with sanitized formContent from form reference', () => {
+ spyOn(_, 'escape').and.callFake(htmlEscape);
+
+ sampleComment = '<script>alert("Boom!");</script>';
+ $form.find('textarea.js-note-text').val(sampleComment);
+
+ const { formContent } = this.notes.getFormData($form);
+
+ expect(_.escape).toHaveBeenCalledWith(sampleComment);
+ expect(formContent).toEqual('&lt;script&gt;alert(&quot;Boom!&quot;);&lt;/script&gt;');
+ });
});
describe('hasSlashCommands', () => {
@@ -549,11 +586,39 @@ import '~/notes';
});
});
+ describe('getSlashCommandDescription', () => {
+ const availableSlashCommands = [
+ { name: 'close', description: 'Close this issue', params: [] },
+ { name: 'title', description: 'Change title', params: [{}] },
+ { name: 'estimate', description: 'Set time estimate', params: [{}] }
+ ];
+
+ beforeEach(() => {
+ this.notes = new Notes();
+ });
+
+ it('should return executing slash command description when note has single slash command', () => {
+ const sampleComment = '/close';
+ expect(this.notes.getSlashCommandDescription(sampleComment, availableSlashCommands)).toBe('Applying command to close this issue');
+ });
+
+ it('should return generic multiple slash command description when note has multiple slash commands', () => {
+ const sampleComment = '/close\n/title [Duplicate] Issue foobar';
+ expect(this.notes.getSlashCommandDescription(sampleComment, availableSlashCommands)).toBe('Applying multiple commands');
+ });
+
+ it('should return generic slash command description when available slash commands list is not populated', () => {
+ const sampleComment = '/close\n/title [Duplicate] Issue foobar';
+ expect(this.notes.getSlashCommandDescription(sampleComment)).toBe('Applying command');
+ });
+ });
+
describe('createPlaceholderNote', () => {
const sampleComment = 'foobar';
const uniqueId = 'b1234-a4567';
const currentUsername = 'root';
const currentUserFullname = 'Administrator';
+ const currentUserAvatar = 'avatar_url';
beforeEach(() => {
this.notes = new Notes('', []);
@@ -581,46 +646,86 @@ import '~/notes';
uniqueId,
isDiscussionNote: false,
currentUsername,
- currentUserFullname
+ currentUserFullname,
+ currentUserAvatar,
});
const $tempNoteHeader = $tempNote.find('.note-header');
expect($tempNote.prop('nodeName')).toEqual('LI');
expect($tempNote.attr('id')).toEqual(uniqueId);
+ expect($tempNote.hasClass('being-posted')).toBeTruthy();
+ expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
$tempNote.find('.timeline-icon > a, .note-header-info > a').each(function() {
expect($(this).attr('href')).toEqual(`/${currentUsername}`);
});
+ expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar);
expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeFalsy();
expect($tempNoteHeader.find('.hidden-xs').text().trim()).toEqual(currentUserFullname);
expect($tempNoteHeader.find('.note-headline-light').text().trim()).toEqual(`@${currentUsername}`);
expect($tempNote.find('.note-body .note-text p').text().trim()).toEqual(sampleComment);
});
- it('should escape HTML characters from note based on form contents', () => {
- const commentWithHtml = '<script>alert("Boom!");</script>';
+ it('should return constructed placeholder element for discussion note based on form contents', () => {
const $tempNote = this.notes.createPlaceholderNote({
- formContent: commentWithHtml,
+ formContent: sampleComment,
uniqueId,
- isDiscussionNote: false,
+ isDiscussionNote: true,
currentUsername,
currentUserFullname
});
- expect(_.escape).toHaveBeenCalledWith(commentWithHtml);
- expect($tempNote.find('.note-body .note-text p').html()).toEqual('&lt;script&gt;alert("Boom!");&lt;/script&gt;');
+ expect($tempNote.prop('nodeName')).toEqual('LI');
+ expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeTruthy();
});
+ });
- it('should return constructed placeholder element for discussion note based on form contents', () => {
- const $tempNote = this.notes.createPlaceholderNote({
- formContent: sampleComment,
+ describe('createPlaceholderSystemNote', () => {
+ const sampleCommandDescription = 'Applying command to close this issue';
+ const uniqueId = 'b1234-a4567';
+
+ beforeEach(() => {
+ this.notes = new Notes('', []);
+ spyOn(_, 'escape').and.callFake(htmlEscape);
+ });
+
+ it('should return constructed placeholder element for system note based on form contents', () => {
+ const $tempNote = this.notes.createPlaceholderSystemNote({
+ formContent: sampleCommandDescription,
uniqueId,
- isDiscussionNote: true,
- currentUsername,
- currentUserFullname
});
expect($tempNote.prop('nodeName')).toEqual('LI');
- expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeTruthy();
+ expect($tempNote.attr('id')).toEqual(uniqueId);
+ expect($tempNote.hasClass('being-posted')).toBeTruthy();
+ expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
+ expect($tempNote.find('.timeline-content i').text().trim()).toEqual(sampleCommandDescription);
+ });
+ });
+
+ describe('appendFlash', () => {
+ beforeEach(() => {
+ this.notes = new Notes();
+ });
+
+ it('shows a flash message', () => {
+ this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline);
+
+ expect($('.flash-alert').is(':visible')).toBeTruthy();
+ });
+ });
+
+ describe('clearFlash', () => {
+ beforeEach(() => {
+ $(document).off('ajax:success');
+ this.notes = new Notes();
+ });
+
+ it('hides visible flash message', () => {
+ this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline);
+
+ this.notes.clearFlash();
+
+ expect($('.flash-alert').is(':visible')).toBeFalsy();
});
});
});
diff --git a/spec/javascripts/pager_spec.js b/spec/javascripts/pager_spec.js
index d966226909b..1d3e1263371 100644
--- a/spec/javascripts/pager_spec.js
+++ b/spec/javascripts/pager_spec.js
@@ -1,6 +1,6 @@
/* global fixture */
-require('~/pager');
+import '~/pager';
describe('pager', () => {
const Pager = window.Pager;
diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js
index f033956c071..85bd87318db 100644
--- a/spec/javascripts/pipelines/graph/action_component_spec.js
+++ b/spec/javascripts/pipelines/graph/action_component_spec.js
@@ -4,7 +4,7 @@ import actionComponent from '~/pipelines/components/graph/action_component.vue';
describe('pipeline graph action component', () => {
let component;
- beforeEach(() => {
+ beforeEach((done) => {
const ActionComponent = Vue.extend(actionComponent);
component = new ActionComponent({
propsData: {
@@ -14,6 +14,8 @@ describe('pipeline graph action component', () => {
actionIcon: 'icon_action_cancel',
},
}).$mount();
+
+ Vue.nextTick(done);
});
it('should render a link', () => {
@@ -27,7 +29,7 @@ describe('pipeline graph action component', () => {
it('should update bootstrap tooltip when title changes', (done) => {
component.tooltipText = 'changed';
- Vue.nextTick(() => {
+ setTimeout(() => {
expect(component.$el.getAttribute('data-original-title')).toBe('changed');
done();
});
diff --git a/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js b/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js
index 14ff1b0d25c..25fd18b197e 100644
--- a/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js
+++ b/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js
@@ -4,7 +4,7 @@ import dropdownActionComponent from '~/pipelines/components/graph/dropdown_actio
describe('action component', () => {
let component;
- beforeEach(() => {
+ beforeEach((done) => {
const DropdownActionComponent = Vue.extend(dropdownActionComponent);
component = new DropdownActionComponent({
propsData: {
@@ -14,6 +14,8 @@ describe('action component', () => {
actionIcon: 'icon_action_cancel',
},
}).$mount();
+
+ Vue.nextTick(done);
});
it('should render a link', () => {
diff --git a/spec/javascripts/pipelines/graph/graph_component_spec.js b/spec/javascripts/pipelines/graph/graph_component_spec.js
index 6bd0eb86263..713baa65a17 100644
--- a/spec/javascripts/pipelines/graph/graph_component_spec.js
+++ b/spec/javascripts/pipelines/graph/graph_component_spec.js
@@ -14,49 +14,42 @@ describe('graph component', () => {
describe('while is loading', () => {
it('should render a loading icon', () => {
- const component = new GraphComponent().$mount('#js-pipeline-graph-vue');
+ const component = new GraphComponent({
+ propsData: {
+ isLoading: true,
+ pipeline: {},
+ },
+ }).$mount('#js-pipeline-graph-vue');
expect(component.$el.querySelector('.loading-icon')).toBeDefined();
});
});
- describe('with a successfull response', () => {
- const interceptor = (request, next) => {
- next(request.respondWith(JSON.stringify(graphJSON), {
- status: 200,
- }));
- };
+ describe('with data', () => {
+ it('should render the graph', () => {
+ const component = new GraphComponent({
+ propsData: {
+ isLoading: false,
+ pipeline: graphJSON,
+ },
+ }).$mount('#js-pipeline-graph-vue');
- beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
- });
-
- it('should render the graph', (done) => {
- const component = new GraphComponent().$mount('#js-pipeline-graph-vue');
-
- setTimeout(() => {
- expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
+ expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
- expect(
- component.$el.querySelector('.stage-column:first-child').classList.contains('no-margin'),
- ).toEqual(true);
+ expect(
+ component.$el.querySelector('.stage-column:first-child').classList.contains('no-margin'),
+ ).toEqual(true);
- expect(
- component.$el.querySelector('.stage-column:nth-child(2)').classList.contains('left-margin'),
- ).toEqual(true);
+ expect(
+ component.$el.querySelector('.stage-column:nth-child(2)').classList.contains('left-margin'),
+ ).toEqual(true);
- expect(
- component.$el.querySelector('.stage-column:nth-child(2) .build:nth-child(1)').classList.contains('left-connector'),
- ).toEqual(true);
+ expect(
+ component.$el.querySelector('.stage-column:nth-child(2) .build:nth-child(1)').classList.contains('left-connector'),
+ ).toEqual(true);
- expect(component.$el.querySelector('loading-icon')).toBe(null);
+ expect(component.$el.querySelector('loading-icon')).toBe(null);
- expect(component.$el.querySelector('.stage-column-list')).toBeDefined();
- done();
- }, 0);
+ expect(component.$el.querySelector('.stage-column-list')).toBeDefined();
});
});
});
diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js
index 63986b6c0db..e90593e0f40 100644
--- a/spec/javascripts/pipelines/graph/job_component_spec.js
+++ b/spec/javascripts/pipelines/graph/job_component_spec.js
@@ -27,26 +27,30 @@ describe('pipeline graph job component', () => {
});
describe('name with link', () => {
- it('should render the job name and status with a link', () => {
+ it('should render the job name and status with a link', (done) => {
const component = new JobComponent({
propsData: {
job: mockJob,
},
}).$mount();
- const link = component.$el.querySelector('a');
+ Vue.nextTick(() => {
+ const link = component.$el.querySelector('a');
- expect(link.getAttribute('href')).toEqual(mockJob.status.details_path);
+ expect(link.getAttribute('href')).toEqual(mockJob.status.details_path);
- expect(
- link.getAttribute('data-original-title'),
- ).toEqual(`${mockJob.name} - ${mockJob.status.label}`);
+ expect(
+ link.getAttribute('data-original-title'),
+ ).toEqual(`${mockJob.name} - ${mockJob.status.label}`);
- expect(component.$el.querySelector('.js-status-icon-success')).toBeDefined();
+ expect(component.$el.querySelector('.js-status-icon-success')).toBeDefined();
- expect(
- component.$el.querySelector('.ci-status-text').textContent.trim(),
- ).toEqual(mockJob.name);
+ expect(
+ component.$el.querySelector('.ci-status-text').textContent.trim(),
+ ).toEqual(mockJob.name);
+
+ done();
+ });
});
});
diff --git a/spec/javascripts/pipelines/header_component_spec.js b/spec/javascripts/pipelines/header_component_spec.js
new file mode 100644
index 00000000000..cecc7ceb53d
--- /dev/null
+++ b/spec/javascripts/pipelines/header_component_spec.js
@@ -0,0 +1,63 @@
+import Vue from 'vue';
+import headerComponent from '~/pipelines/components/header_component.vue';
+import eventHub from '~/pipelines/event_hub';
+
+describe('Pipeline details header', () => {
+ let HeaderComponent;
+ let vm;
+ let props;
+
+ beforeEach(() => {
+ HeaderComponent = Vue.extend(headerComponent);
+
+ const threeWeeksAgo = new Date();
+ threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
+
+ props = {
+ pipeline: {
+ details: {
+ status: {
+ group: 'failed',
+ icon: 'ci-status-failed',
+ label: 'failed',
+ text: 'failed',
+ details_path: 'path',
+ },
+ },
+ id: 123,
+ created_at: threeWeeksAgo.toISOString(),
+ user: {
+ web_url: 'path',
+ name: 'Foo',
+ username: 'foobar',
+ email: 'foo@bar.com',
+ avatar_url: 'link',
+ },
+ retry_path: 'path',
+ },
+ isLoading: false,
+ };
+
+ vm = new HeaderComponent({ propsData: props }).$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render provided pipeline info', () => {
+ expect(
+ vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
+ ).toEqual('failed Pipeline #123 triggered 3 weeks ago by Foo');
+ });
+
+ describe('action buttons', () => {
+ it('should call postAction when button action is clicked', () => {
+ eventHub.$on('headerPostAction', (action) => {
+ expect(action.path).toEqual('path');
+ });
+
+ vm.$el.querySelector('button').click();
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/mock_data.js b/spec/javascripts/pipelines/mock_data.js
deleted file mode 100644
index 2365a662b9f..00000000000
--- a/spec/javascripts/pipelines/mock_data.js
+++ /dev/null
@@ -1,107 +0,0 @@
-export default {
- pipelines: [{
- id: 115,
- user: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- path: '/root/review-app/pipelines/115',
- details: {
- status: {
- icon: 'icon_status_failed',
- text: 'failed',
- label: 'failed',
- group: 'failed',
- has_details: true,
- details_path: '/root/review-app/pipelines/115',
- },
- duration: null,
- finished_at: '2017-03-17T19:00:15.996Z',
- stages: [{
- name: 'build',
- title: 'build: failed',
- status: {
- icon: 'icon_status_failed',
- text: 'failed',
- label: 'failed',
- group: 'failed',
- has_details: true,
- details_path: '/root/review-app/pipelines/115#build',
- },
- path: '/root/review-app/pipelines/115#build',
- dropdown_path: '/root/review-app/pipelines/115/stage.json?stage=build',
- },
- {
- name: 'review',
- title: 'review: skipped',
- status: {
- icon: 'icon_status_skipped',
- text: 'skipped',
- label: 'skipped',
- group: 'skipped',
- has_details: true,
- details_path: '/root/review-app/pipelines/115#review',
- },
- path: '/root/review-app/pipelines/115#review',
- dropdown_path: '/root/review-app/pipelines/115/stage.json?stage=review',
- }],
- artifacts: [],
- manual_actions: [{
- name: 'stop_review',
- path: '/root/review-app/builds/3766/play',
- }],
- },
- flags: {
- latest: true,
- triggered: false,
- stuck: false,
- yaml_errors: false,
- retryable: true,
- cancelable: false,
- },
- ref: {
- name: 'thisisabranch',
- path: '/root/review-app/tree/thisisabranch',
- tag: false,
- branch: true,
- },
- commit: {
- id: '9e87f87625b26c42c59a2ee0398f81d20cdfe600',
- short_id: '9e87f876',
- title: 'Update README.md',
- created_at: '2017-03-15T22:58:28.000+00:00',
- parent_ids: ['3744f9226e699faec2662a8b267e5d3fd0bfff0e'],
- message: 'Update README.md',
- author_name: 'Root',
- author_email: 'admin@example.com',
- authored_date: '2017-03-15T22:58:28.000+00:00',
- committer_name: 'Root',
- committer_email: 'admin@example.com',
- committed_date: '2017-03-15T22:58:28.000+00:00',
- author: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url: 'http://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',
- commit_url: 'http://localhost:3000/root/review-app/commit/9e87f87625b26c42c59a2ee0398f81d20cdfe600',
- commit_path: '/root/review-app/commit/9e87f87625b26c42c59a2ee0398f81d20cdfe600',
- },
- retry_path: '/root/review-app/pipelines/115/retry',
- created_at: '2017-03-15T22:58:33.436Z',
- updated_at: '2017-03-17T19:00:15.997Z',
- }],
- count: {
- all: 52,
- running: 0,
- pending: 0,
- finished: 52,
- },
-};
diff --git a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
new file mode 100644
index 00000000000..9fec2f61f78
--- /dev/null
+++ b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
@@ -0,0 +1,41 @@
+import Vue from 'vue';
+import PipelineMediator from '~/pipelines/pipeline_details_mediatior';
+
+describe('PipelineMdediator', () => {
+ let mediator;
+ beforeEach(() => {
+ mediator = new PipelineMediator({ endpoint: 'foo' });
+ });
+
+ it('should set defaults', () => {
+ expect(mediator.options).toEqual({ endpoint: 'foo' });
+ expect(mediator.state.isLoading).toEqual(false);
+ expect(mediator.store).toBeDefined();
+ expect(mediator.service).toBeDefined();
+ });
+
+ describe('request and store data', () => {
+ const interceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify({ foo: 'bar' }), {
+ status: 200,
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(interceptor);
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptor, interceptor);
+ });
+
+ it('should store received data', (done) => {
+ mediator.fetchPipeline();
+
+ setTimeout(() => {
+ expect(mediator.store.state.pipeline).toEqual({ foo: 'bar' });
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/pipeline_store_spec.js b/spec/javascripts/pipelines/pipeline_store_spec.js
new file mode 100644
index 00000000000..85d13445b01
--- /dev/null
+++ b/spec/javascripts/pipelines/pipeline_store_spec.js
@@ -0,0 +1,27 @@
+import PipelineStore from '~/pipelines/stores/pipeline_store';
+
+describe('Pipeline Store', () => {
+ let store;
+
+ beforeEach(() => {
+ store = new PipelineStore();
+ });
+
+ it('should set defaults', () => {
+ expect(store.state).toEqual({ pipeline: {} });
+ expect(store.state.pipeline).toEqual({});
+ });
+
+ describe('storePipeline', () => {
+ it('should store empty object if none is provided', () => {
+ store.storePipeline();
+
+ expect(store.state.pipeline).toEqual({});
+ });
+
+ it('should store received object', () => {
+ store.storePipeline({ foo: 'bar' });
+ expect(store.state.pipeline).toEqual({ foo: 'bar' });
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js
index 53931d67ad7..594a9856d2c 100644
--- a/spec/javascripts/pipelines/pipeline_url_spec.js
+++ b/spec/javascripts/pipelines/pipeline_url_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import pipelineUrlComp from '~/pipelines/components/pipeline_url';
+import pipelineUrlComp from '~/pipelines/components/pipeline_url.vue';
describe('Pipeline Url Component', () => {
let PipelineUrlComponent;
@@ -47,6 +47,7 @@ describe('Pipeline Url Component', () => {
web_url: '/',
name: 'foo',
avatar_url: '/',
+ path: '/',
},
},
};
@@ -60,7 +61,7 @@ describe('Pipeline Url Component', () => {
expect(
component.$el.querySelector('.js-pipeline-url-user').getAttribute('href'),
).toEqual(mockData.pipeline.user.web_url);
- expect(image.getAttribute('title')).toEqual(mockData.pipeline.user.name);
+ expect(image.getAttribute('data-original-title')).toEqual(mockData.pipeline.user.name);
expect(image.getAttribute('src')).toEqual(mockData.pipeline.user.avatar_url);
});
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index e9c05f74ce6..3a56156358b 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -1,15 +1,20 @@
import Vue from 'vue';
import pipelinesComp from '~/pipelines/pipelines';
import Store from '~/pipelines/stores/pipelines_store';
-import pipelinesData from './mock_data';
describe('Pipelines', () => {
+ const jsonFixtureName = 'pipelines/pipelines.json';
+
preloadFixtures('static/pipelines.html.raw');
+ preloadFixtures(jsonFixtureName);
let PipelinesComponent;
+ let pipeline;
beforeEach(() => {
loadFixtures('static/pipelines.html.raw');
+ const pipelines = getJSONFixture(jsonFixtureName).pipelines;
+ pipeline = pipelines.find(p => p.id === 1);
PipelinesComponent = Vue.extend(pipelinesComp);
});
@@ -17,7 +22,7 @@ describe('Pipelines', () => {
describe('successfull request', () => {
describe('with pipelines', () => {
const pipelinesInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify(pipelinesData), {
+ next(request.respondWith(JSON.stringify(pipeline), {
status: 200,
}));
};
diff --git a/spec/javascripts/pretty_time_spec.js b/spec/javascripts/pretty_time_spec.js
index a4662cfb557..de99e7e3894 100644
--- a/spec/javascripts/pretty_time_spec.js
+++ b/spec/javascripts/pretty_time_spec.js
@@ -1,4 +1,4 @@
-require('~/lib/utils/pretty_time');
+import '~/lib/utils/pretty_time';
(() => {
const prettyTime = gl.utils.prettyTime;
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index 3a1d4e2440f..3dba2e817ff 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -1,12 +1,11 @@
/* eslint-disable space-before-function-paren, no-unused-expressions, no-return-assign, no-param-reassign, no-var, new-cap, wrap-iife, no-unused-vars, quotes, jasmine/no-expect-in-setup-teardown, max-len */
/* global Project */
-require('select2/select2.js');
-require('~/lib/utils/type_utility');
-require('~/gl_dropdown');
-require('~/api');
-require('~/project_select');
-require('~/project');
+import 'select2/select2';
+import '~/gl_dropdown';
+import '~/api';
+import '~/project_select';
+import '~/project';
(function() {
describe('Project Title', function() {
diff --git a/spec/javascripts/raven/raven_config_spec.js b/spec/javascripts/raven/raven_config_spec.js
index b31a7c28ebe..c82658b9262 100644
--- a/spec/javascripts/raven/raven_config_spec.js
+++ b/spec/javascripts/raven/raven_config_spec.js
@@ -140,24 +140,6 @@ describe('RavenConfig', () => {
});
});
- describe('bindRavenErrors', () => {
- let $document;
- let $;
-
- beforeEach(() => {
- $document = jasmine.createSpyObj('$document', ['on']);
- $ = jasmine.createSpy('$').and.returnValue($document);
-
- window.$ = $;
-
- RavenConfig.bindRavenErrors();
- });
-
- it('should call .on', function () {
- expect($document.on).toHaveBeenCalledWith('ajaxError.raven', RavenConfig.handleRavenErrors);
- });
- });
-
describe('handleRavenErrors', () => {
let event;
let req;
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index aaf058bd755..a53f58b5d0d 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -1,10 +1,9 @@
/* eslint-disable space-before-function-paren, max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, comma-dangle, object-shorthand, prefer-template, quotes, new-parens, vars-on-top, new-cap, max-len */
-require('~/gl_dropdown');
-require('~/search_autocomplete');
-require('~/lib/utils/common_utils');
-require('~/lib/utils/type_utility');
-require('vendor/fuzzaldrin-plus');
+import '~/gl_dropdown';
+import '~/search_autocomplete';
+import '~/lib/utils/common_utils';
+import 'vendor/fuzzaldrin-plus';
(function() {
var addBodyAttributes, assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget;
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index 9e19dabd0e3..3515dfbc60b 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -1,8 +1,8 @@
/* eslint-disable space-before-function-paren, no-return-assign, no-var, quotes */
/* global ShortcutsIssuable */
-require('~/copy_as_gfm');
-require('~/shortcuts_issuable');
+import '~/copy_as_gfm';
+import '~/shortcuts_issuable';
(function() {
describe('ShortcutsIssuable', function() {
@@ -13,7 +13,7 @@ require('~/shortcuts_issuable');
document.querySelector('.js-new-note-form').classList.add('js-main-target-form');
this.shortcut = new ShortcutsIssuable();
});
- describe('#replyWithSelectedText', function() {
+ describe('replyWithSelectedText', function() {
var stubSelection;
// Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML.
stubSelection = function(html) {
diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js
index 8ef6c3907dc..929ba75e67d 100644
--- a/spec/javascripts/sidebar/sidebar_assignees_spec.js
+++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js
@@ -24,6 +24,7 @@ describe('sidebar assignees', () => {
SidebarService.singleton = null;
SidebarStore.singleton = null;
SidebarMediator.singleton = null;
+ Vue.http.interceptors = _.without(Vue.http.interceptors, Mock.sidebarMockInterceptor);
});
it('calls the mediator when saves the assignees', () => {
diff --git a/spec/javascripts/sidebar/sidebar_bundle_spec.js b/spec/javascripts/sidebar/sidebar_bundle_spec.js
deleted file mode 100644
index 7760b34e071..00000000000
--- a/spec/javascripts/sidebar/sidebar_bundle_spec.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import Vue from 'vue';
-import SidebarBundleDomContentLoaded from '~/sidebar/sidebar_bundle';
-import SidebarTimeTracking from '~/sidebar/components/time_tracking/sidebar_time_tracking';
-import SidebarMediator from '~/sidebar/sidebar_mediator';
-import SidebarService from '~/sidebar/services/sidebar_service';
-import SidebarStore from '~/sidebar/stores/sidebar_store';
-import Mock from './mock_data';
-
-describe('sidebar bundle', () => {
- gl.sidebarOptions = Mock.mediator;
-
- beforeEach(() => {
- spyOn(SidebarTimeTracking.methods, 'listenForSlashCommands').and.callFake(() => { });
- preloadFixtures('issues/open-issue.html.raw');
- Vue.http.interceptors.push(Mock.sidebarMockInterceptor);
- loadFixtures('issues/open-issue.html.raw');
- spyOn(Vue.prototype, '$mount');
- SidebarBundleDomContentLoaded();
- this.mediator = new SidebarMediator();
- });
-
- afterEach(() => {
- SidebarService.singleton = null;
- SidebarStore.singleton = null;
- SidebarMediator.singleton = null;
- });
-
- it('the mediator should be already defined with some data', () => {
- SidebarBundleDomContentLoaded();
-
- expect(this.mediator.store).toBeDefined();
- expect(this.mediator.service).toBeDefined();
- expect(this.mediator.store.currentUser).toEqual(Mock.mediator.currentUser);
- expect(this.mediator.store.rootPath).toEqual(Mock.mediator.rootPath);
- expect(this.mediator.store.endPoint).toEqual(Mock.mediator.endPoint);
- expect(this.mediator.store.editable).toEqual(Mock.mediator.editable);
- });
-
- it('the sidebar time tracking and assignees components to have been mounted', () => {
- expect(Vue.prototype.$mount).toHaveBeenCalledTimes(2);
- });
-});
diff --git a/spec/javascripts/sidebar/sidebar_mediator_spec.js b/spec/javascripts/sidebar/sidebar_mediator_spec.js
index 2b00fa17334..e246f41ee82 100644
--- a/spec/javascripts/sidebar/sidebar_mediator_spec.js
+++ b/spec/javascripts/sidebar/sidebar_mediator_spec.js
@@ -14,6 +14,7 @@ describe('Sidebar mediator', () => {
SidebarService.singleton = null;
SidebarStore.singleton = null;
SidebarMediator.singleton = null;
+ Vue.http.interceptors = _.without(Vue.http.interceptors, Mock.sidebarMockInterceptor);
});
it('assigns yourself ', () => {
diff --git a/spec/javascripts/sidebar/sidebar_service_spec.js b/spec/javascripts/sidebar/sidebar_service_spec.js
index d41162096a6..91a4dd669a7 100644
--- a/spec/javascripts/sidebar/sidebar_service_spec.js
+++ b/spec/javascripts/sidebar/sidebar_service_spec.js
@@ -10,6 +10,7 @@ describe('Sidebar service', () => {
afterEach(() => {
SidebarService.singleton = null;
+ Vue.http.interceptors = _.without(Vue.http.interceptors, Mock.sidebarMockInterceptor);
});
it('gets the data', (done) => {
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js
index 5b4f5933b34..0a32797c3e2 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
@@ -1,6 +1,6 @@
import AccessorUtilities from '~/lib/utils/accessor';
-require('~/signin_tabs_memoizer');
+import '~/signin_tabs_memoizer';
((global) => {
describe('SigninTabsMemoizer', () => {
diff --git a/spec/javascripts/smart_interval_spec.js b/spec/javascripts/smart_interval_spec.js
index 4366ec2a5b8..7833bf3fb04 100644
--- a/spec/javascripts/smart_interval_spec.js
+++ b/spec/javascripts/smart_interval_spec.js
@@ -1,4 +1,4 @@
-require('~/smart_interval');
+import '~/smart_interval';
(() => {
const DEFAULT_MAX_INTERVAL = 100;
diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js
index cea223bd243..946f98379ce 100644
--- a/spec/javascripts/syntax_highlight_spec.js
+++ b/spec/javascripts/syntax_highlight_spec.js
@@ -1,6 +1,6 @@
/* eslint-disable space-before-function-paren, no-var, no-return-assign, quotes */
-require('~/syntax_highlight');
+import '~/syntax_highlight';
(function() {
describe('Syntax Highlighter', function() {
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index a22014879e8..13827a26571 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -1,13 +1,15 @@
-// enable test fixtures
-require('jasmine-jquery');
+import $ from 'jquery';
+import _ from 'underscore';
+import 'jasmine-jquery';
+import '~/commons';
-jasmine.getFixtures().fixturesPath = 'base/spec/javascripts/fixtures';
-jasmine.getJSONFixtures().fixturesPath = 'base/spec/javascripts/fixtures';
+// enable test fixtures
+jasmine.getFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
+jasmine.getJSONFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
-// include common libraries
-require('~/commons/index.js');
-window.$ = window.jQuery = require('jquery');
-window._ = require('underscore');
+// globalize common libraries
+window.$ = window.jQuery = $;
+window._ = _;
// stub expected globals
window.gl = window.gl || {};
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
index 66e4fbd6304..cd74aba4a4e 100644
--- a/spec/javascripts/todos_spec.js
+++ b/spec/javascripts/todos_spec.js
@@ -1,5 +1,5 @@
-require('~/todos');
-require('~/lib/utils/common_utils');
+import '~/todos';
+import '~/lib/utils/common_utils';
describe('Todos', () => {
preloadFixtures('todos/todos.html.raw');
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index af2d02b6b29..a160c86308d 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -2,11 +2,11 @@
/* global MockU2FDevice */
/* global U2FAuthenticate */
-require('~/u2f/authenticate');
-require('~/u2f/util');
-require('~/u2f/error');
-require('vendor/u2f');
-require('./mock_u2f_device');
+import '~/u2f/authenticate';
+import '~/u2f/util';
+import '~/u2f/error';
+import 'vendor/u2f';
+import './mock_u2f_device';
(function() {
describe('U2FAuthenticate', function() {
diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js
index 6677fe9c1ee..4eb8ad3d9e4 100644
--- a/spec/javascripts/u2f/mock_u2f_device.js
+++ b/spec/javascripts/u2f/mock_u2f_device.js
@@ -1,12 +1,10 @@
/* eslint-disable space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-expressions, no-return-assign, no-param-reassign, max-len */
(function() {
- var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
-
this.MockU2FDevice = (function() {
function MockU2FDevice() {
- this.respondToAuthenticateRequest = bind(this.respondToAuthenticateRequest, this);
- this.respondToRegisterRequest = bind(this.respondToRegisterRequest, this);
+ this.respondToAuthenticateRequest = this.respondToAuthenticateRequest.bind(this);
+ this.respondToRegisterRequest = this.respondToRegisterRequest.bind(this);
window.u2f || (window.u2f = {});
window.u2f.register = (function(_this) {
return function(appId, registerRequests, signRequests, callback) {
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
index 3960759f7cb..a445c80f2af 100644
--- a/spec/javascripts/u2f/register_spec.js
+++ b/spec/javascripts/u2f/register_spec.js
@@ -2,11 +2,11 @@
/* global MockU2FDevice */
/* global U2FRegister */
-require('~/u2f/register');
-require('~/u2f/util');
-require('~/u2f/error');
-require('vendor/u2f');
-require('./mock_u2f_device');
+import '~/u2f/register';
+import '~/u2f/util';
+import '~/u2f/error';
+import 'vendor/u2f';
+import './mock_u2f_device';
(function() {
describe('U2FRegister', function() {
diff --git a/spec/javascripts/version_check_image_spec.js b/spec/javascripts/version_check_image_spec.js
index 464c1fce210..9637bd0414a 100644
--- a/spec/javascripts/version_check_image_spec.js
+++ b/spec/javascripts/version_check_image_spec.js
@@ -1,9 +1,8 @@
-const ClassSpecHelper = require('./helpers/class_spec_helper');
-const VersionCheckImage = require('~/version_check_image');
-require('jquery');
+import VersionCheckImage from '~/version_check_image';
+import ClassSpecHelper from './helpers/class_spec_helper';
describe('VersionCheckImage', function () {
- describe('.bindErrorEvent', function () {
+ describe('bindErrorEvent', function () {
ClassSpecHelper.itShouldBeAStaticMethod(VersionCheckImage, 'bindErrorEvent');
beforeEach(function () {
diff --git a/spec/javascripts/visibility_select_spec.js b/spec/javascripts/visibility_select_spec.js
index 9727c03c91e..c2eaea7c2ed 100644
--- a/spec/javascripts/visibility_select_spec.js
+++ b/spec/javascripts/visibility_select_spec.js
@@ -1,4 +1,4 @@
-require('~/visibility_select');
+import '~/visibility_select';
(() => {
const VisibilitySelect = gl.VisibilitySelect;
@@ -22,7 +22,7 @@ require('~/visibility_select');
spyOn(Element.prototype, 'querySelector').and.callFake(selector => mockElements[selector]);
});
- describe('#constructor', function () {
+ describe('constructor', function () {
beforeEach(function () {
this.visibilitySelect = new VisibilitySelect(mockElements.container);
});
@@ -48,7 +48,7 @@ require('~/visibility_select');
});
});
- describe('#init', function () {
+ describe('init', function () {
describe('if there is a select', function () {
beforeEach(function () {
this.visibilitySelect = new VisibilitySelect(mockElements.container);
@@ -85,7 +85,7 @@ require('~/visibility_select');
});
});
- describe('#updateHelpText', function () {
+ describe('updateHelpText', function () {
beforeEach(function () {
this.visibilitySelect = new VisibilitySelect(mockElements.container);
this.visibilitySelect.init();
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
index da9dff18ada..2c3d0ddff28 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
@@ -7,6 +7,18 @@ const url = '/root/acets-review-apps/environments/15/deployments/1/metrics';
const metricsMockData = {
success: true,
metrics: {
+ memory_before: [
+ {
+ metric: {},
+ value: [1495785220.607, '9572875.906976745'],
+ },
+ ],
+ memory_after: [
+ {
+ metric: {},
+ value: [1495787020.607, '4485853.130206379'],
+ },
+ ],
memory_values: [
{
metric: {},
@@ -39,7 +51,7 @@ const createComponent = () => {
const messages = {
loadingMetrics: 'Loading deployment statistics.',
- hasMetrics: 'Deployment memory usage:',
+ hasMetrics: 'Memory usage unchanged from 0MB to 0MB',
loadFailed: 'Failed to load deployment statistics.',
metricsUnavailable: 'Deployment statistics are not available currently.',
};
@@ -89,17 +101,52 @@ describe('MemoryUsage', () => {
});
});
+ describe('computed', () => {
+ describe('memoryChangeType', () => {
+ it('should return "increased" if memoryFrom value is less than memoryTo value', () => {
+ vm.memoryFrom = 4.28;
+ vm.memoryTo = 9.13;
+
+ expect(vm.memoryChangeType).toEqual('increased');
+ });
+
+ it('should return "decreased" if memoryFrom value is less than memoryTo value', () => {
+ vm.memoryFrom = 9.13;
+ vm.memoryTo = 4.28;
+
+ expect(vm.memoryChangeType).toEqual('decreased');
+ });
+
+ it('should return "unchanged" if memoryFrom value equal to memoryTo value', () => {
+ vm.memoryFrom = 1;
+ vm.memoryTo = 1;
+
+ expect(vm.memoryChangeType).toEqual('unchanged');
+ });
+ });
+ });
+
describe('methods', () => {
const { metrics, deployment_time } = metricsMockData;
+ describe('getMegabytes', () => {
+ it('should return Megabytes from provided Bytes value', () => {
+ const memoryInBytes = '9572875.906976745';
+
+ expect(vm.getMegabytes(memoryInBytes)).toEqual('9.13');
+ });
+ });
+
describe('computeGraphData', () => {
it('should populate sparkline graph', () => {
vm.computeGraphData(metrics, deployment_time);
- const { hasMetrics, memoryMetrics, deploymentTime } = vm;
+ const { hasMetrics, memoryMetrics, deploymentTime, memoryFrom, memoryTo } = vm;
expect(hasMetrics).toBeTruthy();
expect(memoryMetrics.length > 0).toBeTruthy();
expect(deploymentTime).toEqual(deployment_time);
+ expect(memoryFrom).toEqual('9.13');
+ expect(memoryTo).toEqual('4.28');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
index d40c67b189d..a8a02fa6b66 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
@@ -4,14 +4,26 @@ import nothingToMergeComponent from '~/vue_merge_request_widget/components/state
describe('MRWidgetNothingToMerge', () => {
describe('template', () => {
const Component = Vue.extend(nothingToMergeComponent);
+ const newBlobPath = '/foo';
const vm = new Component({
el: document.createElement('div'),
+ propsData: {
+ mr: { newBlobPath },
+ },
});
+
it('should have correct elements', () => {
expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
- expect(vm.$el.innerText).toContain('There is nothing to merge from source branch into target branch.');
+ expect(vm.$el.querySelector('a').href).toContain(newBlobPath);
+ expect(vm.$el.innerText).toContain('Currently there are no changes in this merge request\'s source branch');
expect(vm.$el.innerText).toContain('Please push new commits or use a different branch.');
});
+
+ it('should not show new blob link if there is no link available', () => {
+ vm.mr.newBlobPath = null;
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('a')).toEqual(null);
+ });
+ });
});
});
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 d043ad38b8b..732b516badd 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
@@ -5,7 +5,7 @@ import * as simplePoll from '~/lib/utils/simple_poll';
const commitMessage = 'This is the commit message';
const commitMessageWithDescription = 'This is the commit message description';
-const createComponent = () => {
+const createComponent = (customConfig = {}) => {
const Component = Vue.extend(readyToMergeComponent);
const mr = {
isPipelineActive: false,
@@ -17,8 +17,12 @@ const createComponent = () => {
sha: '12345678',
commitMessage,
commitMessageWithDescription,
+ shouldRemoveSourceBranch: true,
+ canRemoveSourceBranch: false,
};
+ Object.assign(mr, customConfig.mr);
+
const service = {
merge() {},
poll() {},
@@ -51,7 +55,6 @@ describe('MRWidgetReadyToMerge', () => {
describe('data', () => {
it('should have default data', () => {
- expect(vm.removeSourceBranch).toBeTruthy(true);
expect(vm.mergeWhenBuildSucceeds).toBeFalsy();
expect(vm.useCommitMessageWithDescription).toBeFalsy();
expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy();
@@ -166,6 +169,36 @@ describe('MRWidgetReadyToMerge', () => {
expect(vm.isMergeButtonDisabled).toBeTruthy();
});
});
+
+ describe('Remove source branch checkbox', () => {
+ describe('when user can merge but cannot delete branch', () => {
+ it('isRemoveSourceBranchButtonDisabled should be true', () => {
+ expect(vm.isRemoveSourceBranchButtonDisabled).toBe(true);
+ });
+
+ it('should be disabled in the rendered output', () => {
+ const checkboxElement = vm.$el.querySelector('#remove-source-branch-input');
+ expect(checkboxElement.getAttribute('disabled')).toBe('disabled');
+ });
+ });
+
+ describe('when user can merge and can delete branch', () => {
+ beforeEach(() => {
+ this.customVm = createComponent({
+ mr: { canRemoveSourceBranch: true },
+ });
+ });
+
+ it('isRemoveSourceBranchButtonDisabled should be false', () => {
+ expect(this.customVm.isRemoveSourceBranchButtonDisabled).toBe(false);
+ });
+
+ it('should be enabled in rendered output', () => {
+ const checkboxElement = this.customVm.$el.querySelector('#remove-source-branch-input');
+ expect(checkboxElement.getAttribute('disabled')).toBeNull();
+ });
+ });
+ });
});
describe('methods', () => {
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index bdc18243a15..3a0c50b750f 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options';
import eventHub from '~/vue_merge_request_widget/event_hub';
+import notify from '~/lib/utils/notify';
import mockData from './mock_data';
const createComponent = () => {
@@ -107,6 +108,8 @@ describe('mrWidgetOptions', () => {
it('should tell service to check status', (done) => {
spyOn(vm.service, 'checkStatus').and.returnValue(returnPromise(mockData));
spyOn(vm.mr, 'setData');
+ spyOn(vm, 'handleNotification');
+
let isCbExecuted = false;
const cb = () => {
isCbExecuted = true;
@@ -117,6 +120,7 @@ describe('mrWidgetOptions', () => {
setTimeout(() => {
expect(vm.service.checkStatus).toHaveBeenCalled();
expect(vm.mr.setData).toHaveBeenCalled();
+ expect(vm.handleNotification).toHaveBeenCalledWith(mockData);
expect(isCbExecuted).toBeTruthy();
done();
}, 333);
@@ -254,6 +258,39 @@ describe('mrWidgetOptions', () => {
});
});
+ describe('handleNotification', () => {
+ const data = {
+ ci_status: 'running',
+ title: 'title',
+ pipeline: { details: { status: { label: 'running-label' } } },
+ };
+
+ beforeEach(() => {
+ spyOn(notify, 'notifyMe');
+
+ vm.mr.ciStatus = 'failed';
+ vm.mr.gitlabLogo = 'logo.png';
+ });
+
+ it('should call notifyMe', () => {
+ vm.handleNotification(data);
+
+ expect(notify.notifyMe).toHaveBeenCalledWith(
+ 'Pipeline running-label',
+ 'Pipeline running-label for "title"',
+ 'logo.png',
+ );
+ });
+
+ it('should not call notifyMe if the status has not changed', () => {
+ vm.mr.ciStatus = data.ci_status;
+
+ vm.handleNotification(data);
+
+ expect(notify.notifyMe).not.toHaveBeenCalled();
+ });
+ });
+
describe('resumePolling', () => {
it('should call stopTimer on pollingInterval', () => {
spyOn(vm.pollingInterval, 'resume');
diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js
index 01dabf5320e..540245fe71e 100644
--- a/spec/javascripts/vue_shared/components/commit_spec.js
+++ b/spec/javascripts/vue_shared/components/commit_spec.js
@@ -24,6 +24,7 @@ describe('Commit component', () => {
author: {
avatar_url: 'https://gitlab.com/uploads/system/user/avatar/300478/avatar.png',
web_url: 'https://gitlab.com/jschatz1',
+ path: '/jschatz1',
username: 'jschatz1',
},
},
@@ -46,6 +47,7 @@ describe('Commit component', () => {
author: {
avatar_url: 'https://gitlab.com/uploads/system/user/avatar/300478/avatar.png',
web_url: 'https://gitlab.com/jschatz1',
+ path: '/jschatz1',
username: 'jschatz1',
},
commitIconSvg: '<svg></svg>',
@@ -61,16 +63,16 @@ describe('Commit component', () => {
});
it('should render a link to the ref url', () => {
- expect(component.$el.querySelector('.branch-name').getAttribute('href')).toEqual(props.commitRef.ref_url);
+ expect(component.$el.querySelector('.ref-name').getAttribute('href')).toEqual(props.commitRef.ref_url);
});
it('should render the ref name', () => {
- expect(component.$el.querySelector('.branch-name').textContent).toContain(props.commitRef.name);
+ expect(component.$el.querySelector('.ref-name').textContent).toContain(props.commitRef.name);
});
it('should render the commit short sha with a link to the commit url', () => {
- expect(component.$el.querySelector('.commit-id').getAttribute('href')).toEqual(props.commitUrl);
- expect(component.$el.querySelector('.commit-id').textContent).toContain(props.shortSha);
+ expect(component.$el.querySelector('.commit-sha').getAttribute('href')).toEqual(props.commitUrl);
+ expect(component.$el.querySelector('.commit-sha').textContent).toContain(props.shortSha);
});
it('should render the given commitIconSvg', () => {
@@ -81,12 +83,12 @@ describe('Commit component', () => {
it('should render a link to the author profile', () => {
expect(
component.$el.querySelector('.commit-title .avatar-image-container').getAttribute('href'),
- ).toEqual(props.author.web_url);
+ ).toEqual(props.author.path);
});
it('Should render the author avatar with title and alt attributes', () => {
expect(
- component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('title'),
+ component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('data-original-title'),
).toContain(props.author.username);
expect(
component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('alt'),
diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
new file mode 100644
index 00000000000..2b51c89f311
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
@@ -0,0 +1,93 @@
+import Vue from 'vue';
+import headerCi from '~/vue_shared/components/header_ci_component.vue';
+
+describe('Header CI Component', () => {
+ let HeaderCi;
+ let vm;
+ let props;
+
+ beforeEach(() => {
+ HeaderCi = Vue.extend(headerCi);
+
+ props = {
+ status: {
+ group: 'failed',
+ icon: 'ci-status-failed',
+ label: 'failed',
+ text: 'failed',
+ details_path: 'path',
+ },
+ itemName: 'job',
+ itemId: 123,
+ time: '2017-05-08T14:57:39.781Z',
+ user: {
+ web_url: 'path',
+ name: 'Foo',
+ username: 'foobar',
+ email: 'foo@bar.com',
+ avatar_url: 'link',
+ },
+ actions: [
+ {
+ label: 'Retry',
+ path: 'path',
+ type: 'button',
+ cssClass: 'btn',
+ isLoading: false,
+ },
+ {
+ label: 'Go',
+ path: 'path',
+ type: 'link',
+ cssClass: 'link',
+ isLoading: false,
+ },
+ ],
+ };
+
+ vm = new HeaderCi({
+ propsData: props,
+ }).$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render status badge', () => {
+ expect(vm.$el.querySelector('.ci-failed')).toBeDefined();
+ expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined();
+ expect(
+ vm.$el.querySelector('.ci-failed').getAttribute('href'),
+ ).toEqual(props.status.details_path);
+ });
+
+ it('should render item name and id', () => {
+ expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123');
+ });
+
+ it('should render timeago date', () => {
+ expect(vm.$el.querySelector('time')).toBeDefined();
+ });
+
+ it('should render user icon and name', () => {
+ expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name);
+ });
+
+ it('should render provided actions', () => {
+ expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON');
+ expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label);
+ expect(vm.$el.querySelector('.link').tagName).toEqual('A');
+ expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label);
+ expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path);
+ });
+
+ it('should show loading icon', (done) => {
+ vm.actions[0].isLoading = true;
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toEqual('');
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/loading_icon_spec.js b/spec/javascripts/vue_shared/components/loading_icon_spec.js
new file mode 100644
index 00000000000..1baf3537741
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/loading_icon_spec.js
@@ -0,0 +1,53 @@
+import Vue from 'vue';
+import loadingIcon from '~/vue_shared/components/loading_icon.vue';
+
+describe('Loading Icon Component', () => {
+ let LoadingIconComponent;
+
+ beforeEach(() => {
+ LoadingIconComponent = Vue.extend(loadingIcon);
+ });
+
+ it('should render a spinner font awesome icon', () => {
+ const component = new LoadingIconComponent().$mount();
+
+ expect(
+ component.$el.querySelector('i').getAttribute('class'),
+ ).toEqual('fa fa-spin fa-spinner fa-1x');
+
+ expect(component.$el.tagName).toEqual('DIV');
+ expect(component.$el.classList.contains('text-center')).toEqual(true);
+ });
+
+ it('should render accessibility attributes', () => {
+ const component = new LoadingIconComponent().$mount();
+
+ const icon = component.$el.querySelector('i');
+ expect(icon.getAttribute('aria-hidden')).toEqual('true');
+ expect(icon.getAttribute('aria-label')).toEqual('Loading');
+ });
+
+ it('should render the provided label', () => {
+ const component = new LoadingIconComponent({
+ propsData: {
+ label: 'This is a loading icon',
+ },
+ }).$mount();
+
+ expect(
+ component.$el.querySelector('i').getAttribute('aria-label'),
+ ).toEqual('This is a loading icon');
+ });
+
+ it('should render the provided size', () => {
+ const component = new LoadingIconComponent({
+ propsData: {
+ size: '2',
+ },
+ }).$mount();
+
+ expect(
+ component.$el.querySelector('i').classList.contains('fa-2x'),
+ ).toEqual(true);
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js
new file mode 100644
index 00000000000..4bbaff561fc
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js
@@ -0,0 +1,121 @@
+import Vue from 'vue';
+import fieldComponent from '~/vue_shared/components/markdown/field.vue';
+
+describe('Markdown field component', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = new Vue({
+ render(createElement) {
+ return createElement(
+ fieldComponent,
+ {
+ props: {
+ markdownPreviewUrl: '/preview',
+ markdownDocs: '/docs',
+ },
+ },
+ [
+ createElement('textarea', {
+ slot: 'textarea',
+ }),
+ ],
+ );
+ },
+ });
+ });
+
+ it('creates a new instance of GL form', (done) => {
+ spyOn(gl, 'GLForm');
+ vm.$mount();
+
+ Vue.nextTick(() => {
+ expect(
+ gl.GLForm,
+ ).toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ describe('mounted', () => {
+ beforeEach((done) => {
+ vm.$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('renders textarea inside backdrop', () => {
+ expect(
+ vm.$el.querySelector('.zen-backdrop textarea'),
+ ).not.toBeNull();
+ });
+
+ describe('markdown preview', () => {
+ let previewLink;
+
+ beforeEach(() => {
+ spyOn(Vue.http, 'post').and.callFake(() => new Promise((resolve) => {
+ resolve({
+ json() {
+ return {
+ body: '<p>markdown preview</p>',
+ };
+ },
+ });
+ }));
+
+ previewLink = vm.$el.querySelector('.nav-links li:nth-child(2) a');
+ });
+
+ it('sets preview link as active', (done) => {
+ previewLink.click();
+
+ Vue.nextTick(() => {
+ expect(
+ previewLink.parentNode.classList.contains('active'),
+ ).toBeTruthy();
+
+ done();
+ });
+ });
+
+ it('shows preview loading text', (done) => {
+ previewLink.click();
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.md-preview').textContent.trim(),
+ ).toContain('Loading...');
+
+ done();
+ });
+ });
+
+ it('renders markdown preview', (done) => {
+ previewLink.click();
+
+ setTimeout(() => {
+ expect(
+ vm.$el.querySelector('.md-preview').innerHTML,
+ ).toContain('<p>markdown preview</p>');
+
+ done();
+ });
+ });
+
+ it('renders GFM with jQuery', (done) => {
+ spyOn($.fn, 'renderGFM');
+ previewLink.click();
+
+ setTimeout(() => {
+ expect(
+ $.fn.renderGFM,
+ ).toHaveBeenCalled();
+
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js
new file mode 100644
index 00000000000..7110ff36937
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js
@@ -0,0 +1,67 @@
+import Vue from 'vue';
+import headerComponent from '~/vue_shared/components/markdown/header.vue';
+
+describe('Markdown field header component', () => {
+ let vm;
+
+ beforeEach((done) => {
+ const Component = Vue.extend(headerComponent);
+
+ vm = new Component({
+ propsData: {
+ previewMarkdown: false,
+ },
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ it('renders markdown buttons', () => {
+ expect(
+ vm.$el.querySelectorAll('.js-md').length,
+ ).toBe(7);
+ });
+
+ it('renders `write` link as active when previewMarkdown is false', () => {
+ expect(
+ vm.$el.querySelector('li:nth-child(1)').classList.contains('active'),
+ ).toBeTruthy();
+ });
+
+ it('renders `preview` link as active when previewMarkdown is true', (done) => {
+ vm.previewMarkdown = true;
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('li:nth-child(2)').classList.contains('active'),
+ ).toBeTruthy();
+
+ done();
+ });
+ });
+
+ it('emits toggle markdown event when clicking preview', () => {
+ spyOn(vm, '$emit');
+
+ vm.$el.querySelector('li:nth-child(2) a').click();
+
+ expect(
+ vm.$emit,
+ ).toHaveBeenCalledWith('toggle-markdown');
+ });
+
+ it('blurs preview link after click', (done) => {
+ const link = vm.$el.querySelector('li:nth-child(2) a');
+ spyOn(HTMLElement.prototype, 'blur');
+
+ link.click();
+
+ setTimeout(() => {
+ expect(
+ link.blur,
+ ).toHaveBeenCalled();
+
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
index 699625cdbb7..67419cfcbea 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
+++ b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
@@ -1,27 +1,47 @@
import Vue from 'vue';
import tableRowComp from '~/vue_shared/components/pipelines_table_row';
-import pipeline from '../../commit/pipelines/mock_data';
describe('Pipelines Table Row', () => {
- let component;
-
- beforeEach(() => {
+ const jsonFixtureName = 'pipelines/pipelines.json';
+ const buildComponent = (pipeline) => {
const PipelinesTableRowComponent = Vue.extend(tableRowComp);
-
- component = new PipelinesTableRowComponent({
+ return new PipelinesTableRowComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
pipeline,
service: {},
},
}).$mount();
+ };
+
+ let component;
+ let pipeline;
+ let pipelineWithoutAuthor;
+ let pipelineWithoutCommit;
+
+ preloadFixtures(jsonFixtureName);
+
+ 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);
+ });
+
+ afterEach(() => {
+ component.$destroy();
});
it('should render a table row', () => {
+ component = buildComponent(pipeline);
expect(component.$el).toEqual('TR');
});
describe('status column', () => {
+ beforeEach(() => {
+ component = buildComponent(pipeline);
+ });
+
it('should render a pipeline link', () => {
expect(
component.$el.querySelector('td.commit-link a').getAttribute('href'),
@@ -36,6 +56,10 @@ describe('Pipelines Table Row', () => {
});
describe('information column', () => {
+ beforeEach(() => {
+ component = buildComponent(pipeline);
+ });
+
it('should render a pipeline link', () => {
expect(
component.$el.querySelector('td:nth-child(2) a').getAttribute('href'),
@@ -52,10 +76,10 @@ describe('Pipelines Table Row', () => {
it('should render user information', () => {
expect(
component.$el.querySelector('td:nth-child(2) a:nth-child(3)').getAttribute('href'),
- ).toEqual(pipeline.user.web_url);
+ ).toEqual(pipeline.user.path);
expect(
- component.$el.querySelector('td:nth-child(2) img').getAttribute('title'),
+ component.$el.querySelector('td:nth-child(2) img').getAttribute('data-original-title'),
).toEqual(pipeline.user.name);
});
});
@@ -63,13 +87,59 @@ describe('Pipelines Table Row', () => {
describe('commit column', () => {
it('should render link to commit', () => {
- expect(
- component.$el.querySelector('td:nth-child(3) .commit-id').getAttribute('href'),
- ).toEqual(pipeline.commit.commit_path);
+ component = buildComponent(pipeline);
+
+ const commitLink = component.$el.querySelector('.branch-commit .commit-sha');
+ expect(commitLink.getAttribute('href')).toEqual(pipeline.commit.commit_path);
+ });
+
+ const findElements = () => {
+ const commitTitleElement = component.$el.querySelector('.branch-commit .commit-title');
+ const commitAuthorElement = commitTitleElement.querySelector('a.avatar-image-container');
+
+ if (!commitAuthorElement) {
+ return { commitAuthorElement };
+ }
+
+ const commitAuthorLink = commitAuthorElement.getAttribute('href');
+ const commitAuthorName = commitAuthorElement.querySelector('img.avatar').getAttribute('data-original-title');
+
+ return { commitAuthorElement, commitAuthorLink, commitAuthorName };
+ };
+
+ it('renders nothing without commit', () => {
+ expect(pipelineWithoutCommit.commit).toBe(null);
+ component = buildComponent(pipelineWithoutCommit);
+
+ const { commitAuthorElement } = findElements();
+
+ expect(commitAuthorElement).toBe(null);
+ });
+
+ it('renders commit author', () => {
+ component = buildComponent(pipeline);
+ const { commitAuthorLink, commitAuthorName } = findElements();
+
+ expect(commitAuthorLink).toEqual(pipeline.commit.author.path);
+ expect(commitAuthorName).toEqual(pipeline.commit.author.username);
+ });
+
+ it('renders commit with unregistered author', () => {
+ expect(pipelineWithoutAuthor.commit.author).toBe(null);
+ component = buildComponent(pipelineWithoutAuthor);
+
+ const { commitAuthorLink, commitAuthorName } = findElements();
+
+ expect(commitAuthorLink).toEqual(`mailto:${pipelineWithoutAuthor.commit.author_email}`);
+ expect(commitAuthorName).toEqual(pipelineWithoutAuthor.commit.author_name);
});
});
describe('stages column', () => {
+ beforeEach(() => {
+ component = buildComponent(pipeline);
+ });
+
it('should render an icon for each stage', () => {
expect(
component.$el.querySelectorAll('td:nth-child(4) .js-builds-dropdown-button').length,
@@ -78,6 +148,10 @@ describe('Pipelines Table Row', () => {
});
describe('actions column', () => {
+ beforeEach(() => {
+ component = buildComponent(pipeline);
+ });
+
it('should render the provided actions', () => {
expect(
component.$el.querySelectorAll('td:nth-child(6) ul li').length,
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_spec.js b/spec/javascripts/vue_shared/components/pipelines_table_spec.js
index 4d3ced944d7..6cc178b8f1d 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_spec.js
+++ b/spec/javascripts/vue_shared/components/pipelines_table_spec.js
@@ -1,13 +1,19 @@
import Vue from 'vue';
import pipelinesTableComp from '~/vue_shared/components/pipelines_table';
import '~/lib/utils/datetime_utility';
-import pipeline from '../../commit/pipelines/mock_data';
describe('Pipelines Table', () => {
+ const jsonFixtureName = 'pipelines/pipelines.json';
+
+ let pipeline;
let PipelinesTableComponent;
+ preloadFixtures(jsonFixtureName);
+
beforeEach(() => {
PipelinesTableComponent = Vue.extend(pipelinesTableComp);
+ const pipelines = getJSONFixture(jsonFixtureName).pipelines;
+ pipeline = pipelines.find(p => p.id === 1);
});
describe('table', () => {
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 96038718191..895e1c585b4 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import paginationComp from '~/vue_shared/components/table_pagination';
+import paginationComp from '~/vue_shared/components/table_pagination.vue';
import '~/lib/utils/common_utils';
describe('Pagination component', () => {
diff --git a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
new file mode 100644
index 00000000000..bf28019ef24
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
@@ -0,0 +1,68 @@
+import Vue from 'vue';
+import timeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import '~/lib/utils/datetime_utility';
+
+describe('Time ago with tooltip component', () => {
+ let TimeagoTooltip;
+ let vm;
+
+ beforeEach(() => {
+ TimeagoTooltip = Vue.extend(timeagoTooltip);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render timeago with a bootstrap tooltip', () => {
+ vm = new TimeagoTooltip({
+ propsData: {
+ time: '2017-05-08T14:57:39.781Z',
+ },
+ }).$mount();
+
+ expect(vm.$el.tagName).toEqual('TIME');
+ expect(vm.$el.classList.contains('js-timeago')).toEqual(true);
+ expect(
+ vm.$el.getAttribute('data-original-title'),
+ ).toEqual(gl.utils.formatDate('2017-05-08T14:57:39.781Z'));
+ expect(vm.$el.getAttribute('data-placement')).toEqual('top');
+
+ const timeago = gl.utils.getTimeago();
+
+ expect(vm.$el.textContent.trim()).toEqual(timeago.format('2017-05-08T14:57:39.781Z'));
+ });
+
+ it('should render tooltip placed in bottom', () => {
+ vm = new TimeagoTooltip({
+ propsData: {
+ time: '2017-05-08T14:57:39.781Z',
+ tooltipPlacement: 'bottom',
+ },
+ }).$mount();
+
+ expect(vm.$el.getAttribute('data-placement')).toEqual('bottom');
+ });
+
+ it('should render short format class', () => {
+ vm = new TimeagoTooltip({
+ propsData: {
+ time: '2017-05-08T14:57:39.781Z',
+ shortFormat: true,
+ },
+ }).$mount();
+
+ expect(vm.$el.classList.contains('js-short-timeago')).toEqual(true);
+ });
+
+ it('should render provided html class', () => {
+ vm = new TimeagoTooltip({
+ propsData: {
+ time: '2017-05-08T14:57:39.781Z',
+ cssClass: 'foo',
+ },
+ }).$mount();
+
+ expect(vm.$el.classList.contains('foo')).toEqual(true);
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/user_avatar_image_spec.js b/spec/javascripts/vue_shared/components/user_avatar_image_spec.js
new file mode 100644
index 00000000000..8daa7610274
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/user_avatar_image_spec.js
@@ -0,0 +1,54 @@
+import Vue from 'vue';
+import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
+
+const UserAvatarImageComponent = Vue.extend(UserAvatarImage);
+
+describe('User Avatar Image Component', function () {
+ describe('Initialization', function () {
+ beforeEach(function () {
+ this.propsData = {
+ size: 99,
+ imgSrc: 'myavatarurl.com',
+ imgAlt: 'mydisplayname',
+ cssClasses: 'myextraavatarclass',
+ tooltipText: 'tooltip text',
+ tooltipPlacement: 'bottom',
+ };
+
+ this.userAvatarImage = new UserAvatarImageComponent({
+ propsData: this.propsData,
+ }).$mount();
+ });
+
+ it('should return a defined Vue component', function () {
+ expect(this.userAvatarImage).toBeDefined();
+ });
+
+ it('should have <img> as a child element', function () {
+ expect(this.userAvatarImage.$el.tagName).toBe('IMG');
+ });
+
+ it('should properly compute tooltipContainer', function () {
+ expect(this.userAvatarImage.tooltipContainer).toBe('body');
+ });
+
+ it('should properly render tooltipContainer', function () {
+ expect(this.userAvatarImage.$el.getAttribute('data-container')).toBe('body');
+ });
+
+ it('should properly compute avatarSizeClass', function () {
+ expect(this.userAvatarImage.avatarSizeClass).toBe('s99');
+ });
+
+ it('should properly render img css', function () {
+ const classList = this.userAvatarImage.$el.classList;
+ const containsAvatar = classList.contains('avatar');
+ const containsSizeClass = classList.contains('s99');
+ const containsCustomClass = classList.contains('myextraavatarclass');
+
+ expect(containsAvatar).toBe(true);
+ expect(containsSizeClass).toBe(true);
+ expect(containsCustomClass).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/user_avatar_link_spec.js b/spec/javascripts/vue_shared/components/user_avatar_link_spec.js
new file mode 100644
index 00000000000..52e450e9ba5
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/user_avatar_link_spec.js
@@ -0,0 +1,50 @@
+import Vue from 'vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+
+describe('User Avatar Link Component', function () {
+ beforeEach(function () {
+ this.propsData = {
+ linkHref: 'myavatarurl.com',
+ imgSize: 99,
+ imgSrc: 'myavatarurl.com',
+ imgAlt: 'mydisplayname',
+ imgCssClasses: 'myextraavatarclass',
+ tooltipText: 'tooltip text',
+ tooltipPlacement: 'bottom',
+ };
+
+ const UserAvatarLinkComponent = Vue.extend(UserAvatarLink);
+
+ this.userAvatarLink = new UserAvatarLinkComponent({
+ propsData: this.propsData,
+ }).$mount();
+
+ this.userAvatarImage = this.userAvatarLink.$children[0];
+ });
+
+ it('should return a defined Vue component', function () {
+ expect(this.userAvatarLink).toBeDefined();
+ });
+
+ it('should have user-avatar-image registered as child component', function () {
+ expect(this.userAvatarLink.$options.components.userAvatarImage).toBeDefined();
+ });
+
+ it('user-avatar-link should have user-avatar-image as child component', function () {
+ expect(this.userAvatarImage).toBeDefined();
+ });
+
+ it('should render <a> as a child element', function () {
+ expect(this.userAvatarLink.$el.tagName).toBe('A');
+ });
+
+ it('should have <img> as a child element', function () {
+ expect(this.userAvatarLink.$el.querySelector('img')).not.toBeNull();
+ });
+
+ it('should return neccessary props as defined', function () {
+ _.each(this.propsData, (val, key) => {
+ expect(this.userAvatarLink[key]).toBeDefined();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/user_avatar_svg_spec.js b/spec/javascripts/vue_shared/components/user_avatar_svg_spec.js
new file mode 100644
index 00000000000..b8d639ffbec
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/user_avatar_svg_spec.js
@@ -0,0 +1,29 @@
+import Vue from 'vue';
+import UserAvatarSvg from '~/vue_shared/components/user_avatar/user_avatar_svg.vue';
+import avatarSvg from 'icons/_icon_random.svg';
+
+const UserAvatarSvgComponent = Vue.extend(UserAvatarSvg);
+
+describe('User Avatar Svg Component', function () {
+ describe('Initialization', function () {
+ beforeEach(function () {
+ this.propsData = {
+ size: 99,
+ svg: avatarSvg,
+ };
+
+ this.userAvatarSvg = new UserAvatarSvgComponent({
+ propsData: this.propsData,
+ }).$mount();
+ });
+
+ it('should return a defined Vue component', function () {
+ expect(this.userAvatarSvg).toBeDefined();
+ });
+
+ it('should have <svg> as a child element', function () {
+ expect(this.userAvatarSvg.$el.tagName).toEqual('svg');
+ expect(this.userAvatarSvg.$el.innerHTML).toContain('<path');
+ });
+ });
+});
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 99515f2e5f2..4399c8b2025 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -3,7 +3,7 @@
/* global Mousetrap */
/* global ZenMode */
-require('~/zen_mode');
+import '~/zen_mode';
(function() {
var enterZen, escapeKeydown, exitZen;
diff --git a/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
new file mode 100644
index 00000000000..33b812ef425
--- /dev/null
+++ b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe Banzai::Filter::AsciiDocPostProcessingFilter, lib: true do
+ include FilterSpecHelper
+
+ it "adds class for elements with data-math-style" do
+ result = filter('<pre data-math-style="inline">some code</pre><div data-math>and</div>').to_html
+ expect(result).to eq('<pre data-math-style="inline" class="code math js-render-math">some code</pre><div data-math>and</div>')
+ end
+
+ it "keeps content when no data-math-style found" do
+ result = filter('<pre>some code</pre><div data-math>and</div>').to_html
+ expect(result).to eq('<pre>some code</pre><div data-math>and</div>')
+ end
+end
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index fdbc65b5e00..fb7862f49a2 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -97,6 +97,22 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(filter(act).to_html).to eq exp
end
+ it 'allows `data-math-style` attribute on `code` and `pre` elements' do
+ html = <<-HTML
+ <pre class="code" data-math-style="inline">something</pre>
+ <code class="code" data-math-style="inline">something</code>
+ <div class="code" data-math-style="inline">something</div>
+ HTML
+
+ output = <<-HTML
+ <pre data-math-style="inline">something</pre>
+ <code data-math-style="inline">something</code>
+ <div>something</div>
+ HTML
+
+ expect(filter(html).to_html).to eq(output)
+ end
+
it 'removes `rel` attribute from `a` elements' do
act = %q{<a href="#" rel="nofollow">Link</a>}
exp = %q{<a href="#">Link</a>}
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 63b23dac7ed..edf3846b742 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -16,6 +16,11 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
expect(reference_filter(act).to_html).to eq(exp)
end
+ it 'ignores references with text before the @ sign' do
+ exp = act = "Hey foo#{reference}"
+ expect(reference_filter(act).to_html).to eq(exp)
+ end
+
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index 4ec998efe53..592ed0d2b98 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -42,6 +42,29 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
expect(subject.referenced_by([link])).to eq([user])
end
+
+ context 'when RequestStore is active' do
+ let(:other_user) { create(:user) }
+
+ before do
+ RequestStore.begin!
+ end
+
+ after do
+ RequestStore.end!
+ RequestStore.clear!
+ end
+
+ it 'does not return users from the first call in the second' do
+ link['data-user'] = user.id.to_s
+
+ expect(subject.referenced_by([link])).to eq([user])
+
+ link['data-user'] = other_user.id.to_s
+
+ expect(subject.referenced_by([link])).to eq([other_user])
+ end
+ end
end
context 'when the link has a data-project attribute' do
@@ -74,7 +97,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
end
end
- describe '#nodes_visible_to_use?' do
+ describe '#nodes_visible_to_user' do
context 'when the link has a data-group attribute' do
context 'using an existing group ID' do
before do
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index 53abc056602..2ca0773ad1d 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -1,7 +1,8 @@
require 'spec_helper'
module Ci
- describe GitlabCiYamlProcessor, lib: true do
+ describe GitlabCiYamlProcessor, :lib do
+ subject { described_class.new(config, path) }
let(:path) { 'path' }
describe 'our current .gitlab-ci.yml' do
@@ -82,6 +83,67 @@ module Ci
end
end
+ describe '#stage_seeds' do
+ context 'when no refs policy is specified' do
+ let(:config) do
+ YAML.dump(production: { stage: 'deploy', script: 'cap prod' },
+ rspec: { stage: 'test', script: 'rspec' },
+ spinach: { stage: 'test', script: 'spinach' })
+ end
+
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ it 'correctly fabricates a stage seeds object' do
+ seeds = subject.stage_seeds(pipeline)
+
+ expect(seeds.size).to eq 2
+ expect(seeds.first.stage[:name]).to eq 'test'
+ expect(seeds.second.stage[:name]).to eq 'deploy'
+ expect(seeds.first.builds.dig(0, :name)).to eq 'rspec'
+ expect(seeds.first.builds.dig(1, :name)).to eq 'spinach'
+ expect(seeds.second.builds.dig(0, :name)).to eq 'production'
+ end
+ end
+
+ context 'when refs policy is specified' do
+ let(:config) do
+ YAML.dump(production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
+ spinach: { stage: 'test', script: 'spinach', only: ['tags'] })
+ end
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, ref: 'feature', tag: true)
+ end
+
+ it 'returns stage seeds only assigned to master to master' do
+ seeds = subject.stage_seeds(pipeline)
+
+ expect(seeds.size).to eq 1
+ expect(seeds.first.stage[:name]).to eq 'test'
+ expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
+ end
+ end
+
+ context 'when source policy is specified' do
+ let(:config) do
+ YAML.dump(production: { stage: 'deploy', script: 'cap prod', only: ['triggers'] },
+ spinach: { stage: 'test', script: 'spinach', only: ['schedules'] })
+ end
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, source: :schedule)
+ end
+
+ it 'returns stage seeds only assigned to schedules' do
+ seeds = subject.stage_seeds(pipeline)
+
+ expect(seeds.size).to eq 1
+ expect(seeds.first.stage[:name]).to eq 'test'
+ expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
+ end
+ end
+ end
+
describe "#builds_for_ref" do
let(:type) { 'test' }
@@ -176,26 +238,44 @@ module Ci
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
end
- it "returns builds if only has a triggers keyword specified and a trigger is provided" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, only: ["triggers"] }
- })
+ it "returns builds if only has special keywords specified and source matches" do
+ possibilities = [{ keyword: 'pushes', source: 'push' },
+ { keyword: 'web', source: 'web' },
+ { keyword: 'triggers', source: 'trigger' },
+ { keyword: 'schedules', source: 'schedule' },
+ { keyword: 'api', source: 'api' },
+ { keyword: 'external', source: 'external' }]
- config_processor = GitlabCiYamlProcessor.new(config, path)
+ possibilities.each do |possibility|
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, only: [possibility[:keyword]] }
+ })
- expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, true).size).to eq(1)
+ config_processor = GitlabCiYamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(1)
+ end
end
- it "does not return builds if only has a triggers keyword specified and no trigger is provided" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, only: ["triggers"] }
- })
+ it "does not return builds if only has special keywords specified and source doesn't match" do
+ possibilities = [{ keyword: 'pushes', source: 'web' },
+ { keyword: 'web', source: 'push' },
+ { keyword: 'triggers', source: 'schedule' },
+ { keyword: 'schedules', source: 'external' },
+ { keyword: 'api', source: 'trigger' },
+ { keyword: 'external', source: 'api' }]
- config_processor = GitlabCiYamlProcessor.new(config, path)
+ possibilities.each do |possibility|
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, only: [possibility[:keyword]] }
+ })
- expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
+ config_processor = GitlabCiYamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(0)
+ end
end
it "returns builds if only has current repository path" do
@@ -225,7 +305,7 @@ module Ci
before_script: ["pwd"],
rspec: { script: "rspec", type: "test", only: %w(master deploy) },
staging: { script: "deploy", type: "deploy", only: %w(master deploy) },
- production: { script: "deploy", type: "deploy", only: ["master@path", "deploy"] },
+ production: { script: "deploy", type: "deploy", only: ["master@path", "deploy"] }
})
config_processor = GitlabCiYamlProcessor.new(config, 'fork')
@@ -332,26 +412,44 @@ module Ci
expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
end
- it "does not return builds if except has a triggers keyword specified and a trigger is provided" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, except: ["triggers"] }
- })
+ it "does not return builds if except has special keywords specified and source matches" do
+ possibilities = [{ keyword: 'pushes', source: 'push' },
+ { keyword: 'web', source: 'web' },
+ { keyword: 'triggers', source: 'trigger' },
+ { keyword: 'schedules', source: 'schedule' },
+ { keyword: 'api', source: 'api' },
+ { keyword: 'external', source: 'external' }]
- config_processor = GitlabCiYamlProcessor.new(config, path)
+ possibilities.each do |possibility|
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, except: [possibility[:keyword]] }
+ })
- expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, true).size).to eq(0)
+ config_processor = GitlabCiYamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(0)
+ end
end
- it "returns builds if except has a triggers keyword specified and no trigger is provided" do
- config = YAML.dump({
- before_script: ["pwd"],
- rspec: { script: "rspec", type: type, except: ["triggers"] }
- })
+ it "returns builds if except has special keywords specified and source doesn't match" do
+ possibilities = [{ keyword: 'pushes', source: 'web' },
+ { keyword: 'web', source: 'push' },
+ { keyword: 'triggers', source: 'schedule' },
+ { keyword: 'schedules', source: 'external' },
+ { keyword: 'api', source: 'trigger' },
+ { keyword: 'external', source: 'api' }]
- config_processor = GitlabCiYamlProcessor.new(config, path)
+ possibilities.each do |possibility|
+ config = YAML.dump({
+ before_script: ["pwd"],
+ rspec: { script: "rspec", type: type, except: [possibility[:keyword]] }
+ })
- expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+ config_processor = GitlabCiYamlProcessor.new(config, path)
+
+ expect(config_processor.builds_for_stage_and_ref(type, "deploy", false, possibility[:source]).size).to eq(1)
+ end
end
it "does not return builds if except has current repository path" do
@@ -381,7 +479,7 @@ module Ci
before_script: ["pwd"],
rspec: { script: "rspec", type: "test", except: ["master", "deploy", "test@fork"] },
staging: { script: "deploy", type: "deploy", except: ["master"] },
- production: { script: "deploy", type: "deploy", except: ["master@fork"] },
+ production: { script: "deploy", type: "deploy", except: ["master@fork"] }
})
config_processor = GitlabCiYamlProcessor.new(config, 'fork')
@@ -716,7 +814,7 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["logs/", "binaries/"],
untracked: true,
- key: 'key',
+ key: 'key'
)
end
@@ -734,7 +832,7 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["logs/", "binaries/"],
untracked: true,
- key: 'key',
+ key: 'key'
)
end
@@ -743,7 +841,7 @@ module Ci
cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' },
rspec: {
script: "rspec",
- cache: { paths: ["test/"], untracked: false, key: 'local' },
+ cache: { paths: ["test/"], untracked: false, key: 'local' }
}
})
@@ -753,7 +851,7 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["test/"],
untracked: false,
- key: 'local',
+ key: 'local'
)
end
end
diff --git a/spec/lib/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb
index f06e5fd54a2..ab010c6dfeb 100644
--- a/spec/lib/container_registry/blob_spec.rb
+++ b/spec/lib/container_registry/blob_spec.rb
@@ -98,7 +98,7 @@ describe ContainerRegistry::Blob do
context 'for a valid address' do
before do
stub_request(:get, location).
- with(headers: { 'Authorization' => nil }).
+ with { |request| !request.headers.include?('Authorization') }.
to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb
new file mode 100644
index 00000000000..ec03b533383
--- /dev/null
+++ b/spec/lib/container_registry/client_spec.rb
@@ -0,0 +1,39 @@
+# coding: utf-8
+require 'spec_helper'
+
+describe ContainerRegistry::Client do
+ let(:token) { '12345' }
+ let(:options) { { token: token } }
+ let(:client) { described_class.new("http://container-registry", options) }
+
+ describe '#blob' do
+ it 'GET /v2/:name/blobs/:digest' do
+ stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345").
+ with(headers: {
+ 'Accept' => 'application/octet-stream',
+ 'Authorization' => "bearer #{token}"
+ }).
+ to_return(status: 200, body: "Blob")
+
+ expect(client.blob('group/test', 'sha256:0123456789012345')).to eq('Blob')
+ end
+
+ it 'follows 307 redirect for GET /v2/:name/blobs/:digest' do
+ stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345").
+ with(headers: {
+ 'Accept' => 'application/octet-stream',
+ 'Authorization' => "bearer #{token}"
+ }).
+ to_return(status: 307, body: "", headers: { Location: 'http://redirected' })
+ # We should probably use hash_excluding here, but that requires an update to WebMock:
+ # https://github.com/bblimke/webmock/blob/master/lib/webmock/matchers/hash_excluding_matcher.rb
+ stub_request(:get, "http://redirected/").
+ with { |request| !request.headers.include?('Authorization') }.
+ to_return(status: 200, body: "Successfully redirected")
+
+ response = client.blob('group/test', 'sha256:0123456789012345')
+
+ expect(response).to eq('Successfully redirected')
+ end
+ end
+end
diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb
index 90628917943..7faa0f31b68 100644
--- a/spec/lib/expand_variables_spec.rb
+++ b/spec/lib/expand_variables_spec.rb
@@ -25,7 +25,7 @@ describe ExpandVariables do
result: 'keyvalueresult',
variables: [
{ key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
+ { key: 'variable2', value: 'result' }
] },
{ value: 'key${variable}${variable2}',
result: 'keyvalueresult',
@@ -37,7 +37,7 @@ describe ExpandVariables do
result: 'keyresultvalue',
variables: [
{ key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
+ { key: 'variable2', value: 'result' }
] },
{ value: 'key${variable2}${variable}',
result: 'keyresultvalue',
@@ -49,7 +49,7 @@ describe ExpandVariables do
result: 'review/feature/add-review-apps',
variables: [
{ key: 'CI_COMMIT_REF_NAME', value: 'feature/add-review-apps' }
- ] },
+ ] }
]
tests.each do |test|
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
new file mode 100644
index 00000000000..1d92a5cb33f
--- /dev/null
+++ b/spec/lib/feature_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Feature, lib: true do
+ describe '.get' do
+ let(:feature) { double(:feature) }
+ let(:key) { 'my_feature' }
+
+ it 'returns the Flipper feature' do
+ expect_any_instance_of(Flipper::DSL).to receive(:feature).with(key).
+ and_return(feature)
+
+ expect(described_class.get(key)).to be(feature)
+ end
+ end
+
+ describe '.all' do
+ let(:features) { Set.new }
+
+ it 'returns the Flipper features as an array' do
+ expect_any_instance_of(Flipper::DSL).to receive(:features).
+ and_return(features)
+
+ expect(described_class.all).to eq(features.to_a)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 2c7ebb15fd7..43d52b941ab 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -70,6 +70,31 @@ module Gitlab
expect(output).to include('rel="nofollow noreferrer noopener"')
end
end
+
+ context 'LaTex code' do
+ it 'adds class js-render-math to the output' do
+ input = <<~MD
+ :stem: latexmath
+
+ [stem]
+ ++++
+ \sqrt{4} = 2
+ ++++
+
+ another part
+
+ [latexmath]
+ ++++
+ \beta_x \gamma
+ ++++
+
+ stem:[2+2] is 4
+ MD
+
+ expect(render(input, context)).to include('<pre data-math-style="display" class="code math js-render-math"><code>eta_x gamma</code></pre>')
+ expect(render(input, context)).to include('<p><code data-math-style="inline" class="code math js-render-math">2+2</code> is 4</p>')
+ end
+ end
end
def render(*args)
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index d4a43192d03..d6006eab0c9 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -17,7 +17,11 @@ describe Gitlab::Auth, lib: true do
end
it 'OPTIONAL_SCOPES contains all non-default scopes' do
- expect(subject::OPTIONAL_SCOPES).to eq [:read_user, :openid]
+ expect(subject::OPTIONAL_SCOPES).to eq %i[read_user read_registry openid]
+ end
+
+ it 'REGISTRY_SCOPES contains all registry related scopes' do
+ expect(subject::REGISTRY_SCOPES).to eq %i[read_registry]
end
end
@@ -143,6 +147,13 @@ describe Gitlab::Auth, lib: true do
expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, full_authentication_abilities))
end
+ it 'succeeds for personal access tokens with the `read_registry` scope' do
+ personal_access_token = create(:personal_access_token, scopes: ['read_registry'])
+
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
+ expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, [:read_container_image]))
+ end
+
it 'succeeds if it is an impersonation token' do
impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api'])
@@ -150,18 +161,11 @@ describe Gitlab::Auth, lib: true do
expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(impersonation_token.user, nil, :personal_token, full_authentication_abilities))
end
- it 'fails for personal access tokens with other scopes' do
+ it 'limits abilities based on scope' do
personal_access_token = create(:personal_access_token, scopes: ['read_user'])
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
- expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
- end
-
- it 'fails for impersonation token with other scopes' do
- impersonation_token = create(:personal_access_token, scopes: ['read_user'])
-
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
- expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
+ expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, []))
end
it 'fails if password is nil' do
@@ -175,7 +179,7 @@ describe Gitlab::Auth, lib: true do
user = create(
:user,
username: 'normal_user',
- password: 'my-secret',
+ password: 'my-secret'
)
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
@@ -186,7 +190,7 @@ describe Gitlab::Auth, lib: true do
user = create(
:user,
username: 'oauth2',
- password: 'my-secret',
+ password: 'my-secret'
)
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
diff --git a/spec/lib/gitlab/backup/manager_spec.rb b/spec/lib/gitlab/backup/manager_spec.rb
index 2dd428bf20b..1c3d2547fec 100644
--- a/spec/lib/gitlab/backup/manager_spec.rb
+++ b/spec/lib/gitlab/backup/manager_spec.rb
@@ -158,8 +158,8 @@ describe Backup::Manager, lib: true do
before do
allow(Dir).to receive(:glob).and_return(
[
- '1451606400_2016_01_01_gitlab_backup.tar',
- '1451520000_2015_12_31_gitlab_backup.tar',
+ '1451606400_2016_01_01_1.2.3_gitlab_backup.tar',
+ '1451520000_2015_12_31_gitlab_backup.tar'
]
)
end
diff --git a/spec/lib/gitlab/backup/repository_spec.rb b/spec/lib/gitlab/backup/repository_spec.rb
new file mode 100644
index 00000000000..51c1e9d657b
--- /dev/null
+++ b/spec/lib/gitlab/backup/repository_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Backup::Repository, lib: true do
+ let(:progress) { StringIO.new }
+ let!(:project) { create(:empty_project) }
+
+ before do
+ allow(progress).to receive(:puts)
+ allow(progress).to receive(:print)
+
+ allow_any_instance_of(String).to receive(:color) do |string, _color|
+ string
+ end
+
+ allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
+ end
+
+ describe '#dump' do
+ describe 'repo failure' do
+ before do
+ allow_any_instance_of(Repository).to receive(:empty_repo?).and_raise(Rugged::OdbError)
+ allow(Gitlab::Popen).to receive(:popen).and_return(['normal output', 0])
+ end
+
+ it 'does not raise error' do
+ expect { described_class.new.dump }.not_to raise_error
+ end
+
+ it 'shows the appropriate error' do
+ described_class.new.dump
+
+ expect(progress).to have_received(:puts).with("Ignoring repository error and continuing backing up project: #{project.full_path} - Rugged::OdbError")
+ end
+ end
+
+ describe 'command failure' do
+ before do
+ allow_any_instance_of(Repository).to receive(:empty_repo?).and_return(false)
+ allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1])
+ end
+
+ it 'shows the appropriate error' do
+ described_class.new.dump
+
+ expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error")
+ end
+ end
+ end
+
+ describe '#restore' do
+ describe 'command failure' do
+ before do
+ allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1])
+ end
+
+ it 'shows the appropriate error' do
+ described_class.new.restore
+
+ expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error")
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb
index eb4f06b371c..13e6953147b 100644
--- a/spec/lib/gitlab/chat_commands/command_spec.rb
+++ b/spec/lib/gitlab/chat_commands/command_spec.rb
@@ -58,9 +58,12 @@ describe Gitlab::ChatCommands::Command, service: true do
end
end
- context 'and user does have deployment permission' do
+ context 'and user has deployment permission' do
before do
- build.project.add_master(user)
+ build.project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
it 'returns action' do
diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/chat_commands/deploy_spec.rb
index b33389d959e..46dbdeae37c 100644
--- a/spec/lib/gitlab/chat_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/chat_commands/deploy_spec.rb
@@ -7,7 +7,12 @@ describe Gitlab::ChatCommands::Deploy, service: true do
let(:regex_match) { described_class.match('deploy staging to production') }
before do
- project.add_master(user)
+ # Make it possible to trigger protected manual actions for developers.
+ #
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
end
subject do
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
index 959ae02c222..c0c309d8179 100644
--- a/spec/lib/gitlab/checks/change_access_spec.rb
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -23,29 +23,27 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
before { project.add_developer(user) }
context 'without failed checks' do
- it "doesn't return any error" do
- expect(subject.status).to be(true)
+ it "doesn't raise an error" do
+ expect { subject }.not_to raise_error
end
end
context 'when the user is not allowed to push code' do
- it 'returns an error' do
+ it 'raises an error' do
expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false)
- expect(subject.status).to be(false)
- expect(subject.message).to eq('You are not allowed to push code to this project.')
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.')
end
end
context 'tags check' do
let(:ref) { 'refs/tags/v1.0.0' }
- it 'returns an error if the user is not allowed to update tags' do
+ it 'raises an error if the user is not allowed to update tags' do
allow(user_access).to receive(:can_do_action?).with(:push_code).and_return(true)
expect(user_access).to receive(:can_do_action?).with(:admin_project).and_return(false)
- expect(subject.status).to be(false)
- expect(subject.message).to eq('You are not allowed to change existing tags on this project.')
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to change existing tags on this project.')
end
context 'with protected tag' do
@@ -59,8 +57,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
let(:newrev) { '0000000000000000000000000000000000000000' }
it 'is prevented' do
- expect(subject.status).to be(false)
- expect(subject.message).to include('cannot be deleted')
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be deleted/)
end
end
@@ -69,8 +66,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' }
it 'is prevented' do
- expect(subject.status).to be(false)
- expect(subject.message).to include('cannot be updated')
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be updated/)
end
end
end
@@ -81,55 +77,85 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
let(:ref) { 'refs/tags/v9.1.0' }
it 'prevents creation below access level' do
- expect(subject.status).to be(false)
- expect(subject.message).to include('allowed to create this tag as it is protected')
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /allowed to create this tag as it is protected/)
end
context 'when user has access' do
let!(:protected_tag) { create(:protected_tag, :developers_can_create, project: project, name: 'v*') }
it 'allows tag creation' do
- expect(subject.status).to be(true)
+ expect { subject }.not_to raise_error
end
end
end
end
end
- context 'protected branches check' do
- before do
- allow(ProtectedBranch).to receive(:protected?).with(project, 'master').and_return(true)
+ context 'branches check' do
+ context 'trying to delete the default branch' do
+ let(:newrev) { '0000000000000000000000000000000000000000' }
+ let(:ref) { 'refs/heads/master' }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'The default branch of a project cannot be deleted.')
+ end
end
- it 'returns an error if the user is not allowed to do forced pushes to protected branches' do
- expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
+ context 'protected branches check' do
+ before do
+ allow(ProtectedBranch).to receive(:protected?).with(project, 'master').and_return(true)
+ allow(ProtectedBranch).to receive(:protected?).with(project, 'feature').and_return(true)
+ end
- expect(subject.status).to be(false)
- expect(subject.message).to eq('You are not allowed to force push code to a protected branch on this project.')
- end
+ it 'raises an error if the user is not allowed to do forced pushes to protected branches' do
+ expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
- it 'returns an error if the user is not allowed to merge to protected branches' do
- expect_any_instance_of(Gitlab::Checks::MatchingMergeRequest).to receive(:match?).and_return(true)
- expect(user_access).to receive(:can_merge_to_branch?).and_return(false)
- expect(user_access).to receive(:can_push_to_branch?).and_return(false)
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to force push code to a protected branch on this project.')
+ end
- expect(subject.status).to be(false)
- expect(subject.message).to eq('You are not allowed to merge code into protected branches on this project.')
- end
+ it 'raises an error if the user is not allowed to merge to protected branches' do
+ expect_any_instance_of(Gitlab::Checks::MatchingMergeRequest).to receive(:match?).and_return(true)
+ expect(user_access).to receive(:can_merge_to_branch?).and_return(false)
+ expect(user_access).to receive(:can_push_to_branch?).and_return(false)
- it 'returns an error if the user is not allowed to push to protected branches' do
- expect(user_access).to receive(:can_push_to_branch?).and_return(false)
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to merge code into protected branches on this project.')
+ end
- expect(subject.status).to be(false)
- expect(subject.message).to eq('You are not allowed to push code to protected branches on this project.')
- end
+ it 'raises an error if the user is not allowed to push to protected branches' do
+ expect(user_access).to receive(:can_push_to_branch?).and_return(false)
- context 'branch deletion' do
- let(:newrev) { '0000000000000000000000000000000000000000' }
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to protected branches on this project.')
+ end
- it 'returns an error if the user is not allowed to delete protected branches' do
- expect(subject.status).to be(false)
- expect(subject.message).to eq('You are not allowed to delete protected branches from this project.')
+ context 'branch deletion' do
+ let(:newrev) { '0000000000000000000000000000000000000000' }
+ let(:ref) { 'refs/heads/feature' }
+
+ context 'if the user is not allowed to delete protected branches' do
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.')
+ end
+ end
+
+ context 'if the user is allowed to delete protected branches' do
+ before do
+ project.add_master(user)
+ end
+
+ context 'through the web interface' do
+ let(:protocol) { 'web' }
+
+ it 'allows branch deletion' do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context 'over SSH or HTTP' do
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only delete protected branches using the web interface.')
+ end
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index 684d01e9056..23270ad5053 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -113,7 +113,7 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#variables_value' do
it 'returns variables' do
- expect(global.variables_value).to eq(VAR: 'value')
+ expect(global.variables_value).to eq('VAR' => 'value')
end
end
@@ -154,7 +154,7 @@ describe Gitlab::Ci::Config::Entry::Global do
services: ['postgres:9.1', 'mysql:5.5'],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] },
- variables: { VAR: 'value' },
+ variables: { 'VAR' => 'value' },
ignore: false,
after_script: ['make clean'] },
spinach: { name: :spinach,
@@ -167,7 +167,7 @@ describe Gitlab::Ci::Config::Entry::Global do
cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: {},
ignore: false,
- after_script: ['make clean'] },
+ after_script: ['make clean'] }
)
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
index f15f02f403e..84bfef9e8ad 100644
--- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
@@ -13,6 +13,14 @@ describe Gitlab::Ci::Config::Entry::Variables do
it 'returns hash with key value strings' do
expect(entry.value).to eq config
end
+
+ context 'with numeric keys and values in the config' do
+ let(:config) { { 10 => 20 } }
+
+ it 'converts numeric key and numeric value into strings' do
+ expect(entry.value).to eq('10' => '20')
+ end
+ end
end
describe '#errors' do
diff --git a/spec/lib/gitlab/ci/stage/seed_spec.rb b/spec/lib/gitlab/ci/stage/seed_spec.rb
new file mode 100644
index 00000000000..d7e91a5a62c
--- /dev/null
+++ b/spec/lib/gitlab/ci/stage/seed_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Stage::Seed do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ let(:builds) do
+ [{ name: 'rspec' }, { name: 'spinach' }]
+ end
+
+ subject do
+ described_class.new(pipeline, 'test', builds)
+ end
+
+ describe '#stage' do
+ it 'returns hash attributes of a stage' do
+ expect(subject.stage).to be_a Hash
+ expect(subject.stage).to include(:name, :project)
+ end
+ end
+
+ describe '#builds' do
+ it 'returns hash attributes of all builds' do
+ expect(subject.builds.size).to eq 2
+ expect(subject.builds).to all(include(ref: 'master'))
+ expect(subject.builds).to all(include(tag: false))
+ expect(subject.builds).to all(include(project: pipeline.project))
+ expect(subject.builds)
+ .to all(include(trigger_request: pipeline.trigger_requests.first))
+ end
+ end
+
+ describe '#user=' do
+ let(:user) { build(:user) }
+
+ it 'assignes relevant pipeline attributes' do
+ subject.user = user
+
+ expect(subject.builds).to all(include(user: user))
+ end
+ end
+
+ describe '#create!' do
+ it 'creates all stages and builds' do
+ subject.create!
+
+ expect(pipeline.reload.stages.count).to eq 1
+ expect(pipeline.reload.builds.count).to eq 2
+ expect(pipeline.builds).to all(satisfy { |job| job.stage_id.present? })
+ expect(pipeline.builds).to all(satisfy { |job| job.pipeline.present? })
+ expect(pipeline.builds).to all(satisfy { |job| job.project.present? })
+ expect(pipeline.stages)
+ .to all(satisfy { |stage| stage.pipeline.present? })
+ expect(pipeline.stages)
+ .to all(satisfy { |stage| stage.project.present? })
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb
index 40b96b1807b..72bd7c4eb93 100644
--- a/spec/lib/gitlab/ci/status/build/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/common_spec.rb
@@ -31,7 +31,7 @@ describe Gitlab::Ci::Status::Build::Common do
describe '#details_path' do
it 'links to the build details page' do
- expect(subject.details_path).to include "builds/#{build.id}"
+ expect(subject.details_path).to include "jobs/#{build.id}"
end
end
end
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index 185bb9098da..3f30b2c38f2 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -224,7 +224,10 @@ describe Gitlab::Ci::Status::Build::Factory do
context 'when user has ability to play action' do
before do
- build.project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
it 'fabricates status that has action' do
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index f5d0f977768..0e15a5f3c6b 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::Ci::Status::Build::Play do
let(:user) { create(:user) }
+ let(:project) { build.project }
let(:build) { create(:ci_build, :manual) }
let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
@@ -15,8 +16,13 @@ describe Gitlab::Ci::Status::Build::Play do
describe '#has_action?' do
context 'when user is allowed to update build' do
- context 'when user can push to branch' do
- before { build.project.add_master(user) }
+ context 'when user is allowed to trigger protected action' do
+ before do
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
+ end
it { is_expected.to have_action }
end
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 40ac5a3ed37..bbb3f9912a3 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -240,9 +240,50 @@ describe Gitlab::Ci::Trace::Stream do
end
context 'multiple results in content & regex' do
- let(:data) { ' (98.39%) covered. (98.29%) covered' }
+ let(:data) do
+ <<~HEREDOC
+ (98.39%) covered
+ (98.29%) covered
+ HEREDOC
+ end
+
+ let(:regex) { '\(\d+.\d+\%\) covered' }
+
+ it 'returns the last matched coverage' do
+ is_expected.to eq("98.29")
+ end
+ end
+
+ context 'when BUFFER_SIZE is smaller than stream.size' do
+ let(:data) { 'Coverage 1033 / 1051 LOC (98.29%) covered\n' }
+ let(:regex) { '\(\d+.\d+\%\) covered' }
+
+ before do
+ stub_const('Gitlab::Ci::Trace::Stream::BUFFER_SIZE', 5)
+ end
+
+ it { is_expected.to eq("98.29") }
+ end
+
+ context 'when regex is multi-byte char' do
+ let(:data) { '95.0 ゴッドファット\n' }
+ let(:regex) { '\d+\.\d+ ゴッドファット' }
+
+ before do
+ stub_const('Gitlab::Ci::Trace::Stream::BUFFER_SIZE', 5)
+ end
+
+ it { is_expected.to eq('95.0') }
+ end
+
+ context 'when BUFFER_SIZE is equal to stream.size' do
+ let(:data) { 'Coverage 1033 / 1051 LOC (98.29%) covered\n' }
let(:regex) { '\(\d+.\d+\%\) covered' }
+ before do
+ stub_const('Gitlab::Ci::Trace::Stream::BUFFER_SIZE', data.length)
+ end
+
it { is_expected.to eq("98.29") }
end
diff --git a/spec/lib/gitlab/ci_access_spec.rb b/spec/lib/gitlab/ci_access_spec.rb
new file mode 100644
index 00000000000..eaf8f1d0f1c
--- /dev/null
+++ b/spec/lib/gitlab/ci_access_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe Gitlab::CiAccess, lib: true do
+ let(:access) { Gitlab::CiAccess.new }
+
+ describe '#can_do_action?' do
+ context 'when action is :build_download_code' do
+ it { expect(access.can_do_action?(:build_download_code)).to be_truthy }
+ end
+
+ context 'when action is not :build_download_code' do
+ it { expect(access.can_do_action?(:download_code)).to be_falsey }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index e18a219ef36..79632e2b6a3 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -47,7 +47,7 @@ describe Gitlab::ContributionsCalendar do
action: Event::CREATED,
target: @targets[project],
author: contributor,
- created_at: day,
+ created_at: day
)
end
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index c796c98ec9f..fda39d78610 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -14,20 +14,20 @@ describe Gitlab::CurrentSettings do
end
it 'attempts to use cached values first' do
- expect(ApplicationSetting).to receive(:current)
- expect(ApplicationSetting).not_to receive(:last)
+ expect(ApplicationSetting).to receive(:cached)
expect(current_application_settings).to be_a(ApplicationSetting)
end
it 'falls back to DB if Redis returns an empty value' do
+ expect(ApplicationSetting).to receive(:cached).and_return(nil)
expect(ApplicationSetting).to receive(:last).and_call_original
expect(current_application_settings).to be_a(ApplicationSetting)
end
it 'falls back to DB if Redis fails' do
- expect(ApplicationSetting).to receive(:current).and_raise(::Redis::BaseError)
+ expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError)
expect(ApplicationSetting).to receive(:last).and_call_original
expect(current_application_settings).to be_a(ApplicationSetting)
@@ -37,6 +37,7 @@ describe Gitlab::CurrentSettings do
context 'with DB unavailable' do
before do
allow_any_instance_of(described_class).to receive(:connect_to_db?).and_return(false)
+ allow_any_instance_of(described_class).to receive(:retrieve_settings_from_database_cache?).and_return(nil)
end
it 'returns an in-memory ApplicationSetting object' do
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index 9d2ba481919..a1b3fe8509e 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -11,8 +11,6 @@ describe 'cycle analytics events' do
end
before do
- allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([context])
-
setup(context)
end
@@ -128,7 +126,8 @@ describe 'cycle analytics events' do
create(:ci_pipeline,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha,
- project: context.project)
+ project: context.project,
+ head_pipeline_of: merge_request)
end
before do
@@ -224,7 +223,8 @@ describe 'cycle analytics events' do
create(:ci_pipeline,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha,
- project: context.project)
+ project: context.project,
+ head_pipeline_of: merge_request)
end
before do
@@ -332,7 +332,7 @@ describe 'cycle analytics events' do
def setup(context)
milestone = create(:milestone, project: project)
context.update(milestone: milestone)
- mr = create_merge_request_closing_issue(context)
+ mr = create_merge_request_closing_issue(context, commit_message: "References #{context.to_reference}")
ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.to_hash)
end
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index dbcfb9b7400..e59cba35b2f 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -35,6 +35,7 @@ describe Gitlab::DataBuilder::Push, lib: true do
it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
it { expect(data[:user_id]).to eq(user.id) }
it { expect(data[:user_name]).to eq(user.name) }
+ it { expect(data[:user_username]).to eq(user.username) }
it { expect(data[:user_email]).to eq(user.email) }
it { expect(data[:user_avatar]).to eq(user.avatar_url) }
it { expect(data[:project_id]).to eq(project.id) }
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index dfa3ae9142e..3fdafd867da 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -66,16 +66,23 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
context 'using PostgreSQL' do
before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ allow(model).to receive(:supports_drop_index_concurrently?).and_return(true)
allow(model).to receive(:disable_statement_timeout)
end
- it 'removes the index concurrently' do
+ it 'removes the index concurrently by column name' do
expect(model).to receive(:remove_index).
with(:users, { algorithm: :concurrently, column: :foo })
model.remove_concurrent_index(:users, :foo)
end
+
+ it 'removes the index concurrently by index name' do
+ expect(model).to receive(:remove_index).
+ with(:users, { algorithm: :concurrently, name: "index_x_by_y" })
+
+ model.remove_concurrent_index_by_name(:users, "index_x_by_y")
+ end
end
context 'using MySQL' do
@@ -247,6 +254,14 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
expect(Project.where(archived: true).count).to eq(1)
end
end
+
+ context 'when the value is Arel.sql (Arel::Nodes::SqlLiteral)' do
+ it 'updates the value as a SQL expression' do
+ model.update_column_in_batches(:projects, :star_count, Arel.sql('1+1'))
+
+ expect(Project.sum(:star_count)).to eq(2 * Project.count)
+ end
+ end
end
describe '#add_column_with_default' do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
index c56fded7516..ce2b5d620fd 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
@@ -18,8 +18,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
let(:subject) { described_class.new(['parent/the-Path'], migration) }
it 'includes the namespace' do
- parent = create(:namespace, path: 'parent')
- child = create(:namespace, path: 'the-path', parent: parent)
+ parent = create(:group, path: 'parent')
+ child = create(:group, path: 'the-path', parent: parent)
found_ids = subject.namespaces_for_paths(type: :child).
map(&:id)
@@ -30,13 +30,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
context 'for child namespaces' do
it 'only returns child namespaces with the correct path' do
- _root_namespace = create(:namespace, path: 'THE-path')
- _other_path = create(:namespace,
+ _root_namespace = create(:group, path: 'THE-path')
+ _other_path = create(:group,
path: 'other',
- parent: create(:namespace))
- namespace = create(:namespace,
+ parent: create(:group))
+ namespace = create(:group,
path: 'the-path',
- parent: create(:namespace))
+ parent: create(:group))
found_ids = subject.namespaces_for_paths(type: :child).
map(&:id)
@@ -45,13 +45,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
end
it 'has no namespaces that look the same' do
- _root_namespace = create(:namespace, path: 'THE-path')
- _similar_path = create(:namespace,
+ _root_namespace = create(:group, path: 'THE-path')
+ _similar_path = create(:group,
path: 'not-really-the-path',
- parent: create(:namespace))
- namespace = create(:namespace,
+ parent: create(:group))
+ namespace = create(:group,
path: 'the-path',
- parent: create(:namespace))
+ parent: create(:group))
found_ids = subject.namespaces_for_paths(type: :child).
map(&:id)
@@ -62,11 +62,11 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
context 'for top levelnamespaces' do
it 'only returns child namespaces with the correct path' do
- root_namespace = create(:namespace, path: 'the-path')
- _other_path = create(:namespace, path: 'other')
- _child_namespace = create(:namespace,
+ root_namespace = create(:group, path: 'the-path')
+ _other_path = create(:group, path: 'other')
+ _child_namespace = create(:group,
path: 'the-path',
- parent: create(:namespace))
+ parent: create(:group))
found_ids = subject.namespaces_for_paths(type: :top_level).
map(&:id)
@@ -75,11 +75,11 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
end
it 'has no namespaces that just look the same' do
- root_namespace = create(:namespace, path: 'the-path')
- _similar_path = create(:namespace, path: 'not-really-the-path')
- _child_namespace = create(:namespace,
+ root_namespace = create(:group, path: 'the-path')
+ _similar_path = create(:group, path: 'not-really-the-path')
+ _child_namespace = create(:group,
path: 'the-path',
- parent: create(:namespace))
+ parent: create(:group))
found_ids = subject.namespaces_for_paths(type: :top_level).
map(&:id)
@@ -124,10 +124,10 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
describe "#child_ids_for_parent" do
it "collects child ids for all levels" do
- parent = create(:namespace)
- first_child = create(:namespace, parent: parent)
- second_child = create(:namespace, parent: parent)
- third_child = create(:namespace, parent: second_child)
+ parent = create(:group)
+ first_child = create(:group, parent: parent)
+ second_child = create(:group, parent: parent)
+ third_child = create(:group, parent: second_child)
all_ids = [parent.id, first_child.id, second_child.id, third_child.id]
collected_ids = subject.child_ids_for_parent(parent, ids: [parent.id])
@@ -205,9 +205,9 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
end
describe '#rename_namespaces' do
- let!(:top_level_namespace) { create(:namespace, path: 'the-path') }
+ let!(:top_level_namespace) { create(:group, path: 'the-path') }
let!(:child_namespace) do
- create(:namespace, path: 'the-path', parent: create(:namespace))
+ create(:group, path: 'the-path', parent: create(:group))
end
it 'renames top level namespaces the namespace' do
diff --git a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
new file mode 100644
index 00000000000..df77f4037af
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
@@ -0,0 +1,74 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::CartfileLinker, lib: true do
+ describe '.support?' do
+ it 'supports Cartfile' do
+ expect(described_class.support?('Cartfile')).to be_truthy
+ end
+
+ it 'supports Cartfile.private' do
+ expect(described_class.support?('Cartfile.private')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('test.Cartfile')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "Cartfile" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ # Require version 2.3.1 or later
+ github "ReactiveCocoa/ReactiveCocoa" >= 2.3.1
+
+ # Require version 1.x
+ github "Mantle/Mantle" ~> 1.0 # (1.0 or later, but less than 2.0)
+
+ # Require exactly version 0.4.1
+ github "jspahrsummers/libextobjc" == 0.4.1
+
+ # Use the latest version
+ github "jspahrsummers/xcconfigs"
+
+ # Use the branch
+ github "jspahrsummers/xcconfigs" "branch"
+
+ # Use a project from GitHub Enterprise
+ github "https://enterprise.local/ghe/desktop/git-error-translations"
+
+ # Use a project from any arbitrary server, on the "development" branch
+ git "https://enterprise.local/desktop/git-error-translations2.git" "development"
+
+ # Use a local project
+ git "file:///directory/to/project" "branch"
+
+ # A binary only framework
+ binary "https://my.domain.com/release/MyFramework.json" ~> 2.3
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('ReactiveCocoa/ReactiveCocoa', 'https://github.com/ReactiveCocoa/ReactiveCocoa'))
+ expect(subject).to include(link('Mantle/Mantle', 'https://github.com/Mantle/Mantle'))
+ expect(subject).to include(link('jspahrsummers/libextobjc', 'https://github.com/jspahrsummers/libextobjc'))
+ expect(subject).to include(link('jspahrsummers/xcconfigs', 'https://github.com/jspahrsummers/xcconfigs'))
+ end
+
+ it 'links Git repos' do
+ expect(subject).to include(link('https://enterprise.local/ghe/desktop/git-error-translations', 'https://enterprise.local/ghe/desktop/git-error-translations'))
+ expect(subject).to include(link('https://enterprise.local/desktop/git-error-translations2.git', 'https://enterprise.local/desktop/git-error-translations2.git'))
+ end
+
+ it 'links binary-only frameworks' do
+ expect(subject).to include(link('https://my.domain.com/release/MyFramework.json', 'https://my.domain.com/release/MyFramework.json'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
new file mode 100644
index 00000000000..d7a926e800f
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
@@ -0,0 +1,82 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::ComposerJsonLinker, lib: true do
+ describe '.support?' do
+ it 'supports composer.json' do
+ expect(described_class.support?('composer.json')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('composer.json.example')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "composer.json" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ {
+ "name": "laravel/laravel",
+ "homepage": "https://laravel.com/",
+ "description": "The Laravel Framework.",
+ "keywords": ["framework", "laravel"],
+ "license": "MIT",
+ "type": "project",
+ "repositories": [
+ {
+ "type": "git",
+ "url": "https://github.com/laravel/laravel.git"
+ }
+ ],
+ "require": {
+ "php": ">=5.5.9",
+ "laravel/framework": "5.2.*"
+ },
+ "require-dev": {
+ "fzaninotto/faker": "~1.4",
+ "mockery/mockery": "0.9.*",
+ "phpunit/phpunit": "~4.0",
+ "symfony/css-selector": "2.8.*|3.0.*",
+ "symfony/dom-crawler": "2.8.*|3.0.*"
+ }
+ }
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links the module name' do
+ expect(subject).to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel'))
+ end
+
+ it 'links the homepage' do
+ expect(subject).to include(link('https://laravel.com/', 'https://laravel.com/'))
+ end
+
+ it 'links the repository URL' do
+ expect(subject).to include(link('https://github.com/laravel/laravel.git', 'https://github.com/laravel/laravel.git'))
+ end
+
+ it 'links the license' do
+ expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('laravel/framework', 'https://packagist.org/packages/laravel/framework'))
+ expect(subject).to include(link('fzaninotto/faker', 'https://packagist.org/packages/fzaninotto/faker'))
+ expect(subject).to include(link('mockery/mockery', 'https://packagist.org/packages/mockery/mockery'))
+ expect(subject).to include(link('phpunit/phpunit', 'https://packagist.org/packages/phpunit/phpunit'))
+ expect(subject).to include(link('symfony/css-selector', 'https://packagist.org/packages/symfony/css-selector'))
+ expect(subject).to include(link('symfony/dom-crawler', 'https://packagist.org/packages/symfony/dom-crawler'))
+ end
+
+ it 'does not link core dependencies' do
+ expect(subject).not_to include(link('php', 'https://packagist.org/packages/php'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
new file mode 100644
index 00000000000..3f8335f03ea
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
@@ -0,0 +1,60 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::GemfileLinker, lib: true do
+ describe '.support?' do
+ it 'supports Gemfile' do
+ expect(described_class.support?('Gemfile')).to be_truthy
+ end
+
+ it 'supports gems.rb' do
+ expect(described_class.support?('gems.rb')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('Gemfile.lock')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { 'Gemfile' }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ source 'https://rubygems.org'
+
+ gem "rails", '4.2.6', github: "rails/rails"
+ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
+ gem 'responders', '~> 2.0', :github => 'rails/responders'
+ gem 'sprockets', '~> 3.6.0', git: 'https://gitlab.example.com/gems/sprockets'
+ gem 'default_value_for', '~> 3.0.0'
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links sources' do
+ expect(subject).to include(link('https://rubygems.org', 'https://rubygems.org'))
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('rails', 'https://rubygems.org/gems/rails'))
+ expect(subject).to include(link('rails-deprecated_sanitizer', 'https://rubygems.org/gems/rails-deprecated_sanitizer'))
+ expect(subject).to include(link('responders', 'https://rubygems.org/gems/responders'))
+ expect(subject).to include(link('sprockets', 'https://rubygems.org/gems/sprockets'))
+ expect(subject).to include(link('default_value_for', 'https://rubygems.org/gems/default_value_for'))
+ end
+
+ it 'links GitHub repos' do
+ expect(subject).to include(link('rails/rails', 'https://github.com/rails/rails'))
+ expect(subject).to include(link('rails/responders', 'https://github.com/rails/responders'))
+ end
+
+ it 'links Git repos' do
+ expect(subject).to include(link('https://gitlab.example.com/gems/sprockets', 'https://gitlab.example.com/gems/sprockets'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
new file mode 100644
index 00000000000..d4a71403939
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
@@ -0,0 +1,66 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::GemspecLinker, lib: true do
+ describe '.support?' do
+ it 'supports *.gemspec' do
+ expect(described_class.support?('gitlab_git.gemspec')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('.gemspec.example')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "gitlab_git.gemspec" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ Gem::Specification.new do |s|
+ s.name = 'gitlab_git'
+ s.version = `cat VERSION`
+ s.date = Time.now.strftime('%Y-%m-%d')
+ s.summary = "Gitlab::Git library"
+ s.description = "GitLab wrapper around git objects"
+ s.authors = ["Dmitriy Zaporozhets"]
+ s.email = 'dmitriy.zaporozhets@gmail.com'
+ s.license = 'MIT'
+ s.files = `git ls-files lib/`.split('\n') << 'VERSION'
+ s.homepage = 'https://gitlab.com/gitlab-org/gitlab_git'
+
+ s.add_dependency('github-linguist', '~> 4.7.0')
+ s.add_dependency('activesupport', '~> 4.0')
+ s.add_dependency('rugged', '~> 0.24.0')
+ s.add_runtime_dependency('charlock_holmes', '~> 0.7.3')
+ s.add_development_dependency('listen', '~> 3.0.6')
+ end
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links the gem name' do
+ expect(subject).to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git'))
+ end
+
+ it 'links the license' do
+ expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
+ end
+
+ it 'links the homepage' do
+ expect(subject).to include(link('https://gitlab.com/gitlab-org/gitlab_git', 'https://gitlab.com/gitlab-org/gitlab_git'))
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('github-linguist', 'https://rubygems.org/gems/github-linguist'))
+ expect(subject).to include(link('activesupport', 'https://rubygems.org/gems/activesupport'))
+ expect(subject).to include(link('rugged', 'https://rubygems.org/gems/rugged'))
+ expect(subject).to include(link('charlock_holmes', 'https://rubygems.org/gems/charlock_holmes'))
+ expect(subject).to include(link('listen', 'https://rubygems.org/gems/listen'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
new file mode 100644
index 00000000000..e279e0c9019
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
@@ -0,0 +1,84 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::GodepsJsonLinker, lib: true do
+ describe '.support?' do
+ it 'supports Godeps.json' do
+ expect(described_class.support?('Godeps.json')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('Godeps.json.example')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "Godeps.json" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ {
+ "ImportPath": "gitlab.com/gitlab-org/gitlab-pages",
+ "GoVersion": "go1.5",
+ "Packages": [
+ "./..."
+ ],
+ "Deps": [
+ {
+ "ImportPath": "github.com/kardianos/osext",
+ "Rev": "efacde03154693404c65e7aa7d461ac9014acd0c"
+ },
+ {
+ "ImportPath": "github.com/stretchr/testify/assert",
+ "Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
+ },
+ {
+ "ImportPath": "github.com/stretchr/testify/require",
+ "Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
+ },
+ {
+ "ImportPath": "gitlab.com/group/project/path",
+ "Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
+ },
+ {
+ "ImportPath": "gitlab.com/group/subgroup/project.git/path",
+ "Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
+ },
+ {
+ "ImportPath": "golang.org/x/crypto/ssh/terminal",
+ "Rev": "1351f936d976c60a0a48d728281922cf63eafb8d"
+ },
+ {
+ "ImportPath": "golang.org/x/net/http2",
+ "Rev": "b4e17d61b15679caf2335da776c614169a1b4643"
+ }
+ ]
+ }
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links the package name' do
+ expect(subject).to include(link('gitlab.com/gitlab-org/gitlab-pages', 'https://gitlab.com/gitlab-org/gitlab-pages'))
+ end
+
+ it 'links GitHub repos' do
+ expect(subject).to include(link('github.com/kardianos/osext', 'https://github.com/kardianos/osext'))
+ expect(subject).to include(link('github.com/stretchr/testify/assert', 'https://github.com/stretchr/testify/tree/master/assert'))
+ expect(subject).to include(link('github.com/stretchr/testify/require', 'https://github.com/stretchr/testify/tree/master/require'))
+ end
+
+ it 'links GitLab projects' do
+ expect(subject).to include(link('gitlab.com/group/project/path', 'https://gitlab.com/group/project/tree/master/path'))
+ expect(subject).to include(link('gitlab.com/group/subgroup/project.git/path', 'https://gitlab.com/group/subgroup/project/tree/master/path'))
+ end
+
+ it 'links Golang packages' do
+ expect(subject).to include(link('golang.org/x/net/http2', 'https://godoc.org/golang.org/x/net/http2'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
new file mode 100644
index 00000000000..8c979ae1869
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -0,0 +1,94 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::PackageJsonLinker, lib: true do
+ describe '.support?' do
+ it 'supports package.json' do
+ expect(described_class.support?('package.json')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('package.json.example')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "package.json" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ {
+ "name": "module-name",
+ "version": "10.3.1",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/vuejs/vue.git"
+ },
+ "homepage": "https://github.com/vuejs/vue#readme",
+ "scripts": {
+ "karma": "karma start config/karma.config.js --single-run"
+ },
+ "dependencies": {
+ "primus": "*",
+ "async": "~0.8.0",
+ "express": "4.2.x",
+ "bigpipe": "bigpipe/pagelet",
+ "plates": "https://github.com/flatiron/plates/tarball/master",
+ "karma": "^1.4.1"
+ },
+ "devDependencies": {
+ "vows": "^0.7.0",
+ "assume": "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0",
+ "pre-commit": "*"
+ },
+ "license": "MIT"
+ }
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links the module name' do
+ expect(subject).to include(link('module-name', 'https://npmjs.com/package/module-name'))
+ end
+
+ it 'links the homepage' do
+ expect(subject).to include(link('https://github.com/vuejs/vue#readme', 'https://github.com/vuejs/vue#readme'))
+ end
+
+ it 'links the repository URL' do
+ expect(subject).to include(link('https://github.com/vuejs/vue.git', 'https://github.com/vuejs/vue.git'))
+ end
+
+ it 'links the license' do
+ expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('primus', 'https://npmjs.com/package/primus'))
+ expect(subject).to include(link('async', 'https://npmjs.com/package/async'))
+ expect(subject).to include(link('express', 'https://npmjs.com/package/express'))
+ expect(subject).to include(link('bigpipe', 'https://npmjs.com/package/bigpipe'))
+ expect(subject).to include(link('plates', 'https://npmjs.com/package/plates'))
+ expect(subject).to include(link('karma', 'https://npmjs.com/package/karma'))
+ expect(subject).to include(link('vows', 'https://npmjs.com/package/vows'))
+ expect(subject).to include(link('assume', 'https://npmjs.com/package/assume'))
+ expect(subject).to include(link('pre-commit', 'https://npmjs.com/package/pre-commit'))
+ end
+
+ it 'links GitHub repos' do
+ expect(subject).to include(link('bigpipe/pagelet', 'https://github.com/bigpipe/pagelet'))
+ end
+
+ it 'links Git repos' do
+ expect(subject).to include(link('https://github.com/flatiron/plates/tarball/master', 'https://github.com/flatiron/plates/tarball/master'))
+ end
+
+ it 'does not link scripts with the same key as a package' do
+ expect(subject).not_to include(link('karma start config/karma.config.js --single-run', 'https://github.com/karma start config/karma.config.js --single-run'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
new file mode 100644
index 00000000000..06007cf97f7
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
@@ -0,0 +1,53 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::PodfileLinker, lib: true do
+ describe '.support?' do
+ it 'supports Podfile' do
+ expect(described_class.support?('Podfile')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('Podfile.lock')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "Podfile" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ source 'https://github.com/artsy/Specs.git'
+ source 'https://github.com/CocoaPods/Specs.git'
+
+ platform :ios, '8.0'
+ use_frameworks!
+ inhibit_all_warnings!
+
+ target 'Artsy' do
+ pod 'AFNetworking', "~> 2.5"
+ pod 'Interstellar/Core', git: 'https://github.com/ashfurrow/Interstellar.git', branch: 'observable-unsubscribe'
+ end
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links sources' do
+ expect(subject).to include(link('https://github.com/artsy/Specs.git', 'https://github.com/artsy/Specs.git'))
+ expect(subject).to include(link('https://github.com/CocoaPods/Specs.git', 'https://github.com/CocoaPods/Specs.git'))
+ end
+
+ it 'links packages' do
+ expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
+ expect(subject).to include(link('Interstellar/Core', 'https://cocoapods.org/pods/Interstellar'))
+ end
+
+ it 'links Git repos' do
+ expect(subject).to include(link('https://github.com/ashfurrow/Interstellar.git', 'https://github.com/ashfurrow/Interstellar.git'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
new file mode 100644
index 00000000000..d722865264b
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
@@ -0,0 +1,96 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::PodspecJsonLinker, lib: true do
+ describe '.support?' do
+ it 'supports *.podspec.json' do
+ expect(described_class.support?('Reachability.podspec.json')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('.podspec.json.example')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "AFNetworking.podspec.json" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ {
+ "name": "AFNetworking",
+ "version": "2.0.0",
+ "license": "MIT",
+ "summary": "A delightful iOS and OS X networking framework.",
+ "homepage": "https://github.com/AFNetworking/AFNetworking",
+ "authors": {
+ "Mattt Thompson": "m@mattt.me"
+ },
+ "source": {
+ "git": "https://github.com/AFNetworking/AFNetworking.git",
+ "tag": "2.0.0",
+ "submodules": true
+ },
+ "requires_arc": true,
+ "platforms": {
+ "ios": "6.0",
+ "osx": "10.8"
+ },
+ "public_header_files": "AFNetworking/*.h",
+ "subspecs": [
+ {
+ "name": "NSURLConnection",
+ "dependencies": {
+ "AFNetworking/Serialization": [
+
+ ],
+ "AFNetworking/Reachability": [
+
+ ],
+ "AFNetworking/Security": [
+
+ ]
+ },
+ "source_files": [
+ "AFNetworking/AFURLConnectionOperation.{h,m}",
+ "AFNetworking/AFHTTPRequestOperation.{h,m}",
+ "AFNetworking/AFHTTPRequestOperationManager.{h,m}"
+ ]
+ }
+ ]
+ }
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links the gem name' do
+ expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
+ end
+
+ it 'links the license' do
+ expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
+ end
+
+ it 'links the homepage' do
+ expect(subject).to include(link('https://github.com/AFNetworking/AFNetworking', 'https://github.com/AFNetworking/AFNetworking'))
+ end
+
+ it 'links the source URL' do
+ expect(subject).to include(link('https://github.com/AFNetworking/AFNetworking.git', 'https://github.com/AFNetworking/AFNetworking.git'))
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('AFNetworking/Serialization', 'https://cocoapods.org/pods/AFNetworking'))
+ expect(subject).to include(link('AFNetworking/Reachability', 'https://cocoapods.org/pods/AFNetworking'))
+ expect(subject).to include(link('AFNetworking/Security', 'https://cocoapods.org/pods/AFNetworking'))
+ end
+
+ it 'does not link subspec names' do
+ expect(subject).not_to include(link('NSURLConnection', 'https://cocoapods.org/pods/NSURLConnection'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
new file mode 100644
index 00000000000..dfc366b5817
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
@@ -0,0 +1,69 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::PodspecLinker, lib: true do
+ describe '.support?' do
+ it 'supports *.podspec' do
+ expect(described_class.support?('Reachability.podspec')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('.podspec.example')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "Reachability.podspec" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ Pod::Spec.new do |spec|
+ spec.name = 'Reachability'
+ spec.version = '3.1.0'
+ spec.license = { :type => 'GPL-3.0' }
+ spec.license = "MIT"
+ spec.license = { type: 'Apache-2.0' }
+ spec.homepage = 'https://github.com/tonymillion/Reachability'
+ spec.authors = { 'Tony Million' => 'tonymillion@gmail.com' }
+ spec.summary = 'ARC and GCD Compatible Reachability Class for iOS and OS X.'
+ spec.source = { :git => 'https://github.com/tonymillion/Reachability.git', :tag => 'v3.1.0' }
+ spec.source_files = 'Reachability.{h,m}'
+ spec.framework = 'SystemConfiguration'
+
+ spec.dependency 'AFNetworking', '~> 1.0'
+ spec.dependency 'RestKit/CoreData', '~> 0.20.0'
+ spec.ios.dependency 'MBProgressHUD', '~> 0.5'
+ end
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links the gem name' do
+ expect(subject).to include(link('Reachability', 'https://cocoapods.org/pods/Reachability'))
+ end
+
+ it 'links the license' do
+ expect(subject).to include(link('GPL-3.0', 'http://choosealicense.com/licenses/gpl-3.0/'))
+ expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
+ expect(subject).to include(link('Apache-2.0', 'http://choosealicense.com/licenses/apache-2.0/'))
+ end
+
+ it 'links the homepage' do
+ expect(subject).to include(link('https://github.com/tonymillion/Reachability', 'https://github.com/tonymillion/Reachability'))
+ end
+
+ it 'links the source URL' do
+ expect(subject).to include(link('https://github.com/tonymillion/Reachability.git', 'https://github.com/tonymillion/Reachability.git'))
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
+ expect(subject).to include(link('RestKit/CoreData', 'https://cocoapods.org/pods/RestKit'))
+ expect(subject).to include(link('MBProgressHUD', 'https://cocoapods.org/pods/MBProgressHUD'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
new file mode 100644
index 00000000000..4da8821726c
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
@@ -0,0 +1,87 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::RequirementsTxtLinker, lib: true do
+ describe '.support?' do
+ it 'supports requirements.txt' do
+ expect(described_class.support?('requirements.txt')).to be_truthy
+ end
+
+ it 'supports doc-requirements.txt' do
+ expect(described_class.support?('doc-requirements.txt')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('requirements')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "requirements.txt" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ #
+ ####### example-requirements.txt #######
+ #
+ ###### Requirements without Version Specifiers ######
+ nose
+ nose-cov
+ beautifulsoup4
+ #
+ ###### Requirements with Version Specifiers ######
+ # See https://www.python.org/dev/peps/pep-0440/#version-specifiers
+ docopt == 0.6.1 # Version Matching. Must be version 0.6.1
+ keyring >= 4.1.1 # Minimum version 4.1.1
+ coverage != 3.5 # Version Exclusion. Anything except version 3.5
+ Mopidy-Dirble ~= 1.1 # Compatible release. Same as >= 1.1, == 1.*
+ #
+ ###### Refer to other requirements files ######
+ -r other-requirements.txt
+ #
+ #
+ ###### A particular file ######
+ ./downloads/numpy-1.9.2-cp34-none-win32.whl
+ http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl
+ #
+ ###### Additional Requirements without Version Specifiers ######
+ # Same as 1st section, just here to show that you can put things in any order.
+ rejected
+ green
+ #
+
+ Jinja2>=2.3
+ Pygments>=1.2
+ Sphinx>=1.3
+ docutils>=0.7
+ markupsafe
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('nose', 'https://pypi.python.org/pypi/nose'))
+ expect(subject).to include(link('nose-cov', 'https://pypi.python.org/pypi/nose-cov'))
+ expect(subject).to include(link('beautifulsoup4', 'https://pypi.python.org/pypi/beautifulsoup4'))
+ expect(subject).to include(link('docopt', 'https://pypi.python.org/pypi/docopt'))
+ expect(subject).to include(link('keyring', 'https://pypi.python.org/pypi/keyring'))
+ expect(subject).to include(link('coverage', 'https://pypi.python.org/pypi/coverage'))
+ expect(subject).to include(link('Mopidy-Dirble', 'https://pypi.python.org/pypi/Mopidy-Dirble'))
+ expect(subject).to include(link('rejected', 'https://pypi.python.org/pypi/rejected'))
+ expect(subject).to include(link('green', 'https://pypi.python.org/pypi/green'))
+ expect(subject).to include(link('Jinja2', 'https://pypi.python.org/pypi/Jinja2'))
+ expect(subject).to include(link('Pygments', 'https://pypi.python.org/pypi/Pygments'))
+ expect(subject).to include(link('Sphinx', 'https://pypi.python.org/pypi/Sphinx'))
+ expect(subject).to include(link('docutils', 'https://pypi.python.org/pypi/docutils'))
+ expect(subject).to include(link('markupsafe', 'https://pypi.python.org/pypi/markupsafe'))
+ end
+
+ it 'links URLs' do
+ expect(subject).to include(link('http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl', 'http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
new file mode 100644
index 00000000000..3d1cfbcfbf7
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -0,0 +1,85 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker, lib: true do
+ describe '.link' do
+ it 'links using GemfileLinker' do
+ blob_name = 'Gemfile'
+
+ expect(described_class::GemfileLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using GemspecLinker' do
+ blob_name = 'gitlab_git.gemspec'
+
+ expect(described_class::GemspecLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using PackageJsonLinker' do
+ blob_name = 'package.json'
+
+ expect(described_class::PackageJsonLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using ComposerJsonLinker' do
+ blob_name = 'composer.json'
+
+ expect(described_class::ComposerJsonLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using PodfileLinker' do
+ blob_name = 'Podfile'
+
+ expect(described_class::PodfileLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using PodspecLinker' do
+ blob_name = 'Reachability.podspec'
+
+ expect(described_class::PodspecLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using PodspecJsonLinker' do
+ blob_name = 'AFNetworking.podspec.json'
+
+ expect(described_class::PodspecJsonLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using CartfileLinker' do
+ blob_name = 'Cartfile'
+
+ expect(described_class::CartfileLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using GodepsJsonLinker' do
+ blob_name = 'Godeps.json'
+
+ expect(described_class::GodepsJsonLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using RequirementsTxtLinker' do
+ blob_name = 'requirements.txt'
+
+ expect(described_class::RequirementsTxtLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/diff_refs_spec.rb b/spec/lib/gitlab/diff/diff_refs_spec.rb
new file mode 100644
index 00000000000..a8173558c00
--- /dev/null
+++ b/spec/lib/gitlab/diff/diff_refs_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::DiffRefs, lib: true do
+ let(:project) { create(:project, :repository) }
+
+ describe '#compare_in' do
+ context 'with diff refs for the initial commit' do
+ let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') }
+ subject { commit.diff_refs }
+
+ it 'returns an appropriate comparison' do
+ compare = subject.compare_in(project)
+
+ expect(compare.diff_refs).to eq(subject)
+ end
+ end
+
+ context 'with diff refs for a commit' do
+ let(:commit) { project.commit('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
+ subject { commit.diff_refs }
+
+ it 'returns an appropriate comparison' do
+ compare = subject.compare_in(project)
+
+ expect(compare.diff_refs).to eq(subject)
+ end
+ end
+
+ context 'with diff refs for a comparison through the base' do
+ subject do
+ described_class.new(
+ start_sha: '0b4bc9a49b562e85de7cc9e834518ea6828729b9', # feature
+ base_sha: 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f',
+ head_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a' # master
+ )
+ end
+
+ it 'returns an appropriate comparison' do
+ compare = subject.compare_in(project)
+
+ expect(compare.diff_refs).to eq(subject)
+ end
+ end
+
+ context 'with diff refs for a straight comparison' do
+ subject do
+ described_class.new(
+ start_sha: '0b4bc9a49b562e85de7cc9e834518ea6828729b9', # feature
+ base_sha: '0b4bc9a49b562e85de7cc9e834518ea6828729b9',
+ head_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a' # master
+ )
+ end
+
+ it 'returns an appropriate comparison' do
+ compare = subject.compare_in(project)
+
+ expect(compare.diff_refs).to eq(subject)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index c6bd4e81f4f..7d7d4a55e63 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -34,7 +34,7 @@ describe Gitlab::Diff::Highlight, lib: true do
end
it 'highlights and marks added lines' do
- code = %Q{+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n}
+ code = %Q{+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class="idiff left">RuntimeError</span></span><span class="p"><span class="idiff">,</span></span><span class="idiff right"> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n}
expect(subject[5].text).to eq(code)
end
@@ -67,7 +67,7 @@ describe Gitlab::Diff::Highlight, lib: true do
end
it 'marks added lines' do
- code = %q{+ raise <span class='idiff left right'>RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;}
+ code = %q{+ raise <span class="idiff left right">RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;}
expect(subject[5].text).to eq(code)
expect(subject[5].text).to be_html_safe
diff --git a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
new file mode 100644
index 00000000000..d6e8b8ac4b2
--- /dev/null
+++ b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::InlineDiffMarkdownMarker, lib: true do
+ describe '#mark' do
+ let(:raw) { "abc 'def'" }
+ let(:inline_diffs) { [2..5] }
+ let(:subject) { described_class.new(raw).mark(inline_diffs, mode: :deletion) }
+
+ it 'marks the range' do
+ expect(subject).to eq("ab{-c &#39;d-}ef&#39;")
+ expect(subject).to be_html_safe
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
index 198ff977f24..95da344802d 100644
--- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -1,26 +1,26 @@
require 'spec_helper'
describe Gitlab::Diff::InlineDiffMarker, lib: true do
- describe '#inline_diffs' do
+ describe '#mark' do
context "when the rich text is html safe" do
- let(:raw) { "abc 'def'" }
+ let(:raw) { "abc 'def'" }
let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>}.html_safe }
let(:inline_diffs) { [2..5] }
- let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) }
+ let(:subject) { described_class.new(raw, rich).mark(inline_diffs) }
- it 'marks the inline diffs' do
- expect(subject).to eq(%{<span class="abc">ab<span class='idiff left'>c</span></span><span class="space"><span class='idiff'> </span></span><span class="def"><span class='idiff right'>&#39;d</span>ef&#39;</span>})
+ it 'marks the range' do
+ expect(subject).to eq(%{<span class="abc">ab<span class="idiff left">c</span></span><span class="space"><span class="idiff"> </span></span><span class="def"><span class="idiff right">&#39;d</span>ef&#39;</span>})
expect(subject).to be_html_safe
end
end
context "when the text text is not html safe" do
- let(:raw) { "abc 'def'" }
+ let(:raw) { "abc 'def'" }
let(:inline_diffs) { [2..5] }
- let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw).mark(inline_diffs) }
+ let(:subject) { described_class.new(raw).mark(inline_diffs) }
- it 'marks the inline diffs' do
- expect(subject).to eq(%{ab<span class='idiff left right'>c &#39;d</span>ef&#39;})
+ it 'marks the range' do
+ expect(subject).to eq(%{ab<span class="idiff left right">c &#39;d</span>ef&#39;})
expect(subject).to be_html_safe
end
end
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index cdf0af6d7ef..b3d46e69ccb 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -22,7 +22,7 @@ describe Gitlab::Diff::Position, lib: true do
it "returns the correct diff file" do
diff_file = subject.diff_file(project.repository)
- expect(diff_file.new_file).to be true
+ expect(diff_file.new_file?).to be true
expect(diff_file.new_path).to eq(subject.new_path)
expect(diff_file.diff_refs).to eq(subject.diff_refs)
end
@@ -314,7 +314,7 @@ describe Gitlab::Diff::Position, lib: true do
it "returns the correct diff file" do
diff_file = subject.diff_file(project.repository)
- expect(diff_file.deleted_file).to be true
+ expect(diff_file.deleted_file?).to be true
expect(diff_file.old_path).to eq(subject.old_path)
expect(diff_file.diff_refs).to eq(subject.diff_refs)
end
@@ -356,7 +356,7 @@ describe Gitlab::Diff::Position, lib: true do
it "returns the correct diff file" do
diff_file = subject.diff_file(project.repository)
- expect(diff_file.new_file).to be true
+ expect(diff_file.new_file?).to be true
expect(diff_file.new_path).to eq(subject.new_path)
expect(diff_file.diff_refs).to eq(subject.diff_refs)
end
@@ -381,6 +381,54 @@ describe Gitlab::Diff::Position, lib: true do
end
end
+ describe "position for a file in a straight comparison" do
+ let(:diff_refs) do
+ Gitlab::Diff::DiffRefs.new(
+ start_sha: '0b4bc9a49b562e85de7cc9e834518ea6828729b9', # feature
+ base_sha: '0b4bc9a49b562e85de7cc9e834518ea6828729b9',
+ head_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a' # master
+ )
+ end
+
+ subject do
+ described_class.new(
+ old_path: "files/ruby/feature.rb",
+ new_path: "files/ruby/feature.rb",
+ old_line: 3,
+ new_line: nil,
+ diff_refs: diff_refs
+ )
+ end
+
+ describe "#diff_file" do
+ it "returns the correct diff file" do
+ diff_file = subject.diff_file(project.repository)
+
+ expect(diff_file.deleted_file?).to be true
+ expect(diff_file.old_path).to eq(subject.old_path)
+ expect(diff_file.diff_refs).to eq(subject.diff_refs)
+ end
+ end
+
+ describe "#diff_line" do
+ it "returns the correct diff line" do
+ diff_line = subject.diff_line(project.repository)
+
+ expect(diff_line.removed?).to be true
+ expect(diff_line.old_line).to eq(subject.old_line)
+ expect(diff_line.text).to eq("- puts 'bar'")
+ end
+ end
+
+ describe "#line_code" do
+ it "returns the correct line code" do
+ line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.old_line)
+
+ expect(subject.line_code(project.repository)).to eq(line_code)
+ end
+ end
+ end
+
describe "#to_json" do
let(:hash) do
{
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index a10a251dc4a..93d30b90937 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -61,9 +61,10 @@ describe Gitlab::Diff::PositionTracer, lib: true do
let(:old_diff_refs) { raise NotImplementedError }
let(:new_diff_refs) { raise NotImplementedError }
+ let(:change_diff_refs) { raise NotImplementedError }
let(:old_position) { raise NotImplementedError }
- let(:position_tracer) { described_class.new(repository: project.repository, old_diff_refs: old_diff_refs, new_diff_refs: new_diff_refs) }
+ let(:position_tracer) { described_class.new(project: project, old_diff_refs: old_diff_refs, new_diff_refs: new_diff_refs) }
subject { position_tracer.trace(old_position) }
def diff_refs(base_commit, head_commit)
@@ -77,16 +78,40 @@ describe Gitlab::Diff::PositionTracer, lib: true do
Gitlab::Diff::Position.new(attrs)
end
- def expect_new_position(attrs, new_position = subject)
- if attrs.nil?
- expect(new_position).to be_nil
- else
- expect(new_position).not_to be_nil
+ def expect_new_position(attrs, result = subject)
+ aggregate_failures("expect new position #{attrs.inspect}") do
+ if attrs.nil?
+ expect(result[:outdated]).to be_truthy
+ else
+ expect(result[:outdated]).to be_falsey
- expect(new_position.diff_refs).to eq(new_diff_refs)
+ new_position = result[:position]
+ expect(new_position).not_to be_nil
- attrs.each do |attr, value|
- expect(new_position.send(attr)).to eq(value)
+ expect(new_position.diff_refs).to eq(new_diff_refs)
+
+ attrs.each do |attr, value|
+ expect(new_position.send(attr)).to eq(value)
+ end
+ end
+ end
+ end
+
+ def expect_change_position(attrs, result = subject)
+ aggregate_failures("expect change position #{attrs.inspect}") do
+ expect(result[:outdated]).to be_truthy
+
+ change_position = result[:position]
+ if attrs.nil? || attrs.empty?
+ expect(change_position).to be_nil
+ else
+ expect(change_position).not_to be_nil
+
+ expect(change_position.diff_refs).to eq(change_diff_refs)
+
+ attrs.each do |attr, value|
+ expect(change_position.send(attr)).to eq(value)
+ end
end
end
end
@@ -395,6 +420,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was changed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -407,14 +433,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 + BB
# 3 + C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
context "when that line was deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
+ let(:change_diff_refs) { diff_refs(update_line_commit, delete_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 3) }
# old diff:
@@ -426,8 +458,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 + A
# 2 + BB
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
end
end
end
@@ -512,6 +549,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was changed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -525,14 +563,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 + BB
# 3 3 C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
context "when that line was deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 3) }
# old diff:
@@ -545,8 +589,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 2 A
# 3 - C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
end
end
end
@@ -558,6 +607,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when the file's content was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
let(:new_diff_refs) { diff_refs(delete_line_commit, rename_file_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -569,8 +619,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 1 BB
# 2 2 A
- it "returns nil since the line doesn't exist in the new diffs anymore" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: nil,
+ new_line: 2
+ )
end
end
@@ -628,6 +683,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was changed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) }
+ let(:change_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -640,28 +696,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 - A
# 2 + AA
- it "returns nil" do
- expect(subject).to be_nil
- end
- end
-
- context "when that line was deleted between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
- let(:new_diff_refs) { diff_refs(delete_line_commit, delete_line_again_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 1) }
-
- # old diff:
- # 1 + BB
- # 2 + A
- #
- # new diff:
- # file_name -> new_file_name
- # 1 - BB
- # 2 - A
- # 1 + AA
-
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: new_file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
end
@@ -673,6 +714,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when the file's content was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -683,8 +725,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 - BB
# 2 - A
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
@@ -692,6 +739,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -703,14 +751,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 - BB
# 2 - A
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
context "when that line was moved between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
let(:new_diff_refs) { diff_refs(move_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(update_line_commit, delete_file_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -723,14 +777,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 - A
# 3 - C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
context "when that line was changed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:new_diff_refs) { diff_refs(update_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, delete_file_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -743,14 +803,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 - BB
# 3 - C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
context "when that line was deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) }
let(:old_position) { position(new_path: file_name, new_line: 3) }
# old diff:
@@ -762,8 +828,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 - BB
# 2 - A
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
end
end
end
@@ -775,6 +846,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when the file's content was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, create_second_file_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -787,8 +859,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 2 B
# 3 3 C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: nil,
+ new_line: 2
+ )
end
end
@@ -796,6 +873,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, update_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 1) }
# old diff:
@@ -808,14 +886,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 2 BB
# 3 3 C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: nil,
+ new_line: 1
+ )
end
end
context "when that line was moved between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
let(:new_diff_refs) { diff_refs(move_line_commit, move_second_file_line_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, move_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -828,14 +912,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 2 A
# 3 3 C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: nil,
+ new_line: 1
+ )
end
end
context "when that line was changed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -848,14 +938,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 2 BB
# 3 3 C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
context "when that line was deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(delete_line_commit, delete_second_file_line_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_second_file_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 3) }
# old diff:
@@ -867,8 +963,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 1 BB
# 2 2 A
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
end
end
end
@@ -957,6 +1058,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was changed or deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, create_file_commit) }
let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) }
# old diff:
@@ -970,8 +1072,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 + B
# 3 + C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 1,
+ new_line: nil
+ )
end
end
end
@@ -980,6 +1087,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when the position pointed at a deleted line in the old diff" do
let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, initial_commit) }
let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) }
# old diff:
@@ -993,8 +1101,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 + BB
# 3 + C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
@@ -1076,6 +1189,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was changed or deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 3, new_line: 3) }
# old diff:
@@ -1088,8 +1202,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 + A
# 2 + B
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
end
end
end
@@ -1182,6 +1301,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was changed or deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, update_line_commit) }
let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) }
# old diff:
@@ -1196,8 +1316,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 + BB
# 3 3 C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 1,
+ new_line: nil
+ )
end
end
end
@@ -1239,7 +1364,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
describe "typical use scenarios" do
let(:second_branch_name) { "#{branch_name}-2" }
- def expect_positions(old_attrs, new_attrs)
+ def expect_new_positions(old_attrs, new_attrs)
old_positions = old_attrs.map do |old_attrs|
position(old_attrs)
end
@@ -1248,8 +1373,14 @@ describe Gitlab::Diff::PositionTracer, lib: true do
position_tracer.trace(old_position)
end
- new_positions.zip(new_attrs).each do |new_position, new_attrs|
- expect_new_position(new_attrs, new_position)
+ aggregate_failures do
+ new_positions.zip(new_attrs).each do |new_position, new_attrs|
+ if new_attrs&.delete(:change)
+ expect_change_position(new_attrs, new_position)
+ else
+ expect_new_position(new_attrs, new_position)
+ end
+ end
end
end
@@ -1330,6 +1461,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
describe "simple push of new commit" do
let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) }
# old diff:
# 1 1 A
@@ -1368,14 +1500,14 @@ describe Gitlab::Diff::PositionTracer, lib: true do
{ old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
{ old_path: file_name, old_line: 2 },
{ old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 },
- { old_path: file_name, old_line: 4, new_line: 4 },
- nil,
+ { new_path: file_name, new_line: 4, change: true },
+ { new_path: file_name, old_line: 3, change: true },
{ old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 },
- { old_path: file_name, old_line: 6 },
- { new_path: file_name, new_line: 7 },
+ { new_path: file_name, old_line: 5, change: true },
+ { new_path: file_name, new_line: 7 }
]
- expect_positions(old_position_attrs, new_position_attrs)
+ expect_new_positions(old_position_attrs, new_position_attrs)
end
end
@@ -1402,6 +1534,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, second_create_file_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_commit, second_create_file_commit) }
# old diff:
# 1 1 A
@@ -1440,20 +1573,21 @@ describe Gitlab::Diff::PositionTracer, lib: true do
{ old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
{ old_path: file_name, old_line: 2 },
{ old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 },
- { old_path: file_name, old_line: 4, new_line: 4 },
- nil,
+ { new_path: file_name, new_line: 4, change: true },
+ { old_path: file_name, old_line: 3, change: true },
{ old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 },
- { old_path: file_name, old_line: 6 },
- { new_path: file_name, new_line: 7 },
+ { old_path: file_name, old_line: 5, change: true },
+ { new_path: file_name, new_line: 7 }
]
- expect_positions(old_position_attrs, new_position_attrs)
+ expect_new_positions(old_position_attrs, new_position_attrs)
end
end
describe "force push to delete last commit" do
let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_again_commit, update_file_commit) }
# old diff:
# 1 1 A
@@ -1492,16 +1626,16 @@ describe Gitlab::Diff::PositionTracer, lib: true do
new_position_attrs = [
{ old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
{ old_path: file_name, old_line: 2 },
- nil,
+ { old_path: file_name, old_line: 2, change: true },
{ old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 },
- { old_path: file_name, old_line: 4 },
+ { old_path: file_name, old_line: 4, change: true },
{ old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 },
- { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 },
- nil,
- { new_path: file_name, new_line: 6 },
+ { new_path: file_name, new_line: 5, change: true },
+ { old_path: file_name, old_line: 6, change: true },
+ { new_path: file_name, new_line: 6 }
]
- expect_positions(old_position_attrs, new_position_attrs)
+ expect_new_positions(old_position_attrs, new_position_attrs)
end
end
@@ -1567,6 +1701,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, overwrite_update_file_again_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_again_commit, overwrite_update_file_again_commit) }
# old diff:
# 1 1 A
@@ -1618,7 +1753,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
{ new_path: file_name, new_line: 10 }, # + G
]
- expect_positions(old_position_attrs, new_position_attrs)
+ expect_new_positions(old_position_attrs, new_position_attrs)
end
end
@@ -1643,6 +1778,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, merge_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_again_commit, merge_commit) }
# old diff:
# 1 1 A
@@ -1694,13 +1830,14 @@ describe Gitlab::Diff::PositionTracer, lib: true do
{ new_path: file_name, new_line: 10 }, # + G
]
- expect_positions(old_position_attrs, new_position_attrs)
+ expect_new_positions(old_position_attrs, new_position_attrs)
end
end
describe "changing target branch" do
let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
let(:new_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
# old diff:
# 1 1 A
@@ -1739,17 +1876,17 @@ describe Gitlab::Diff::PositionTracer, lib: true do
new_position_attrs = [
{ old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
- nil,
+ { old_path: file_name, old_line: 2, change: true },
{ new_path: file_name, new_line: 2 },
{ old_path: file_name, new_path: file_name, old_line: 2, new_line: 3 },
{ new_path: file_name, new_line: 4 },
{ old_path: file_name, new_path: file_name, old_line: 4, new_line: 5 },
{ old_path: file_name, old_line: 5 },
{ new_path: file_name, new_line: 6 },
- { new_path: file_name, new_line: 7 },
+ { new_path: file_name, new_line: 7 }
]
- expect_positions(old_position_attrs, new_position_attrs)
+ expect_new_positions(old_position_attrs, new_position_attrs)
end
end
end
diff --git a/spec/lib/gitlab/git/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index f6ac7b23d1d..1482ef7132d 100644
--- a/spec/lib/gitlab/git/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -1,8 +1,8 @@
require "spec_helper"
-describe Gitlab::Git::EncodingHelper do
- let(:ext_class) { Class.new { extend Gitlab::Git::EncodingHelper } }
- let(:binary_string) { File.join(SEED_STORAGE_PATH, 'gitlab_logo.png') }
+describe Gitlab::EncodingHelper do
+ let(:ext_class) { Class.new { extend Gitlab::EncodingHelper } }
+ let(:binary_string) { File.read(Rails.root + "spec/fixtures/dk.png") }
describe '#encode!' do
[
@@ -19,8 +19,8 @@ describe Gitlab::Git::EncodingHelper do
[
'removes invalid bytes from ASCII-8bit encoded multibyte string. This can occur when a git diff match line truncates in the middle of a multibyte character. This occurs after the second word in this example. The test string is as short as we can get while still triggering the error condition when not looking at `detect[:confidence]`.',
"mu ns\xC3\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ".force_encoding('ASCII-8BIT'),
- "mu ns\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ",
- ],
+ "mu ns\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi "
+ ]
].each do |description, test_string, xpect|
it description do
expect(ext_class.encode!(test_string)).to eq(xpect)
@@ -37,18 +37,18 @@ describe Gitlab::Git::EncodingHelper do
[
"encodes valid utf8 encoded string to utf8",
"λ, λ, λ".encode("UTF-8"),
- "λ, λ, λ".encode("UTF-8"),
+ "λ, λ, λ".encode("UTF-8")
],
[
"encodes valid ASCII-8BIT encoded string to utf8",
"ascii only".encode("ASCII-8BIT"),
- "ascii only".encode("UTF-8"),
+ "ascii only".encode("UTF-8")
],
[
"encodes valid ISO-8859-1 encoded string to utf8",
"Rüby ist eine Programmiersprache. Wir verlängern den text damit ICU die Sprache erkennen kann.".encode("ISO-8859-1", "UTF-8"),
- "Rüby ist eine Programmiersprache. Wir verlängern den text damit ICU die Sprache erkennen kann.".encode("UTF-8"),
- ],
+ "Rüby ist eine Programmiersprache. Wir verlängern den text damit ICU die Sprache erkennen kann.".encode("UTF-8")
+ ]
].each do |description, test_string, xpect|
it description do
r = ext_class.encode_utf8(test_string.force_encoding('UTF-8'))
@@ -77,8 +77,8 @@ describe Gitlab::Git::EncodingHelper do
[
'removes invalid bytes from ASCII-8bit encoded multibyte string.',
"Lorem ipsum\xC3\n dolor sit amet, xy\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg".force_encoding('ASCII-8BIT'),
- "Lorem ipsum\n dolor sit amet, xyàyùabcdùefg",
- ],
+ "Lorem ipsum\n dolor sit amet, xyàyùabcdùefg"
+ ]
].each do |description, test_string, xpect|
it description do
expect(ext_class.encode!(test_string)).to eq(xpect)
diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb
index 24df04e985a..3c6ef7c7ccb 100644
--- a/spec/lib/gitlab/etag_caching/middleware_spec.rb
+++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb
@@ -164,6 +164,25 @@ describe Gitlab::EtagCaching::Middleware do
end
end
+ context 'when GitLab instance is using a relative URL' do
+ before do
+ mock_app_response
+ end
+
+ it 'uses full path as cache key' do
+ env = {
+ 'PATH_INFO' => enabled_path,
+ 'SCRIPT_NAME' => '/relative-gitlab'
+ }
+
+ expect_any_instance_of(Gitlab::EtagCaching::Store)
+ .to receive(:get).with("/relative-gitlab#{enabled_path}")
+ .and_return(nil)
+
+ middleware.call(env)
+ end
+ end
+
def mock_app_response
allow(app).to receive(:call).and_return([app_status_code, {}, ['body']])
end
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
index 410df116a3a..2bb40827fcf 100644
--- a/spec/lib/gitlab/etag_caching/router_spec.rb
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -2,93 +2,115 @@ require 'spec_helper'
describe Gitlab::EtagCaching::Router do
it 'matches issue notes endpoint' do
- env = build_env(
+ request = build_request(
'/my-group/and-subgroup/here-comes-the-project/noteable/issue/1/notes'
)
- result = described_class.match(env)
+ result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'issue_notes'
end
it 'matches issue title endpoint' do
- env = build_env(
- '/my-group/my-project/issues/123/rendered_title'
+ request = build_request(
+ '/my-group/my-project/issues/123/realtime_changes'
)
- result = described_class.match(env)
+ result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'issue_title'
end
it 'matches project pipelines endpoint' do
- env = build_env(
+ request = build_request(
'/my-group/my-project/pipelines.json'
)
- result = described_class.match(env)
+ result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'project_pipelines'
end
it 'matches commit pipelines endpoint' do
- env = build_env(
+ request = build_request(
'/my-group/my-project/commit/aa8260d253a53f73f6c26c734c72fdd600f6e6d4/pipelines.json'
)
- result = described_class.match(env)
+ result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'commit_pipelines'
end
it 'matches new merge request pipelines endpoint' do
- env = build_env(
+ request = build_request(
'/my-group/my-project/merge_requests/new.json'
)
- result = described_class.match(env)
+ result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'new_merge_request_pipelines'
end
it 'matches merge request pipelines endpoint' do
- env = build_env(
+ request = build_request(
'/my-group/my-project/merge_requests/234/pipelines.json'
)
- result = described_class.match(env)
+ result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'merge_request_pipelines'
end
+ it 'matches build endpoint' do
+ request = build_request(
+ '/my-group/my-project/builds/234.json'
+ )
+
+ result = described_class.match(request)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'project_build'
+ end
+
it 'does not match blob with confusing name' do
- env = build_env(
+ request = build_request(
'/my-group/my-project/blob/master/pipelines.json'
)
- result = described_class.match(env)
+ result = described_class.match(request)
expect(result).to be_blank
end
+ it 'matches the environments path' do
+ request = build_request(
+ '/my-group/my-project/environments.json'
+ )
+
+ result = described_class.match(request)
+ expect(result).to be_present
+
+ expect(result.name).to eq 'environments'
+ end
+
it 'matches pipeline#show endpoint' do
- env = build_env(
+ request = build_request(
'/my-group/my-project/pipelines/2.json'
)
- result = described_class.match(env)
+ result = described_class.match(request)
expect(result).to be_present
expect(result.name).to eq 'project_pipeline'
end
- def build_env(path)
- { 'PATH_INFO' => path }
+ def build_request(path)
+ double(path_info: path)
end
end
diff --git a/spec/lib/gitlab/file_finder_spec.rb b/spec/lib/gitlab/file_finder_spec.rb
new file mode 100644
index 00000000000..5a32ffd462c
--- /dev/null
+++ b/spec/lib/gitlab/file_finder_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::FileFinder, lib: true do
+ describe '#find' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:finder) { described_class.new(project, project.default_branch) }
+
+ it 'finds by name' do
+ results = finder.find('files')
+ expect(results.map(&:first)).to include('files/images/wm.svg')
+ end
+
+ it 'finds by content' do
+ results = finder.find('files')
+
+ blob = results.select { |result| result.first == "CHANGELOG" }.flatten.last
+
+ expect(blob.filename).to eq("CHANGELOG")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index cdf1b8beee3..9eac7660cd1 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -7,6 +7,51 @@ describe Gitlab::Git::Branch, seed_helper: true do
it { is_expected.to be_kind_of Array }
+ describe 'initialize' do
+ let(:commit_id) { 'f00' }
+ let(:commit_subject) { "My commit".force_encoding('ASCII-8BIT') }
+ let(:committer) do
+ Gitaly::FindLocalBranchCommitAuthor.new(
+ name: generate(:name),
+ email: generate(:email),
+ date: Google::Protobuf::Timestamp.new(seconds: 123)
+ )
+ end
+ let(:author) do
+ Gitaly::FindLocalBranchCommitAuthor.new(
+ name: generate(:name),
+ email: generate(:email),
+ date: Google::Protobuf::Timestamp.new(seconds: 456)
+ )
+ end
+ let(:gitaly_branch) do
+ Gitaly::FindLocalBranchResponse.new(
+ name: 'foo', commit_id: commit_id, commit_subject: commit_subject,
+ commit_author: author, commit_committer: committer
+ )
+ end
+ let(:attributes) do
+ {
+ id: commit_id,
+ message: commit_subject,
+ authored_date: Time.at(author.date.seconds),
+ author_name: author.name,
+ author_email: author.email,
+ committed_date: Time.at(committer.date.seconds),
+ committer_name: committer.name,
+ committer_email: committer.email
+ }
+ end
+ let(:branch) { described_class.new(repository, 'foo', gitaly_branch) }
+
+ it 'parses Gitaly::FindLocalBranchResponse correctly' do
+ expect(Gitlab::Git::Commit).to receive(:decorate).
+ with(hash_including(attributes)).and_call_original
+
+ expect(branch.dereferenced_target.message.encoding).to be(Encoding::UTF_8)
+ end
+ end
+
describe '#size' do
subject { super().size }
it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) }
diff --git a/spec/lib/gitlab/git/compare_spec.rb b/spec/lib/gitlab/git/compare_spec.rb
index 7c45071ec45..4c9f4a28f32 100644
--- a/spec/lib/gitlab/git/compare_spec.rb
+++ b/spec/lib/gitlab/git/compare_spec.rb
@@ -2,8 +2,8 @@ require "spec_helper"
describe Gitlab::Git::Compare, seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
- let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, false) }
- let(:compare_straight) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, true) }
+ let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, straight: false) }
+ let(:compare_straight) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, straight: true) }
describe '#commits' do
subject do
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index 122c93dcd69..a9a7bba2c05 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -6,18 +6,18 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
iterator,
max_files: max_files,
max_lines: max_lines,
- all_diffs: all_diffs,
- no_collapse: no_collapse
+ limits: limits,
+ expanded: expanded
)
end
- let(:iterator) { Array.new(file_count, fake_diff(line_length, line_count)) }
+ let(:iterator) { MutatingConstantIterator.new(file_count, fake_diff(line_length, line_count)) }
let(:file_count) { 0 }
let(:line_length) { 1 }
let(:line_count) { 1 }
let(:max_files) { 10 }
let(:max_lines) { 100 }
- let(:all_diffs) { false }
- let(:no_collapse) { true }
+ let(:limits) { true }
+ let(:expanded) { true }
describe '#to_a' do
subject { super().to_a }
@@ -64,10 +64,18 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
subject { super().real_size }
it { is_expected.to eq('3') }
end
- it { expect(subject.size).to eq(3) }
+
+ describe '#size' do
+ it { expect(subject.size).to eq(3) }
+
+ it 'does not change after peeking' do
+ subject.any?
+ expect(subject.size).to eq(3)
+ end
+ end
context 'when limiting is disabled' do
- let(:all_diffs) { true }
+ let(:limits) { false }
describe '#overflow?' do
subject { super().overflow? }
@@ -83,7 +91,15 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
subject { super().real_size }
it { is_expected.to eq('3') }
end
- it { expect(subject.size).to eq(3) }
+
+ describe '#size' do
+ it { expect(subject.size).to eq(3) }
+
+ it 'does not change after peeking' do
+ subject.any?
+ expect(subject.size).to eq(3)
+ end
+ end
end
end
@@ -107,7 +123,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
it { expect(subject.size).to eq(0) }
context 'when limiting is disabled' do
- let(:all_diffs) { true }
+ let(:limits) { false }
describe '#overflow?' do
subject { super().overflow? }
@@ -151,7 +167,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
it { expect(subject.size).to eq(10) }
context 'when limiting is disabled' do
- let(:all_diffs) { true }
+ let(:limits) { false }
describe '#overflow?' do
subject { super().overflow? }
@@ -191,7 +207,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
it { expect(subject.size).to eq(3) }
context 'when limiting is disabled' do
- let(:all_diffs) { true }
+ let(:limits) { false }
describe '#overflow?' do
subject { super().overflow? }
@@ -257,7 +273,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
it { expect(subject.size).to eq(9) }
context 'when limiting is disabled' do
- let(:all_diffs) { true }
+ let(:limits) { false }
describe '#overflow?' do
subject { super().overflow? }
@@ -325,10 +341,11 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
end
context 'when diff is quite large will collapse by default' do
- let(:iterator) { [{ diff: 'a' * 20480 }] }
+ let(:iterator) { [{ diff: 'a' * (Gitlab::Git::Diff.collapse_limit + 1) }] }
+ let(:max_files) { 100 }
context 'when no collapse is set' do
- let(:no_collapse) { true }
+ let(:expanded) { true }
it 'yields Diff instances even when they are quite big' do
expect { |b| subject.each(&b) }.
@@ -347,7 +364,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
end
context 'when no collapse is unset' do
- let(:no_collapse) { false }
+ let(:expanded) { false }
it 'yields Diff instances even when they are quite big' do
expect { |b| subject.each(&b) }.
@@ -434,7 +451,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
end
context 'when limiting is disabled' do
- let(:all_diffs) { true }
+ let(:limits) { false }
it 'yields Diff instances even when they are quite big' do
expect { |b| subject.each(&b) }.
@@ -457,4 +474,22 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
def fake_diff(line_length, line_count)
{ 'diff' => "#{'a' * line_length}\n" * line_count }
end
+
+ class MutatingConstantIterator
+ include Enumerable
+
+ def initialize(count, value)
+ @count = count
+ @value = value
+ end
+
+ def each
+ loop do
+ break if @count.zero?
+ # It is critical to decrement before yielding. We may never reach the lines after 'yield'.
+ @count -= 1
+ yield @value
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index 7253a2edeff..da213f617cc 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -31,6 +31,36 @@ EOT
[".gitmodules"]).patches.first
end
+ describe 'size limit feature toggles' do
+ context 'when the feature gitlab_git_diff_size_limit_increase is enabled' do
+ before do
+ Feature.enable('gitlab_git_diff_size_limit_increase')
+ end
+
+ it 'returns 200 KB for size_limit' do
+ expect(described_class.size_limit).to eq(200.kilobytes)
+ end
+
+ it 'returns 100 KB for collapse_limit' do
+ expect(described_class.collapse_limit).to eq(100.kilobytes)
+ end
+ end
+
+ context 'when the feature gitlab_git_diff_size_limit_increase is disabled' do
+ before do
+ Feature.disable('gitlab_git_diff_size_limit_increase')
+ end
+
+ it 'returns 100 KB for size_limit' do
+ expect(described_class.size_limit).to eq(100.kilobytes)
+ end
+
+ it 'returns 10 KB for collapse_limit' do
+ expect(described_class.collapse_limit).to eq(10.kilobytes)
+ end
+ end
+ end
+
describe '.new' do
context 'using a Hash' do
context 'with a small diff' do
@@ -47,7 +77,7 @@ EOT
context 'using a diff that is too large' do
it 'prunes the diff' do
- diff = described_class.new(diff: 'a' * 204800)
+ diff = described_class.new(diff: 'a' * (described_class.size_limit + 1))
expect(diff.diff).to be_empty
expect(diff).to be_too_large
@@ -85,12 +115,12 @@ EOT
# The patch total size is 200, with lines between 21 and 54.
# This is a quick-and-dirty way to test this. Ideally, a new patch is
# added to the test repo with a size that falls between the real limits.
- stub_const("#{described_class}::DIFF_SIZE_LIMIT", 150)
- stub_const("#{described_class}::DIFF_COLLAPSE_LIMIT", 100)
+ allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(150)
+ allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(100)
end
it 'prunes the diff as a large diff instead of as a collapsed diff' do
- diff = described_class.new(@rugged_diff, collapse: true)
+ diff = described_class.new(@rugged_diff, expanded: false)
expect(diff.diff).to be_empty
expect(diff).to be_too_large
@@ -110,23 +140,23 @@ EOT
end
end
- context 'using a Gitaly::CommitDiffResponse' do
+ context 'using a GitalyClient::Diff' do
let(:diff) do
described_class.new(
- Gitaly::CommitDiffResponse.new(
+ Gitlab::GitalyClient::Diff.new(
to_path: ".gitmodules",
from_path: ".gitmodules",
old_mode: 0100644,
new_mode: 0100644,
from_id: '357406f3075a57708d0163752905cc1576fceacc',
to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0',
- raw_chunks: raw_chunks,
+ patch: raw_patch
)
)
end
context 'with a small diff' do
- let(:raw_chunks) { [@raw_diff_hash[:diff]] }
+ let(:raw_patch) { @raw_diff_hash[:diff] }
it 'initializes the diff' do
expect(diff.to_hash).to eq(@raw_diff_hash)
@@ -138,7 +168,7 @@ EOT
end
context 'using a diff that is too large' do
- let(:raw_chunks) { ['a' * 204800] }
+ let(:raw_patch) { 'a' * 204800 }
it 'prunes the diff' do
expect(diff.diff).to be_empty
@@ -269,7 +299,7 @@ EOT
it 'returns true for a diff that was explicitly marked as being too large' do
diff = described_class.new(diff: 'a')
- diff.prune_large_diff!
+ diff.too_large!
expect(diff.too_large?).to eq(true)
end
@@ -291,31 +321,31 @@ EOT
it 'returns true for a diff that was explicitly marked as being collapsed' do
diff = described_class.new(diff: 'a')
- diff.prune_collapsed_diff!
+ diff.collapse!
expect(diff).to be_collapsed
end
end
- describe '#collapsible?' do
+ describe '#collapsed?' do
it 'returns true for a diff that is quite large' do
- diff = described_class.new(diff: 'a' * 20480)
+ diff = described_class.new({ diff: 'a' * (described_class.collapse_limit + 1) }, expanded: false)
- expect(diff).to be_collapsible
+ expect(diff).to be_collapsed
end
it 'returns false for a diff that is small enough' do
- diff = described_class.new(diff: 'a')
+ diff = described_class.new({ diff: 'a' }, expanded: false)
- expect(diff).not_to be_collapsible
+ expect(diff).not_to be_collapsed
end
end
- describe '#prune_collapsed_diff!' do
+ describe '#collapse!' do
it 'prunes the diff' do
diff = described_class.new(diff: "foo\nbar")
- diff.prune_collapsed_diff!
+ diff.collapse!
expect(diff.diff).to eq('')
expect(diff.line_count).to eq(0)
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 53d492b8f74..26215381cc4 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1,7 +1,7 @@
require "spec_helper"
describe Gitlab::Git::Repository, seed_helper: true do
- include Gitlab::Git::EncodingHelper
+ include Gitlab::EncodingHelper
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
@@ -381,6 +381,19 @@ describe Gitlab::Git::Repository, seed_helper: true do
}
])
end
+
+ it 'should not break on invalid syntax' do
+ allow(repository).to receive(:blob_content).and_return(<<-GITMODULES.strip_heredoc)
+ [submodule "six"]
+ path = six
+ url = git://github.com/randx/six.git
+
+ [submodule]
+ foo = bar
+ GITMODULES
+
+ expect(submodules).to have_key('six')
+ end
end
context 'where repo doesn\'t have submodules' do
@@ -1105,7 +1118,9 @@ describe Gitlab::Git::Repository, seed_helper: true do
ref = double()
allow(ref).to receive(:name) { 'bad-branch' }
allow(ref).to receive(:target) { raise Rugged::ReferenceError }
- allow(repository.rugged).to receive(:branches) { [ref] }
+ branches = double()
+ allow(branches).to receive(:each) { [ref].each }
+ allow(repository.rugged).to receive(:branches) { branches }
end
it 'should return empty branches' do
@@ -1289,7 +1304,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe '#local_branches' do
before(:all) do
- @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH)
+ @repo = Gitlab::Git::Repository.new('default', File.join(TEST_MUTABLE_REPO_PATH, '.git'))
end
after(:all) do
@@ -1304,6 +1319,29 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(@repo.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
end
+
+ context 'with gitaly enabled' do
+ before { stub_gitaly }
+ after { Gitlab::GitalyClient.clear_stubs! }
+
+ it 'gets the branches from GitalyClient' do
+ expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches).
+ and_return([])
+ @repo.local_branches
+ end
+
+ it 'wraps GRPC not found' do
+ expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches).
+ and_raise(GRPC::NotFound)
+ expect { @repo.local_branches }.to raise_error(Gitlab::Git::Repository::NoRepository)
+ end
+
+ it 'wraps GRPC exceptions' do
+ expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches).
+ and_raise(GRPC::Unknown)
+ expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError)
+ end
+ end
end
def create_remote_branch(remote_name, branch_name, source_branch_name)
diff --git a/spec/lib/gitlab/git/util_spec.rb b/spec/lib/gitlab/git/util_spec.rb
index 69d3ca55397..88c871855df 100644
--- a/spec/lib/gitlab/git/util_spec.rb
+++ b/spec/lib/gitlab/git/util_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Git::Util do
["", 0],
["foo", 1],
["foo\n", 1],
- ["foo\n\n", 2],
+ ["foo\n\n", 2]
].each do |string, line_count|
it "counts #{line_count} lines in #{string.inspect}" do
expect(described_class.count_lines(string)).to eq(line_count)
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index d8b72615fab..36d1d777583 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -1,10 +1,13 @@
require 'spec_helper'
describe Gitlab::GitAccess, lib: true do
- let(:access) { Gitlab::GitAccess.new(actor, project, 'web', authentication_abilities: authentication_abilities) }
+ let(:pull_access_check) { access.check('git-upload-pack', '_any') }
+ let(:push_access_check) { access.check('git-receive-pack', '_any') }
+ let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:actor) { user }
+ let(:protocol) { 'ssh' }
let(:authentication_abilities) do
[
:read_project,
@@ -15,49 +18,188 @@ describe Gitlab::GitAccess, lib: true do
describe '#check with single protocols allowed' do
def disable_protocol(protocol)
- settings = ::ApplicationSetting.create_from_defaults
- settings.update_attribute(:enabled_git_access_protocol, protocol)
+ allow(Gitlab::ProtocolAccess).to receive(:allowed?).with(protocol).and_return(false)
end
context 'ssh disabled' do
before do
disable_protocol('ssh')
- @acc = Gitlab::GitAccess.new(actor, project, 'ssh', authentication_abilities: authentication_abilities)
end
it 'blocks ssh git push' do
- expect(@acc.check('git-receive-pack', '_any').allowed?).to be_falsey
+ expect { push_access_check }.to raise_unauthorized('Git access over SSH is not allowed')
end
it 'blocks ssh git pull' do
- expect(@acc.check('git-upload-pack', '_any').allowed?).to be_falsey
+ expect { pull_access_check }.to raise_unauthorized('Git access over SSH is not allowed')
end
end
context 'http disabled' do
+ let(:protocol) { 'http' }
+
before do
disable_protocol('http')
- @acc = Gitlab::GitAccess.new(actor, project, 'http', authentication_abilities: authentication_abilities)
end
it 'blocks http push' do
- expect(@acc.check('git-receive-pack', '_any').allowed?).to be_falsey
+ expect { push_access_check }.to raise_unauthorized('Git access over HTTP is not allowed')
end
it 'blocks http git pull' do
- expect(@acc.check('git-upload-pack', '_any').allowed?).to be_falsey
+ expect { pull_access_check }.to raise_unauthorized('Git access over HTTP is not allowed')
end
end
end
- describe '#check_download_access!' do
- subject { access.check('git-upload-pack', '_any') }
+ describe '#check_project_accessibility!' 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(:actor) { deploy_key }
+
+ context 'when the DeployKey has access to the project' do
+ before { deploy_key.projects << project }
+
+ it 'allows pull access' do
+ expect { pull_access_check }.not_to raise_error
+ end
+
+ it 'allows push access' do
+ expect { push_access_check }.not_to raise_error
+ end
+ end
+
+ context 'when the Deploykey does not have access to the project' do
+ it 'blocks pulls with "not found"' do
+ expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.')
+ end
+
+ it 'blocks pushes with "not found"' do
+ expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.')
+ end
+ end
+ end
+ context 'when actor is a User' do
+ context 'when the User can read the project' do
+ before { project.team << [user, :master] }
+
+ it 'allows pull access' do
+ expect { pull_access_check }.not_to raise_error
+ end
+
+ it 'allows push access' do
+ expect { push_access_check }.not_to raise_error
+ end
+ end
+
+ context 'when the User cannot read the project' do
+ it 'blocks pulls with "not found"' do
+ expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.')
+ end
+
+ it 'blocks pushes with "not found"' do
+ expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.')
+ end
+ end
+ end
+
+ # For backwards compatibility
+ context 'when actor is :ci' do
+ let(:actor) { :ci }
+ let(:authentication_abilities) { build_authentication_abilities }
+
+ it 'allows pull access' do
+ expect { pull_access_check }.not_to raise_error
+ end
+
+ it 'does not block pushes with "not found"' do
+ expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.')
+ end
+ end
+ end
+
+ context 'when actor is nil' do
+ let(:actor) { nil }
+
+ context 'when guests can read the project' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it 'allows pull access' do
+ expect { pull_access_check }.not_to raise_error
+ end
+
+ it 'does not block pushes with "not found"' do
+ expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.')
+ end
+ end
+
+ context 'when guests cannot read the project' do
+ it 'blocks pulls with "not found"' do
+ expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.')
+ end
+
+ it 'blocks pushes with "not found"' do
+ expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.')
+ end
+ end
+ end
+ end
+
+ context 'when the project is nil' do
+ let(:project) { nil }
+
+ it 'blocks any command with "not found"' do
+ expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.')
+ expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.')
+ end
+ end
+ end
+
+ describe '#check_command_disabled!' do
+ before { project.team << [user, :master] }
+
+ context 'over http' do
+ let(:protocol) { 'http' }
+
+ context 'when the git-upload-pack command is disabled in config' do
+ before do
+ allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
+ end
+
+ context 'when calling git-upload-pack' do
+ it { expect { pull_access_check }.to raise_unauthorized('Pulling over HTTP is not allowed.') }
+ end
+
+ context 'when calling git-receive-pack' do
+ it { expect { push_access_check }.not_to raise_error }
+ end
+ end
+
+ context 'when the git-receive-pack command is disabled in config' do
+ before do
+ allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
+ end
+
+ context 'when calling git-receive-pack' do
+ it { expect { push_access_check }.to raise_unauthorized('Pushing over HTTP is not allowed.') }
+ end
+
+ context 'when calling git-upload-pack' do
+ it { expect { pull_access_check }.not_to raise_error }
+ end
+ end
+ end
+ end
+
+ describe '#check_download_access!' do
describe 'master permissions' do
before { project.team << [user, :master] }
context 'pull code' do
- it { expect(subject.allowed?).to be_truthy }
+ it { expect { pull_access_check }.not_to raise_error }
end
end
@@ -65,8 +207,7 @@ describe Gitlab::GitAccess, lib: true do
before { project.team << [user, :guest] }
context 'pull code' do
- it { expect(subject.allowed?).to be_falsey }
- it { expect(subject.message).to match(/You are not allowed to download code/) }
+ it { expect { pull_access_check }.to raise_unauthorized('You are not allowed to download code from this project.') }
end
end
@@ -77,24 +218,22 @@ describe Gitlab::GitAccess, lib: true do
end
context 'pull code' do
- it { expect(subject.allowed?).to be_falsey }
- it { expect(subject.message).to match(/Your account has been blocked/) }
+ it { expect { pull_access_check }.to raise_unauthorized('Your account has been blocked.') }
end
end
describe 'without access to project' do
context 'pull code' do
- it { expect(subject.allowed?).to be_falsey }
+ it { expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end
context 'when project is public' do
let(:public_project) { create(:project, :public, :repository) }
- let(:guest_access) { Gitlab::GitAccess.new(nil, public_project, 'web', authentication_abilities: []) }
- subject { guest_access.check('git-upload-pack', '_any') }
+ let(:access) { Gitlab::GitAccess.new(nil, public_project, 'web', authentication_abilities: []) }
context 'when repository is enabled' do
it 'give access to download code' do
- expect(subject.allowed?).to be_truthy
+ expect { pull_access_check }.not_to raise_error
end
end
@@ -102,8 +241,7 @@ describe Gitlab::GitAccess, lib: true do
it 'does not give access to download code' do
public_project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
- expect(subject.allowed?).to be_falsey
- expect(subject.message).to match(/You are not allowed to download code/)
+ expect { pull_access_check }.to raise_unauthorized('You are not allowed to download code from this project.')
end
end
end
@@ -117,26 +255,26 @@ describe Gitlab::GitAccess, lib: true do
context 'when project is authorized' do
before { key.projects << project }
- it { expect(subject).to be_allowed }
+ it { expect { pull_access_check }.not_to raise_error }
end
context 'when unauthorized' do
context 'from public project' do
let(:project) { create(:project, :public, :repository) }
- it { expect(subject).to be_allowed }
+ it { expect { pull_access_check }.not_to raise_error }
end
context 'from internal project' do
let(:project) { create(:project, :internal, :repository) }
- it { expect(subject).not_to be_allowed }
+ it { expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end
context 'from private project' do
let(:project) { create(:project, :private, :repository) }
- it { expect(subject).not_to be_allowed }
+ it { expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end
end
end
@@ -149,7 +287,7 @@ describe Gitlab::GitAccess, lib: true do
let(:project) { create(:project, :repository, namespace: user.namespace) }
context 'pull code' do
- it { expect(subject).to be_allowed }
+ it { expect { pull_access_check }.not_to raise_error }
end
end
@@ -157,7 +295,7 @@ describe Gitlab::GitAccess, lib: true do
before { project.team << [user, :reporter] }
context 'pull code' do
- it { expect(subject).to be_allowed }
+ it { expect { pull_access_check }.not_to raise_error }
end
end
@@ -168,16 +306,24 @@ describe Gitlab::GitAccess, lib: true do
before { project.team << [user, :reporter] }
context 'pull code' do
- it { expect(subject).to be_allowed }
+ it { expect { pull_access_check }.not_to raise_error }
end
end
context 'when is not member of the project' do
context 'pull code' do
- it { expect(subject).not_to be_allowed }
+ it { expect { pull_access_check }.to raise_unauthorized('You are not allowed to download code from this project.') }
end
end
end
+
+ describe 'generic CI (build without a user)' do
+ let(:actor) { :ci }
+
+ context 'pull code' do
+ it { expect { pull_access_check }.not_to raise_error }
+ end
+ end
end
end
@@ -365,42 +511,32 @@ describe Gitlab::GitAccess, lib: true do
end
end
- shared_examples 'pushing code' do |can|
- subject { access.check('git-receive-pack', '_any') }
+ describe 'build authentication abilities' do
+ let(:authentication_abilities) { build_authentication_abilities }
context 'when project is authorized' do
- before { authorize }
+ before { project.team << [user, :reporter] }
- it { expect(subject).public_send(can, be_allowed) }
+ it { expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.') }
end
context 'when unauthorized' do
context 'to public project' do
let(:project) { create(:project, :public, :repository) }
- it { expect(subject).not_to be_allowed }
+ it { expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.') }
end
context 'to internal project' do
let(:project) { create(:project, :internal, :repository) }
- it { expect(subject).not_to be_allowed }
+ it { expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.') }
end
context 'to private project' do
let(:project) { create(:project, :private, :repository) }
- it { expect(subject).not_to be_allowed }
- end
- end
- end
-
- describe 'build authentication abilities' do
- let(:authentication_abilities) { build_authentication_abilities }
-
- it_behaves_like 'pushing code', :not_to do
- def authorize
- project.team << [user, :reporter]
+ it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end
end
end
@@ -412,9 +548,29 @@ describe Gitlab::GitAccess, lib: true do
context 'when deploy_key can push' do
let(:can_push) { true }
- it_behaves_like 'pushing code', :to do
- def authorize
- key.projects << project
+ context 'when project is authorized' do
+ before { key.projects << project }
+
+ it { expect { push_access_check }.not_to raise_error }
+ end
+
+ context 'when unauthorized' do
+ context 'to public project' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it { expect { push_access_check }.to raise_unauthorized('This deploy key does not have write access to this project.') }
+ end
+
+ context 'to internal project' do
+ let(:project) { create(:project, :internal, :repository) }
+
+ it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') }
+ end
+
+ context 'to private project' do
+ let(:project) { create(:project, :private, :repository) }
+
+ it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end
end
end
@@ -422,9 +578,29 @@ describe Gitlab::GitAccess, lib: true do
context 'when deploy_key cannot push' do
let(:can_push) { false }
- it_behaves_like 'pushing code', :not_to do
- def authorize
- key.projects << project
+ context 'when project is authorized' do
+ before { key.projects << project }
+
+ it { expect { push_access_check }.to raise_unauthorized('This deploy key does not have write access to this project.') }
+ end
+
+ context 'when unauthorized' do
+ context 'to public project' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it { expect { push_access_check }.to raise_unauthorized('This deploy key does not have write access to this project.') }
+ end
+
+ context 'to internal project' do
+ let(:project) { create(:project, :internal, :repository) }
+
+ it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') }
+ end
+
+ context 'to private project' do
+ let(:project) { create(:project, :private, :repository) }
+
+ it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end
end
end
@@ -432,6 +608,14 @@ describe Gitlab::GitAccess, lib: true do
private
+ def raise_unauthorized(message)
+ raise_error(Gitlab::GitAccess::UnauthorizedError, message)
+ end
+
+ def raise_not_found(message)
+ raise_error(Gitlab::GitAccess::NotFoundError, message)
+ end
+
def build_authentication_abilities
[
:read_project,
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 1ae293416e4..a1eb95750ba 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::GitAccessWiki, lib: true do
subject { access.check('git-receive-pack', changes) }
- it { expect(subject.allowed?).to be_truthy }
+ it { expect { subject }.not_to raise_error }
end
def changes
@@ -36,7 +36,7 @@ describe Gitlab::GitAccessWiki, lib: true do
context 'when wiki feature is enabled' do
it 'give access to download wiki code' do
- expect(subject.allowed?).to be_truthy
+ expect { subject }.not_to raise_error
end
end
@@ -44,8 +44,7 @@ describe Gitlab::GitAccessWiki, lib: true do
it 'does not give access to download wiki code' do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
- expect(subject.allowed?).to be_falsey
- expect(subject.message).to match(/You are not allowed to download code/)
+ expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to download code from this project.')
end
end
end
diff --git a/spec/lib/gitlab/gitaly_client/commit_spec.rb b/spec/lib/gitlab/gitaly_client/commit_spec.rb
index abe08ccdfa1..cf1bc74779e 100644
--- a/spec/lib/gitlab/gitaly_client/commit_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_spec.rb
@@ -1,23 +1,24 @@
require 'spec_helper'
describe Gitlab::GitalyClient::Commit do
- describe '.diff_from_parent' do
- let(:diff_stub) { double('Gitaly::Diff::Stub') }
- let(:project) { create(:project, :repository) }
- let(:repository_message) { project.repository.gitaly_repository }
- let(:commit) { project.commit('913c66a37b4a45b9769037c55c2d238bd0942d2e') }
+ let(:diff_stub) { double('Gitaly::Diff::Stub') }
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:repository_message) { repository.gitaly_repository }
+ let(:commit) { project.commit('913c66a37b4a45b9769037c55c2d238bd0942d2e') }
+ describe '#diff_from_parent' do
context 'when a commit has a parent' do
it 'sends an RPC request with the parent ID as left commit' do
request = Gitaly::CommitDiffRequest.new(
repository: repository_message,
left_commit_id: 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660',
- right_commit_id: commit.id,
+ right_commit_id: commit.id
)
expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request)
- described_class.diff_from_parent(commit)
+ described_class.new(repository).diff_from_parent(commit)
end
end
@@ -27,17 +28,17 @@ describe Gitlab::GitalyClient::Commit do
request = Gitaly::CommitDiffRequest.new(
repository: repository_message,
left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
- right_commit_id: initial_commit.id,
+ right_commit_id: initial_commit.id
)
expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request)
- described_class.diff_from_parent(initial_commit)
+ described_class.new(repository).diff_from_parent(initial_commit)
end
end
it 'returns a Gitlab::Git::DiffCollection' do
- ret = described_class.diff_from_parent(commit)
+ ret = described_class.new(repository).diff_from_parent(commit)
expect(ret).to be_kind_of(Gitlab::Git::DiffCollection)
end
@@ -47,7 +48,38 @@ describe Gitlab::GitalyClient::Commit do
expect(Gitlab::Git::DiffCollection).to receive(:new).with(kind_of(Enumerable), options)
- described_class.diff_from_parent(commit, options)
+ described_class.new(repository).diff_from_parent(commit, options)
+ end
+ end
+
+ describe '#commit_deltas' do
+ context 'when a commit has a parent' do
+ it 'sends an RPC request with the parent ID as left commit' do
+ request = Gitaly::CommitDeltaRequest.new(
+ repository: repository_message,
+ left_commit_id: 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660',
+ right_commit_id: commit.id
+ )
+
+ expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_delta).with(request).and_return([])
+
+ described_class.new(repository).commit_deltas(commit)
+ end
+ end
+
+ context 'when a commit does not have a parent' do
+ it 'sends an RPC request with empty tree ref as left commit' do
+ initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
+ request = Gitaly::CommitDeltaRequest.new(
+ repository: repository_message,
+ left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
+ right_commit_id: initial_commit.id
+ )
+
+ expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_delta).with(request).and_return([])
+
+ described_class.new(repository).commit_deltas(initial_commit)
+ end
end
end
end
diff --git a/spec/lib/gitlab/gitaly_client/diff_spec.rb b/spec/lib/gitlab/gitaly_client/diff_spec.rb
new file mode 100644
index 00000000000..2960c9a79ad
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/diff_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::Diff, lib: true do
+ let(:diff_fields) do
+ {
+ to_path: ".gitmodules",
+ from_path: ".gitmodules",
+ old_mode: 0100644,
+ new_mode: 0100644,
+ from_id: '357406f3075a57708d0163752905cc1576fceacc',
+ to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0',
+ patch: 'a' * 100
+ }
+ end
+
+ subject { described_class.new(diff_fields) }
+
+ it { is_expected.to respond_to(:from_path) }
+ it { is_expected.to respond_to(:to_path) }
+ it { is_expected.to respond_to(:old_mode) }
+ it { is_expected.to respond_to(:new_mode) }
+ it { is_expected.to respond_to(:from_id) }
+ it { is_expected.to respond_to(:to_id) }
+ it { is_expected.to respond_to(:patch) }
+
+ describe '#==' do
+ it { expect(subject).to eq(described_class.new(diff_fields)) }
+ it { expect(subject).not_to eq(described_class.new(diff_fields.merge(patch: 'a'))) }
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
new file mode 100644
index 00000000000..07650013052
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::DiffStitcher, lib: true do
+ describe 'enumeration' do
+ it 'combines segregated diff messages together' do
+ diff_1 = OpenStruct.new(
+ to_path: ".gitmodules",
+ from_path: ".gitmodules",
+ old_mode: 0100644,
+ new_mode: 0100644,
+ from_id: '357406f3075a57708d0163752905cc1576fceacc',
+ to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0',
+ patch: 'a' * 100
+ )
+ diff_2 = OpenStruct.new(
+ to_path: ".gitignore",
+ from_path: ".gitignore",
+ old_mode: 0100644,
+ new_mode: 0100644,
+ from_id: '357406f3075a57708d0163752905cc1576fceacc',
+ to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0',
+ patch: 'a' * 200
+ )
+ diff_3 = OpenStruct.new(
+ to_path: "README",
+ from_path: "README",
+ old_mode: 0100644,
+ new_mode: 0100644,
+ from_id: '357406f3075a57708d0163752905cc1576fceacc',
+ to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0',
+ patch: 'a' * 100
+ )
+
+ msg_1 = OpenStruct.new(diff_1.to_h.except(:patch))
+ msg_1.raw_patch_data = diff_1.patch
+ msg_1.end_of_patch = true
+
+ msg_2 = OpenStruct.new(diff_2.to_h.except(:patch))
+ msg_2.raw_patch_data = diff_2.patch[0..100]
+ msg_2.end_of_patch = false
+
+ msg_3 = OpenStruct.new(raw_patch_data: diff_2.patch[101..-1], end_of_patch: true)
+
+ msg_4 = OpenStruct.new(diff_3.to_h.except(:patch))
+ msg_4.raw_patch_data = diff_3.patch
+ msg_4.end_of_patch = true
+
+ diff_msgs = [msg_1, msg_2, msg_3, msg_4]
+
+ expected_diffs = [
+ Gitlab::GitalyClient::Diff.new(diff_1.to_h),
+ Gitlab::GitalyClient::Diff.new(diff_2.to_h),
+ Gitlab::GitalyClient::Diff.new(diff_3.to_h)
+ ]
+
+ expect(described_class.new(diff_msgs).to_a).to eq(expected_diffs)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/ref_spec.rb b/spec/lib/gitlab/gitaly_client/ref_spec.rb
index 255f23e6270..d8cd2dcbd2a 100644
--- a/spec/lib/gitlab/gitaly_client/ref_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_spec.rb
@@ -9,6 +9,13 @@ describe Gitlab::GitalyClient::Ref do
allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
end
+ after do
+ # When we say `expect_any_instance_of(Gitaly::Ref::Stub)` a double is created,
+ # and because GitalyClient shares stubs these will get passed from example to
+ # example, which will cause an error, so we clean the stubs after each example.
+ Gitlab::GitalyClient.clear_stubs!
+ end
+
describe '#branch_names' do
it 'sends a find_all_branch_names message' do
expect_any_instance_of(Gitaly::Ref::Stub).
@@ -38,4 +45,27 @@ describe Gitlab::GitalyClient::Ref do
client.default_branch_name
end
end
+
+ describe '#local_branches' do
+ it 'sends a find_local_branches message' do
+ expect_any_instance_of(Gitaly::Ref::Stub).
+ to receive(:find_local_branches).with(gitaly_request_with_repo_path(repo_path)).
+ and_return([])
+
+ client.local_branches
+ end
+
+ it 'parses and sends the sort parameter' do
+ expect_any_instance_of(Gitaly::Ref::Stub).
+ to receive(:find_local_branches).
+ with(gitaly_request_with_params(sort_by: :UPDATED_DESC)).
+ and_return([])
+
+ client.local_branches(sort_by: 'updated_desc')
+ end
+
+ it 'raises an argument error if an invalid sort_by parameter is passed' do
+ expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError)
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index 08ee0dff6b2..95ecba67532 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -1,7 +1,10 @@
require 'spec_helper'
-describe Gitlab::GitalyClient, lib: true do
+# 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, lib: true, skip_gitaly_mock: true do
describe '.stub' do
+ # Notice that this is referring to gRPC "stubs", not rspec stubs
before { described_class.clear_stubs! }
context 'when passed a UNIX socket address' do
@@ -32,4 +35,81 @@ describe Gitlab::GitalyClient, lib: true do
end
end
end
+
+ describe 'feature_enabled?' do
+ let(:feature_name) { 'my_feature' }
+ let(:real_feature_name) { "gitaly_#{feature_name}" }
+
+ context 'when Gitaly is disabled' do
+ before { allow(described_class).to receive(:enabled?).and_return(false) }
+
+ it 'returns false' do
+ expect(described_class.feature_enabled?(feature_name)).to be(false)
+ end
+ end
+
+ context 'when the feature status is DISABLED' do
+ let(:feature_status) { Gitlab::GitalyClient::MigrationStatus::DISABLED }
+
+ it 'returns false' do
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
+ end
+ end
+
+ context 'when the feature_status is OPT_IN' do
+ let(:feature_status) { Gitlab::GitalyClient::MigrationStatus::OPT_IN }
+
+ context "when the feature flag hasn't been set" do
+ it 'returns false' do
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
+ end
+ end
+
+ context "when the feature flag is set to disable" do
+ before { Feature.get(real_feature_name).disable }
+
+ it 'returns false' do
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
+ end
+ end
+
+ context "when the feature flag is set to enable" do
+ before { Feature.get(real_feature_name).enable }
+
+ it 'returns true' do
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
+ end
+ end
+
+ context "when the feature flag is set to a percentage of time" do
+ before { Feature.get(real_feature_name).enable_percentage_of_time(70) }
+
+ it 'bases the result on pseudo-random numbers' do
+ expect(Random).to receive(:rand).and_return(0.3)
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
+
+ expect(Random).to receive(:rand).and_return(0.8)
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
+ end
+ end
+ end
+
+ context 'when the feature_status is OPT_OUT' do
+ let(:feature_status) { Gitlab::GitalyClient::MigrationStatus::OPT_OUT }
+
+ context "when the feature flag hasn't been set" do
+ it 'returns true' do
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
+ end
+ end
+
+ context "when the feature flag is set to disable" do
+ before { Feature.get(real_feature_name).disable }
+
+ it 'returns false' do
+ expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
index 45ccd3d6459..61c10d47434 100644
--- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
@@ -1,6 +1,24 @@
require 'spec_helper'
describe Gitlab::HealthChecks::FsShardsCheck do
+ def command_exists?(command)
+ _, status = Gitlab::Popen.popen(%W{ #{command} 1 echo })
+ status == 0
+ rescue Errno::ENOENT
+ false
+ end
+
+ def timeout_command
+ @timeout_command ||=
+ if command_exists?('timeout')
+ 'timeout'
+ elsif command_exists?('gtimeout')
+ 'gtimeout'
+ else
+ ''
+ end
+ end
+
let(:metric_class) { Gitlab::HealthChecks::Metric }
let(:result_class) { Gitlab::HealthChecks::Result }
let(:repository_storages) { [:default] }
@@ -15,6 +33,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
before do
allow(described_class).to receive(:repository_storages) { repository_storages }
allow(described_class).to receive(:storages_paths) { storages_paths }
+ stub_const('Gitlab::HealthChecks::FsShardsCheck::TIMEOUT_EXECUTABLE', timeout_command)
end
after do
@@ -78,40 +97,76 @@ describe Gitlab::HealthChecks::FsShardsCheck do
}.with_indifferent_access
end
- it { is_expected.to include(metric_class.new(:filesystem_accessible, 0, shard: :default)) }
- it { is_expected.to include(metric_class.new(:filesystem_readable, 0, shard: :default)) }
- it { is_expected.to include(metric_class.new(:filesystem_writable, 0, shard: :default)) }
+ it { is_expected.to all(have_attributes(labels: { shard: :default })) }
+
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 0)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) }
- it { is_expected.to include(have_attributes(name: :filesystem_access_latency, value: be >= 0, labels: { shard: :default })) }
- it { is_expected.to include(have_attributes(name: :filesystem_read_latency, value: be >= 0, labels: { shard: :default })) }
- it { is_expected.to include(have_attributes(name: :filesystem_write_latency, value: be >= 0, labels: { shard: :default })) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) }
end
context 'storage points to directory that has both read and write rights' do
before do
FileUtils.chmod_R(0755, tmp_dir)
end
+ it { is_expected.to all(have_attributes(labels: { shard: :default })) }
- it { is_expected.to include(metric_class.new(:filesystem_accessible, 1, shard: :default)) }
- it { is_expected.to include(metric_class.new(:filesystem_readable, 1, shard: :default)) }
- it { is_expected.to include(metric_class.new(:filesystem_writable, 1, shard: :default)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 1)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 1)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 1)) }
- it { is_expected.to include(have_attributes(name: :filesystem_access_latency, value: be >= 0, labels: { shard: :default })) }
- it { is_expected.to include(have_attributes(name: :filesystem_read_latency, value: be >= 0, labels: { shard: :default })) }
- it { is_expected.to include(have_attributes(name: :filesystem_write_latency, value: be >= 0, labels: { shard: :default })) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) }
+ end
+ end
+ end
+
+ context 'when timeout kills fs checks' do
+ before do
+ stub_const('Gitlab::HealthChecks::FsShardsCheck::COMMAND_TIMEOUT', '1')
+
+ allow(described_class).to receive(:exec_with_timeout).and_wrap_original { |m| m.call(%w(sleep 60)) }
+ FileUtils.chmod_R(0755, tmp_dir)
+ end
+
+ describe '#readiness' do
+ subject { described_class.readiness }
+
+ it { is_expected.to include(result_class.new(false, 'cannot stat storage', shard: :default)) }
+ end
+
+ describe '#metrics' do
+ subject { described_class.metrics }
+
+ it 'provides metrics' do
+ expect(subject).to all(have_attributes(labels: { shard: :default }))
+
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
+
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
end
end
end
context 'when popen always finds required binaries' do
before do
- allow(Gitlab::Popen).to receive(:popen).and_wrap_original do |method, *args, &block|
+ allow(described_class).to receive(:exec_with_timeout).and_wrap_original do |method, *args, &block|
begin
method.call(*args, &block)
- rescue RuntimeError
+ rescue RuntimeError, Errno::ENOENT
raise 'expected not to happen'
end
end
+
+ stub_const('Gitlab::HealthChecks::FsShardsCheck::COMMAND_TIMEOUT', '10')
end
it_behaves_like 'filesystem checks'
diff --git a/spec/lib/gitlab/health_checks/prometheus_text_format_spec.rb b/spec/lib/gitlab/health_checks/prometheus_text_format_spec.rb
new file mode 100644
index 00000000000..ed757ed60d8
--- /dev/null
+++ b/spec/lib/gitlab/health_checks/prometheus_text_format_spec.rb
@@ -0,0 +1,41 @@
+describe Gitlab::HealthChecks::PrometheusTextFormat do
+ let(:metric_class) { Gitlab::HealthChecks::Metric }
+ subject { described_class.new }
+
+ describe '#marshal' do
+ let(:sample_metrics) do
+ [metric_class.new('metric1', 1),
+ metric_class.new('metric2', 2)]
+ end
+
+ it 'marshal to text with non repeating type definition' do
+ expected = <<-EXPECTED.strip_heredoc
+ # TYPE metric1 gauge
+ metric1 1
+ # TYPE metric2 gauge
+ metric2 2
+ EXPECTED
+
+ expect(subject.marshal(sample_metrics)).to eq(expected)
+ end
+
+ context 'metrics where name repeats' do
+ let(:sample_metrics) do
+ [metric_class.new('metric1', 1),
+ metric_class.new('metric1', 2),
+ metric_class.new('metric2', 3)]
+ end
+
+ it 'marshal to text with non repeating type definition' do
+ expected = <<-EXPECTED.strip_heredoc
+ # TYPE metric1 gauge
+ metric1 1
+ metric1 2
+ # TYPE metric2 gauge
+ metric2 3
+ EXPECTED
+ expect(subject.marshal(sample_metrics)).to eq(expected)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index e49799ad105..a20cef3b000 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -57,4 +57,13 @@ describe Gitlab::Highlight, lib: true do
end
end
end
+
+ describe '#highlight' do
+ it 'links dependencies via DependencyLinker' do
+ expect(Gitlab::DependencyLinker).to receive(:link).
+ with('file.name', 'Contents', anything).and_call_original
+
+ described_class.highlight('file.name', 'Contents')
+ end
+ end
end
diff --git a/spec/lib/gitlab/i18n_spec.rb b/spec/lib/gitlab/i18n_spec.rb
index 52f2614d5ca..a3dbeaa3753 100644
--- a/spec/lib/gitlab/i18n_spec.rb
+++ b/spec/lib/gitlab/i18n_spec.rb
@@ -1,27 +1,27 @@
require 'spec_helper'
-module Gitlab
- describe I18n, lib: true do
- let(:user) { create(:user, preferred_language: 'es') }
+describe Gitlab::I18n, lib: true do
+ let(:user) { create(:user, preferred_language: 'es') }
- describe '.set_locale' do
- it 'sets the locale based on current user preferred language' do
- Gitlab::I18n.set_locale(user)
+ describe '.locale=' do
+ after { described_class.use_default_locale }
- expect(FastGettext.locale).to eq('es')
- expect(::I18n.locale).to eq(:es)
- end
+ it 'sets the locale based on current user preferred language' do
+ described_class.locale = user.preferred_language
+
+ expect(FastGettext.locale).to eq('es')
+ expect(::I18n.locale).to eq(:es)
end
+ end
- describe '.reset_locale' do
- it 'resets the locale to the default language' do
- Gitlab::I18n.set_locale(user)
+ describe '.use_default_locale' do
+ it 'resets the locale to the default language' do
+ described_class.locale = user.preferred_language
- Gitlab::I18n.reset_locale
+ described_class.use_default_locale
- expect(FastGettext.locale).to eq('en')
- expect(::I18n.locale).to eq(:en)
- end
+ expect(FastGettext.locale).to eq('en')
+ expect(::I18n.locale).to eq(:en)
end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 688e731bf15..412eb33b35b 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -85,11 +85,13 @@ merge_requests:
- merge_requests_closing_issues
- metrics
- timelogs
+- head_pipeline
merge_request_diff:
- merge_request
pipelines:
- project
- user
+- stages
- statuses
- builds
- trigger_requests
@@ -102,9 +104,16 @@ pipelines:
- manual_actions
- artifacts
- pipeline_schedule
+- merge_requests
+stages:
+- project
+- pipeline
+- statuses
+- builds
statuses:
- project
- pipeline
+- stage
- user
- auto_canceled_by
variables:
@@ -129,6 +138,7 @@ services:
- service_hook
hooks:
- project
+- web_hook_logs
protected_branches:
- project
- merge_access_levels
@@ -141,7 +151,9 @@ merge_access_levels:
push_access_levels:
- protected_branch
create_access_levels:
+- user
- protected_tag
+- group
container_repositories:
- project
- name
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index fdbb6a0556d..e3599d6fe59 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -6997,7 +6997,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "TeamcityService",
"category": "ci",
"default": false,
@@ -7041,7 +7041,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "RedmineService",
"category": "issue_tracker",
"default": false,
@@ -7063,7 +7063,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "PushoverService",
"category": "common",
"default": false,
@@ -7085,7 +7085,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "PivotalTrackerService",
"category": "common",
"default": false,
@@ -7108,7 +7108,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "JiraService",
"category": "issue_tracker",
"default": false,
@@ -7130,7 +7130,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "IrkerService",
"category": "common",
"default": false,
@@ -7174,7 +7174,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "GemnasiumService",
"category": "common",
"default": false,
@@ -7196,7 +7196,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "FlowdockService",
"category": "common",
"default": false,
@@ -7218,7 +7218,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "ExternalWikiService",
"category": "common",
"default": false,
@@ -7240,7 +7240,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "EmailsOnPushService",
"category": "common",
"default": false,
@@ -7262,7 +7262,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "DroneCiService",
"category": "ci",
"default": false,
@@ -7284,7 +7284,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "CustomIssueTrackerService",
"category": "issue_tracker",
"default": false,
@@ -7306,7 +7306,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "CampfireService",
"category": "common",
"default": false,
@@ -7328,7 +7328,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "BuildkiteService",
"category": "ci",
"default": false,
@@ -7350,7 +7350,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "BambooService",
"category": "ci",
"default": false,
@@ -7372,7 +7372,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "AssemblaService",
"category": "common",
"default": false,
@@ -7394,7 +7394,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"type": "AssemblaService",
"category": "common",
"default": false,
@@ -7416,7 +7416,7 @@
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"category": "common",
"default": false,
"wiki_page_events": true,
diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb
index 06cd8ab87ed..5417c7534ea 100644
--- a/spec/lib/gitlab/import_export/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb
@@ -33,7 +33,7 @@ describe Gitlab::ImportExport::RelationFactory, lib: true do
'tag_push_events' => false,
'note_events' => true,
'enable_ssl_verification' => true,
- 'build_events' => false,
+ 'job_events' => false,
'wiki_page_events' => true,
'token' => token
}
@@ -95,7 +95,7 @@ describe Gitlab::ImportExport::RelationFactory, lib: true do
'random_id' => 99,
'milestone_id' => 99,
'project_id' => 99,
- 'user_id' => 99,
+ 'user_id' => 99
}
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 29a9ad453fb..50ff6ecc1e0 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -54,6 +54,7 @@ Note:
- type
- position
- original_position
+- change_position
- resolved_at
- resolved_by_id
- discussion_id
@@ -91,6 +92,7 @@ Milestone:
ProjectSnippet:
- id
- title
+- description
- content
- author_id
- project_id
@@ -158,6 +160,7 @@ MergeRequest:
- time_estimate
- last_edited_at
- last_edited_by_id
+- head_pipeline_id
MergeRequestDiff:
- id
- state
@@ -172,6 +175,7 @@ MergeRequestDiff:
Ci::Pipeline:
- id
- project_id
+- source
- ref
- sha
- before_sha
@@ -189,6 +193,13 @@ Ci::Pipeline:
- lock_version
- auto_canceled_by_id
- pipeline_schedule_id
+Ci::Stage:
+- id
+- name
+- project_id
+- pipeline_id
+- created_at
+- updated_at
CommitStatus:
- id
- project_id
@@ -210,6 +221,7 @@ CommitStatus:
- stage
- trigger_request_id
- stage_idx
+- stage_id
- tag
- ref
- user_id
@@ -291,7 +303,7 @@ Service:
- tag_push_events
- note_events
- pipeline_events
-- build_events
+- job_events
- category
- default
- wiki_page_events
@@ -311,11 +323,12 @@ ProjectHook:
- note_events
- pipeline_events
- enable_ssl_verification
-- build_events
+- job_events
- wiki_page_events
- token
- group_id
- confidential_issues_events
+- repository_update_events
ProtectedBranch:
- id
- project_id
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index f4aab429931..a0eda685ca3 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -37,7 +37,7 @@ describe Gitlab::LDAP::User, lib: true do
end
it "does not mark existing ldap user as changed" do
- create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain', ldap_email: true)
+ create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain', external_email: true, email_provider: 'ldapmain')
expect(ldap_user.changed?).to be_falsey
end
end
@@ -141,8 +141,12 @@ describe Gitlab::LDAP::User, lib: true do
expect(ldap_user.gl_user.email).to eq(info[:email])
end
- it "has ldap_email set to true" do
- expect(ldap_user.gl_user.ldap_email?).to be(true)
+ it "has external_email set to true" do
+ expect(ldap_user.gl_user.external_email?).to be(true)
+ end
+
+ it "has email_provider set to provider" do
+ expect(ldap_user.gl_user.email_provider).to eql 'ldapmain'
end
end
@@ -155,8 +159,8 @@ describe Gitlab::LDAP::User, lib: true do
expect(ldap_user.gl_user.temp_oauth_email?).to be(true)
end
- it "has ldap_email set to false" do
- expect(ldap_user.gl_user.ldap_email?).to be(false)
+ it "has external_email set to false" do
+ expect(ldap_user.gl_user.external_email?).to be(false)
end
end
end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 208a8d028cd..5a87b906609 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Gitlab::Metrics do
+ include StubENV
+
describe '.settings' do
it 'returns a Hash' do
expect(described_class.settings).to be_an_instance_of(Hash)
@@ -9,7 +11,19 @@ describe Gitlab::Metrics do
describe '.enabled?' do
it 'returns a boolean' do
- expect([true, false].include?(described_class.enabled?)).to eq(true)
+ expect(described_class.enabled?).to be_in([true, false])
+ end
+ end
+
+ describe '.prometheus_metrics_enabled?' do
+ it 'returns a boolean' do
+ expect(described_class.prometheus_metrics_enabled?).to be_in([true, false])
+ end
+ end
+
+ describe '.influx_metrics_enabled?' do
+ it 'returns a boolean' do
+ expect(described_class.influx_metrics_enabled?).to be_in([true, false])
end
end
@@ -177,4 +191,133 @@ describe Gitlab::Metrics do
end
end
end
+
+ shared_examples 'prometheus metrics API' do
+ describe '#counter' do
+ subject { described_class.counter(:couter, 'doc') }
+
+ describe '#increment' do
+ it 'successfully calls #increment without arguments' do
+ expect { subject.increment }.not_to raise_exception
+ end
+
+ it 'successfully calls #increment with 1 argument' do
+ expect { subject.increment({}) }.not_to raise_exception
+ end
+
+ it 'successfully calls #increment with 2 arguments' do
+ expect { subject.increment({}, 1) }.not_to raise_exception
+ end
+ end
+ end
+
+ describe '#summary' do
+ subject { described_class.summary(:summary, 'doc') }
+
+ describe '#observe' do
+ it 'successfully calls #observe with 2 arguments' do
+ expect { subject.observe({}, 2) }.not_to raise_exception
+ end
+ end
+ end
+
+ describe '#gauge' do
+ subject { described_class.gauge(:gauge, 'doc') }
+
+ describe '#set' do
+ it 'successfully calls #set with 2 arguments' do
+ expect { subject.set({}, 1) }.not_to raise_exception
+ end
+ end
+ end
+
+ describe '#histogram' do
+ subject { described_class.histogram(:histogram, 'doc') }
+
+ describe '#observe' do
+ it 'successfully calls #observe with 2 arguments' do
+ expect { subject.observe({}, 2) }.not_to raise_exception
+ end
+ end
+ end
+ end
+
+ context 'prometheus metrics disabled' do
+ before do
+ allow(described_class).to receive(:prometheus_metrics_enabled?).and_return(false)
+ end
+
+ it_behaves_like 'prometheus metrics API'
+
+ describe '#null_metric' do
+ subject { described_class.provide_metric(:test) }
+
+ it { is_expected.to be_a(Gitlab::Metrics::NullMetric) }
+ end
+
+ describe '#counter' do
+ subject { described_class.counter(:counter, 'doc') }
+
+ it { is_expected.to be_a(Gitlab::Metrics::NullMetric) }
+ end
+
+ describe '#summary' do
+ subject { described_class.summary(:summary, 'doc') }
+
+ it { is_expected.to be_a(Gitlab::Metrics::NullMetric) }
+ end
+
+ describe '#gauge' do
+ subject { described_class.gauge(:gauge, 'doc') }
+
+ it { is_expected.to be_a(Gitlab::Metrics::NullMetric) }
+ end
+
+ describe '#histogram' do
+ subject { described_class.histogram(:histogram, 'doc') }
+
+ it { is_expected.to be_a(Gitlab::Metrics::NullMetric) }
+ end
+ end
+
+ context 'prometheus metrics enabled' do
+ let(:metrics_multiproc_dir) { Dir.mktmpdir }
+
+ before do
+ stub_const('Prometheus::Client::Multiprocdir', metrics_multiproc_dir)
+ allow(described_class).to receive(:prometheus_metrics_enabled?).and_return(true)
+ end
+
+ it_behaves_like 'prometheus metrics API'
+
+ describe '#null_metric' do
+ subject { described_class.provide_metric(:test) }
+
+ it { is_expected.to be_nil }
+ end
+
+ describe '#counter' do
+ subject { described_class.counter(:name, 'doc') }
+
+ it { is_expected.not_to be_a(Gitlab::Metrics::NullMetric) }
+ end
+
+ describe '#summary' do
+ subject { described_class.summary(:name, 'doc') }
+
+ it { is_expected.not_to be_a(Gitlab::Metrics::NullMetric) }
+ end
+
+ describe '#gauge' do
+ subject { described_class.gauge(:name, 'doc') }
+
+ it { is_expected.not_to be_a(Gitlab::Metrics::NullMetric) }
+ end
+
+ describe '#histogram' do
+ subject { described_class.histogram(:name, 'doc') }
+
+ it { is_expected.not_to be_a(Gitlab::Metrics::NullMetric) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/o_auth/provider_spec.rb b/spec/lib/gitlab/o_auth/provider_spec.rb
new file mode 100644
index 00000000000..1e2a1f8c039
--- /dev/null
+++ b/spec/lib/gitlab/o_auth/provider_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Gitlab::OAuth::Provider, lib: true do
+ describe '#config_for' do
+ context 'for an LDAP provider' do
+ context 'when the provider exists' do
+ it 'returns the config' do
+ expect(described_class.config_for('ldapmain')).to be_a(Hash)
+ end
+ end
+
+ context 'when the provider does not exist' do
+ it 'returns nil' do
+ expect(described_class.config_for('ldapfoo')).to be_nil
+ end
+ end
+ end
+
+ context 'for an OmniAuth provider' do
+ before do
+ provider = OpenStruct.new(
+ name: 'google',
+ app_id: 'asd123',
+ app_secret: 'asd123'
+ )
+ allow(Gitlab.config.omniauth).to receive(:providers).and_return([provider])
+ end
+
+ context 'when the provider exists' do
+ it 'returns the config' do
+ expect(described_class.config_for('google')).to be_a(OpenStruct)
+ end
+ end
+
+ context 'when the provider does not exist' do
+ it 'returns nil' do
+ expect(described_class.config_for('foo')).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 828c953197d..8943d1aa488 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -28,11 +28,11 @@ describe Gitlab::OAuth::User, lib: true do
end
end
- describe '#save' do
- def stub_omniauth_config(messages)
- allow(Gitlab.config.omniauth).to receive_messages(messages)
- end
+ def stub_omniauth_config(messages)
+ allow(Gitlab.config.omniauth).to receive_messages(messages)
+ end
+ describe '#save' do
def stub_ldap_config(messages)
allow(Gitlab::LDAP::Config).to receive_messages(messages)
end
@@ -377,4 +377,40 @@ describe Gitlab::OAuth::User, lib: true do
end
end
end
+
+ describe 'updating email' do
+ let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
+
+ before do
+ stub_omniauth_config(sync_email_from_provider: 'my-provider')
+ end
+
+ context "when provider sets an email" do
+ it "updates the user email" do
+ expect(gl_user.email).to eq(info_hash[:email])
+ end
+
+ it "has external_email set to true" do
+ expect(gl_user.external_email?).to be(true)
+ end
+
+ it "has email_provider set to provider" do
+ expect(gl_user.email_provider).to eql 'my-provider'
+ end
+ end
+
+ context "when provider doesn't set an email" do
+ before do
+ info_hash.delete(:email)
+ end
+
+ it "does not update the user email" do
+ expect(gl_user.email).not_to eq(info_hash[:email])
+ end
+
+ it "has external_email set to false" do
+ expect(gl_user.external_email?).to be(false)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/otp_key_rotator_spec.rb b/spec/lib/gitlab/otp_key_rotator_spec.rb
new file mode 100644
index 00000000000..6e6e9ce29ac
--- /dev/null
+++ b/spec/lib/gitlab/otp_key_rotator_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+describe Gitlab::OtpKeyRotator do
+ let(:file) { Tempfile.new("otp-key-rotator-test") }
+ let(:filename) { file.path }
+ let(:old_key) { Gitlab::Application.secrets.otp_key_base }
+ let(:new_key) { "00" * 32 }
+ let!(:users) { create_list(:user, 5, :two_factor) }
+
+ after do
+ file.close
+ file.unlink
+ end
+
+ def data
+ CSV.read(filename)
+ end
+
+ def build_row(user, applied = false)
+ [user.id.to_s, encrypt_otp(user, old_key), encrypt_otp(user, new_key)]
+ end
+
+ def encrypt_otp(user, key)
+ opts = {
+ value: user.otp_secret,
+ iv: user.encrypted_otp_secret_iv.unpack("m").join,
+ salt: user.encrypted_otp_secret_salt.unpack("m").join,
+ algorithm: 'aes-256-cbc',
+ insecure_mode: true,
+ key: key
+ }
+ [Encryptor.encrypt(opts)].pack("m")
+ end
+
+ subject(:rotator) { described_class.new(filename) }
+
+ describe '#rotate!' do
+ subject(:rotation) { rotator.rotate!(old_key: old_key, new_key: new_key) }
+
+ it 'stores the calculated values in a spreadsheet' do
+ rotation
+
+ expect(data).to match_array(users.map {|u| build_row(u) })
+ end
+
+ context 'new key is too short' do
+ let(:new_key) { "00" * 31 }
+
+ it { expect { rotation }.to raise_error(ArgumentError) }
+ end
+
+ context 'new key is the same as the old key' do
+ let(:new_key) { old_key }
+
+ it { expect { rotation }.to raise_error(ArgumentError) }
+ end
+ end
+
+ describe '#rollback!' do
+ it 'updates rows to the old value' do
+ file.puts("#{users[0].id},old,new")
+ file.close
+
+ rotator.rollback!
+
+ expect(users[0].reload.encrypted_otp_secret).to eq('old')
+ expect(users[1].reload.encrypted_otp_secret).not_to eq('old')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
new file mode 100644
index 00000000000..1eea710c80b
--- /dev/null
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -0,0 +1,384 @@
+# coding: utf-8
+require 'spec_helper'
+
+describe Gitlab::PathRegex, lib: true do
+ # Pass in a full path to remove the format segment:
+ # `/ci/lint(.:format)` -> `/ci/lint`
+ def without_format(path)
+ path.split('(', 2)[0]
+ end
+
+ # Pass in a full path and get the last segment before a wildcard
+ # That's not a parameter
+ # `/*namespace_id/:project_id/builds/artifacts/*ref_name_and_path`
+ # -> 'builds/artifacts'
+ def path_before_wildcard(path)
+ path = path.gsub(STARTING_WITH_NAMESPACE, "")
+ path_segments = path.split('/').reject(&:empty?)
+ wildcard_index = path_segments.index { |segment| parameter?(segment) }
+
+ segments_before_wildcard = path_segments[0..wildcard_index - 1]
+
+ segments_before_wildcard.join('/')
+ end
+
+ def parameter?(segment)
+ segment =~ /[*:]/
+ end
+
+ # If the path is reserved. Then no conflicting paths can# be created for any
+ # route using this reserved word.
+ #
+ # Both `builds/artifacts` & `build` are covered by reserving the word
+ # `build`
+ def wildcards_include?(path)
+ described_class::PROJECT_WILDCARD_ROUTES.include?(path) ||
+ described_class::PROJECT_WILDCARD_ROUTES.include?(path.split('/').first)
+ end
+
+ def failure_message(missing_words, constant_name, migration_helper)
+ missing_words = Array(missing_words)
+ <<-MSG
+ Found new routes that could cause conflicts with existing namespaced routes
+ for groups or projects.
+
+ Add <#{missing_words.join(', ')}> to `Gitlab::PathRegex::#{constant_name}
+ to make sure no projects or namespaces can be created with those paths.
+
+ To rename any existing records with those paths you can use the
+ `Gitlab::Database::RenameReservedpathsMigration::<VERSION>.#{migration_helper}`
+ migration helper.
+
+ Make sure to make a note of the renamed records in the release blog post.
+
+ MSG
+ end
+
+ let(:all_routes) do
+ route_set = Rails.application.routes
+ routes_collection = route_set.routes
+ routes_array = routes_collection.routes
+ routes_array.map { |route| route.path.spec.to_s }
+ end
+
+ let(:routes_without_format) { all_routes.map { |path| without_format(path) } }
+
+ # Routes not starting with `/:` or `/*`
+ # all routes not starting with a param
+ let(:routes_not_starting_in_wildcard) { routes_without_format.select { |p| p !~ %r{^/[:*]} } }
+
+ let(:top_level_words) do
+ routes_not_starting_in_wildcard.map do |route|
+ route.split('/')[1]
+ end.compact.uniq
+ end
+
+ # All routes that start with a namespaced path, that have 1 or more
+ # path-segments before having another wildcard parameter.
+ # - Starting with paths:
+ # - `/*namespace_id/:project_id/`
+ # - `/*namespace_id/:id/`
+ # - Followed by one or more path-parts not starting with `:` or `*`
+ # - Followed by a path-part that includes a wildcard parameter `*`
+ # At the time of writing these routes match: http://rubular.com/r/Rv2pDE5Dvw
+ STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id}
+ NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*}
+ ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*}
+ WILDCARD_SEGMENT = %r{\*}
+ 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}}
+ end
+ end
+
+ # This will return all paths that are used in a namespaced route
+ # before another wildcard path:
+ #
+ # /*namespace_id/:project_id/builds/artifacts/*ref_name_and_path
+ # /*namespace_id/:project_id/info/lfs/objects/*oid
+ # /*namespace_id/:project_id/commits/*id
+ # /*namespace_id/:project_id/builds/:build_id/artifacts/file/*path
+ # -> ['builds/artifacts', 'info/lfs/objects', 'commits', 'artifacts/file']
+ let(:all_wildcard_paths) do
+ namespaced_wildcard_routes.map do |route|
+ path_before_wildcard(route)
+ end.uniq
+ end
+
+ STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/}
+ let(:group_routes) do
+ routes_without_format.select do |path|
+ path =~ STARTING_WITH_GROUP
+ end
+ end
+
+ let(:paths_after_group_id) do
+ group_routes.map do |route|
+ route.gsub(STARTING_WITH_GROUP, '').split('/').first
+ end.uniq
+ end
+
+ describe 'TOP_LEVEL_ROUTES' do
+ it 'includes all the top level namespaces' do
+ failure_block = lambda do
+ missing_words = top_level_words - described_class::TOP_LEVEL_ROUTES
+ failure_message(missing_words, 'TOP_LEVEL_ROUTES', 'rename_root_paths')
+ end
+
+ expect(described_class::TOP_LEVEL_ROUTES)
+ .to include(*top_level_words), failure_block
+ end
+ end
+
+ describe 'GROUP_ROUTES' do
+ it "don't contain a second wildcard" do
+ failure_block = lambda do
+ missing_words = paths_after_group_id - described_class::GROUP_ROUTES
+ failure_message(missing_words, 'GROUP_ROUTES', 'rename_child_paths')
+ end
+
+ expect(described_class::GROUP_ROUTES)
+ .to include(*paths_after_group_id), failure_block
+ end
+ end
+
+ describe 'PROJECT_WILDCARD_ROUTES' do
+ it 'includes all paths that can be used after a namespace/project path' do
+ aggregate_failures do
+ all_wildcard_paths.each do |path|
+ expect(wildcards_include?(path))
+ .to be(true), failure_message(path, 'PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths')
+ end
+ end
+ end
+ end
+
+ describe '.root_namespace_path_regex' do
+ subject { described_class.root_namespace_path_regex }
+
+ it 'rejects top level routes' do
+ expect(subject).not_to match('admin/')
+ expect(subject).not_to match('api/')
+ expect(subject).not_to match('.well-known/')
+ end
+
+ it 'accepts project wildcard routes' do
+ expect(subject).to match('blob/')
+ expect(subject).to match('edit/')
+ expect(subject).to match('wikis/')
+ end
+
+ it 'accepts group routes' do
+ expect(subject).to match('activity/')
+ expect(subject).to match('group_members/')
+ expect(subject).to match('subgroups/')
+ end
+
+ it 'is not case sensitive' do
+ expect(subject).not_to match('Users/')
+ end
+
+ it 'does not allow extra slashes' do
+ expect(subject).not_to match('/blob/')
+ expect(subject).not_to match('blob//')
+ end
+ end
+
+ describe '.full_namespace_path_regex' do
+ subject { described_class.full_namespace_path_regex }
+
+ context 'at the top level' do
+ context 'when the final level' do
+ it 'rejects top level routes' do
+ expect(subject).not_to match('admin/')
+ expect(subject).not_to match('api/')
+ expect(subject).not_to match('.well-known/')
+ end
+
+ it 'accepts project wildcard routes' do
+ expect(subject).to match('blob/')
+ expect(subject).to match('edit/')
+ expect(subject).to match('wikis/')
+ end
+
+ it 'accepts group routes' do
+ expect(subject).to match('activity/')
+ expect(subject).to match('group_members/')
+ expect(subject).to match('subgroups/')
+ end
+ end
+
+ context 'when more levels follow' do
+ it 'rejects top level routes' do
+ expect(subject).not_to match('admin/more/')
+ expect(subject).not_to match('api/more/')
+ expect(subject).not_to match('.well-known/more/')
+ end
+
+ it 'accepts project wildcard routes' do
+ expect(subject).to match('blob/more/')
+ expect(subject).to match('edit/more/')
+ expect(subject).to match('wikis/more/')
+ expect(subject).to match('environments/folders/')
+ expect(subject).to match('info/lfs/objects/')
+ end
+
+ it 'accepts group routes' do
+ expect(subject).to match('activity/more/')
+ expect(subject).to match('group_members/more/')
+ expect(subject).to match('subgroups/more/')
+ end
+ end
+ end
+
+ context 'at the second level' do
+ context 'when the final level' do
+ it 'accepts top level routes' do
+ expect(subject).to match('root/admin/')
+ expect(subject).to match('root/api/')
+ expect(subject).to match('root/.well-known/')
+ end
+
+ it 'rejects project wildcard routes' do
+ expect(subject).not_to match('root/blob/')
+ expect(subject).not_to match('root/edit/')
+ expect(subject).not_to match('root/wikis/')
+ expect(subject).not_to match('root/environments/folders/')
+ expect(subject).not_to match('root/info/lfs/objects/')
+ end
+
+ it 'rejects group routes' do
+ expect(subject).not_to match('root/activity/')
+ expect(subject).not_to match('root/group_members/')
+ expect(subject).not_to match('root/subgroups/')
+ end
+ end
+
+ context 'when more levels follow' do
+ it 'accepts top level routes' do
+ expect(subject).to match('root/admin/more/')
+ expect(subject).to match('root/api/more/')
+ expect(subject).to match('root/.well-known/more/')
+ end
+
+ it 'rejects project wildcard routes' do
+ expect(subject).not_to match('root/blob/more/')
+ expect(subject).not_to match('root/edit/more/')
+ expect(subject).not_to match('root/wikis/more/')
+ expect(subject).not_to match('root/environments/folders/more/')
+ expect(subject).not_to match('root/info/lfs/objects/more/')
+ end
+
+ it 'rejects group routes' do
+ expect(subject).not_to match('root/activity/more/')
+ expect(subject).not_to match('root/group_members/more/')
+ expect(subject).not_to match('root/subgroups/more/')
+ end
+ end
+ end
+
+ it 'is not case sensitive' do
+ expect(subject).not_to match('root/Blob/')
+ end
+
+ it 'does not allow extra slashes' do
+ expect(subject).not_to match('/root/admin/')
+ expect(subject).not_to match('root/admin//')
+ end
+ end
+
+ describe '.project_path_regex' do
+ subject { described_class.project_path_regex }
+
+ it 'accepts top level routes' do
+ expect(subject).to match('admin/')
+ expect(subject).to match('api/')
+ expect(subject).to match('.well-known/')
+ end
+
+ it 'rejects project wildcard routes' do
+ expect(subject).not_to match('blob/')
+ expect(subject).not_to match('edit/')
+ expect(subject).not_to match('wikis/')
+ expect(subject).not_to match('environments/folders/')
+ expect(subject).not_to match('info/lfs/objects/')
+ end
+
+ it 'accepts group routes' do
+ expect(subject).to match('activity/')
+ expect(subject).to match('group_members/')
+ expect(subject).to match('subgroups/')
+ end
+
+ it 'is not case sensitive' do
+ expect(subject).not_to match('Blob/')
+ end
+
+ it 'does not allow extra slashes' do
+ expect(subject).not_to match('/admin/')
+ expect(subject).not_to match('admin//')
+ end
+ end
+
+ describe '.full_project_path_regex' do
+ subject { described_class.full_project_path_regex }
+
+ it 'accepts top level routes' do
+ expect(subject).to match('root/admin/')
+ expect(subject).to match('root/api/')
+ expect(subject).to match('root/.well-known/')
+ end
+
+ it 'rejects project wildcard routes' do
+ expect(subject).not_to match('root/blob/')
+ expect(subject).not_to match('root/edit/')
+ expect(subject).not_to match('root/wikis/')
+ expect(subject).not_to match('root/environments/folders/')
+ expect(subject).not_to match('root/info/lfs/objects/')
+ end
+
+ it 'accepts group routes' do
+ expect(subject).to match('root/activity/')
+ expect(subject).to match('root/group_members/')
+ expect(subject).to match('root/subgroups/')
+ end
+
+ it 'is not case sensitive' do
+ expect(subject).not_to match('root/Blob/')
+ end
+
+ it 'does not allow extra slashes' do
+ expect(subject).not_to match('/root/admin/')
+ expect(subject).not_to match('root/admin//')
+ end
+ end
+
+ describe '.namespace_format_regex' do
+ subject { described_class.namespace_format_regex }
+
+ it { is_expected.to match('gitlab-ce') }
+ it { is_expected.to match('gitlab_git') }
+ it { is_expected.to match('_underscore.js') }
+ it { is_expected.to match('100px.com') }
+ it { is_expected.to match('gitlab.org') }
+ it { is_expected.not_to match('?gitlab') }
+ it { is_expected.not_to match('git lab') }
+ it { is_expected.not_to match('gitlab.git') }
+ it { is_expected.not_to match('gitlab.org.') }
+ it { is_expected.not_to match('gitlab.org/') }
+ it { is_expected.not_to match('/gitlab.org') }
+ it { is_expected.not_to match('gitlab git') }
+ end
+
+ describe '.project_path_format_regex' do
+ subject { described_class.project_path_format_regex }
+
+ it { is_expected.to match('gitlab-ce') }
+ it { is_expected.to match('gitlab_git') }
+ it { is_expected.to match('_underscore.js') }
+ it { is_expected.to match('100px.com') }
+ it { is_expected.not_to match('?gitlab') }
+ it { is_expected.not_to match('git lab') }
+ it { is_expected.not_to match('gitlab.git') }
+ end
+end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 6e0b1192706..3d22784909d 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -55,7 +55,7 @@ describe Gitlab::ProjectSearchResults, lib: true do
end
it 'finds by name' do
- expect(results).to include(["files/images/wm.svg", nil])
+ expect(results.map(&:first)).to include('files/images/wm.svg')
end
it 'finds by content' do
@@ -123,8 +123,8 @@ describe Gitlab::ProjectSearchResults, lib: true do
context 'when wiki is internal' do
let(:project) { create(:project, :public, :wiki_private) }
- it 'finds wiki blobs for members' do
- project.add_reporter(user)
+ it 'finds wiki blobs for guest' do
+ project.add_guest(user)
is_expected.not_to be_empty
end
diff --git a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
new file mode 100644
index 00000000000..d957dd932c4
--- /dev/null
+++ b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::Prometheus::Queries::DeploymentQuery, lib: true do
+ let(:environment) { create(:environment, slug: 'environment-slug') }
+ let(:deployment) { create(:deployment, environment: environment) }
+
+ let(:client) { double('prometheus_client') }
+ subject { described_class.new(client) }
+
+ around do |example|
+ time_without_subsecond_values = Time.local(2008, 9, 1, 12, 0, 0)
+ Timecop.freeze(time_without_subsecond_values) { example.run }
+ end
+
+ it 'sends appropriate queries to prometheus' do
+ start_time = (deployment.created_at - 30.minutes).to_f
+ stop_time = (deployment.created_at + 30.minutes).to_f
+ created_at = deployment.created_at.to_f
+
+ expect(client).to receive(:query_range).with('avg(container_memory_usage_bytes{container_name!="POD",environment="environment-slug"}) / 2^20',
+ start: start_time, stop: stop_time)
+ expect(client).to receive(:query).with('avg(avg_over_time(container_memory_usage_bytes{container_name!="POD",environment="environment-slug"}[30m]))',
+ time: created_at)
+ expect(client).to receive(:query).with('avg(avg_over_time(container_memory_usage_bytes{container_name!="POD",environment="environment-slug"}[30m]))',
+ time: stop_time)
+
+ expect(client).to receive(:query_range).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[2m])) * 100',
+ start: start_time, stop: stop_time)
+ expect(client).to receive(:query).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[30m])) * 100',
+ time: created_at)
+ expect(client).to receive(:query).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[30m])) * 100',
+ time: stop_time)
+
+ expect(subject.query(deployment.id)).to eq(memory_values: nil, memory_before: nil, memory_after: nil,
+ cpu_values: nil, cpu_before: nil, cpu_after: nil)
+ end
+end
diff --git a/spec/lib/gitlab/prometheus_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index 9d67e3d2f37..2d8bd2f6b97 100644
--- a/spec/lib/gitlab/prometheus_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Prometheus, lib: true do
+describe Gitlab::PrometheusClient, lib: true do
include PrometheusHelpers
subject { described_class.new(api_url: 'https://prometheus.example.com') }
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index a7d1283acb8..0bee892fe0c 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -2,386 +2,6 @@
require 'spec_helper'
describe Gitlab::Regex, lib: true do
- # Pass in a full path to remove the format segment:
- # `/ci/lint(.:format)` -> `/ci/lint`
- def without_format(path)
- path.split('(', 2)[0]
- end
-
- # Pass in a full path and get the last segment before a wildcard
- # That's not a parameter
- # `/*namespace_id/:project_id/builds/artifacts/*ref_name_and_path`
- # -> 'builds/artifacts'
- def path_before_wildcard(path)
- path = path.gsub(STARTING_WITH_NAMESPACE, "")
- path_segments = path.split('/').reject(&:empty?)
- wildcard_index = path_segments.index { |segment| parameter?(segment) }
-
- segments_before_wildcard = path_segments[0..wildcard_index - 1]
-
- segments_before_wildcard.join('/')
- end
-
- def parameter?(segment)
- segment =~ /[*:]/
- end
-
- # If the path is reserved. Then no conflicting paths can# be created for any
- # route using this reserved word.
- #
- # Both `builds/artifacts` & `build` are covered by reserving the word
- # `build`
- def wildcards_include?(path)
- described_class::PROJECT_WILDCARD_ROUTES.include?(path) ||
- described_class::PROJECT_WILDCARD_ROUTES.include?(path.split('/').first)
- end
-
- def failure_message(missing_words, constant_name, migration_helper)
- missing_words = Array(missing_words)
- <<-MSG
- Found new routes that could cause conflicts with existing namespaced routes
- for groups or projects.
-
- Add <#{missing_words.join(', ')}> to `Gitlab::Regex::#{constant_name}
- to make sure no projects or namespaces can be created with those paths.
-
- To rename any existing records with those paths you can use the
- `Gitlab::Database::RenameReservedpathsMigration::<VERSION>.#{migration_helper}`
- migration helper.
-
- Make sure to make a note of the renamed records in the release blog post.
-
- MSG
- end
-
- let(:all_routes) do
- route_set = Rails.application.routes
- routes_collection = route_set.routes
- routes_array = routes_collection.routes
- routes_array.map { |route| route.path.spec.to_s }
- end
-
- let(:routes_without_format) { all_routes.map { |path| without_format(path) } }
-
- # Routes not starting with `/:` or `/*`
- # all routes not starting with a param
- let(:routes_not_starting_in_wildcard) { routes_without_format.select { |p| p !~ %r{^/[:*]} } }
-
- let(:top_level_words) do
- routes_not_starting_in_wildcard.map do |route|
- route.split('/')[1]
- end.compact.uniq
- end
-
- # All routes that start with a namespaced path, that have 1 or more
- # path-segments before having another wildcard parameter.
- # - Starting with paths:
- # - `/*namespace_id/:project_id/`
- # - `/*namespace_id/:id/`
- # - Followed by one or more path-parts not starting with `:` or `*`
- # - Followed by a path-part that includes a wildcard parameter `*`
- # At the time of writing these routes match: http://rubular.com/r/Rv2pDE5Dvw
- STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id}
- NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*}
- ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*}
- WILDCARD_SEGMENT = %r{\*}
- 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}}
- end
- end
-
- # This will return all paths that are used in a namespaced route
- # before another wildcard path:
- #
- # /*namespace_id/:project_id/builds/artifacts/*ref_name_and_path
- # /*namespace_id/:project_id/info/lfs/objects/*oid
- # /*namespace_id/:project_id/commits/*id
- # /*namespace_id/:project_id/builds/:build_id/artifacts/file/*path
- # -> ['builds/artifacts', 'info/lfs/objects', 'commits', 'artifacts/file']
- let(:all_wildcard_paths) do
- namespaced_wildcard_routes.map do |route|
- path_before_wildcard(route)
- end.uniq
- end
-
- STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/}
- let(:group_routes) do
- routes_without_format.select do |path|
- path =~ STARTING_WITH_GROUP
- end
- end
-
- let(:paths_after_group_id) do
- group_routes.map do |route|
- route.gsub(STARTING_WITH_GROUP, '').split('/').first
- end.uniq
- end
-
- describe 'TOP_LEVEL_ROUTES' do
- it 'includes all the top level namespaces' do
- failure_block = lambda do
- missing_words = top_level_words - described_class::TOP_LEVEL_ROUTES
- failure_message(missing_words, 'TOP_LEVEL_ROUTES', 'rename_root_paths')
- end
-
- expect(described_class::TOP_LEVEL_ROUTES)
- .to include(*top_level_words), failure_block
- end
- end
-
- describe 'GROUP_ROUTES' do
- it "don't contain a second wildcard" do
- failure_block = lambda do
- missing_words = paths_after_group_id - described_class::GROUP_ROUTES
- failure_message(missing_words, 'GROUP_ROUTES', 'rename_child_paths')
- end
-
- expect(described_class::GROUP_ROUTES)
- .to include(*paths_after_group_id), failure_block
- end
- end
-
- describe 'PROJECT_WILDCARD_ROUTES' do
- it 'includes all paths that can be used after a namespace/project path' do
- aggregate_failures do
- all_wildcard_paths.each do |path|
- expect(wildcards_include?(path))
- .to be(true), failure_message(path, 'PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths')
- end
- end
- end
- end
-
- describe '.root_namespace_path_regex' do
- subject { described_class.root_namespace_path_regex }
-
- it 'rejects top level routes' do
- expect(subject).not_to match('admin/')
- expect(subject).not_to match('api/')
- expect(subject).not_to match('.well-known/')
- end
-
- it 'accepts project wildcard routes' do
- expect(subject).to match('blob/')
- expect(subject).to match('edit/')
- expect(subject).to match('wikis/')
- end
-
- it 'accepts group routes' do
- expect(subject).to match('activity/')
- expect(subject).to match('group_members/')
- expect(subject).to match('subgroups/')
- end
-
- it 'is not case sensitive' do
- expect(subject).not_to match('Users/')
- end
-
- it 'does not allow extra slashes' do
- expect(subject).not_to match('/blob/')
- expect(subject).not_to match('blob//')
- end
- end
-
- describe '.full_namespace_path_regex' do
- subject { described_class.full_namespace_path_regex }
-
- context 'at the top level' do
- context 'when the final level' do
- it 'rejects top level routes' do
- expect(subject).not_to match('admin/')
- expect(subject).not_to match('api/')
- expect(subject).not_to match('.well-known/')
- end
-
- it 'accepts project wildcard routes' do
- expect(subject).to match('blob/')
- expect(subject).to match('edit/')
- expect(subject).to match('wikis/')
- end
-
- it 'accepts group routes' do
- expect(subject).to match('activity/')
- expect(subject).to match('group_members/')
- expect(subject).to match('subgroups/')
- end
- end
-
- context 'when more levels follow' do
- it 'rejects top level routes' do
- expect(subject).not_to match('admin/more/')
- expect(subject).not_to match('api/more/')
- expect(subject).not_to match('.well-known/more/')
- end
-
- it 'accepts project wildcard routes' do
- expect(subject).to match('blob/more/')
- expect(subject).to match('edit/more/')
- expect(subject).to match('wikis/more/')
- expect(subject).to match('environments/folders/')
- expect(subject).to match('info/lfs/objects/')
- end
-
- it 'accepts group routes' do
- expect(subject).to match('activity/more/')
- expect(subject).to match('group_members/more/')
- expect(subject).to match('subgroups/more/')
- end
- end
- end
-
- context 'at the second level' do
- context 'when the final level' do
- it 'accepts top level routes' do
- expect(subject).to match('root/admin/')
- expect(subject).to match('root/api/')
- expect(subject).to match('root/.well-known/')
- end
-
- it 'rejects project wildcard routes' do
- expect(subject).not_to match('root/blob/')
- expect(subject).not_to match('root/edit/')
- expect(subject).not_to match('root/wikis/')
- expect(subject).not_to match('root/environments/folders/')
- expect(subject).not_to match('root/info/lfs/objects/')
- end
-
- it 'rejects group routes' do
- expect(subject).not_to match('root/activity/')
- expect(subject).not_to match('root/group_members/')
- expect(subject).not_to match('root/subgroups/')
- end
- end
-
- context 'when more levels follow' do
- it 'accepts top level routes' do
- expect(subject).to match('root/admin/more/')
- expect(subject).to match('root/api/more/')
- expect(subject).to match('root/.well-known/more/')
- end
-
- it 'rejects project wildcard routes' do
- expect(subject).not_to match('root/blob/more/')
- expect(subject).not_to match('root/edit/more/')
- expect(subject).not_to match('root/wikis/more/')
- expect(subject).not_to match('root/environments/folders/more/')
- expect(subject).not_to match('root/info/lfs/objects/more/')
- end
-
- it 'rejects group routes' do
- expect(subject).not_to match('root/activity/more/')
- expect(subject).not_to match('root/group_members/more/')
- expect(subject).not_to match('root/subgroups/more/')
- end
- end
- end
-
- it 'is not case sensitive' do
- expect(subject).not_to match('root/Blob/')
- end
-
- it 'does not allow extra slashes' do
- expect(subject).not_to match('/root/admin/')
- expect(subject).not_to match('root/admin//')
- end
- end
-
- describe '.project_path_regex' do
- subject { described_class.project_path_regex }
-
- it 'accepts top level routes' do
- expect(subject).to match('admin/')
- expect(subject).to match('api/')
- expect(subject).to match('.well-known/')
- end
-
- it 'rejects project wildcard routes' do
- expect(subject).not_to match('blob/')
- expect(subject).not_to match('edit/')
- expect(subject).not_to match('wikis/')
- expect(subject).not_to match('environments/folders/')
- expect(subject).not_to match('info/lfs/objects/')
- end
-
- it 'accepts group routes' do
- expect(subject).to match('activity/')
- expect(subject).to match('group_members/')
- expect(subject).to match('subgroups/')
- end
-
- it 'is not case sensitive' do
- expect(subject).not_to match('Blob/')
- end
-
- it 'does not allow extra slashes' do
- expect(subject).not_to match('/admin/')
- expect(subject).not_to match('admin//')
- end
- end
-
- describe '.full_project_path_regex' do
- subject { described_class.full_project_path_regex }
-
- it 'accepts top level routes' do
- expect(subject).to match('root/admin/')
- expect(subject).to match('root/api/')
- expect(subject).to match('root/.well-known/')
- end
-
- it 'rejects project wildcard routes' do
- expect(subject).not_to match('root/blob/')
- expect(subject).not_to match('root/edit/')
- expect(subject).not_to match('root/wikis/')
- expect(subject).not_to match('root/environments/folders/')
- expect(subject).not_to match('root/info/lfs/objects/')
- end
-
- it 'accepts group routes' do
- expect(subject).to match('root/activity/')
- expect(subject).to match('root/group_members/')
- expect(subject).to match('root/subgroups/')
- end
-
- it 'is not case sensitive' do
- expect(subject).not_to match('root/Blob/')
- end
-
- it 'does not allow extra slashes' do
- expect(subject).not_to match('/root/admin/')
- expect(subject).not_to match('root/admin//')
- end
- end
-
- describe '.namespace_regex' do
- subject { described_class.namespace_regex }
-
- it { is_expected.to match('gitlab-ce') }
- it { is_expected.to match('gitlab_git') }
- it { is_expected.to match('_underscore.js') }
- it { is_expected.to match('100px.com') }
- it { is_expected.to match('gitlab.org') }
- it { is_expected.not_to match('?gitlab') }
- it { is_expected.not_to match('git lab') }
- it { is_expected.not_to match('gitlab.git') }
- it { is_expected.not_to match('gitlab.org.') }
- it { is_expected.not_to match('gitlab.org/') }
- it { is_expected.not_to match('/gitlab.org') }
- it { is_expected.not_to match('gitlab git') }
- end
-
- describe '.project_path_format_regex' do
- subject { described_class.project_path_format_regex }
-
- it { is_expected.to match('gitlab-ce') }
- it { is_expected.to match('gitlab_git') }
- it { is_expected.to match('_underscore.js') }
- it { is_expected.to match('100px.com') }
- it { is_expected.not_to match('?gitlab') }
- it { is_expected.not_to match('git lab') }
- it { is_expected.not_to match('gitlab.git') }
- end
-
describe '.project_name_regex' do
subject { described_class.project_name_regex }
@@ -412,16 +32,4 @@ describe Gitlab::Regex, lib: true do
it { is_expected.not_to match('9foo') }
it { is_expected.not_to match('foo-') }
end
-
- describe '.full_namespace_regex' do
- subject { described_class.full_namespace_regex }
-
- it { is_expected.to match('gitlab.org') }
- it { is_expected.to match('gitlab.org/gitlab-git') }
- it { is_expected.not_to match('gitlab.org.') }
- it { is_expected.not_to match('gitlab.org/') }
- it { is_expected.not_to match('/gitlab.org') }
- it { is_expected.not_to match('gitlab.git') }
- it { is_expected.not_to match('gitlab git') }
- end
end
diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index f94c9c2e315..f9025397107 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -29,7 +29,7 @@ describe ::Gitlab::RepoPath do
before do
allow(Gitlab.config.repositories).to receive(:storages).and_return({
'storage1' => { 'path' => '/foo' },
- 'storage2' => { 'path' => '/bar' },
+ 'storage2' => { 'path' => '/bar' }
})
end
diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb
new file mode 100644
index 00000000000..7c77772b3f6
--- /dev/null
+++ b/spec/lib/gitlab/string_range_marker_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Gitlab::StringRangeMarker, lib: true do
+ describe '#mark' do
+ context "when the rich text is html safe" do
+ let(:raw) { "abc <def>" }
+ let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&lt;def&gt;</span>}.html_safe }
+ let(:inline_diffs) { [2..5] }
+ subject do
+ described_class.new(raw, rich).mark(inline_diffs) do |text, left:, right:|
+ "LEFT#{text}RIGHT"
+ end
+ end
+
+ it 'marks the inline diffs' do
+ expect(subject).to eq(%{<span class="abc">abLEFTcRIGHT</span><span class="space">LEFT RIGHT</span><span class="def">LEFT&lt;dRIGHTef&gt;</span>})
+ expect(subject).to be_html_safe
+ end
+ end
+
+ context "when the rich text is not html safe" do
+ let(:raw) { "abc <def>" }
+ let(:inline_diffs) { [2..5] }
+ subject do
+ described_class.new(raw).mark(inline_diffs) do |text, left:, right:|
+ "LEFT#{text}RIGHT"
+ end
+ end
+
+ it 'marks the inline diffs' do
+ expect(subject).to eq(%{abLEFTc &lt;dRIGHTef&gt;})
+ expect(subject).to be_html_safe
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb
new file mode 100644
index 00000000000..2f5cf6c6e3b
--- /dev/null
+++ b/spec/lib/gitlab/string_regex_marker_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Gitlab::StringRegexMarker, lib: true do
+ describe '#mark' do
+ let(:raw) { %{"name": "AFNetworking"} }
+ let(:rich) { %{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"AFNetworking"</span>}.html_safe }
+ subject do
+ described_class.new(raw, rich).mark(/"[^"]+":\s*"(?<name>[^"]+)"/, group: :name) do |text, left:, right:|
+ %{<a href="#">#{text}</a>}
+ end
+ end
+
+ it 'marks the inline diffs' do
+ expect(subject).to eq(%{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"<a href="#">AFNetworking</a>"</span>})
+ expect(subject).to be_html_safe
+ end
+ end
+end
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 3fe8cf43934..e8a37e8d77b 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -97,6 +97,17 @@ describe Gitlab::UrlBuilder, lib: true do
end
end
+ context 'on a PersonalSnippet' do
+ it 'returns a proper URL' do
+ personal_snippet = create(:personal_snippet)
+ note = build_stubbed(:note_on_personal_snippet, noteable: personal_snippet)
+
+ url = described_class.build(note)
+
+ expect(url).to eq "#{Settings.gitlab['url']}/snippets/#{note.noteable_id}#note_#{note.id}"
+ end
+ end
+
context 'on another object' do
it 'returns a proper URL' do
project = build_stubbed(:empty_project)
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index fc144a2556a..6bce724a3f6 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -62,11 +62,6 @@ describe Gitlab::UrlSanitizer, lib: true do
end
end
- describe '.http_credentials_for_user' do
- it { expect(described_class.http_credentials_for_user(user)).to eq({ user: 'john.doe' }) }
- it { expect(described_class.http_credentials_for_user('foo')).to eq({}) }
- end
-
describe '#sanitized_url' do
it { expect(url_sanitizer.sanitized_url).to eq("https://github.com/me/project.git") }
end
@@ -76,7 +71,7 @@ describe Gitlab::UrlSanitizer, lib: true do
context 'when user is given to #initialize' do
let(:url_sanitizer) do
- described_class.new("https://github.com/me/project.git", credentials: described_class.http_credentials_for_user(user))
+ described_class.new("https://github.com/me/project.git", credentials: { user: user.username })
end
it { expect(url_sanitizer.credentials).to eq({ user: 'john.doe' }) }
@@ -94,7 +89,7 @@ describe Gitlab::UrlSanitizer, lib: true do
context 'when user is given to #initialize' do
let(:url_sanitizer) do
- described_class.new("https://github.com/me/project.git", credentials: described_class.http_credentials_for_user(user))
+ described_class.new("https://github.com/me/project.git", credentials: { user: user.username })
end
it { expect(url_sanitizer.full_url).to eq("https://john.doe@github.com/me/project.git") }
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index 2b27ff66c09..0d87cf25dbb 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::UserAccess, lib: true do
let(:project) { create(:project) }
let(:user) { create(:user) }
- describe 'can_push_to_branch?' do
+ describe '#can_push_to_branch?' do
describe 'push to none protected branch' do
it 'returns true if user is a master' do
project.team << [user, :master]
@@ -143,7 +143,7 @@ describe Gitlab::UserAccess, lib: true do
end
end
- describe 'can_create_tag?' do
+ describe '#can_create_tag?' do
describe 'push to none protected tag' do
it 'returns true if user is a master' do
project.add_user(user, :master)
@@ -211,4 +211,48 @@ describe Gitlab::UserAccess, lib: true do
end
end
end
+
+ describe '#can_delete_branch?' do
+ describe 'delete unprotected branch' do
+ it 'returns true if user is a master' do
+ project.add_user(user, :master)
+
+ expect(access.can_delete_branch?('random_branch')).to be_truthy
+ end
+
+ it 'returns true if user is a developer' do
+ project.add_user(user, :developer)
+
+ expect(access.can_delete_branch?('random_branch')).to be_truthy
+ end
+
+ it 'returns false if user is a reporter' do
+ project.add_user(user, :reporter)
+
+ expect(access.can_delete_branch?('random_branch')).to be_falsey
+ end
+ end
+
+ describe 'delete protected branch' do
+ let(:branch) { create(:protected_branch, project: project, name: "test") }
+
+ it 'returns true if user is a master' do
+ project.add_user(user, :master)
+
+ expect(access.can_delete_branch?(branch.name)).to be_truthy
+ end
+
+ it 'returns false if user is a developer' do
+ project.add_user(user, :developer)
+
+ expect(access.can_delete_branch?(branch.name)).to be_falsey
+ end
+
+ it 'returns false if user is a reporter' do
+ project.add_user(user, :reporter)
+
+ expect(access.can_delete_branch?(branch.name)).to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 56772409989..00941aec380 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -1,5 +1,7 @@
+require 'spec_helper'
+
describe Gitlab::Utils, lib: true do
- delegate :to_boolean, to: :described_class
+ delegate :to_boolean, :boolean_to_yes_no, to: :described_class
describe '.to_boolean' do
it 'accepts booleans' do
@@ -30,4 +32,11 @@ describe Gitlab::Utils, lib: true do
expect(to_boolean(nil)).to be_nil
end
end
+
+ describe '.boolean_to_yes_no' do
+ it 'converts booleans to Yes or No' do
+ expect(boolean_to_yes_no(true)).to eq('Yes')
+ expect(boolean_to_yes_no(false)).to eq('No')
+ end
+ end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 093f9301603..b1999409170 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -214,7 +214,7 @@ describe Gitlab::Workhorse, lib: true do
repo_param = { Repository: {
path: repo_path,
storage_name: 'default',
- relative_path: project.full_path + '.git',
+ relative_path: project.full_path + '.git'
} }
expect(subject).to include(repo_param)
@@ -244,7 +244,7 @@ describe Gitlab::Workhorse, lib: true do
context "when git_receive_pack action is passed" do
let(:action) { 'git_receive_pack' }
- it { expect(subject).not_to include(gitaly_params) }
+ it { expect(subject).to include(gitaly_params) }
end
context "when info_refs action is passed" do
diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb
new file mode 100644
index 00000000000..a5c6170cd7d
--- /dev/null
+++ b/spec/lib/system_check/simple_executor_spec.rb
@@ -0,0 +1,223 @@
+require 'spec_helper'
+require 'rake_helper'
+
+describe SystemCheck::SimpleExecutor, lib: true do
+ class SimpleCheck < SystemCheck::BaseCheck
+ set_name 'my simple check'
+
+ def check?
+ true
+ end
+ end
+
+ class OtherCheck < SystemCheck::BaseCheck
+ set_name 'other check'
+
+ def check?
+ false
+ end
+
+ def show_error
+ $stdout.puts 'this is an error text'
+ end
+ end
+
+ class SkipCheck < SystemCheck::BaseCheck
+ set_name 'skip check'
+ set_skip_reason 'this is a skip reason'
+
+ def skip?
+ true
+ end
+
+ def check?
+ raise 'should not execute this'
+ end
+ end
+
+ class MultiCheck < SystemCheck::BaseCheck
+ set_name 'multi check'
+
+ def multi_check
+ $stdout.puts 'this is a multi output check'
+ end
+
+ def check?
+ raise 'should not execute this'
+ end
+ end
+
+ class SkipMultiCheck < SystemCheck::BaseCheck
+ set_name 'skip multi check'
+
+ def skip?
+ true
+ end
+
+ def multi_check
+ raise 'should not execute this'
+ end
+ end
+
+ class RepairCheck < SystemCheck::BaseCheck
+ set_name 'repair check'
+
+ def check?
+ false
+ end
+
+ def repair!
+ true
+ end
+
+ def show_error
+ $stdout.puts 'this is an error message'
+ end
+ end
+
+ describe '#component' do
+ it 'returns stored component name' do
+ expect(subject.component).to eq('Test')
+ end
+ end
+
+ describe '#checks' do
+ before do
+ subject << SimpleCheck
+ end
+
+ it 'returns a set of classes' do
+ expect(subject.checks).to include(SimpleCheck)
+ end
+ end
+
+ describe '#<<' do
+ before do
+ subject << SimpleCheck
+ end
+
+ it 'appends a new check to the Set' do
+ subject << OtherCheck
+ stored_checks = subject.checks.to_a
+
+ expect(stored_checks.first).to eq(SimpleCheck)
+ expect(stored_checks.last).to eq(OtherCheck)
+ end
+
+ it 'inserts unique itens only' do
+ subject << SimpleCheck
+
+ expect(subject.checks.size).to eq(1)
+ end
+ end
+
+ subject { described_class.new('Test') }
+
+ describe '#execute' do
+ before do
+ silence_output
+
+ subject << SimpleCheck
+ subject << OtherCheck
+ end
+
+ it 'runs included checks' do
+ expect(subject).to receive(:run_check).with(SimpleCheck)
+ expect(subject).to receive(:run_check).with(OtherCheck)
+
+ subject.execute
+ end
+ end
+
+ describe '#run_check' do
+ it 'prints check name' do
+ expect(SimpleCheck).to receive(:display_name).and_call_original
+ expect { subject.run_check(SimpleCheck) }.to output(/my simple check/).to_stdout
+ end
+
+ context 'when check pass' do
+ it 'prints yes' do
+ expect_any_instance_of(SimpleCheck).to receive(:check?).and_call_original
+ expect { subject.run_check(SimpleCheck) }.to output(/ \.\.\. yes/).to_stdout
+ end
+ end
+
+ context 'when check fails' do
+ it 'prints no' do
+ expect_any_instance_of(OtherCheck).to receive(:check?).and_call_original
+ expect { subject.run_check(OtherCheck) }.to output(/ \.\.\. no/).to_stdout
+ end
+
+ it 'displays error message from #show_error' do
+ expect_any_instance_of(OtherCheck).to receive(:show_error).and_call_original
+ expect { subject.run_check(OtherCheck) }.to output(/this is an error text/).to_stdout
+ end
+
+ context 'when check implements #repair!' do
+ it 'executes #repair!' do
+ expect_any_instance_of(RepairCheck).to receive(:repair!)
+
+ subject.run_check(RepairCheck)
+ end
+
+ context 'when repair succeeds' do
+ it 'does not execute #show_error' do
+ expect_any_instance_of(RepairCheck).to receive(:repair!).and_call_original
+ expect_any_instance_of(RepairCheck).not_to receive(:show_error)
+
+ subject.run_check(RepairCheck)
+ end
+ end
+
+ context 'when repair fails' do
+ it 'does not execute #show_error' do
+ expect_any_instance_of(RepairCheck).to receive(:repair!) { false }
+ expect_any_instance_of(RepairCheck).to receive(:show_error)
+
+ subject.run_check(RepairCheck)
+ end
+ end
+ end
+ end
+
+ context 'when check implements skip?' do
+ it 'executes #skip? method' do
+ expect_any_instance_of(SkipCheck).to receive(:skip?).and_call_original
+
+ subject.run_check(SkipCheck)
+ end
+
+ it 'displays #skip_reason' do
+ expect { subject.run_check(SkipCheck) }.to output(/this is a skip reason/).to_stdout
+ end
+
+ it 'does not execute #check when #skip? is true' do
+ expect_any_instance_of(SkipCheck).not_to receive(:check?)
+
+ subject.run_check(SkipCheck)
+ end
+ end
+
+ context 'when implements a #multi_check' do
+ it 'executes #multi_check method' do
+ expect_any_instance_of(MultiCheck).to receive(:multi_check)
+
+ subject.run_check(MultiCheck)
+ end
+
+ it 'does not execute #check method' do
+ expect_any_instance_of(MultiCheck).not_to receive(:check)
+
+ subject.run_check(MultiCheck)
+ end
+
+ context 'when check implements #skip?' do
+ it 'executes #skip? method' do
+ expect_any_instance_of(SkipMultiCheck).to receive(:skip?).and_call_original
+
+ subject.run_check(SkipMultiCheck)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/system_check_spec.rb b/spec/lib/system_check_spec.rb
new file mode 100644
index 00000000000..23d9beddb08
--- /dev/null
+++ b/spec/lib/system_check_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+require 'rake_helper'
+
+describe SystemCheck, lib: true do
+ class SimpleCheck < SystemCheck::BaseCheck
+ def check?
+ true
+ end
+ end
+
+ class OtherCheck < SystemCheck::BaseCheck
+ def check?
+ false
+ end
+ end
+
+ before do
+ silence_output
+ end
+
+ describe '.run' do
+ subject { SystemCheck }
+
+ it 'detects execution of SimpleCheck' do
+ is_expected.to execute_check(SimpleCheck)
+
+ subject.run('Test', [SimpleCheck])
+ end
+
+ it 'detects exclusion of OtherCheck in execution' do
+ is_expected.not_to execute_check(OtherCheck)
+
+ subject.run('Test', [SimpleCheck])
+ end
+ end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 1e6260270fe..ec6f6c42eac 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -128,6 +128,15 @@ describe Notify do
is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
end
end
+
+ context 'with a preferred language' do
+ before { Gitlab::I18n.locale = :es }
+ after { Gitlab::I18n.use_default_locale }
+
+ it 'always generates the email using the default language' do
+ is_expected.to have_body_text('foo, bar, and baz')
+ end
+ end
end
describe 'status changed' do
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
new file mode 100644
index 00000000000..bd5f85b901d
--- /dev/null
+++ b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb')
+
+describe AddHeadPipelineForEachMergeRequest do
+ let(:migration) { described_class.new }
+
+ let!(:project) { create(:empty_project) }
+ let!(:forked_project_link) { create(:forked_project_link, forked_from_project: project) }
+ let!(:other_project) { forked_project_link.forked_to_project }
+
+ let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: "branch_1") }
+ let!(:pipeline_2) { create(:ci_pipeline, project: other_project, ref: "branch_1") }
+ let!(:pipeline_3) { create(:ci_pipeline, project: other_project, ref: "branch_1") }
+ let!(:pipeline_4) { create(:ci_pipeline, project: project, ref: "branch_2") }
+
+ let!(:mr_1) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_1", target_branch: "target_1") }
+ let!(:mr_2) { create(:merge_request, source_project: other_project, target_project: project, source_branch: "branch_1", target_branch: "target_2") }
+ let!(:mr_3) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_2", target_branch: "master") }
+ let!(:mr_4) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_3", target_branch: "master") }
+
+ context "#up" do
+ context "when source_project and source_branch of pipeline are the same of merge request" do
+ it "sets head_pipeline_id of given merge requests" do
+ migration.up
+
+ expect(mr_1.reload.head_pipeline_id).to eq(pipeline_1.id)
+ expect(mr_2.reload.head_pipeline_id).to eq(pipeline_3.id)
+ expect(mr_3.reload.head_pipeline_id).to eq(pipeline_4.id)
+ expect(mr_4.reload.head_pipeline_id).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
new file mode 100644
index 00000000000..49e750a3f4d
--- /dev/null
+++ b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170502101023_cleanup_namespaceless_pending_delete_projects.rb')
+
+describe CleanupNamespacelessPendingDeleteProjects do
+ before do
+ # Stub after_save callbacks that will fail when Project has no namespace
+ allow_any_instance_of(Project).to receive(:ensure_dir_exist).and_return(nil)
+ allow_any_instance_of(Project).to receive(:update_project_statistics).and_return(nil)
+ end
+
+ describe '#up' do
+ it 'only cleans up pending delete projects' do
+ create(:empty_project)
+ create(:empty_project, pending_delete: true)
+ project = build(:empty_project, pending_delete: true, namespace_id: nil)
+ project.save(validate: false)
+
+ expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]])
+
+ described_class.new.up
+ end
+
+ it 'does nothing when no pending delete projects without namespace found' do
+ create(:empty_project)
+ create(:empty_project, pending_delete: true)
+
+ expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async)
+
+ described_class.new.up
+ end
+ end
+end
diff --git a/spec/migrations/migrate_build_events_to_pipeline_events_spec.rb b/spec/migrations/migrate_build_events_to_pipeline_events_spec.rb
deleted file mode 100644
index 57eb03e3c80..00000000000
--- a/spec/migrations/migrate_build_events_to_pipeline_events_spec.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170301205640_migrate_build_events_to_pipeline_events.rb')
-
-# This migration uses multiple threads, and thus different transactions. This
-# means data created in this spec may not be visible to some threads. To work
-# around this we use the TRUNCATE cleaning strategy.
-describe MigrateBuildEventsToPipelineEvents, truncate: true do
- let(:migration) { described_class.new }
- let(:project_with_pipeline_service) { create(:empty_project) }
- let(:project_with_build_service) { create(:empty_project) }
-
- before do
- ActiveRecord::Base.connection.execute <<-SQL
- INSERT INTO services (properties, build_events, pipeline_events, type)
- VALUES
- ('{"notify_only_broken_builds":true}', true, false, 'SlackService')
- , ('{"notify_only_broken_builds":true}', true, false, 'MattermostService')
- , ('{"notify_only_broken_builds":true}', true, false, 'HipchatService')
- ;
- SQL
-
- ActiveRecord::Base.connection.execute <<-SQL
- INSERT INTO services
- (properties, build_events, pipeline_events, type, project_id)
- VALUES
- ('{"notify_only_broken_builds":true}', true, false,
- 'BuildsEmailService', #{project_with_pipeline_service.id})
- , ('{"notify_only_broken_pipelines":true}', false, true,
- 'PipelinesEmailService', #{project_with_pipeline_service.id})
- , ('{"notify_only_broken_builds":true}', true, false,
- 'BuildsEmailService', #{project_with_build_service.id})
- ;
- SQL
- end
-
- describe '#up' do
- before do
- silence_migration = Module.new do
- # rubocop:disable Rails/Delegate
- def execute(query)
- connection.execute(query)
- end
- end
-
- migration.extend(silence_migration)
- migration.up
- end
-
- it 'migrates chat service properly' do
- [SlackService, MattermostService, HipchatService].each do |service|
- expect(service.count).to eq(1)
-
- verify_service_record(service.first)
- end
- end
-
- it 'migrates pipelines email service only if it has none before' do
- Project.find_each do |project|
- pipeline_service_count =
- project.services.where(type: 'PipelinesEmailService').count
-
- expect(pipeline_service_count).to eq(1)
-
- verify_service_record(project.pipelines_email_service)
- end
- end
-
- def verify_service_record(service)
- expect(service.notify_only_broken_pipelines).to be(true)
- expect(service.build_events).to be(false)
- expect(service.pipeline_events).to be(true)
- end
- end
-end
diff --git a/spec/migrations/migrate_build_stage_reference_spec.rb b/spec/migrations/migrate_build_stage_reference_spec.rb
new file mode 100644
index 00000000000..80b321860c2
--- /dev/null
+++ b/spec/migrations/migrate_build_stage_reference_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170526185921_migrate_build_stage_reference.rb')
+
+describe MigrateBuildStageReference, :migration do
+ ##
+ # Create test data - pipeline and CI/CD jobs.
+ #
+
+ let(:jobs) { table(:ci_builds) }
+ let(:stages) { table(:ci_stages) }
+ let(:pipelines) { table(:ci_pipelines) }
+ let(:projects) { table(:projects) }
+
+ before do
+ # Create projects
+ #
+ projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
+ projects.create!(id: 456, name: 'gitlab2', path: 'gitlab2')
+
+ # Create CI/CD pipelines
+ #
+ pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
+ pipelines.create!(id: 2, project_id: 456, ref: 'feature', sha: '21a3deb')
+
+ # Create CI/CD jobs
+ #
+ jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
+ jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
+ jobs.create!(id: 3, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test')
+ jobs.create!(id: 4, commit_id: 1, project_id: 123, stage_idx: 3, stage: 'deploy')
+ jobs.create!(id: 5, commit_id: 2, project_id: 456, stage_idx: 2, stage: 'test:2')
+ jobs.create!(id: 6, commit_id: 2, project_id: 456, stage_idx: 1, stage: 'test:1')
+ jobs.create!(id: 7, commit_id: 2, project_id: 456, stage_idx: 1, stage: 'test:1')
+ jobs.create!(id: 8, commit_id: 3, project_id: 789, stage_idx: 3, stage: 'deploy')
+
+ # Create CI/CD stages
+ #
+ stages.create(id: 101, pipeline_id: 1, project_id: 123, name: 'test')
+ stages.create(id: 102, pipeline_id: 1, project_id: 123, name: 'build')
+ stages.create(id: 103, pipeline_id: 1, project_id: 123, name: 'deploy')
+ stages.create(id: 104, pipeline_id: 2, project_id: 456, name: 'test:1')
+ stages.create(id: 105, pipeline_id: 2, project_id: 456, name: 'test:2')
+ stages.create(id: 106, pipeline_id: 2, project_id: 456, name: 'deploy')
+ end
+
+ it 'correctly migrate build stage references' do
+ expect(jobs.where(stage_id: nil).count).to eq 8
+
+ migrate!
+
+ expect(jobs.where(stage_id: nil).count).to eq 1
+
+ expect(jobs.find(1).stage_id).to eq 102
+ expect(jobs.find(2).stage_id).to eq 102
+ expect(jobs.find(3).stage_id).to eq 101
+ expect(jobs.find(4).stage_id).to eq 103
+ expect(jobs.find(5).stage_id).to eq 105
+ expect(jobs.find(6).stage_id).to eq 104
+ expect(jobs.find(7).stage_id).to eq 104
+ expect(jobs.find(8).stage_id).to eq nil
+ end
+end
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
new file mode 100644
index 00000000000..50f4bbda001
--- /dev/null
+++ b/spec/migrations/migrate_old_artifacts_spec.rb
@@ -0,0 +1,117 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170523083112_migrate_old_artifacts.rb')
+
+describe MigrateOldArtifacts do
+ let(:migration) { described_class.new }
+ let!(:directory) { Dir.mktmpdir }
+
+ before do
+ allow(Gitlab.config.artifacts).to receive(:path).and_return(directory)
+ end
+
+ after do
+ FileUtils.remove_entry_secure(directory)
+ end
+
+ context 'with migratable data' do
+ let(:project1) { create(:empty_project, ci_id: 2) }
+ let(:project2) { create(:empty_project, ci_id: 3) }
+ let(:project3) { create(:empty_project) }
+
+ let(:pipeline1) { create(:ci_empty_pipeline, project: project1) }
+ let(:pipeline2) { create(:ci_empty_pipeline, project: project2) }
+ let(:pipeline3) { create(:ci_empty_pipeline, project: project3) }
+
+ let!(:build_with_legacy_artifacts) { create(:ci_build, pipeline: pipeline1) }
+ let!(:build_without_artifacts) { create(:ci_build, pipeline: pipeline1) }
+ let!(:build2) { create(:ci_build, :artifacts, pipeline: pipeline2) }
+ let!(:build3) { create(:ci_build, :artifacts, pipeline: pipeline3) }
+
+ before do
+ store_artifacts_in_legacy_path(build_with_legacy_artifacts)
+ end
+
+ it "legacy artifacts are not accessible" do
+ expect(build_with_legacy_artifacts.artifacts?).to be_falsey
+ end
+
+ it "legacy artifacts are set" do
+ expect(build_with_legacy_artifacts.artifacts_file_identifier).not_to be_nil
+ end
+
+ describe '#min_id' do
+ subject { migration.send(:min_id) }
+
+ it 'returns the newest build for which ci_id is not defined' do
+ is_expected.to eq(build3.id)
+ end
+ end
+
+ describe '#builds_with_artifacts' do
+ subject { migration.send(:builds_with_artifacts).map(&:id) }
+
+ it 'returns a list of builds that has artifacts and could be migrated' do
+ is_expected.to contain_exactly(build_with_legacy_artifacts.id, build2.id)
+ end
+ end
+
+ describe '#up' do
+ context 'when migrating artifacts' do
+ before do
+ migration.up
+ end
+
+ it 'all files do have artifacts' do
+ Ci::Build.with_artifacts do |build|
+ expect(build).to have_artifacts
+ end
+ end
+
+ it 'artifacts are no longer present on legacy path' do
+ expect(File.exist?(legacy_path(build_with_legacy_artifacts))).to eq(false)
+ end
+ end
+
+ context 'when there are aritfacts in old and new directory' do
+ before do
+ store_artifacts_in_legacy_path(build2)
+
+ migration.up
+ end
+
+ it 'does not move old files' do
+ expect(File.exist?(legacy_path(build2))).to eq(true)
+ end
+ end
+ end
+
+ private
+
+ def store_artifacts_in_legacy_path(build)
+ FileUtils.mkdir_p(legacy_path(build))
+
+ FileUtils.copy(
+ Rails.root.join('spec/fixtures/ci_build_artifacts.zip'),
+ File.join(legacy_path(build), "ci_build_artifacts.zip"))
+
+ FileUtils.copy(
+ Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
+ File.join(legacy_path(build), "ci_build_artifacts_metadata.gz"))
+
+ build.update_columns(
+ artifacts_file: 'ci_build_artifacts.zip',
+ artifacts_metadata: 'ci_build_artifacts_metadata.gz')
+
+ build.reload
+ end
+
+ def legacy_path(build)
+ File.join(directory,
+ build.created_at.utc.strftime('%Y_%m'),
+ build.project.ci_id.to_s,
+ build.id.to_s)
+ end
+ end
+end
diff --git a/spec/migrations/migrate_pipeline_stages_spec.rb b/spec/migrations/migrate_pipeline_stages_spec.rb
new file mode 100644
index 00000000000..c47f2bb8ff9
--- /dev/null
+++ b/spec/migrations/migrate_pipeline_stages_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170526185842_migrate_pipeline_stages.rb')
+
+describe MigratePipelineStages, :migration do
+ ##
+ # Create test data - pipeline and CI/CD jobs.
+ #
+
+ let(:jobs) { table(:ci_builds) }
+ let(:stages) { table(:ci_stages) }
+ let(:pipelines) { table(:ci_pipelines) }
+ let(:projects) { table(:projects) }
+
+ before do
+ # Create projects
+ #
+ projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
+ projects.create!(id: 456, name: 'gitlab2', path: 'gitlab2')
+
+ # Create CI/CD pipelines
+ #
+ pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
+ pipelines.create!(id: 2, project_id: 456, ref: 'feature', sha: '21a3deb')
+
+ # Create CI/CD jobs
+ #
+ jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
+ jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
+ jobs.create!(id: 3, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test')
+ jobs.create!(id: 4, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test')
+ jobs.create!(id: 5, commit_id: 1, project_id: 123, stage_idx: 3, stage: 'deploy')
+ jobs.create!(id: 6, commit_id: 2, project_id: 456, stage_idx: 3, stage: 'deploy')
+ jobs.create!(id: 7, commit_id: 2, project_id: 456, stage_idx: 2, stage: 'test:2')
+ jobs.create!(id: 8, commit_id: 2, project_id: 456, stage_idx: 1, stage: 'test:1')
+ jobs.create!(id: 9, commit_id: 2, project_id: 456, stage_idx: 1, stage: 'test:1')
+ jobs.create!(id: 10, commit_id: 2, project_id: 456, stage_idx: 2, stage: 'test:2')
+ jobs.create!(id: 11, commit_id: 3, project_id: 456, stage_idx: 3, stage: 'deploy')
+ jobs.create!(id: 12, commit_id: 2, project_id: 789, stage_idx: 3, stage: 'deploy')
+ end
+
+ it 'correctly migrates pipeline stages' do
+ expect(stages.count).to be_zero
+
+ migrate!
+
+ expect(stages.count).to eq 6
+ expect(stages.all.pluck(:name))
+ .to match_array %w[test build deploy test:1 test:2 deploy]
+ expect(stages.where(pipeline_id: 1).order(:id).pluck(:name))
+ .to eq %w[test build deploy]
+ expect(stages.where(pipeline_id: 2).order(:id).pluck(:name))
+ .to eq %w[test:1 test:2 deploy]
+ expect(stages.where(pipeline_id: 3).count).to be_zero
+ expect(stages.where(project_id: 789).count).to be_zero
+ end
+end
diff --git a/spec/migrations/migrate_user_project_view_spec.rb b/spec/migrations/migrate_user_project_view_spec.rb
index dacaa834aa9..70f8e0d6082 100644
--- a/spec/migrations/migrate_user_project_view_spec.rb
+++ b/spec/migrations/migrate_user_project_view_spec.rb
@@ -5,7 +5,12 @@ require Rails.root.join('db', 'post_migrate', '20170406142253_migrate_user_proje
describe MigrateUserProjectView do
let(:migration) { described_class.new }
- let!(:user) { create(:user, project_view: 'readme') }
+ let!(:user) { create(:user) }
+
+ before do
+ # 0 is the numeric value for the old 'readme' option
+ user.update_column(:project_view, 0)
+ end
describe '#up' do
it 'updates project view setting with new value' do
diff --git a/spec/migrations/update_retried_for_ci_build_spec.rb b/spec/migrations/update_retried_for_ci_build_spec.rb
new file mode 100644
index 00000000000..3742b4dafe5
--- /dev/null
+++ b/spec/migrations/update_retried_for_ci_build_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170503004427_update_retried_for_ci_build.rb')
+
+describe UpdateRetriedForCiBuild, truncate: true 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') }
+
+ before do
+ described_class.new.up
+ end
+
+ it 'updates ci_builds.is_retried' do
+ expect(build_old.reload).to be_retried
+ expect(build_new.reload).not_to be_retried
+ end
+end
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index ced93c8f762..90aec2b45e6 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -28,9 +28,7 @@ RSpec.describe AbuseReport, type: :model do
end
it 'lets a worker delete the user' do
- expect(DeleteUserWorker).to receive(:perform_async).with(user.id, subject.user.id,
- delete_solo_owned_groups: true,
- hard_delete: true)
+ expect(DeleteUserWorker).to receive(:perform_async).with(user.id, subject.user.id, hard_delete: true)
subject.remove_user(deleted_by: user)
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 119482b5f32..fa229542f70 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -89,7 +89,7 @@ describe ApplicationSetting, models: true do
storages = {
'custom1' => 'tmp/tests/custom_repositories_1',
'custom2' => 'tmp/tests/custom_repositories_2',
- 'custom3' => 'tmp/tests/custom_repositories_3',
+ 'custom3' => 'tmp/tests/custom_repositories_3'
}
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index f84c6b48173..f19e1af65a6 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -271,6 +271,52 @@ describe Blob do
end
end
+ describe '#auxiliary_viewer' do
+ context 'when the blob has an external storage error' do
+ before do
+ project.lfs_enabled = false
+ end
+
+ it 'returns nil' do
+ blob = fake_blob(path: 'LICENSE', lfs: true)
+
+ expect(blob.auxiliary_viewer).to be_nil
+ end
+ end
+
+ context 'when the blob is empty' do
+ it 'returns nil' do
+ blob = fake_blob(data: '')
+
+ expect(blob.auxiliary_viewer).to be_nil
+ end
+ end
+
+ context 'when the blob is stored externally' do
+ it 'returns a matching viewer' do
+ blob = fake_blob(path: 'LICENSE', lfs: true)
+
+ expect(blob.auxiliary_viewer).to be_a(BlobViewer::License)
+ end
+ end
+
+ context 'when the blob is binary' do
+ it 'returns nil' do
+ blob = fake_blob(path: 'LICENSE', binary: true)
+
+ expect(blob.auxiliary_viewer).to be_nil
+ end
+ end
+
+ context 'when the blob is text-based' do
+ it 'returns a matching text-based viewer' do
+ blob = fake_blob(path: 'LICENSE')
+
+ expect(blob.auxiliary_viewer).to be_a(BlobViewer::License)
+ end
+ end
+ end
+
describe '#rendered_as_text?' do
context 'when ignoring errors' do
context 'when the simple viewer is text-based' do
diff --git a/spec/models/blob_viewer/base_spec.rb b/spec/models/blob_viewer/base_spec.rb
index 740ad9d275e..d56379eb59d 100644
--- a/spec/models/blob_viewer/base_spec.rb
+++ b/spec/models/blob_viewer/base_spec.rb
@@ -7,10 +7,12 @@ describe BlobViewer::Base, model: true do
let(:viewer_class) do
Class.new(described_class) do
+ include BlobViewer::ServerSide
+
self.extensions = %w(pdf)
- self.max_size = 1.megabyte
- self.absolute_max_size = 5.megabytes
- self.client_side = false
+ self.binary = true
+ self.collapse_limit = 1.megabyte
+ self.size_limit = 5.megabytes
end
end
@@ -18,93 +20,98 @@ describe BlobViewer::Base, model: true do
describe '.can_render?' do
context 'when the extension is supported' do
- let(:blob) { fake_blob(path: 'file.pdf') }
+ context 'when the binaryness matches' do
+ let(:blob) { fake_blob(path: 'file.pdf', binary: true) }
- it 'returns true' do
- expect(viewer_class.can_render?(blob)).to be_truthy
+ it 'returns true' do
+ expect(viewer_class.can_render?(blob)).to be_truthy
+ end
end
- end
- context 'when the extension is not supported' do
- let(:blob) { fake_blob(path: 'file.txt') }
+ context 'when the binaryness does not match' do
+ let(:blob) { fake_blob(path: 'file.pdf', binary: false) }
- it 'returns false' do
- expect(viewer_class.can_render?(blob)).to be_falsey
+ it 'returns false' do
+ expect(viewer_class.can_render?(blob)).to be_falsey
+ end
end
end
- end
- describe '#too_large?' do
- context 'when the blob size is larger than the max size' do
- let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
+ context 'when the file type is supported' do
+ before do
+ viewer_class.file_types = %i(license)
+ viewer_class.binary = false
+ end
- it 'returns true' do
- expect(viewer.too_large?).to be_truthy
+ context 'when the binaryness matches' do
+ let(:blob) { fake_blob(path: 'LICENSE', binary: false) }
+
+ it 'returns true' do
+ expect(viewer_class.can_render?(blob)).to be_truthy
+ end
+ end
+
+ context 'when the binaryness does not match' do
+ let(:blob) { fake_blob(path: 'LICENSE', binary: true) }
+
+ it 'returns false' do
+ expect(viewer_class.can_render?(blob)).to be_falsey
+ end
end
end
- context 'when the blob size is smaller than the max size' do
- let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) }
+ context 'when the extension and file type are not supported' do
+ let(:blob) { fake_blob(path: 'file.txt') }
it 'returns false' do
- expect(viewer.too_large?).to be_falsey
+ expect(viewer_class.can_render?(blob)).to be_falsey
end
end
end
- describe '#absolutely_too_large?' do
- context 'when the blob size is larger than the absolute max size' do
- let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) }
+ describe '#collapsed?' do
+ context 'when the blob size is larger than the collapse limit' do
+ let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
it 'returns true' do
- expect(viewer.absolutely_too_large?).to be_truthy
+ expect(viewer.collapsed?).to be_truthy
end
end
- context 'when the blob size is smaller than the absolute max size' do
- let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
+ context 'when the blob size is smaller than the collapse limit' do
+ let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) }
it 'returns false' do
- expect(viewer.absolutely_too_large?).to be_falsey
+ expect(viewer.collapsed?).to be_falsey
end
end
end
- describe '#can_override_max_size?' do
- context 'when the blob size is larger than the max size' do
- context 'when the blob size is larger than the absolute max size' do
- let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) }
-
- it 'returns false' do
- expect(viewer.can_override_max_size?).to be_falsey
- end
- end
-
- context 'when the blob size is smaller than the absolute max size' do
- let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
+ describe '#too_large?' do
+ context 'when the blob size is larger than the size limit' do
+ let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) }
- it 'returns true' do
- expect(viewer.can_override_max_size?).to be_truthy
- end
+ it 'returns true' do
+ expect(viewer.too_large?).to be_truthy
end
end
- context 'when the blob size is smaller than the max size' do
- let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) }
+ context 'when the blob size is smaller than the size limit' do
+ let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
it 'returns false' do
- expect(viewer.can_override_max_size?).to be_falsey
+ expect(viewer.too_large?).to be_falsey
end
end
end
describe '#render_error' do
- context 'when the max size is overridden' do
+ context 'when expanded' do
before do
- viewer.override_max_size = true
+ viewer.expanded = true
end
- context 'when the blob size is larger than the absolute max size' do
+ context 'when the blob size is larger than the size limit' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) }
it 'returns :too_large' do
@@ -112,7 +119,7 @@ describe BlobViewer::Base, model: true do
end
end
- context 'when the blob size is smaller than the absolute max size' do
+ context 'when the blob size is smaller than the size limit' do
let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
it 'returns nil' do
@@ -121,16 +128,16 @@ describe BlobViewer::Base, model: true do
end
end
- context 'when the max size is not overridden' do
- context 'when the blob size is larger than the max size' do
+ context 'when not expanded' do
+ context 'when the blob size is larger than the collapse limit' do
let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
- it 'returns :too_large' do
- expect(viewer.render_error).to eq(:too_large)
+ it 'returns :collapsed' do
+ expect(viewer.render_error).to eq(:collapsed)
end
end
- context 'when the blob size is smaller than the max size' do
+ context 'when the blob size is smaller than the collapse limit' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) }
it 'returns nil' do
@@ -138,49 +145,5 @@ describe BlobViewer::Base, model: true do
end
end
end
-
- context 'when the viewer is server side but the blob is stored externally' do
- let(:project) { build(:empty_project, lfs_enabled: true) }
-
- let(:blob) { fake_blob(path: 'file.pdf', lfs: true) }
-
- before do
- allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
- end
-
- it 'return :server_side_but_stored_externally' do
- expect(viewer.render_error).to eq(:server_side_but_stored_externally)
- end
- end
- end
-
- describe '#prepare!' do
- context 'when the viewer is server side' do
- let(:blob) { fake_blob(path: 'file.md') }
-
- before do
- viewer_class.client_side = false
- end
-
- it 'loads all blob data' do
- expect(blob).to receive(:load_all_data!)
-
- viewer.prepare!
- end
- end
-
- context 'when the viewer is client side' do
- let(:blob) { fake_blob(path: 'file.md') }
-
- before do
- viewer_class.client_side = true
- end
-
- it "doesn't load all blob data" do
- expect(blob).not_to receive(:load_all_data!)
-
- viewer.prepare!
- end
- end
end
end
diff --git a/spec/models/blob_viewer/changelog_spec.rb b/spec/models/blob_viewer/changelog_spec.rb
new file mode 100644
index 00000000000..9066c5a05ac
--- /dev/null
+++ b/spec/models/blob_viewer/changelog_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe BlobViewer::Changelog, model: true do
+ include FakeBlobHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:blob) { fake_blob(path: 'CHANGELOG') }
+ subject { described_class.new(blob) }
+
+ describe '#render_error' do
+ context 'when there are no tags' do
+ before do
+ allow(project.repository).to receive(:tag_count).and_return(0)
+ end
+
+ it 'returns :no_tags' do
+ expect(subject.render_error).to eq(:no_tags)
+ end
+ end
+
+ context 'when there are tags' do
+ it 'returns nil' do
+ expect(subject.render_error).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/models/blob_viewer/composer_json_spec.rb b/spec/models/blob_viewer/composer_json_spec.rb
new file mode 100644
index 00000000000..df4f1f4815c
--- /dev/null
+++ b/spec/models/blob_viewer/composer_json_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe BlobViewer::ComposerJson, model: true do
+ include FakeBlobHelpers
+
+ let(:project) { build(:project) }
+ let(:data) do
+ <<-SPEC.strip_heredoc
+ {
+ "name": "laravel/laravel",
+ "homepage": "https://laravel.com/"
+ }
+ SPEC
+ end
+ let(:blob) { fake_blob(path: 'composer.json', data: data) }
+ subject { described_class.new(blob) }
+
+ describe '#package_name' do
+ it 'returns the package name' do
+ expect(subject).to receive(:prepare!)
+
+ expect(subject.package_name).to eq('laravel/laravel')
+ end
+ end
+end
diff --git a/spec/models/blob_viewer/gemspec_spec.rb b/spec/models/blob_viewer/gemspec_spec.rb
new file mode 100644
index 00000000000..81e932de290
--- /dev/null
+++ b/spec/models/blob_viewer/gemspec_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe BlobViewer::Gemspec, model: true do
+ include FakeBlobHelpers
+
+ let(:project) { build(:project) }
+ let(:data) do
+ <<-SPEC.strip_heredoc
+ Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = "activerecord"
+ end
+ SPEC
+ end
+ let(:blob) { fake_blob(path: 'activerecord.gemspec', data: data) }
+ subject { described_class.new(blob) }
+
+ describe '#package_name' do
+ it 'returns the package name' do
+ expect(subject).to receive(:prepare!)
+
+ expect(subject.package_name).to eq('activerecord')
+ end
+ end
+end
diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
new file mode 100644
index 00000000000..0c6c24ece21
--- /dev/null
+++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe BlobViewer::GitlabCiYml, model: true do
+ include FakeBlobHelpers
+
+ let(:project) { build(:project) }
+ let(:data) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
+ let(:blob) { fake_blob(path: '.gitlab-ci.yml', data: data) }
+ subject { described_class.new(blob) }
+
+ describe '#validation_message' do
+ it 'calls prepare! on the viewer' do
+ expect(subject).to receive(:prepare!)
+
+ subject.validation_message
+ end
+
+ context 'when the configuration is valid' do
+ it 'returns nil' do
+ expect(subject.validation_message).to be_nil
+ end
+ end
+
+ context 'when the configuration is invalid' do
+ let(:data) { 'oof' }
+
+ it 'returns the error message' do
+ expect(subject.validation_message).to eq('Invalid configuration format')
+ end
+ end
+ end
+end
diff --git a/spec/models/blob_viewer/license_spec.rb b/spec/models/blob_viewer/license_spec.rb
new file mode 100644
index 00000000000..944ddd32b92
--- /dev/null
+++ b/spec/models/blob_viewer/license_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe BlobViewer::License, model: true do
+ include FakeBlobHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:blob) { fake_blob(path: 'LICENSE') }
+ subject { described_class.new(blob) }
+
+ describe '#license' do
+ it 'returns the blob project repository license' do
+ expect(subject.license).not_to be_nil
+ expect(subject.license).to eq(project.repository.license)
+ end
+ end
+
+ describe '#render_error' do
+ context 'when there is no license' do
+ before do
+ allow(project.repository).to receive(:license).and_return(nil)
+ end
+
+ it 'returns :unknown_license' do
+ expect(subject.render_error).to eq(:unknown_license)
+ end
+ end
+
+ context 'when there is a license' do
+ it 'returns nil' do
+ expect(subject.render_error).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb
new file mode 100644
index 00000000000..5c9a9c81963
--- /dev/null
+++ b/spec/models/blob_viewer/package_json_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe BlobViewer::PackageJson, model: true do
+ include FakeBlobHelpers
+
+ let(:project) { build(:project) }
+ let(:data) do
+ <<-SPEC.strip_heredoc
+ {
+ "name": "module-name",
+ "version": "10.3.1"
+ }
+ SPEC
+ end
+ let(:blob) { fake_blob(path: 'package.json', data: data) }
+ subject { described_class.new(blob) }
+
+ describe '#package_name' do
+ it 'returns the package name' do
+ expect(subject).to receive(:prepare!)
+
+ expect(subject.package_name).to eq('module-name')
+ end
+ end
+end
diff --git a/spec/models/blob_viewer/podspec_json_spec.rb b/spec/models/blob_viewer/podspec_json_spec.rb
new file mode 100644
index 00000000000..42a00940bc5
--- /dev/null
+++ b/spec/models/blob_viewer/podspec_json_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe BlobViewer::PodspecJson, model: true do
+ include FakeBlobHelpers
+
+ let(:project) { build(:project) }
+ let(:data) do
+ <<-SPEC.strip_heredoc
+ {
+ "name": "AFNetworking",
+ "version": "2.0.0"
+ }
+ SPEC
+ end
+ let(:blob) { fake_blob(path: 'AFNetworking.podspec.json', data: data) }
+ subject { described_class.new(blob) }
+
+ describe '#package_name' do
+ it 'returns the package name' do
+ expect(subject).to receive(:prepare!)
+
+ expect(subject.package_name).to eq('AFNetworking')
+ end
+ end
+end
diff --git a/spec/models/blob_viewer/podspec_spec.rb b/spec/models/blob_viewer/podspec_spec.rb
new file mode 100644
index 00000000000..6c9f0f42d53
--- /dev/null
+++ b/spec/models/blob_viewer/podspec_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe BlobViewer::Podspec, model: true do
+ include FakeBlobHelpers
+
+ let(:project) { build(:project) }
+ let(:data) do
+ <<-SPEC.strip_heredoc
+ Pod::Spec.new do |spec|
+ spec.name = 'Reachability'
+ spec.version = '3.1.0'
+ end
+ SPEC
+ end
+ let(:blob) { fake_blob(path: 'Reachability.podspec', data: data) }
+ subject { described_class.new(blob) }
+
+ describe '#package_name' do
+ it 'returns the package name' do
+ expect(subject).to receive(:prepare!)
+
+ expect(subject.package_name).to eq('Reachability')
+ end
+ end
+end
diff --git a/spec/models/blob_viewer/route_map_spec.rb b/spec/models/blob_viewer/route_map_spec.rb
new file mode 100644
index 00000000000..4854e0262d9
--- /dev/null
+++ b/spec/models/blob_viewer/route_map_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe BlobViewer::RouteMap, model: true do
+ include FakeBlobHelpers
+
+ let(:project) { build(:project) }
+ let(:data) do
+ <<-MAP.strip_heredoc
+ # Team data
+ - source: 'data/team.yml'
+ public: 'team/'
+ MAP
+ end
+ let(:blob) { fake_blob(path: '.gitlab/route-map.yml', data: data) }
+ subject { described_class.new(blob) }
+
+ describe '#validation_message' do
+ it 'calls prepare! on the viewer' do
+ expect(subject).to receive(:prepare!)
+
+ subject.validation_message
+ end
+
+ context 'when the configuration is valid' do
+ it 'returns nil' do
+ expect(subject.validation_message).to be_nil
+ end
+ end
+
+ context 'when the configuration is invalid' do
+ let(:data) { 'oof' }
+
+ it 'returns the error message' do
+ expect(subject.validation_message).to eq('Route map is not an array')
+ end
+ end
+ end
+end
diff --git a/spec/models/blob_viewer/server_side_spec.rb b/spec/models/blob_viewer/server_side_spec.rb
new file mode 100644
index 00000000000..f047953d540
--- /dev/null
+++ b/spec/models/blob_viewer/server_side_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe BlobViewer::ServerSide, model: true do
+ include FakeBlobHelpers
+
+ let(:project) { build(:empty_project) }
+
+ let(:viewer_class) do
+ Class.new(BlobViewer::Base) do
+ include BlobViewer::ServerSide
+ end
+ end
+
+ subject { viewer_class.new(blob) }
+
+ describe '#prepare!' do
+ let(:blob) { fake_blob(path: 'file.txt') }
+
+ it 'loads all blob data' do
+ expect(blob).to receive(:load_all_data!)
+
+ subject.prepare!
+ end
+ end
+
+ describe '#render_error' do
+ context 'when the blob is stored externally' do
+ let(:project) { build(:empty_project, lfs_enabled: true) }
+
+ let(:blob) { fake_blob(path: 'file.pdf', lfs: true) }
+
+ before do
+ allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+ end
+
+ it 'return :server_side_but_stored_externally' do
+ expect(subject.render_error).to eq(:server_side_but_stored_externally)
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 5231ce28c9d..b0716e04d3d 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -427,6 +427,42 @@ describe Ci::Build, :models do
end
end
+ describe '#environment_url' do
+ subject { job.environment_url }
+
+ context 'when yaml environment uses $CI_COMMIT_REF_NAME' do
+ let(:job) do
+ create(:ci_build,
+ ref: 'master',
+ options: { environment: { url: 'http://review/$CI_COMMIT_REF_NAME' } })
+ end
+
+ it { is_expected.to eq('http://review/master') }
+ end
+
+ context 'when yaml environment uses yaml_variables containing symbol keys' do
+ let(:job) do
+ create(:ci_build,
+ yaml_variables: [{ key: :APP_HOST, value: 'host' }],
+ options: { environment: { url: 'http://review/$APP_HOST' } })
+ end
+
+ it { is_expected.to eq('http://review/host') }
+ end
+
+ context 'when yaml environment does not have url' do
+ let(:job) { create(:ci_build, environment: 'staging') }
+
+ let!(:environment) do
+ create(:environment, project: job.project, name: job.environment)
+ end
+
+ it 'returns the external_url from persisted environment' do
+ is_expected.to eq(environment.external_url)
+ end
+ end
+ end
+
describe '#starts_environment?' do
subject { build.starts_environment? }
@@ -918,6 +954,10 @@ describe Ci::Build, :models do
it { is_expected.to eq(environment) }
end
+
+ context 'when there is no environment' do
+ it { is_expected.to be_nil }
+ end
end
describe '#play' do
@@ -972,7 +1012,7 @@ describe Ci::Build, :models do
'fix-1-foo' => 'fix-1-foo',
'a' * 63 => 'a' * 63,
'a' * 64 => 'a' * 63,
- 'FOO' => 'foo',
+ 'FOO' => 'foo'
}.each do |ref, slug|
it "transforms #{ref} to #{slug}" do
build.ref = ref
@@ -1139,12 +1179,13 @@ describe Ci::Build, :models do
{ key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: project.path, public: true },
{ key: 'CI_PROJECT_PATH', value: project.full_path, public: true },
+ { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path.parameterize, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
{ key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
{ key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
- { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false },
+ { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false }
]
end
@@ -1176,11 +1217,6 @@ describe Ci::Build, :models do
end
context 'when build has an environment' do
- before do
- build.update(environment: 'production')
- create(:environment, project: build.project, name: 'production', slug: 'prod-slug')
- end
-
let(:environment_variables) do
[
{ key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true },
@@ -1188,7 +1224,56 @@ describe Ci::Build, :models do
]
end
- it { environment_variables.each { |v| is_expected.to include(v) } }
+ let!(:environment) do
+ create(:environment,
+ project: build.project,
+ name: 'production',
+ slug: 'prod-slug',
+ external_url: '')
+ end
+
+ before do
+ build.update(environment: 'production')
+ end
+
+ shared_examples 'containing environment variables' do
+ it { environment_variables.each { |v| is_expected.to include(v) } }
+ end
+
+ context 'when no URL was set' do
+ it_behaves_like 'containing environment variables'
+
+ it 'does not have CI_ENVIRONMENT_URL' do
+ keys = subject.map { |var| var[:key] }
+
+ expect(keys).not_to include('CI_ENVIRONMENT_URL')
+ end
+ end
+
+ context 'when an URL was set' do
+ let(:url) { 'http://host/test' }
+
+ before do
+ environment_variables <<
+ { key: 'CI_ENVIRONMENT_URL', value: url, public: true }
+ end
+
+ context 'when the URL was set from the job' do
+ before do
+ build.update(options: { environment: { url: 'http://host/$CI_JOB_NAME' } })
+ end
+
+ it_behaves_like 'containing environment variables'
+ end
+
+ context 'when the URL was not set from the job, but environment' do
+ before do
+ environment.update(external_url: url)
+ end
+
+ it_behaves_like 'containing environment variables'
+ end
+ end
end
context 'when build started manually' do
@@ -1215,16 +1300,49 @@ describe Ci::Build, :models do
it { is_expected.to include(tag_variable) }
end
- context 'when secure variable is defined' do
- let(:secure_variable) do
+ context 'when secret variable is defined' do
+ let(:secret_variable) do
{ key: 'SECRET_KEY', value: 'secret_value', public: false }
end
before do
- build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
+ create(:ci_variable,
+ secret_variable.slice(:key, :value).merge(project: project))
+ end
+
+ it { is_expected.to include(secret_variable) }
+ end
+
+ context 'when protected variable is defined' do
+ let(:protected_variable) do
+ { key: 'PROTECTED_KEY', value: 'protected_value', public: false }
+ end
+
+ before do
+ create(:ci_variable,
+ :protected,
+ protected_variable.slice(:key, :value).merge(project: project))
+ end
+
+ context 'when the branch is protected' do
+ before do
+ create(:protected_branch, project: build.project, name: build.ref)
+ end
+
+ it { is_expected.to include(protected_variable) }
end
- it { is_expected.to include(secure_variable) }
+ context 'when the tag is protected' do
+ before do
+ create(:protected_tag, project: build.project, name: build.ref)
+ end
+
+ it { is_expected.to include(protected_variable) }
+ end
+
+ context 'when the ref is not protected' do
+ it { is_expected.not_to include(protected_variable) }
+ end
end
context 'when build is for triggers' do
@@ -1346,15 +1464,30 @@ describe Ci::Build, :models do
end
context 'returns variables in valid order' do
+ let(:build_pre_var) { { key: 'build', value: 'value' } }
+ let(:project_pre_var) { { key: 'project', value: 'value' } }
+ let(:pipeline_pre_var) { { key: 'pipeline', value: 'value' } }
+ let(:build_yaml_var) { { key: 'yaml', value: 'value' } }
+
before do
- allow(build).to receive(:predefined_variables) { ['predefined'] }
- allow(project).to receive(:predefined_variables) { ['project'] }
- allow(pipeline).to receive(:predefined_variables) { ['pipeline'] }
- allow(build).to receive(:yaml_variables) { ['yaml'] }
- allow(project).to receive(:secret_variables) { ['secret'] }
+ allow(build).to receive(:predefined_variables) { [build_pre_var] }
+ allow(project).to receive(:predefined_variables) { [project_pre_var] }
+ allow(pipeline).to receive(:predefined_variables) { [pipeline_pre_var] }
+ allow(build).to receive(:yaml_variables) { [build_yaml_var] }
+
+ allow(project).to receive(:secret_variables_for).with(build.ref) do
+ [create(:ci_variable, key: 'secret', value: 'value')]
+ end
end
- it { is_expected.to eq(%w[predefined project pipeline yaml secret]) }
+ it do
+ is_expected.to eq(
+ [build_pre_var,
+ project_pre_var,
+ pipeline_pre_var,
+ build_yaml_var,
+ { key: 'secret', value: 'value', public: false }])
+ end
end
end
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/legacy_stage_spec.rb
index 8f6ab908987..48116c7e701 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/legacy_stage_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::Stage, models: true do
+describe Ci::LegacyStage, :models do
let(:stage) { build(:ci_stage) }
let(:pipeline) { stage.pipeline }
let(:stage_name) { stage.name }
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 822b98c5f6c..b00e7a73571 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -25,6 +25,14 @@ describe Ci::PipelineSchedule, models: true do
expect(pipeline_schedule).not_to be_valid
end
+
+ context 'when active is false' do
+ it 'does not allow nullified ref' do
+ pipeline_schedule = build(:ci_pipeline_schedule, :inactive, ref: nil)
+
+ expect(pipeline_schedule).not_to be_valid
+ end
+ end
end
describe '#set_next_run_at' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 72c8dccb185..b50c7700bd3 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -21,13 +21,35 @@ describe Ci::Pipeline, models: true do
it { is_expected.to have_many(:auto_canceled_pipelines) }
it { is_expected.to have_many(:auto_canceled_jobs) }
- it { is_expected.to validate_presence_of :sha }
- it { is_expected.to validate_presence_of :status }
+ it { is_expected.to validate_presence_of(:sha) }
+ it { is_expected.to validate_presence_of(:status) }
it { is_expected.to respond_to :git_author_name }
it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha }
+ describe '#source' do
+ context 'when creating new pipeline' do
+ let(:pipeline) do
+ build(:ci_empty_pipeline, status: :created, project: project, source: nil)
+ end
+
+ it "prevents from creating an object" do
+ expect(pipeline).not_to be_valid
+ end
+ end
+
+ context 'when updating existing pipeline' do
+ before do
+ pipeline.update_attribute(:source, nil)
+ end
+
+ it "object is valid" do
+ expect(pipeline).to be_valid
+ end
+ end
+ end
+
describe '#block' do
it 'changes pipeline status to manual' do
expect(pipeline.block).to be true
@@ -202,8 +224,19 @@ describe Ci::Pipeline, models: true do
status: 'success')
end
- describe '#stages' do
- subject { pipeline.stages }
+ describe '#stage_seeds' do
+ let(:pipeline) do
+ create(:ci_pipeline, config: { rspec: { script: 'rake' } })
+ end
+
+ it 'returns preseeded stage seeds object' do
+ expect(pipeline.stage_seeds).to all(be_a Gitlab::Ci::Stage::Seed)
+ expect(pipeline.stage_seeds.count).to eq 1
+ end
+ end
+
+ describe '#legacy_stages' do
+ subject { pipeline.legacy_stages }
context 'stages list' do
it 'returns ordered list of stages' do
@@ -252,7 +285,7 @@ describe Ci::Pipeline, models: true do
end
it 'populates stage with correct number of warnings' do
- deploy_stage = pipeline.stages.third
+ deploy_stage = pipeline.legacy_stages.third
expect(deploy_stage).not_to receive(:statuses)
expect(deploy_stage).to have_warnings
@@ -266,22 +299,22 @@ describe Ci::Pipeline, models: true do
end
end
- describe '#stages_name' do
+ describe '#stages_names' do
it 'returns a valid names of stages' do
- expect(pipeline.stages_name).to eq(%w(build test deploy))
+ expect(pipeline.stages_names).to eq(%w(build test deploy))
end
end
end
- describe '#stage' do
- subject { pipeline.stage('test') }
+ describe '#legacy_stage' do
+ subject { pipeline.legacy_stage('test') }
context 'with status in stage' do
before do
create(:commit_status, pipeline: pipeline, stage: 'test')
end
- it { expect(subject).to be_a Ci::Stage }
+ it { expect(subject).to be_a Ci::LegacyStage }
it { expect(subject.name).to eq 'test' }
it { expect(subject.statuses).not_to be_empty }
end
@@ -502,6 +535,20 @@ describe Ci::Pipeline, models: true do
end
end
+ describe '#has_stage_seeds?' do
+ context 'when pipeline has stage seeds' do
+ subject { build(:ci_pipeline_with_one_job) }
+
+ it { is_expected.to have_stage_seeds }
+ end
+
+ context 'when pipeline does not have stage seeds' do
+ subject { create(:ci_pipeline_without_jobs) }
+
+ it { is_expected.not_to have_stage_seeds }
+ end
+ end
+
describe '#has_warnings?' do
subject { pipeline.has_warnings? }
@@ -854,6 +901,16 @@ describe Ci::Pipeline, models: true do
end
end
end
+
+ context 'when there is a manual action present in the pipeline' do
+ before do
+ create(:ci_build, :manual, pipeline: pipeline)
+ end
+
+ it 'is not cancelable' do
+ expect(pipeline).not_to be_cancelable
+ end
+ end
end
describe '#cancel_running' do
@@ -955,7 +1012,7 @@ describe Ci::Pipeline, models: true do
end
before do
- ProjectWebHookWorker.drain
+ WebHookWorker.drain
end
context 'with pipeline hooks enabled' do
@@ -1050,8 +1107,8 @@ describe Ci::Pipeline, models: true do
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') }
it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do
- merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref)
allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { 'a288a022a53a5a944fae87bcec6efc87b7061808' }
+ merge_request = create(:merge_request, source_project: project, head_pipeline: pipeline, source_branch: pipeline.ref)
expect(pipeline.merge_requests).to eq([merge_request])
end
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 048d25869bc..077b10227d7 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Ci::Variable, models: true do
- subject { Ci::Variable.new }
+ subject { build(:ci_variable) }
let(:secret_value) { 'secret' }
@@ -12,11 +12,33 @@ describe Ci::Variable, models: true do
it { is_expected.not_to allow_value('foo bar').for(:key) }
it { is_expected.not_to allow_value('foo/bar').for(:key) }
- before :each do
- subject.value = secret_value
+ describe '.unprotected' do
+ subject { described_class.unprotected }
+
+ context 'when variable is protected' do
+ before do
+ create(:ci_variable, :protected)
+ end
+
+ it 'returns nothing' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when variable is not protected' do
+ let(:variable) { create(:ci_variable, protected: false) }
+
+ it 'returns the variable' do
+ is_expected.to contain_exactly(variable)
+ end
+ end
end
describe '#value' do
+ before do
+ subject.value = secret_value
+ end
+
it 'stores the encrypted value' do
expect(subject.encrypted_value).not_to be_nil
end
@@ -36,4 +58,11 @@ describe Ci::Variable, models: true do
to raise_error(OpenSSL::Cipher::CipherError, 'bad decrypt')
end
end
+
+ describe '#to_runner_variable' do
+ it 'returns a hash for the runner' do
+ expect(subject.to_runner_variable)
+ .to eq(key: subject.key, value: subject.value, public: false)
+ end
+ end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 852889d4540..72f83d63224 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -388,32 +388,4 @@ eos
expect(described_class.valid_hash?('a' * 41)).to be false
end
end
-
- describe '#raw_diffs' do
- context 'Gitaly commit_raw_diffs feature enabled' do
- before do
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:commit_raw_diffs).and_return(true)
- end
-
- context 'when a truthy deltas_only is not passed to args' do
- it 'fetches diffs from Gitaly server' do
- expect(Gitlab::GitalyClient::Commit).to receive(:diff_from_parent).
- with(commit)
-
- commit.raw_diffs
- end
- end
-
- context 'when a truthy deltas_only is passed to args' do
- it 'fetches diffs using Rugged' do
- opts = { deltas_only: true }
-
- expect(Gitlab::GitalyClient::Commit).not_to receive(:diff_from_parent)
- expect(commit.raw).to receive(:diffs).with(opts)
-
- commit.raw_diffs(opts)
- end
- end
- end
- end
end
diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb
index 8571e85627c..f3e148f95f0 100644
--- a/spec/models/concerns/discussion_on_diff_spec.rb
+++ b/spec/models/concerns/discussion_on_diff_spec.rb
@@ -21,4 +21,30 @@ describe DiscussionOnDiff, model: true do
end
end
end
+
+ describe '#line_code_in_diffs' do
+ context 'when the discussion is active in the diff' do
+ let(:diff_refs) { subject.position.diff_refs }
+
+ it 'returns the current line code' do
+ expect(subject.line_code_in_diffs(diff_refs)).to eq(subject.line_code)
+ end
+ end
+
+ context 'when the discussion was created in the diff' do
+ let(:diff_refs) { subject.original_position.diff_refs }
+
+ it 'returns the original line code' do
+ expect(subject.line_code_in_diffs(diff_refs)).to eq(subject.original_line_code)
+ end
+ end
+
+ context 'when the discussion is unrelated to the diff' do
+ let(:diff_refs) { subject.project.commit(RepoHelpers.sample_commit.id).diff_refs }
+
+ it 'returns nil' do
+ expect(subject.line_code_in_diffs(diff_refs)).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 2092576e981..e382c7120de 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -163,3 +163,52 @@ describe Issue, "Mentionable" do
end
end
end
+
+describe Commit, 'Mentionable' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:commit) { project.commit }
+
+ describe '#matches_cross_reference_regex?' do
+ it "is false when message doesn't reference anything" do
+ allow(commit.raw).to receive(:message).and_return "WIP: Do something"
+
+ expect(commit.matches_cross_reference_regex?).to be false
+ end
+
+ it 'is true if issue #number mentioned in title' do
+ allow(commit.raw).to receive(:message).and_return "#1"
+
+ expect(commit.matches_cross_reference_regex?).to be true
+ end
+
+ it 'is true if references an MR' do
+ allow(commit.raw).to receive(:message).and_return "See merge request !12"
+
+ expect(commit.matches_cross_reference_regex?).to be true
+ end
+
+ it 'is true if references a commit' do
+ allow(commit.raw).to receive(:message).and_return "a1b2c3d4"
+
+ expect(commit.matches_cross_reference_regex?).to be true
+ end
+
+ it 'is true if issue referenced by url' do
+ issue = create(:issue, project: project)
+
+ allow(commit.raw).to receive(:message).and_return Gitlab::UrlBuilder.build(issue)
+
+ expect(commit.matches_cross_reference_regex?).to be true
+ end
+
+ context 'with external issue tracker' do
+ let(:project) { create(:jira_project) }
+
+ it 'is true if external issues referenced' do
+ allow(commit.raw).to receive(:message).and_return 'JIRA-123'
+
+ expect(commit.matches_cross_reference_regex?).to be true
+ end
+ end
+ end
+end
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index c2ba012a0e6..fd58bd1d6ad 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -13,7 +13,7 @@ describe 'CycleAnalytics#test', feature: true do
data_fn: lambda do |context|
issue = context.create(:issue, project: context.project)
merge_request = context.create_merge_request_closing_issue(issue)
- pipeline = context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project)
+ pipeline = context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project, head_pipeline_of: merge_request)
{ pipeline: pipeline, issue: issue }
end,
start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]],
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 212fcd884a8..6f0d2db23c7 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -16,6 +16,19 @@ describe Deployment, models: true do
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to validate_presence_of(:sha) }
+ describe 'after_create callbacks' do
+ let(:environment) { create(:environment) }
+ let(:store) { Gitlab::EtagCaching::Store.new }
+
+ it 'invalidates the environment etag cache' do
+ old_value = store.get(environment.etag_cache_key)
+
+ create(:deployment, environment: environment)
+
+ expect(store.get(environment.etag_cache_key)).not_to eq(old_value)
+ end
+ end
+
describe '#includes_commit?' do
let(:project) { create(:project, :repository) }
let(:environment) { create(:environment, project: project) }
@@ -52,7 +65,7 @@ describe Deployment, models: true do
describe '#metrics' do
let(:deployment) { create(:deployment) }
- subject { deployment.metrics(1.hour) }
+ subject { deployment.metrics }
context 'metrics are disabled' do
it { is_expected.to eq({}) }
@@ -63,16 +76,17 @@ describe Deployment, models: true do
{
success: true,
metrics: {},
- last_update: 42
+ last_update: 42,
+ deployment_time: 1494408956
}
end
before do
- allow(deployment.project).to receive_message_chain(:monitoring_service, :metrics)
+ allow(deployment.project).to receive_message_chain(:monitoring_service, :deployment_metrics)
.with(any_args).and_return(simple_metrics)
end
- it { is_expected.to eq(simple_metrics.merge(deployment_time: deployment.created_at.utc.to_i)) }
+ it { is_expected.to eq(simple_metrics) }
end
end
diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb
index 81f338745b1..45b2f6e4beb 100644
--- a/spec/models/diff_discussion_spec.rb
+++ b/spec/models/diff_discussion_spec.rb
@@ -48,7 +48,7 @@ describe DiffDiscussion, model: true do
end
it 'returns the diff ID for the version to show' do
- expect(diff_id: merge_request_diff1.id)
+ expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff1.id)
end
end
@@ -65,6 +65,11 @@ describe DiffDiscussion, model: true do
let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position) }
+ before do
+ diff_note.position = diff_note.original_position
+ diff_note.save!
+ end
+
it 'returns the diff ID and start sha of the versions to compare' do
expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff3.id, start_sha: merge_request_diff1.head_commit_sha)
end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index ab4c51a87b0..297c2108dc2 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -145,7 +145,7 @@ describe DiffNote, models: true do
context "when the merge request's diff refs don't match that of the diff note" do
before do
- allow(subject.noteable).to receive(:diff_sha_refs).and_return(commit.diff_refs)
+ allow(subject.noteable).to receive(:diff_refs).and_return(commit.diff_refs)
end
it "returns false" do
@@ -160,12 +160,6 @@ describe DiffNote, models: true do
context "when noteable is a commit" do
let(:diff_note) { create(:diff_note_on_commit, project: project, position: position) }
- it "doesn't use the DiffPositionUpdateService" do
- expect(Notes::DiffPositionUpdateService).not_to receive(:new)
-
- diff_note
- end
-
it "doesn't update the position" do
diff_note
@@ -178,12 +172,6 @@ describe DiffNote, models: true do
let(:diff_note) { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) }
context "when the note is active" do
- it "doesn't use the DiffPositionUpdateService" do
- expect(Notes::DiffPositionUpdateService).not_to receive(:new)
-
- diff_note
- end
-
it "doesn't update the position" do
diff_note
@@ -194,21 +182,14 @@ describe DiffNote, models: true do
context "when the note is outdated" do
before do
- allow(merge_request).to receive(:diff_sha_refs).and_return(commit.diff_refs)
+ allow(merge_request).to receive(:diff_refs).and_return(commit.diff_refs)
end
- it "uses the DiffPositionUpdateService" do
- service = instance_double("Notes::DiffPositionUpdateService")
- expect(Notes::DiffPositionUpdateService).to receive(:new).with(
- project,
- nil,
- old_diff_refs: position.diff_refs,
- new_diff_refs: commit.diff_refs,
- paths: [path]
- ).and_return(service)
- expect(service).to receive(:execute)
-
+ it "updates the position" do
diff_note
+
+ expect(diff_note.original_position).to eq(position)
+ expect(diff_note.position).not_to eq(position)
end
end
end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 28e5c3f80f4..fe69c8e351d 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Environment, models: true do
- let(:project) { create(:empty_project) }
+ set(:project) { create(:empty_project) }
subject(:environment) { create(:environment, project: project) }
it { is_expected.to belong_to(:project) }
@@ -34,6 +34,26 @@ describe Environment, models: true do
end
end
+ describe 'state machine' do
+ it 'invalidates the cache after a change' do
+ expect(environment).to receive(:expire_etag_cache)
+
+ environment.stop
+ end
+ end
+
+ describe '#expire_etag_cache' do
+ let(:store) { Gitlab::EtagCaching::Store.new }
+
+ it 'changes the cached value' do
+ old_value = store.get(environment.etag_cache_key)
+
+ environment.stop
+
+ expect(store.get(environment.etag_cache_key)).not_to eq(old_value)
+ end
+ end
+
describe '#nullify_external_url' do
it 'replaces a blank url with nil' do
env = build(:environment, external_url: "")
@@ -227,7 +247,10 @@ describe Environment, models: true do
context 'when user is allowed to stop environment' do
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
end
context 'when action did not yet finish' do
@@ -393,7 +416,7 @@ describe Environment, models: true do
it 'returns the metrics from the deployment service' do
expect(project.monitoring_service)
- .to receive(:metrics).with(environment)
+ .to receive(:environment_metrics).with(environment)
.and_return(:fake_metrics)
is_expected.to eq(:fake_metrics)
@@ -438,7 +461,7 @@ describe Environment, models: true do
"foo**bar" => "foo-bar" + SUFFIX,
"*-foo" => "env-foo" + SUFFIX,
"staging-12345678-" => "staging-12345678" + SUFFIX,
- "staging-12345678-01234567" => "staging-12345678" + SUFFIX,
+ "staging-12345678-01234567" => "staging-12345678" + SUFFIX
}.each do |name, matcher|
it "returns a slug matching #{matcher}, given #{name}" do
slug = described_class.new(name: name).generate_slug
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
index 454550c9710..6e8d43f988c 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe ForkedProjectLink, "add link on fork" do
let(:project_from) { create(:project, :repository) }
- let(:namespace) { create(:namespace) }
- let(:user) { create(:user, namespace: namespace) }
+ let(:user) { create(:user) }
+ let(:namespace) { user.namespace }
before do
create(:project_member, :reporter, user: user, project: project_from)
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index 55b87d1c48a..a14efda3eda 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -137,7 +137,7 @@ describe GlobalMilestone, models: true do
[
milestone1_project1,
milestone1_project2,
- milestone1_project3,
+ milestone1_project3
]
milestones_relation = Milestone.where(id: milestones.map(&:id))
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 91b235c267c..316bf153660 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -178,16 +178,20 @@ describe Group, models: true do
describe '#avatar_url' do
let!(:group) { create(:group, :access_requestable, :with_avatar) }
let(:user) { create(:user) }
- subject { group.avatar_url }
+ let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
+ let(:avatar_path) { "/uploads/group/avatar/#{group.id}/dk.png" }
context 'when avatar file is uploaded' do
- before do
- group.add_master(user)
- end
+ before { group.add_master(user) }
- let(:avatar_path) { "/uploads/system/group/avatar/#{group.id}/dk.png" }
+ it 'shows correct avatar url' do
+ expect(group.avatar_url).to eq(avatar_path)
+ expect(group.avatar_url(only_path: false)).to eq([gitlab_host, avatar_path].join)
- it { should eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
+ allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)
+
+ expect(group.avatar_url).to eq([gitlab_host, avatar_path].join)
+ end
end
end
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 1a83c836652..57454d2a773 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -1,36 +1,19 @@
-require "spec_helper"
+require 'spec_helper'
describe ServiceHook, models: true do
- describe "Associations" do
+ describe 'associations' do
it { is_expected.to belong_to :service }
end
- describe "execute" do
- before(:each) do
- @service_hook = create(:service_hook)
- @data = { project_id: 1, data: {} }
+ describe 'execute' do
+ let(:hook) { build(:service_hook) }
+ let(:data) { { key: 'value' } }
- WebMock.stub_request(:post, @service_hook.url)
- end
-
- it "POSTs to the webhook URL" do
- @service_hook.execute(@data)
- expect(WebMock).to have_requested(:post, @service_hook.url).with(
- headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Service Hook' }
- ).once
- end
-
- it "POSTs the data as JSON" do
- @service_hook.execute(@data)
- expect(WebMock).to have_requested(:post, @service_hook.url).with(
- headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Service Hook' }
- ).once
- end
-
- it "catches exceptions" do
- expect(WebHook).to receive(:post).and_raise("Some HTTP Post error")
+ it '#execute' do
+ expect(WebHookService).to receive(:new).with(hook, data, 'service_hook').and_call_original
+ expect_any_instance_of(WebHookService).to receive(:execute)
- expect { @service_hook.execute(@data) }.to raise_error(RuntimeError)
+ hook.execute(data)
end
end
end
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 8acec805584..0d2b622132e 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -1,6 +1,19 @@
require "spec_helper"
describe SystemHook, models: true do
+ context 'default attributes' do
+ let(:system_hook) { build(:system_hook) }
+
+ it 'sets defined default parameters' do
+ attrs = {
+ push_events: false,
+ repository_update_events: true,
+ enable_ssl_verification: true
+ }
+ expect(system_hook).to have_attributes(attrs)
+ end
+ end
+
describe "execute" do
let(:system_hook) { create(:system_hook) }
let(:user) { create(:user) }
@@ -105,4 +118,34 @@ describe SystemHook, models: true do
).once
end
end
+
+ describe '.repository_update_hooks' do
+ it 'returns hooks for repository update events only' do
+ hook = create(:system_hook, repository_update_events: true)
+ create(:system_hook, repository_update_events: false)
+ expect(SystemHook.repository_update_hooks).to eq([hook])
+ end
+ end
+
+ describe 'execute WebHookService' do
+ let(:hook) { build(:system_hook) }
+ let(:data) { { key: 'value' } }
+ let(:hook_name) { 'system_hook' }
+
+ before do
+ expect(WebHookService).to receive(:new).with(hook, data, hook_name).and_call_original
+ end
+
+ it '#execute' do
+ expect_any_instance_of(WebHookService).to receive(:execute)
+
+ hook.execute(data, hook_name)
+ end
+
+ it '#async_execute' do
+ expect_any_instance_of(WebHookService).to receive(:async_execute)
+
+ hook.async_execute(data, hook_name)
+ end
+ end
end
diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb
new file mode 100644
index 00000000000..c649cf3b589
--- /dev/null
+++ b/spec/models/hooks/web_hook_log_spec.rb
@@ -0,0 +1,30 @@
+require 'rails_helper'
+
+describe WebHookLog, models: true do
+ it { is_expected.to belong_to(:web_hook) }
+
+ it { is_expected.to serialize(:request_headers).as(Hash) }
+ it { is_expected.to serialize(:request_data).as(Hash) }
+ it { is_expected.to serialize(:response_headers).as(Hash) }
+
+ it { is_expected.to validate_presence_of(:web_hook) }
+
+ describe '#success?' do
+ let(:web_hook_log) { build(:web_hook_log, response_status: status) }
+
+ describe '2xx' do
+ let(:status) { '200' }
+ it { expect(web_hook_log.success?).to be_truthy }
+ end
+
+ describe 'not 2xx' do
+ let(:status) { '500' }
+ it { expect(web_hook_log.success?).to be_falsey }
+ end
+
+ describe 'internal erorr' do
+ let(:status) { 'internal error' }
+ it { expect(web_hook_log.success?).to be_falsey }
+ end
+ end
+end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 9d4db1bfb52..53157c24477 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -1,89 +1,54 @@
require 'spec_helper'
describe WebHook, models: true do
- describe "Validations" do
+ let(:hook) { build(:project_hook) }
+
+ describe 'associations' do
+ it { is_expected.to have_many(:web_hook_logs).dependent(:destroy) }
+ end
+
+ describe 'validations' do
it { is_expected.to validate_presence_of(:url) }
describe 'url' do
- it { is_expected.to allow_value("http://example.com").for(:url) }
- it { is_expected.to allow_value("https://example.com").for(:url) }
- it { is_expected.to allow_value(" https://example.com ").for(:url) }
- it { is_expected.to allow_value("http://test.com/api").for(:url) }
- it { is_expected.to allow_value("http://test.com/api?key=abc").for(:url) }
- it { is_expected.to allow_value("http://test.com/api?key=abc&type=def").for(:url) }
+ it { is_expected.to allow_value('http://example.com').for(:url) }
+ it { is_expected.to allow_value('https://example.com').for(:url) }
+ it { is_expected.to allow_value(' https://example.com ').for(:url) }
+ it { is_expected.to allow_value('http://test.com/api').for(:url) }
+ it { is_expected.to allow_value('http://test.com/api?key=abc').for(:url) }
+ it { is_expected.to allow_value('http://test.com/api?key=abc&type=def').for(:url) }
- it { is_expected.not_to allow_value("example.com").for(:url) }
- it { is_expected.not_to allow_value("ftp://example.com").for(:url) }
- it { is_expected.not_to allow_value("herp-and-derp").for(:url) }
+ it { is_expected.not_to allow_value('example.com').for(:url) }
+ it { is_expected.not_to allow_value('ftp://example.com').for(:url) }
+ it { is_expected.not_to allow_value('herp-and-derp').for(:url) }
it 'strips :url before saving it' do
- hook = create(:project_hook, url: ' https://example.com ')
+ hook.url = ' https://example.com '
+ hook.save
expect(hook.url).to eq('https://example.com')
end
end
end
- describe "execute" do
- let(:project) { create(:empty_project) }
- let(:project_hook) { create(:project_hook) }
-
- before(:each) do
- project.hooks << [project_hook]
- @data = { before: 'oldrev', after: 'newrev', ref: 'ref' }
-
- WebMock.stub_request(:post, project_hook.url)
- end
-
- context 'when token is defined' do
- let(:project_hook) { create(:project_hook, :token) }
-
- it 'POSTs to the webhook URL' do
- project_hook.execute(@data, 'push_hooks')
- expect(WebMock).to have_requested(:post, project_hook.url).with(
- headers: { 'Content-Type' => 'application/json',
- 'X-Gitlab-Event' => 'Push Hook',
- 'X-Gitlab-Token' => project_hook.token }
- ).once
- end
- end
-
- it "POSTs to the webhook URL" do
- project_hook.execute(@data, 'push_hooks')
- expect(WebMock).to have_requested(:post, project_hook.url).with(
- headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Push Hook' }
- ).once
- end
-
- it "POSTs the data as JSON" do
- project_hook.execute(@data, 'push_hooks')
- expect(WebMock).to have_requested(:post, project_hook.url).with(
- headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Push Hook' }
- ).once
- end
-
- it "catches exceptions" do
- expect(WebHook).to receive(:post).and_raise("Some HTTP Post error")
-
- expect { project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError)
- end
-
- it "handles SSL exceptions" do
- expect(WebHook).to receive(:post).and_raise(OpenSSL::SSL::SSLError.new('SSL error'))
+ describe 'execute' do
+ let(:data) { { key: 'value' } }
+ let(:hook_name) { 'project hook' }
- expect(project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
+ before do
+ expect(WebHookService).to receive(:new).with(hook, data, hook_name).and_call_original
end
- it "handles 200 status code" do
- WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "Success")
+ it '#execute' do
+ expect_any_instance_of(WebHookService).to receive(:execute)
- expect(project_hook.execute(@data, 'push_hooks')).to eq([200, 'Success'])
+ hook.execute(data, hook_name)
end
- it "handles 2xx status codes" do
- WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: "Success")
+ it '#async_execute' do
+ expect_any_instance_of(WebHookService).to receive(:async_execute)
- expect(project_hook.execute(@data, 'push_hooks')).to eq([201, 'Success'])
+ hook.async_execute(data, hook_name)
end
end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 7c40cfd8253..f1e2a2cc518 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -66,14 +66,16 @@ describe Key, models: true do
end
it "does not accept the exact same key twice" do
- create(:key, user: user)
- expect(build(:key, user: user)).not_to be_valid
+ first_key = create(:key, user: user)
+
+ expect(build(:key, user: user, key: first_key.key)).not_to be_valid
end
it "does not accept a duplicate key with a different comment" do
- create(:key, user: user)
- duplicate = build(:key, user: user)
+ first_key = create(:key, user: user)
+ duplicate = build(:key, user: user, key: first_key.key)
duplicate.key << ' extra comment'
+
expect(duplicate).not_to be_valid
end
end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 80ca19acdda..84867e3d96b 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -49,6 +49,23 @@ describe Label, models: true do
expect(label.color).to eq('#abcdef')
end
+
+ it 'uses default color if color is missing' do
+ label = described_class.new(color: nil)
+
+ expect(label.color).to be(Label::DEFAULT_COLOR)
+ end
+ end
+
+ describe '#text_color' do
+ it 'uses default color if color is missing' do
+ expect(LabelsHelper).to receive(:text_color_for_bg).with(Label::DEFAULT_COLOR).
+ and_return(spy)
+
+ label = described_class.new(color: nil)
+
+ label.text_color
+ end
end
describe '#title' do
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 0a10ee01506..ed9fde57bf7 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -139,4 +139,15 @@ describe MergeRequestDiff, models: true do
expect(subject.commits_count).to eq 2
end
end
+
+ describe '#utf8_st_diffs' do
+ it 'does not raise error when a hash value is in binary' do
+ subject.st_diffs = [
+ { diff: "\0" },
+ { diff: "\x05\x00\x68\x65\x6c\x6c\x6f" }
+ ]
+
+ expect { subject.utf8_st_diffs }.not_to raise_error
+ end
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 23cbc56cb0e..060754fab63 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -238,10 +238,10 @@ describe MergeRequest, models: true do
end
context 'when there are no MR diffs' do
- it 'delegates to the compare object, setting no_collapse: true' do
+ it 'delegates to the compare object, setting expanded: true' do
merge_request.compare = double(:compare)
- expect(merge_request.compare).to receive(:diffs).with(options.merge(no_collapse: true))
+ expect(merge_request.compare).to receive(:diffs).with(options.merge(expanded: true))
merge_request.diffs(options)
end
@@ -718,13 +718,7 @@ describe MergeRequest, models: true do
describe '#head_pipeline' do
describe 'when the source project exists' do
it 'returns the latest pipeline' do
- pipeline = double(:ci_pipeline, ref: 'master')
-
- allow(subject).to receive(:diff_head_sha).and_return('123abc')
-
- expect(subject.source_project).to receive(:pipeline_for).
- with('master', '123abc').
- and_return(pipeline)
+ pipeline = create(:ci_empty_pipeline, project: subject.source_project, ref: 'master', status: 'running', sha: "123abc", head_pipeline_of: subject)
expect(subject.head_pipeline).to eq(pipeline)
end
@@ -1184,7 +1178,7 @@ describe MergeRequest, models: true do
end
describe "#reload_diff" do
- let(:note) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject) }
+ let(:discussion) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject).to_discussion }
let(:commit) { subject.project.commit(sample_commit.id) }
@@ -1203,7 +1197,7 @@ describe MergeRequest, models: true do
subject.reload_diff
end
- it "updates diff note positions" do
+ it "updates diff discussion positions" do
old_diff_refs = subject.diff_refs
# Update merge_request_diff so that #diff_refs will return commit.diff_refs
@@ -1217,18 +1211,18 @@ describe MergeRequest, models: true do
subject.merge_request_diff(true)
end
- expect(Notes::DiffPositionUpdateService).to receive(:new).with(
+ expect(Discussions::UpdateDiffPositionService).to receive(:new).with(
subject.project,
- nil,
+ subject.author,
old_diff_refs: old_diff_refs,
new_diff_refs: commit.diff_refs,
- paths: note.position.paths
+ paths: discussion.position.paths
).and_call_original
- expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note)
+ expect_any_instance_of(Discussions::UpdateDiffPositionService).to receive(:execute).with(discussion).and_call_original
expect_any_instance_of(DiffNote).to receive(:save).once
- subject.reload_diff
+ subject.reload_diff(subject.author)
end
end
@@ -1249,7 +1243,7 @@ describe MergeRequest, models: true do
end
end
- describe "#diff_sha_refs" do
+ describe "#diff_refs" do
context "with diffs" do
subject { create(:merge_request, :with_diffs) }
@@ -1258,7 +1252,7 @@ describe MergeRequest, models: true do
expect_any_instance_of(Repository).not_to receive(:commit)
- subject.diff_sha_refs
+ subject.diff_refs
end
it "returns expected diff_refs" do
@@ -1268,7 +1262,7 @@ describe MergeRequest, models: true do
head_sha: subject.merge_request_diff.head_commit_sha
)
- expect(subject.diff_sha_refs).to eq(expected_diff_refs)
+ expect(subject.diff_refs).to eq(expected_diff_refs)
end
end
end
@@ -1397,11 +1391,14 @@ describe MergeRequest, models: true do
describe '#mergeable_with_slash_command?' do
def create_pipeline(status)
- create(:ci_pipeline_with_one_job,
+ pipeline = create(:ci_pipeline_with_one_job,
project: project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha,
- status: status)
+ status: status,
+ head_pipeline_of: merge_request)
+
+ pipeline
end
let(:project) { create(:project, :public, :repository, only_allow_merge_if_pipeline_succeeds: true) }
@@ -1537,4 +1534,36 @@ describe MergeRequest, models: true do
end
end
end
+
+ describe '#version_params_for' do
+ subject { create(:merge_request, importing: true) }
+ let(:project) { subject.project }
+ let!(:merge_request_diff1) { subject.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
+ let!(:merge_request_diff2) { subject.merge_request_diffs.create(head_commit_sha: nil) }
+ let!(:merge_request_diff3) { subject.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
+
+ context 'when the diff refs are for an older merge request version' do
+ let(:diff_refs) { merge_request_diff1.diff_refs }
+
+ it 'returns the diff ID for the version to show' do
+ expect(subject.version_params_for(diff_refs)).to eq(diff_id: merge_request_diff1.id)
+ end
+ end
+
+ context 'when the diff refs are for a comparison between merge request versions' do
+ let(:diff_refs) { merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs }
+
+ it 'returns the diff ID and start sha of the versions to compare' do
+ expect(subject.version_params_for(diff_refs)).to eq(diff_id: merge_request_diff3.id, start_sha: merge_request_diff1.head_commit_sha)
+ end
+ end
+
+ context 'when the diff refs are not for a merge request version' do
+ let(:diff_refs) { project.commit(sample_commit.id).diff_refs }
+
+ it 'returns nil' do
+ expect(subject.version_params_for(diff_refs)).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index e3e8e6d571c..aa1ce89ffd7 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -249,4 +249,17 @@ describe Milestone, models: true do
expect(milestone.to_reference(another_project)).to eq "sample-project%1"
end
end
+
+ describe '#participants' do
+ let(:project) { build(:empty_project, name: 'sample-project') }
+ let(:milestone) { build(:milestone, iid: 1, project: project) }
+
+ it 'returns participants without duplicates' do
+ user = create :user
+ create :issue, project: project, milestone: milestone, assignees: [user]
+ create :issue, project: project, milestone: milestone, assignees: [user]
+
+ expect(milestone.participants).to eq [user]
+ end
+ end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 38179c60af4..145c7ad5770 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -244,8 +244,8 @@ describe Namespace, models: true do
end
context 'in sub-groups' do
- let(:parent) { create(:namespace, path: 'parent') }
- let(:child) { create(:namespace, parent: parent, path: 'child') }
+ let(:parent) { create(:group, path: 'parent') }
+ let(:child) { create(:group, parent: parent, path: 'child') }
let!(:project) { create(:project_empty_repo, namespace: child) }
let(:path_in_dir) { File.join(repository_storage_path, 'parent', 'child') }
let(:deleted_path) { File.join('parent', "child+#{child.id}+deleted") }
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index c6c45d78990..f9d060d4e0e 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -6,7 +6,7 @@ describe PagesDomain, models: true do
end
describe 'validate domain' do
- subject { build(:pages_domain, domain: domain) }
+ subject(:pages_domain) { build(:pages_domain, domain: domain) }
context 'is unique' do
let(:domain) { 'my.domain.com' }
@@ -14,36 +14,25 @@ describe PagesDomain, models: true do
it { is_expected.to validate_uniqueness_of(:domain) }
end
- context 'valid domain' do
- let(:domain) { 'my.domain.com' }
-
- it { is_expected.to be_valid }
- end
-
- context 'valid hexadecimal-looking domain' do
- let(:domain) { '0x12345.com'}
-
- it { is_expected.to be_valid }
- end
-
- context 'no domain' do
- let(:domain) { nil }
-
- it { is_expected.not_to be_valid }
- end
-
- context 'invalid domain' do
- let(:domain) { '0123123' }
-
- it { is_expected.not_to be_valid }
- end
-
- context 'domain from .example.com' do
- let(:domain) { 'my.domain.com' }
-
- before { allow(Settings.pages).to receive(:host).and_return('domain.com') }
-
- it { is_expected.not_to be_valid }
+ {
+ 'my.domain.com' => true,
+ '123.456.789' => true,
+ '0x12345.com' => true,
+ '0123123' => true,
+ '_foo.com' => false,
+ 'reserved.com' => false,
+ 'a.reserved.com' => false,
+ nil => false
+ }.each do |value, validity|
+ context "domain #{value.inspect} validity" do
+ before do
+ allow(Settings.pages).to receive(:host).and_return('reserved.com')
+ end
+
+ let(:domain) { value }
+
+ it { expect(pages_domain.valid?).to eq(validity) }
+ end
end
end
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index 823623d96fa..fa781195608 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -35,6 +35,16 @@ describe PersonalAccessToken, models: true do
end
end
+ describe 'revoke!' do
+ let(:active_personal_access_token) { create(:personal_access_token) }
+
+ it 'revokes the token' do
+ active_personal_access_token.revoke!
+
+ expect(active_personal_access_token.revoked?).to be true
+ end
+ end
+
context "validations" do
let(:personal_access_token) { build(:personal_access_token) }
@@ -51,11 +61,17 @@ describe PersonalAccessToken, models: true do
expect(personal_access_token).to be_valid
end
- it "rejects creating a token with non-API scopes" do
+ it "allows creating a token with read_registry scope" do
+ personal_access_token.scopes = [:read_registry]
+
+ expect(personal_access_token).to be_valid
+ end
+
+ it "rejects creating a token with unavailable scopes" do
personal_access_token.scopes = [:openid, :api]
expect(personal_access_token).not_to be_valid
- expect(personal_access_token.errors[:scopes].first).to eq "can only contain API scopes"
+ expect(personal_access_token.errors[:scopes].first).to eq "can only contain available scopes"
end
end
end
diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb
index 33ef67f97a7..cd0a4a94809 100644
--- a/spec/models/project_authorization_spec.rb
+++ b/spec/models/project_authorization_spec.rb
@@ -16,7 +16,7 @@ describe ProjectAuthorization do
it 'inserts rows in batches' do
described_class.insert_authorizations([
[user.id, project1.id, Gitlab::Access::MASTER],
- [user.id, project2.id, Gitlab::Access::MASTER],
+ [user.id, project2.id, Gitlab::Access::MASTER]
], 1)
expect(user.project_authorizations.count).to eq(2)
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index 48aef3a93f2..95c35162d96 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -28,7 +28,7 @@ describe AsanaService, models: true do
commits: messages.map do |m|
{
message: m,
- url: 'https://gitlab.com/',
+ url: 'https://gitlab.com/'
}
end
}
diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb
index 34e2d94b1ed..c159ab00ab1 100644
--- a/spec/models/project_services/chat_message/issue_message_spec.rb
+++ b/spec/models/project_services/chat_message/issue_message_spec.rb
@@ -48,7 +48,7 @@ describe ChatMessage::IssueMessage, models: true do
title: "#100 Issue title",
title_link: "http://url.com",
text: "issue description",
- color: color,
+ color: color
}
])
end
diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb
index fa0a1f4a5b7..61f17031172 100644
--- a/spec/models/project_services/chat_message/merge_message_spec.rb
+++ b/spec/models/project_services/chat_message/merge_message_spec.rb
@@ -22,7 +22,7 @@ describe ChatMessage::MergeMessage, models: true do
state: 'opened',
description: 'merge request description',
source_branch: 'source_branch',
- target_branch: 'target_branch',
+ target_branch: 'target_branch'
}
}
end
diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb
index 7cd9c61ee2b..7996536218a 100644
--- a/spec/models/project_services/chat_message/note_message_spec.rb
+++ b/spec/models/project_services/chat_message/note_message_spec.rb
@@ -15,7 +15,7 @@ describe ChatMessage::NoteMessage, models: true do
project_url: 'http://somewhere.com',
repository: {
name: 'project_name',
- url: 'http://somewhere.com',
+ url: 'http://somewhere.com'
},
object_attributes: {
id: 10,
diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb
index e005be42b0d..7d2599dc703 100644
--- a/spec/models/project_services/chat_message/pipeline_message_spec.rb
+++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb
@@ -62,7 +62,7 @@ describe ChatMessage::PipelineMessage do
def build_message(status_text = status, name = user[:name])
"<http://example.gitlab.com|project_name>:" \
" Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
- " of <http://example.gitlab.com/commits/develop|develop> branch" \
+ " of branch `<http://example.gitlab.com/commits/develop|develop>`" \
" by #{name} #{status_text} in 02:00:10"
end
end
@@ -81,7 +81,7 @@ describe ChatMessage::PipelineMessage do
expect(subject.pretext).to be_empty
expect(subject.attachments).to eq(message)
expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of [develop](http://example.gitlab.com/commits/develop) branch by hacker passed',
+ title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch `[develop](http://example.gitlab.com/commits/develop)` by hacker passed',
subtitle: 'in [project_name](http://example.gitlab.com)',
text: 'in 02:00:10',
image: ''
@@ -98,7 +98,7 @@ describe ChatMessage::PipelineMessage do
expect(subject.pretext).to be_empty
expect(subject.attachments).to eq(message)
expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of [develop](http://example.gitlab.com/commits/develop) branch by hacker failed',
+ title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch `[develop](http://example.gitlab.com/commits/develop)` by hacker failed',
subtitle: 'in [project_name](http://example.gitlab.com)',
text: 'in 02:00:10',
image: ''
@@ -113,7 +113,7 @@ describe ChatMessage::PipelineMessage do
expect(subject.pretext).to be_empty
expect(subject.attachments).to eq(message)
expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of [develop](http://example.gitlab.com/commits/develop) branch by API failed',
+ title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch `[develop](http://example.gitlab.com/commits/develop)` by API failed',
subtitle: 'in [project_name](http://example.gitlab.com)',
text: 'in 02:00:10',
image: ''
@@ -125,8 +125,8 @@ describe ChatMessage::PipelineMessage do
def build_markdown_message(status_text = status, name = user[:name])
"[project_name](http://example.gitlab.com):" \
" Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
- " of [develop](http://example.gitlab.com/commits/develop)" \
- " branch by #{name} #{status_text} in 02:00:10"
+ " of branch `[develop](http://example.gitlab.com/commits/develop)`" \
+ " by #{name} #{status_text} in 02:00:10"
end
end
end
diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb
index 63eb078c44e..e38117b75f6 100644
--- a/spec/models/project_services/chat_message/push_message_spec.rb
+++ b/spec/models/project_services/chat_message/push_message_spec.rb
@@ -21,19 +21,19 @@ describe ChatMessage::PushMessage, models: true do
before do
args[:commits] = [
{ message: 'message1', url: 'http://url1.com', id: 'abcdefghijkl', author: { name: 'author1' } },
- { message: 'message2', url: 'http://url2.com', id: '123456789012', author: { name: 'author2' } },
+ { message: 'message2', url: 'http://url2.com', id: '123456789012', author: { name: 'author2' } }
]
end
context 'without markdown' do
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq(
- 'test.user pushed to branch <http://url.com/commits/master|master> of '\
+ 'test.user pushed to branch `<http://url.com/commits/master|master>` of '\
'<http://url.com|project_name> (<http://url.com/compare/before...after|Compare changes>)')
expect(subject.attachments).to eq([{
text: "<http://url1.com|abcdefgh>: message1 - author1\n\n"\
"<http://url2.com|12345678>: message2 - author2",
- color: color,
+ color: color
}])
end
end
@@ -45,7 +45,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq(
- 'test.user pushed to branch [master](http://url.com/commits/master) of [project_name](http://url.com) ([Compare changes](http://url.com/compare/before...after))')
+ 'test.user pushed to branch `[master](http://url.com/commits/master)` of [project_name](http://url.com) ([Compare changes](http://url.com/compare/before...after))')
expect(subject.attachments).to eq(
"[abcdefgh](http://url1.com): message1 - author1\n\n[12345678](http://url2.com): message2 - author2")
expect(subject.activity).to eq({
@@ -74,7 +74,7 @@ describe ChatMessage::PushMessage, models: true do
context 'without markdown' do
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq('test.user pushed new tag ' \
- '<http://url.com/commits/new_tag|new_tag> to ' \
+ '`<http://url.com/commits/new_tag|new_tag>` to ' \
'<http://url.com|project_name>')
expect(subject.attachments).to be_empty
end
@@ -87,7 +87,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq(
- 'test.user pushed new tag [new_tag](http://url.com/commits/new_tag) to [project_name](http://url.com)')
+ 'test.user pushed new tag `[new_tag](http://url.com/commits/new_tag)` to [project_name](http://url.com)')
expect(subject.attachments).to be_empty
expect(subject.activity).to eq({
title: 'test.user created tag',
@@ -107,7 +107,7 @@ describe ChatMessage::PushMessage, models: true do
context 'without markdown' do
it 'returns a message regarding a new branch' do
expect(subject.pretext).to eq(
- 'test.user pushed new branch <http://url.com/commits/master|master> to '\
+ 'test.user pushed new branch `<http://url.com/commits/master|master>` to '\
'<http://url.com|project_name>')
expect(subject.attachments).to be_empty
end
@@ -120,7 +120,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding a new branch' do
expect(subject.pretext).to eq(
- 'test.user pushed new branch [master](http://url.com/commits/master) to [project_name](http://url.com)')
+ 'test.user pushed new branch `[master](http://url.com/commits/master)` to [project_name](http://url.com)')
expect(subject.attachments).to be_empty
expect(subject.activity).to eq({
title: 'test.user created branch',
@@ -140,7 +140,7 @@ describe ChatMessage::PushMessage, models: true do
context 'without markdown' do
it 'returns a message regarding a removed branch' do
expect(subject.pretext).to eq(
- 'test.user removed branch master from <http://url.com|project_name>')
+ 'test.user removed branch `master` from <http://url.com|project_name>')
expect(subject.attachments).to be_empty
end
end
@@ -152,7 +152,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding a removed branch' do
expect(subject.pretext).to eq(
- 'test.user removed branch master from [project_name](http://url.com)')
+ 'test.user removed branch `master` from [project_name](http://url.com)')
expect(subject.attachments).to be_empty
expect(subject.activity).to eq({
title: 'test.user removed branch',
diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
index 0df7db2abc2..4ca1b8aa7b7 100644
--- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb
+++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
@@ -53,7 +53,7 @@ describe ChatMessage::WikiPageMessage, models: true do
expect(subject.attachments).to eq([
{
text: "Wiki page description",
- color: color,
+ color: color
}
])
end
@@ -66,7 +66,7 @@ describe ChatMessage::WikiPageMessage, models: true do
expect(subject.attachments).to eq([
{
text: "Wiki page description",
- color: color,
+ color: color
}
])
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 4bca0229e7a..0ee050196e4 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -22,49 +22,50 @@ describe JiraService, models: true do
it { is_expected.not_to validate_presence_of(:url) }
end
- end
- describe '#reference_pattern' do
- it_behaves_like 'allows project key on reference pattern'
+ context 'validating urls' do
+ let(:service) do
+ described_class.new(
+ project: create(:empty_project),
+ active: true,
+ username: 'username',
+ password: 'test',
+ project_key: 'TEST',
+ jira_issue_transition_id: 24,
+ url: 'http://jira.test.com'
+ )
+ end
- it 'does not allow # on the code' do
- expect(subject.reference_pattern.match('#123')).to be_nil
- expect(subject.reference_pattern.match('1#23#12')).to be_nil
- end
- end
+ it 'is valid when all fields have required values' do
+ expect(service).to be_valid
+ end
- describe '#can_test?' do
- let(:jira_service) { described_class.new }
+ it 'is not valid when url is not a valid url' do
+ service.url = 'not valid'
- it 'returns false if username is blank' do
- allow(jira_service).to receive_messages(
- url: 'http://jira.example.com',
- username: '',
- password: '12345678'
- )
+ expect(service).not_to be_valid
+ end
- expect(jira_service.can_test?).to be_falsy
- end
+ it 'is not valid when api url is not a valid url' do
+ service.api_url = 'not valid'
- it 'returns false if password is blank' do
- allow(jira_service).to receive_messages(
- url: 'http://jira.example.com',
- username: 'tester',
- password: ''
- )
+ expect(service).not_to be_valid
+ end
+
+ it 'is valid when api url is a valid url' do
+ service.api_url = 'http://jira.test.com/api'
- expect(jira_service.can_test?).to be_falsy
+ expect(service).to be_valid
+ end
end
+ end
- it 'returns true if password and username are present' do
- jira_service = described_class.new
- allow(jira_service).to receive_messages(
- url: 'http://jira.example.com',
- username: 'tester',
- password: '12345678'
- )
+ describe '#reference_pattern' do
+ it_behaves_like 'allows project key on reference pattern'
- expect(jira_service.can_test?).to be_truthy
+ it 'does not allow # on the code' do
+ expect(subject.reference_pattern.match('#123')).to be_nil
+ expect(subject.reference_pattern.match('1#23#12')).to be_nil
end
end
@@ -97,6 +98,7 @@ describe JiraService, models: true do
allow(JIRA::Resource::Issue).to receive(:find).and_return(open_issue, closed_issue)
allow_any_instance_of(JIRA::Resource::Issue).to receive(:key).and_return("JIRA-123")
+ allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
@jira_service.save
@@ -187,22 +189,29 @@ describe JiraService, models: true do
describe '#test_settings' do
let(:jira_service) do
described_class.new(
+ project: create(:project),
url: 'http://jira.example.com',
- username: 'gitlab_jira_username',
- password: 'gitlab_jira_password',
+ username: 'jira_username',
+ password: 'jira_password',
project_key: 'GitLabProject'
)
end
- let(:project_url) { 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/project/GitLabProject' }
- before do
+ def test_settings(api_url)
+ project_url = "http://jira_username:jira_password@#{api_url}/rest/api/2/project/GitLabProject"
+
WebMock.stub_request(:get, project_url)
- end
- it 'tries to get JIRA project' do
jira_service.test_settings
+ end
- expect(WebMock).to have_requested(:get, project_url)
+ it 'tries to get JIRA project with URL when API URL not set' do
+ test_settings('jira.example.com')
+ end
+
+ it 'tries to get JIRA project with API URL if set' do
+ jira_service.update(api_url: 'http://jira.api.com')
+ test_settings('jira.api.com')
end
end
@@ -214,34 +223,75 @@ describe JiraService, models: true do
@jira_service = JiraService.create!(
project: project,
properties: {
- url: 'http://jira.example.com/rest/api/2',
+ url: 'http://jira.example.com/web',
username: 'mic',
password: "password"
}
)
end
- it "reset password if url changed" do
- @jira_service.url = 'http://jira_edited.example.com/rest/api/2'
- @jira_service.save
- expect(@jira_service.password).to be_nil
+ context 'when only web url present' do
+ it 'reset password if url changed' do
+ @jira_service.url = 'http://jira_edited.example.com/rest/api/2'
+ @jira_service.save
+
+ expect(@jira_service.password).to be_nil
+ end
+
+ it 'reset password if url not changed but api url added' do
+ @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
+ @jira_service.save
+
+ expect(@jira_service.password).to be_nil
+ end
end
- it "does not reset password if username changed" do
- @jira_service.username = "some_name"
+ context 'when both web and api url present' do
+ before do
+ @jira_service.api_url = 'http://jira.example.com/rest/api/2'
+ @jira_service.password = 'password'
+
+ @jira_service.save
+ end
+ it 'reset password if api url changed' do
+ @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
+ @jira_service.save
+
+ expect(@jira_service.password).to be_nil
+ end
+
+ it 'does not reset password if url changed' do
+ @jira_service.url = 'http://jira_edited.example.com/rweb'
+ @jira_service.save
+
+ expect(@jira_service.password).to eq("password")
+ end
+
+ it 'reset password if api url set to ""' do
+ @jira_service.api_url = ''
+ @jira_service.save
+
+ expect(@jira_service.password).to be_nil
+ end
+ end
+
+ it 'does not reset password if username changed' do
+ @jira_service.username = 'some_name'
@jira_service.save
- expect(@jira_service.password).to eq("password")
+
+ expect(@jira_service.password).to eq('password')
end
- it "does not reset password if new url is set together with password, even if it's the same password" do
+ it 'does not reset password if new url is set together with password, even if it\'s the same password' do
@jira_service.url = 'http://jira_edited.example.com/rest/api/2'
@jira_service.password = 'password'
@jira_service.save
- expect(@jira_service.password).to eq("password")
- expect(@jira_service.url).to eq("http://jira_edited.example.com/rest/api/2")
+
+ expect(@jira_service.password).to eq('password')
+ expect(@jira_service.url).to eq('http://jira_edited.example.com/rest/api/2')
end
- it "resets password if url changed, even if setter called multiple times" do
+ it 'resets password if url changed, even if setter called multiple times' do
@jira_service.url = 'http://jira1.example.com/rest/api/2'
@jira_service.url = 'http://jira1.example.com/rest/api/2'
@jira_service.save
@@ -249,7 +299,7 @@ describe JiraService, models: true do
end
end
- context "when no password was previously set" do
+ context 'when no password was previously set' do
before do
@jira_service = JiraService.create(
project: project,
@@ -260,26 +310,16 @@ describe JiraService, models: true do
)
end
- it "saves password if new url is set together with password" do
+ it 'saves password if new url is set together with password' do
@jira_service.url = 'http://jira_edited.example.com/rest/api/2'
@jira_service.password = 'password'
@jira_service.save
- expect(@jira_service.password).to eq("password")
- expect(@jira_service.url).to eq("http://jira_edited.example.com/rest/api/2")
+ expect(@jira_service.password).to eq('password')
+ expect(@jira_service.url).to eq('http://jira_edited.example.com/rest/api/2')
end
end
end
- describe "Validations" do
- context "active" do
- before do
- subject.active = true
- end
-
- it { is_expected.to validate_presence_of :url }
- end
- end
-
describe 'description and title' do
let(:project) { create(:empty_project) }
@@ -321,9 +361,10 @@ describe JiraService, models: true do
context 'when gitlab.yml was initialized' do
before do
settings = {
- "jira" => {
- "title" => "Jira",
- "url" => "http://jira.sample/projects/project_a"
+ 'jira' => {
+ 'title' => 'Jira',
+ 'url' => 'http://jira.sample/projects/project_a',
+ 'api_url' => 'http://jira.sample/api'
}
}
allow(Gitlab.config).to receive(:issues_tracker).and_return(settings)
@@ -335,8 +376,9 @@ describe JiraService, models: true do
end
it 'is prepopulated with the settings' do
- expect(@service.properties["title"]).to eq('Jira')
- expect(@service.properties["url"]).to eq('http://jira.sample/projects/project_a')
+ expect(@service.properties['title']).to eq('Jira')
+ expect(@service.properties['url']).to eq('http://jira.sample/projects/project_a')
+ expect(@service.properties['api_url']).to eq('http://jira.sample/api')
end
end
end
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index e69eb0098dd..0dcf4a4b5d6 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -13,7 +13,7 @@ describe KubernetesService, models: true, caching: true do
let(:discovery_url) { service.api_url + '/api/v1' }
let(:discovery_response) { { body: kube_discovery_body.to_json } }
- let(:pods_url) { service.api_url + "/api/v1/namespaces/#{service.namespace}/pods" }
+ let(:pods_url) { service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods" }
let(:pods_response) { { body: kube_pods_body(kube_pod).to_json } }
def stub_kubeclient_discover
@@ -54,7 +54,7 @@ describe KubernetesService, models: true, caching: true do
'a' * 63 => true,
'a' * 64 => false,
'a.b' => false,
- 'a*b' => false,
+ 'a*b' => false
}.each do |namespace, validity|
it "validates #{namespace} as #{validity ? 'valid' : 'invalid'}" do
subject.namespace = namespace
@@ -100,7 +100,35 @@ describe KubernetesService, models: true, caching: true do
it 'sets the namespace to the default' do
expect(kube_namespace).not_to be_nil
- expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::Regex::PATH_REGEX_STR}-\d+\z/)
+ expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
+ end
+ end
+ end
+
+ describe '#actual_namespace' do
+ subject { service.actual_namespace }
+
+ it "returns the default namespace" do
+ is_expected.to eq(service.send(:default_namespace))
+ end
+
+ context 'when namespace is specified' do
+ before do
+ service.namespace = 'my-namespace'
+ end
+
+ it "returns the user-namespace" do
+ is_expected.to eq('my-namespace')
+ end
+ end
+
+ context 'when service is not assigned to project' do
+ before do
+ service.project = nil
+ end
+
+ it "does not return namespace" do
+ is_expected.to be_nil
end
end
end
@@ -168,7 +196,7 @@ describe KubernetesService, models: true, caching: true do
{ key: 'KUBE_TOKEN', value: 'token', public: false },
{ key: 'KUBE_NAMESPACE', value: 'my-project', public: true },
{ key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true },
- { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true },
+ { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true }
)
end
end
@@ -179,7 +207,7 @@ describe KubernetesService, models: true, caching: true do
{ key: 'KUBE_URL', value: 'https://kube.domain.com', public: true },
{ key: 'KUBE_TOKEN', value: 'token', public: false },
{ key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true },
- { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true },
+ { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true }
)
end
@@ -187,13 +215,14 @@ describe KubernetesService, models: true, caching: true do
kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
expect(kube_namespace).not_to be_nil
- expect(kube_namespace[:value]).to match(/\A#{Gitlab::Regex::PATH_REGEX_STR}-\d+\z/)
+ expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
end
end
end
describe '#terminals' do
let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") }
+
subject { service.terminals(environment) }
context 'with invalid pods' do
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
index 45b2f1068bf..a76e909d04d 100644
--- a/spec/models/project_services/pivotaltracker_service_spec.rb
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -40,7 +40,7 @@ describe PivotaltrackerService, models: true do
name: 'Some User'
},
url: 'https://example.com/commit',
- message: 'commit message',
+ message: 'commit message'
}
]
}
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index 82a3e2698c1..1f9d3c07b51 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -6,6 +6,7 @@ describe PrometheusService, models: true, caching: true do
let(:project) { create(:prometheus_project) }
let(:service) { project.prometheus_service }
+ let(:environment_query) { Gitlab::Prometheus::Queries::EnvironmentQuery }
describe "Associations" do
it { is_expected.to belong_to :project }
@@ -45,49 +46,56 @@ describe PrometheusService, models: true, caching: true do
end
end
- describe '#metrics' do
+ describe '#environment_metrics' do
let(:environment) { build_stubbed(:environment, slug: 'env-slug') }
around do |example|
Timecop.freeze { example.run }
end
- context 'with valid data without time range' do
- subject { service.metrics(environment) }
+ context 'with valid data' do
+ subject { service.environment_metrics(environment) }
before do
- stub_reactive_cache(service, prometheus_data, 'env-slug', nil, nil)
+ stub_reactive_cache(service, prometheus_data, environment_query, environment.id)
end
it 'returns reactive data' do
is_expected.to eq(prometheus_data)
end
end
+ end
+
+ describe '#deployment_metrics' do
+ let(:deployment) { build_stubbed(:deployment)}
+ let(:deployment_query) { Gitlab::Prometheus::Queries::DeploymentQuery }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
- context 'with valid data with time range' do
- let(:t_start) { 1.hour.ago.utc }
- let(:t_end) { Time.now.utc }
- subject { service.metrics(environment, timeframe_start: t_start, timeframe_end: t_end) }
+ context 'with valid data' do
+ subject { service.deployment_metrics(deployment) }
before do
- stub_reactive_cache(service, prometheus_data, 'env-slug', t_start, t_end)
+ stub_reactive_cache(service, prometheus_data, deployment_query, deployment.id)
end
it 'returns reactive data' do
- is_expected.to eq(prometheus_data)
+ is_expected.to eq(prometheus_data.merge(deployment_time: deployment.created_at.to_i))
end
end
end
describe '#calculate_reactive_cache' do
- let(:environment) { build_stubbed(:environment, slug: 'env-slug') }
+ let(:environment) { create(:environment, slug: 'env-slug') }
around do |example|
Timecop.freeze { example.run }
end
subject do
- service.calculate_reactive_cache(environment.slug, nil, nil)
+ service.calculate_reactive_cache(environment_query.to_s, environment.id)
end
context 'when service is inactive' do
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index d9d7c0b0aaa..5fe4885eeb4 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -5,9 +5,6 @@ describe ProjectSnippet, models: true do
it { is_expected.to belong_to(:project) }
end
- describe "Mass assignment" do
- end
-
describe "Validation" do
it { is_expected.to validate_presence_of(:project) }
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 692f28ea5e7..3ed52d42f86 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -50,7 +50,7 @@ describe Project, models: true do
it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) }
it { is_expected.to have_one(:project_feature).dependent(:destroy) }
it { is_expected.to have_one(:statistics).class_name('ProjectStatistics').dependent(:delete) }
- it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:destroy) }
+ it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:delete) }
it { is_expected.to have_one(:last_event).class_name('Event') }
it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) }
it { is_expected.to have_many(:commit_statuses) }
@@ -812,9 +812,17 @@ describe Project, models: true do
context 'when avatar file is uploaded' do
let(:project) { create(:empty_project, :with_avatar) }
- let(:avatar_path) { "/uploads/system/project/avatar/#{project.id}/dk.png" }
+ let(:avatar_path) { "/uploads/project/avatar/#{project.id}/dk.png" }
+ let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
- it { should eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
+ it 'shows correct url' do
+ expect(project.avatar_url).to eq(avatar_path)
+ expect(project.avatar_url(only_path: false)).to eq([gitlab_host, avatar_path].join)
+
+ allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)
+
+ expect(project.avatar_url).to eq([gitlab_host, avatar_path].join)
+ end
end
context 'When avatar file in git' do
@@ -940,6 +948,20 @@ describe Project, models: true do
end
end
+ describe '.starred_by' do
+ it 'returns only projects starred by the given user' do
+ user1 = create(:user)
+ user2 = create(:user)
+ project1 = create(:empty_project)
+ project2 = create(:empty_project)
+ create(:empty_project)
+ user1.toggle_star(project1)
+ user2.toggle_star(project2)
+
+ expect(Project.starred_by(user1)).to contain_exactly(project1)
+ end
+ end
+
describe '.visible_to_user' do
let!(:project) { create(:empty_project, :private) }
let!(:user) { create(:user) }
@@ -965,7 +987,7 @@ describe Project, models: true do
before do
storages = {
'default' => { 'path' => 'tmp/tests/repositories' },
- 'picked' => { 'path' => 'tmp/tests/repositories' },
+ 'picked' => { 'path' => 'tmp/tests/repositories' }
}
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
@@ -1423,6 +1445,27 @@ describe Project, models: true do
end
end
+ describe 'Project import job' do
+ let(:project) { create(:empty_project, import_url: generate(:url)) }
+
+ before do
+ allow_any_instance_of(Gitlab::Shell).to receive(:import_repository)
+ .with(project.repository_storage_path, project.path_with_namespace, project.import_url)
+ .and_return(true)
+
+ expect_any_instance_of(Repository).to receive(:after_import)
+ .and_call_original
+ end
+
+ it 'imports a project' do
+ expect_any_instance_of(RepositoryImportWorker).to receive(:perform).and_call_original
+
+ project.import_schedule
+
+ expect(project.reload.import_status).to eq('finished')
+ end
+ end
+
describe '#latest_successful_builds_for' do
def create_pipeline(status = 'success')
create(:ci_pipeline, project: project,
@@ -1504,7 +1547,7 @@ describe Project, models: true do
describe '#add_import_job' do
context 'forked' do
- let(:forked_project_link) { create(:forked_project_link) }
+ let(:forked_project_link) { create(:forked_project_link, :forked_to_empty_project) }
let(:forked_from_project) { forked_project_link.forked_from_project }
let(:project) { forked_project_link.forked_to_project }
@@ -1518,9 +1561,9 @@ describe Project, models: true do
end
context 'not forked' do
- let(:project) { create(:empty_project) }
-
it 'schedules a RepositoryImportWorker job' do
+ project = create(:empty_project, import_url: generate(:url))
+
expect(RepositoryImportWorker).to receive(:perform_async).with(project.id)
project.add_import_job
@@ -1702,6 +1745,90 @@ describe Project, models: true do
end
end
+ describe '#secret_variables_for' do
+ let(:project) { create(:empty_project) }
+
+ let!(:secret_variable) do
+ create(:ci_variable, value: 'secret', project: project)
+ end
+
+ let!(:protected_variable) do
+ create(:ci_variable, :protected, value: 'protected', project: project)
+ end
+
+ subject { project.secret_variables_for('ref') }
+
+ shared_examples 'ref is protected' do
+ it 'contains all the variables' do
+ is_expected.to contain_exactly(secret_variable, protected_variable)
+ end
+ end
+
+ context 'when the ref is not protected' do
+ before do
+ stub_application_setting(
+ default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+ end
+
+ it 'contains only the secret variables' do
+ is_expected.to contain_exactly(secret_variable)
+ end
+ end
+
+ context 'when the ref is a protected branch' do
+ before do
+ create(:protected_branch, name: 'ref', project: project)
+ end
+
+ it_behaves_like 'ref is protected'
+ end
+
+ context 'when the ref is a protected tag' do
+ before do
+ create(:protected_tag, name: 'ref', project: project)
+ end
+
+ it_behaves_like 'ref is protected'
+ end
+ end
+
+ describe '#protected_for?' do
+ let(:project) { create(:empty_project) }
+
+ subject { project.protected_for?('ref') }
+
+ context 'when the ref is not protected' do
+ before do
+ stub_application_setting(
+ default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+ end
+
+ it 'returns false' do
+ is_expected.to be_falsey
+ end
+ end
+
+ context 'when the ref is a protected branch' do
+ before do
+ create(:protected_branch, name: 'ref', project: project)
+ end
+
+ it 'returns true' do
+ is_expected.to be_truthy
+ end
+ end
+
+ context 'when the ref is a protected tag' do
+ before do
+ create(:protected_tag, name: 'ref', project: project)
+ end
+
+ it 'returns true' do
+ is_expected.to be_truthy
+ end
+ end
+ end
+
describe '#update_project_statistics' do
let(:project) { create(:empty_project) }
@@ -1876,19 +2003,9 @@ describe Project, models: true do
describe '#http_url_to_repo' do
let(:project) { create :empty_project }
- context 'when no user is given' do
- it 'returns the url to the repo without a username' do
- expect(project.http_url_to_repo).to eq("#{project.web_url}.git")
- expect(project.http_url_to_repo).not_to include('@')
- end
- end
-
- context 'when user is given' do
- it 'returns the url to the repo with the username' do
- user = build_stubbed(:user)
-
- expect(project.http_url_to_repo(user)).to start_with("http://#{user.username}@")
- end
+ it 'returns the url to the repo without a username' do
+ expect(project.http_url_to_repo).to eq("#{project.web_url}.git")
+ expect(project.http_url_to_repo).not_to include('@')
end
end
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index ff29f6f66ba..c5ffbda9821 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -35,7 +35,7 @@ describe ProjectStatistics, models: true do
commit_count: 8.exabytes - 1,
repository_size: 2.exabytes,
lfs_objects_size: 2.exabytes,
- build_artifacts_size: 4.exabytes - 1,
+ build_artifacts_size: 4.exabytes - 1
)
statistics.reload
@@ -149,7 +149,7 @@ describe ProjectStatistics, models: true do
it "sums all storage counters" do
statistics.update!(
repository_size: 2,
- lfs_objects_size: 3,
+ lfs_objects_size: 3
)
statistics.reload
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index fb2d5f60009..362565506e5 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -327,69 +327,114 @@ describe ProjectTeam, models: true do
end
end
- shared_examples_for "#max_member_access_for_users" do |enable_request_store|
- describe "#max_member_access_for_users" do
+ shared_examples 'max member access for users' do
+ let(:project) { create(:project) }
+ let(:group) { create(:group) }
+ let(:second_group) { create(:group) }
+
+ let(:master) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+
+ let(:promoted_guest) { create(:user) }
+
+ let(:group_developer) { create(:user) }
+ let(:second_developer) { create(:user) }
+
+ let(:user_without_access) { create(:user) }
+ let(:second_user_without_access) { create(:user) }
+
+ let(:users) do
+ [master, reporter, promoted_guest, guest, group_developer, second_developer, user_without_access].map(&:id)
+ end
+
+ let(:expected) do
+ {
+ master.id => Gitlab::Access::MASTER,
+ reporter.id => Gitlab::Access::REPORTER,
+ promoted_guest.id => Gitlab::Access::DEVELOPER,
+ guest.id => Gitlab::Access::GUEST,
+ group_developer.id => Gitlab::Access::DEVELOPER,
+ second_developer.id => Gitlab::Access::MASTER,
+ user_without_access.id => Gitlab::Access::NO_ACCESS
+ }
+ end
+
+ before do
+ project.add_master(master)
+ project.add_reporter(reporter)
+ project.add_guest(promoted_guest)
+ project.add_guest(guest)
+
+ project.project_group_links.create(
+ group: group,
+ group_access: Gitlab::Access::DEVELOPER
+ )
+
+ group.add_master(promoted_guest)
+ group.add_developer(group_developer)
+ group.add_developer(second_developer)
+
+ project.project_group_links.create(
+ group: second_group,
+ group_access: Gitlab::Access::MASTER
+ )
+
+ second_group.add_master(second_developer)
+ end
+
+ it 'returns correct roles for different users' do
+ expect(project.team.max_member_access_for_user_ids(users)).to eq(expected)
+ end
+ end
+
+ describe '#max_member_access_for_user_ids' do
+ context 'with RequestStore enabled' do
before do
- RequestStore.begin! if enable_request_store
+ RequestStore.begin!
end
after do
- if enable_request_store
- RequestStore.end!
- RequestStore.clear!
- end
+ RequestStore.end!
+ RequestStore.clear!
end
- it 'returns correct roles for different users' do
- master = create(:user)
- reporter = create(:user)
- promoted_guest = create(:user)
- guest = create(:user)
- project = create(:empty_project)
+ include_examples 'max member access for users'
- project.add_master(master)
- project.add_reporter(reporter)
- project.add_guest(promoted_guest)
- project.add_guest(guest)
+ def access_levels(users)
+ project.team.max_member_access_for_user_ids(users)
+ end
+
+ it 'does not perform extra queries when asked for users who have already been found' do
+ access_levels(users)
+
+ expect { access_levels(users) }.not_to exceed_query_limit(0)
- group = create(:group)
- group_developer = create(:user)
- second_developer = create(:user)
- project.project_group_links.create(
- group: group,
- group_access: Gitlab::Access::DEVELOPER)
-
- group.add_master(promoted_guest)
- group.add_developer(group_developer)
- group.add_developer(second_developer)
-
- second_group = create(:group)
- project.project_group_links.create(
- group: second_group,
- group_access: Gitlab::Access::MASTER)
- second_group.add_master(second_developer)
-
- users = [master, reporter, promoted_guest, guest, group_developer, second_developer].map(&:id)
-
- expected = {
- master.id => Gitlab::Access::MASTER,
- reporter.id => Gitlab::Access::REPORTER,
- promoted_guest.id => Gitlab::Access::DEVELOPER,
- guest.id => Gitlab::Access::GUEST,
- group_developer.id => Gitlab::Access::DEVELOPER,
- second_developer.id => Gitlab::Access::MASTER
- }
-
- expect(project.team.max_member_access_for_user_ids(users)).to eq(expected)
+ expect(access_levels(users)).to eq(expected)
end
- end
- end
- describe '#max_member_access_for_users with RequestStore' do
- it_behaves_like "#max_member_access_for_users", true
- end
+ it 'only requests the extra users when uncached users are passed' do
+ new_user = create(:user)
+ second_new_user = create(:user)
+ all_users = users + [new_user.id, second_new_user.id]
+
+ expected_all = expected.merge(new_user.id => Gitlab::Access::NO_ACCESS,
+ second_new_user.id => Gitlab::Access::NO_ACCESS)
+
+ access_levels(users)
- describe '#max_member_access_for_users without RequestStore' do
- it_behaves_like "#max_member_access_for_users", false
+ queries = ActiveRecord::QueryRecorder.new { access_levels(all_users) }
+
+ expect(queries.count).to eq(1)
+ expect(queries.log_message).to match(/\W#{new_user.id}\W/)
+ expect(queries.log_message).to match(/\W#{second_new_user.id}\W/)
+ expect(queries.log_message).not_to match(/\W#{promoted_guest.id}\W/)
+ expect(access_levels(all_users)).to eq(expected_all)
+ end
+ end
+
+ context 'with RequestStore disabled' do
+ include_examples 'max member access for users'
+ end
end
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 969e9f7a130..224067f58dd 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -37,21 +37,11 @@ describe ProjectWiki, models: true do
describe "#http_url_to_repo" do
let(:project) { create :empty_project }
- context 'when no user is given' do
- it 'returns the url to the repo without a username' do
- expected_url = "#{Gitlab.config.gitlab.url}/#{subject.path_with_namespace}.git"
+ it 'returns the full http url to the repo' do
+ expected_url = "#{Gitlab.config.gitlab.url}/#{subject.path_with_namespace}.git"
- expect(project_wiki.http_url_to_repo).to eq(expected_url)
- expect(project_wiki.http_url_to_repo).not_to include('@')
- end
- end
-
- context 'when user is given' do
- it 'returns the url to the repo with the username' do
- user = build_stubbed(:user)
-
- expect(project_wiki.http_url_to_repo(user)).to start_with("http://#{user.username}@")
- end
+ expect(project_wiki.http_url_to_repo).to eq(expected_url)
+ expect(project_wiki.http_url_to_repo).not_to include('@')
end
end
diff --git a/spec/models/protected_branch/merge_access_level_spec.rb b/spec/models/protected_branch/merge_access_level_spec.rb
new file mode 100644
index 00000000000..1e7242e9fa8
--- /dev/null
+++ b/spec/models/protected_branch/merge_access_level_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe ProtectedBranch::MergeAccessLevel, :models do
+ it { is_expected.to validate_inclusion_of(:access_level).in_array([Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS]) }
+end
diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb
new file mode 100644
index 00000000000..de68351198c
--- /dev/null
+++ b/spec/models/protected_branch/push_access_level_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe ProtectedBranch::PushAccessLevel, :models do
+ it { is_expected.to validate_inclusion_of(:access_level).in_array([Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS]) }
+end
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index 179a443c43d..ca347cf92c9 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -7,9 +7,6 @@ describe ProtectedBranch, models: true do
it { is_expected.to belong_to(:project) }
end
- describe "Mass assignment" do
- end
-
describe 'Validation' do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:name) }
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 3209589ca52..a6d4d92c450 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Repository, models: true do
include RepoHelpers
- TestBlob = Struct.new(:name)
+ TestBlob = Struct.new(:path)
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
@@ -554,31 +554,31 @@ describe Repository, models: true do
it 'accepts changelog' do
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changelog')])
- expect(repository.changelog.name).to eq('changelog')
+ expect(repository.changelog.path).to eq('changelog')
end
it 'accepts news instead of changelog' do
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('news')])
- expect(repository.changelog.name).to eq('news')
+ expect(repository.changelog.path).to eq('news')
end
it 'accepts history instead of changelog' do
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('history')])
- expect(repository.changelog.name).to eq('history')
+ expect(repository.changelog.path).to eq('history')
end
it 'accepts changes instead of changelog' do
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changes')])
- expect(repository.changelog.name).to eq('changes')
+ expect(repository.changelog.path).to eq('changes')
end
it 'is case-insensitive' do
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('CHANGELOG')])
- expect(repository.changelog.name).to eq('CHANGELOG')
+ expect(repository.changelog.path).to eq('CHANGELOG')
end
end
@@ -613,7 +613,7 @@ describe Repository, models: true do
repository.create_file(user, 'LICENSE', 'Copyright!',
message: 'Add LICENSE', branch_name: 'master')
- expect(repository.license_blob.name).to eq('LICENSE')
+ expect(repository.license_blob.path).to eq('LICENSE')
end
%w[LICENSE LICENCE LiCensE LICENSE.md LICENSE.foo COPYING COPYING.md].each do |filename|
@@ -643,7 +643,7 @@ describe Repository, models: true do
expect(repository.license_key).to be_nil
end
- it 'detects license file with no recognizable open-source license content' do
+ it 'returns nil when the content is not recognizable' do
repository.create_file(user, 'LICENSE', 'Copyright!',
message: 'Add LICENSE', branch_name: 'master')
@@ -659,12 +659,45 @@ describe Repository, models: true do
end
end
+ describe '#license' do
+ before do
+ repository.delete_file(user, 'LICENSE',
+ message: 'Remove LICENSE', branch_name: 'master')
+ end
+
+ it 'returns nil when no license is detected' do
+ expect(repository.license).to be_nil
+ end
+
+ it 'returns nil when the repository does not exist' do
+ expect(repository).to receive(:exists?).and_return(false)
+
+ expect(repository.license).to be_nil
+ end
+
+ it 'returns nil when the content is not recognizable' do
+ repository.create_file(user, 'LICENSE', 'Copyright!',
+ message: 'Add LICENSE', branch_name: 'master')
+
+ expect(repository.license).to be_nil
+ end
+
+ it 'returns the license' do
+ license = Licensee::License.new('mit')
+ repository.create_file(user, 'LICENSE',
+ license.content,
+ message: 'Add LICENSE', branch_name: 'master')
+
+ expect(repository.license).to eq(license)
+ end
+ end
+
describe "#gitlab_ci_yml", caching: true do
it 'returns valid file' do
files = [TestBlob.new('file'), TestBlob.new('.gitlab-ci.yml'), TestBlob.new('copying')]
expect(repository.tree).to receive(:blobs).and_return(files)
- expect(repository.gitlab_ci_yml.name).to eq('.gitlab-ci.yml')
+ expect(repository.gitlab_ci_yml.path).to eq('.gitlab-ci.yml')
end
it 'returns nil if not exists' do
@@ -1615,15 +1648,25 @@ describe Repository, models: true do
describe '#readme', caching: true do
context 'with a non-existing repository' do
it 'returns nil' do
- expect(repository).to receive(:tree).with(:head).and_return(nil)
+ allow(repository).to receive(:tree).with(:head).and_return(nil)
expect(repository.readme).to be_nil
end
end
context 'with an existing repository' do
- it 'returns the README' do
- expect(repository.readme).to be_an_instance_of(Gitlab::Git::Blob)
+ context 'when no README exists' do
+ it 'returns nil' do
+ allow_any_instance_of(Tree).to receive(:readme).and_return(nil)
+
+ expect(repository.readme).to be_nil
+ end
+ end
+
+ context 'when a README exists' do
+ it 'returns the README' do
+ expect(repository.readme).to be_an_instance_of(ReadmeBlob)
+ end
end
end
end
@@ -1814,11 +1857,12 @@ describe Repository, models: true do
describe '#refresh_method_caches' do
it 'refreshes the caches of the given types' do
expect(repository).to receive(:expire_method_caches).
- with(%i(rendered_readme license_blob license_key))
+ with(%i(rendered_readme license_blob license_key license))
expect(repository).to receive(:rendered_readme)
expect(repository).to receive(:license_blob)
expect(repository).to receive(:license_key)
+ expect(repository).to receive(:license)
repository.refresh_method_caches(%i(readme license))
end
@@ -1861,19 +1905,43 @@ describe Repository, models: true do
end
describe '#is_ancestor?' do
- context 'Gitaly is_ancestor feature enabled' do
- let(:commit) { repository.commit }
- let(:ancestor) { commit.parents.first }
+ let(:commit) { repository.commit }
+ let(:ancestor) { commit.parents.first }
+ context 'with Gitaly enabled' do
+ it 'it is an ancestor' do
+ expect(repository.is_ancestor?(ancestor.id, commit.id)).to eq(true)
+ end
+
+ it 'it is not an ancestor' do
+ expect(repository.is_ancestor?(commit.id, ancestor.id)).to eq(false)
+ end
+
+ it 'returns false on nil-values' do
+ expect(repository.is_ancestor?(nil, commit.id)).to eq(false)
+ expect(repository.is_ancestor?(ancestor.id, nil)).to eq(false)
+ expect(repository.is_ancestor?(nil, nil)).to eq(false)
+ end
+ end
+
+ context 'with Gitaly disabled' do
before do
- allow(Gitlab::GitalyClient).to receive(:enabled?).and_return(true)
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:is_ancestor).and_return(true)
+ allow(Gitlab::GitalyClient).to receive(:enabled?).and_return(false)
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:is_ancestor).and_return(false)
end
- it "asks Gitaly server if it's an ancestor" do
- expect_any_instance_of(Gitlab::GitalyClient::Commit).to receive(:is_ancestor).with(ancestor.id, commit.id)
+ it 'it is an ancestor' do
+ expect(repository.is_ancestor?(ancestor.id, commit.id)).to eq(true)
+ end
+
+ it 'it is not an ancestor' do
+ expect(repository.is_ancestor?(commit.id, ancestor.id)).to eq(false)
+ end
- repository.is_ancestor?(ancestor.id, commit.id)
+ it 'returns false on nil-values' do
+ expect(repository.is_ancestor?(nil, commit.id)).to eq(false)
+ expect(repository.is_ancestor?(ancestor.id, nil)).to eq(false)
+ expect(repository.is_ancestor?(nil, nil)).to eq(false)
end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index eac9a6d8e64..a83726b48a0 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -13,6 +13,10 @@ describe User, models: true do
it { is_expected.to include_module(TokenAuthenticatable) }
end
+ describe 'delegations' do
+ it { is_expected.to delegate_method(:path).to(:namespace).with_prefix }
+ end
+
describe 'associations' do
it { is_expected.to have_one(:namespace) }
it { is_expected.to have_many(:snippets).dependent(:destroy) }
@@ -22,7 +26,7 @@ describe User, models: true do
it { is_expected.to have_many(:deploy_keys).dependent(:destroy) }
it { is_expected.to have_many(:events).dependent(:destroy) }
it { is_expected.to have_many(:recent_events).class_name('Event') }
- it { is_expected.to have_many(:issues).dependent(:restrict_with_exception) }
+ it { is_expected.to have_many(:issues).dependent(:destroy) }
it { is_expected.to have_many(:notes).dependent(:destroy) }
it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
it { is_expected.to have_many(:identities).dependent(:destroy) }
@@ -344,6 +348,35 @@ describe User, models: true do
end
end
+ describe '#update_tracked_fields!', :redis do
+ let(:request) { OpenStruct.new(remote_ip: "127.0.0.1") }
+ let(:user) { create(:user) }
+
+ it 'writes trackable attributes' do
+ expect do
+ user.update_tracked_fields!(request)
+ end.to change { user.reload.current_sign_in_at }
+ end
+
+ it 'does not write trackable attributes when called a second time within the hour' do
+ user.update_tracked_fields!(request)
+
+ expect do
+ user.update_tracked_fields!(request)
+ end.not_to change { user.reload.current_sign_in_at }
+ end
+
+ it 'writes trackable attributes for a different user' do
+ user2 = create(:user)
+
+ user.update_tracked_fields!(request)
+
+ expect do
+ user2.update_tracked_fields!(request)
+ end.to change { user2.reload.current_sign_in_at }
+ end
+ end
+
shared_context 'user keys' do
let(:user) { create(:user) }
let!(:key) { create(:key, user: user) }
@@ -411,6 +444,22 @@ describe User, models: true do
end
end
+ describe 'ensure incoming email token' do
+ it 'has incoming email token' do
+ user = create(:user)
+ expect(user.incoming_email_token).not_to be_blank
+ end
+ end
+
+ describe 'rss token' do
+ it 'ensures an rss token on read' do
+ user = create(:user, rss_token: nil)
+ rss_token = user.rss_token
+ expect(rss_token).not_to be_blank
+ expect(user.reload.rss_token).to eq rss_token
+ end
+ end
+
describe '#recently_sent_password_reset?' do
it 'is false when reset_password_sent_at is nil' do
user = build_stubbed(:user, reset_password_sent_at: nil)
@@ -637,7 +686,7 @@ describe User, models: true do
protocol_and_expectation = {
'http' => false,
'ssh' => true,
- '' => true,
+ '' => true
}
protocol_and_expectation.each do |protocol, expected|
@@ -935,12 +984,19 @@ describe User, models: true do
describe '#avatar_url' do
let(:user) { create(:user, :with_avatar) }
- subject { user.avatar_url }
context 'when avatar file is uploaded' do
- let(:avatar_path) { "/uploads/system/user/avatar/#{user.id}/dk.png" }
+ let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
+ let(:avatar_path) { "/uploads/user/avatar/#{user.id}/dk.png" }
+
+ it 'shows correct avatar url' do
+ expect(user.avatar_url).to eq(avatar_path)
+ expect(user.avatar_url(only_path: false)).to eq([gitlab_host, avatar_path].join)
+
+ allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)
- it { should eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
+ expect(user.avatar_url).to eq([gitlab_host, avatar_path].join)
+ end
end
end
@@ -1444,25 +1500,6 @@ describe User, models: true do
end
end
- describe '#viewable_starred_projects' do
- let(:user) { create(:user) }
- let(:public_project) { create(:empty_project, :public) }
- let(:private_project) { create(:empty_project, :private) }
- let(:private_viewable_project) { create(:empty_project, :private) }
-
- before do
- private_viewable_project.team << [user, Gitlab::Access::MASTER]
-
- [public_project, private_project, private_viewable_project].each do |project|
- user.toggle_star(project)
- end
- end
-
- it 'returns only starred projects the user can view' do
- expect(user.viewable_starred_projects).not_to include(private_project)
- end
- end
-
describe '#projects_with_reporter_access_limited_to' do
let(:project1) { create(:empty_project) }
let(:project2) { create(:empty_project) }
@@ -1792,4 +1829,32 @@ describe User, models: true do
expect(user.preferred_language).to eq('en')
end
end
+
+ context '#invalidate_issue_cache_counts' do
+ let(:user) { build_stubbed(:user) }
+
+ it 'invalidates cache for issue counter' do
+ cache_mock = double
+
+ expect(cache_mock).to receive(:delete).with(['users', user.id, 'assigned_open_issues_count'])
+
+ allow(Rails).to receive(:cache).and_return(cache_mock)
+
+ user.invalidate_issue_cache_counts
+ end
+ end
+
+ context '#invalidate_merge_request_cache_counts' do
+ let(:user) { build_stubbed(:user) }
+
+ it 'invalidates cache for Merge Request counter' do
+ cache_mock = double
+
+ expect(cache_mock).to receive(:delete).with(['users', user.id, 'assigned_open_merge_requests_count'])
+
+ allow(Rails).to receive(:cache).and_return(cache_mock)
+
+ user.invalidate_merge_request_cache_counts
+ end
+ end
end
diff --git a/spec/policies/deploy_key_policy_spec.rb b/spec/policies/deploy_key_policy_spec.rb
new file mode 100644
index 00000000000..28e10f0bfe2
--- /dev/null
+++ b/spec/policies/deploy_key_policy_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe DeployKeyPolicy, models: true do
+ subject { described_class.abilities(current_user, deploy_key).to_set }
+
+ describe 'updating a deploy_key' do
+ context 'when a regular user' do
+ let(:current_user) { create(:user) }
+
+ context 'tries to update private deploy key attached to project' do
+ let(:deploy_key) { create(:deploy_key, public: false) }
+ let(:project) { create(:project_empty_repo) }
+
+ before do
+ project.add_master(current_user)
+ project.deploy_keys << deploy_key
+ end
+
+ it { is_expected.to include(:update_deploy_key) }
+ end
+
+ context 'tries to update private deploy key attached to other project' do
+ let(:deploy_key) { create(:deploy_key, public: false) }
+ let(:other_project) { create(:project_empty_repo) }
+
+ before do
+ other_project.deploy_keys << deploy_key
+ end
+
+ it { is_expected.not_to include(:update_deploy_key) }
+ end
+
+ context 'tries to update public deploy key' do
+ let(:deploy_key) { create(:another_deploy_key, public: true) }
+
+ it { is_expected.not_to include(:update_deploy_key) }
+ end
+ end
+
+ context 'when an admin user' do
+ let(:current_user) { create(:user, :admin) }
+
+ context ' tries to update private deploy key' do
+ let(:deploy_key) { create(:deploy_key, public: false) }
+
+ it { is_expected.to include(:update_deploy_key) }
+ end
+
+ context 'when an admin user tries to update public deploy key' do
+ let(:deploy_key) { create(:another_deploy_key, public: true) }
+
+ it { is_expected.to include(:update_deploy_key) }
+ end
+ end
+ end
+end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 4c37a553227..a8331ceb5ff 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -9,11 +9,12 @@ describe GroupPolicy, models: true do
let(:admin) { create(:admin) }
let(:group) { create(:group) }
+ let(:reporter_permissions) { [:admin_label] }
+
let(:master_permissions) do
[
:create_projects,
- :admin_milestones,
- :admin_label
+ :admin_milestones
]
end
@@ -42,6 +43,7 @@ describe GroupPolicy, models: true do
it do
is_expected.to include(:read_group)
+ is_expected.not_to include(*reporter_permissions)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
@@ -52,6 +54,7 @@ describe GroupPolicy, models: true do
it do
is_expected.to include(:read_group)
+ is_expected.not_to include(*reporter_permissions)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
@@ -62,6 +65,7 @@ describe GroupPolicy, models: true do
it do
is_expected.to include(:read_group)
+ is_expected.to include(*reporter_permissions)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
@@ -72,6 +76,7 @@ describe GroupPolicy, models: true do
it do
is_expected.to include(:read_group)
+ is_expected.to include(*reporter_permissions)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
@@ -82,6 +87,7 @@ describe GroupPolicy, models: true do
it do
is_expected.to include(:read_group)
+ is_expected.to include(*reporter_permissions)
is_expected.to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
@@ -92,6 +98,7 @@ describe GroupPolicy, models: true do
it do
is_expected.to include(:read_group)
+ is_expected.to include(*reporter_permissions)
is_expected.to include(*master_permissions)
is_expected.to include(*owner_permissions)
end
@@ -102,14 +109,27 @@ describe GroupPolicy, models: true do
it do
is_expected.to include(:read_group)
+ is_expected.to include(*reporter_permissions)
is_expected.to include(*master_permissions)
is_expected.to include(*owner_permissions)
end
end
- describe 'private nested group inherit permissions', :nested_groups do
+ describe 'private nested group use the highest access level from the group and inherited permissions', :nested_groups do
let(:nested_group) { create(:group, :private, parent: group) }
+ before do
+ nested_group.add_guest(guest)
+ nested_group.add_guest(reporter)
+ nested_group.add_guest(developer)
+ nested_group.add_guest(master)
+
+ group.owners.destroy_all
+
+ group.add_guest(owner)
+ nested_group.add_owner(owner)
+ end
+
subject { described_class.abilities(current_user, nested_group).to_set }
context 'with no user' do
@@ -117,6 +137,7 @@ describe GroupPolicy, models: true do
it do
is_expected.not_to include(:read_group)
+ is_expected.not_to include(*reporter_permissions)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
@@ -127,6 +148,7 @@ describe GroupPolicy, models: true do
it do
is_expected.to include(:read_group)
+ is_expected.not_to include(*reporter_permissions)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
@@ -137,6 +159,7 @@ describe GroupPolicy, models: true do
it do
is_expected.to include(:read_group)
+ is_expected.to include(*reporter_permissions)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
@@ -147,6 +170,7 @@ describe GroupPolicy, models: true do
it do
is_expected.to include(:read_group)
+ is_expected.to include(*reporter_permissions)
is_expected.not_to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
@@ -157,6 +181,7 @@ describe GroupPolicy, models: true do
it do
is_expected.to include(:read_group)
+ is_expected.to include(*reporter_permissions)
is_expected.to include(*master_permissions)
is_expected.not_to include(*owner_permissions)
end
@@ -167,6 +192,7 @@ describe GroupPolicy, models: true do
it do
is_expected.to include(:read_group)
+ is_expected.to include(*reporter_permissions)
is_expected.to include(*master_permissions)
is_expected.to include(*owner_permissions)
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 064847ee3dc..0d3af1f4499 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -43,7 +43,7 @@ describe ProjectPolicy, models: true do
let(:master_permissions) do
%i[
- push_code_to_protected_branches update_project_snippet update_environment
+ delete_protected_branch update_project_snippet update_environment
update_deployment admin_milestone admin_project_snippet
admin_project_member admin_note admin_wiki admin_project
admin_commit_status admin_build admin_container_image
diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb
index ddbed5f781e..e1771b636b8 100644
--- a/spec/policies/project_snippet_policy_spec.rb
+++ b/spec/policies/project_snippet_policy_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe ProjectSnippetPolicy, models: true do
let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) }
- let(:project) { create(:empty_project, :public) }
+ let(:project) { create(:empty_project) }
let(:author_permissions) do
[
@@ -107,7 +107,7 @@ describe ProjectSnippetPolicy, models: true do
end
context 'snippet author' do
- let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) }
+ let(:snippet) { create(:project_snippet, :private, author: regular_user) }
subject { described_class.abilities(regular_user, snippet).to_set }
diff --git a/spec/presenters/conversational_development_index/metric_presenter_spec.rb b/spec/presenters/conversational_development_index/metric_presenter_spec.rb
new file mode 100644
index 00000000000..1e015c71f5b
--- /dev/null
+++ b/spec/presenters/conversational_development_index/metric_presenter_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe ConversationalDevelopmentIndex::MetricPresenter do
+ subject { described_class.new(metric) }
+ let(:metric) { build(:conversational_development_index_metric) }
+
+ describe '#cards' do
+ it 'includes instance score, leader score and percentage score' do
+ issues_card = subject.cards.first
+
+ expect(issues_card.instance_score).to eq 1.234
+ expect(issues_card.leader_score).to eq 9.256
+ expect(issues_card.percentage_score).to be_within(0.1).of(13.3)
+ end
+ end
+
+ describe '#idea_to_production_steps' do
+ it 'returns percentage score when it depends on a single feature' do
+ code_step = subject.idea_to_production_steps.fourth
+
+ expect(code_step.percentage_score).to be_within(0.1).of(50.0)
+ end
+
+ it 'returns percentage score when it depends on two features' do
+ issue_step = subject.idea_to_production_steps.second
+
+ expect(issue_step.percentage_score).to be_within(0.1).of(53.0)
+ end
+ end
+
+ describe '#average_percentage_score' do
+ it 'calculates an average value across all the features' do
+ expect(subject.average_percentage_score).to be_within(0.1).of(55.8)
+ end
+ end
+end
diff --git a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
index 6443f86b6a1..5c39e1b5f96 100644
--- a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
+++ b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
@@ -51,10 +51,6 @@ describe Projects::Settings::DeployKeysPresenter do
expect(presenter.available_project_keys).not_to be_empty
end
- it 'returns false if any available_project_keys are enabled' do
- expect(presenter.any_available_project_keys_enabled?).to eq(true)
- end
-
it 'returns the available_project_keys size' do
expect(presenter.available_project_keys_size).to eq(1)
end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 7eaa89837c8..c64499fc8c0 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -406,19 +406,6 @@ describe API::Branches do
delete api("/projects/#{project.id}/repository/branches/foobar", user)
expect(response).to have_http_status(404)
end
-
- it "removes protected branch" do
- create(:protected_branch, project: project, name: branch_name)
- delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
- expect(response).to have_http_status(405)
- expect(json_response['message']).to eq('Protected branch cant be removed')
- end
-
- it "does not remove HEAD branch" do
- delete api("/projects/#{project.id}/repository/branches/master", user)
- expect(response).to have_http_status(405)
- expect(json_response['message']).to eq('Cannot remove HEAD branch')
- end
end
describe "DELETE /projects/:id/repository/merged_branches" do
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 1c163cee152..6b637a03b6f 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -16,8 +16,8 @@ describe API::CommitStatuses do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
context 'ci commit exists' do
- let!(:master) { project.pipelines.create(sha: commit.id, ref: 'master') }
- let!(:develop) { project.pipelines.create(sha: commit.id, ref: 'develop') }
+ let!(:master) { project.pipelines.create(source: :push, sha: commit.id, ref: 'master') }
+ let!(:develop) { project.pipelines.create(source: :push, sha: commit.id, ref: 'develop') }
context "reporter user" do
let(:statuses_id) { json_response.map { |status| status['id'] } }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index b84361d3abd..b0c265b6453 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -485,7 +485,7 @@ describe API::Commits do
end
it "returns status for CI" do
- pipeline = project.ensure_pipeline('master', project.repository.commit.sha)
+ pipeline = project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha)
pipeline.update(status: 'success')
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
@@ -495,7 +495,7 @@ describe API::Commits do
end
it "returns status for CI when pipeline is created" do
- project.ensure_pipeline('master', project.repository.commit.sha)
+ project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha)
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 843e9862b0c..4d9cd5f3a27 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -13,7 +13,7 @@ describe API::DeployKeys do
describe 'GET /deploy_keys' do
context 'when unauthenticated' do
- it 'should return authentication error' do
+ it 'returns authentication error' do
get api('/deploy_keys')
expect(response.status).to eq(401)
@@ -21,7 +21,7 @@ describe API::DeployKeys do
end
context 'when authenticated as non-admin user' do
- it 'should return a 403 error' do
+ it 'returns a 403 error' do
get api('/deploy_keys', user)
expect(response.status).to eq(403)
@@ -29,7 +29,7 @@ describe API::DeployKeys do
end
context 'when authenticated as admin' do
- it 'should return all deploy keys' do
+ it 'returns all deploy keys' do
get api('/deploy_keys', admin)
expect(response.status).to eq(200)
@@ -43,7 +43,7 @@ describe API::DeployKeys do
describe 'GET /projects/:id/deploy_keys' do
before { deploy_key }
- it 'should return array of ssh keys' do
+ it 'returns array of ssh keys' do
get api("/projects/#{project.id}/deploy_keys", admin)
expect(response).to have_http_status(200)
@@ -54,14 +54,14 @@ describe API::DeployKeys do
end
describe 'GET /projects/:id/deploy_keys/:key_id' do
- it 'should return a single key' do
+ it 'returns a single key' do
get api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(deploy_key.title)
end
- it 'should return 404 Not Found with invalid ID' do
+ it 'returns 404 Not Found with invalid ID' do
get api("/projects/#{project.id}/deploy_keys/404", admin)
expect(response).to have_http_status(404)
@@ -69,26 +69,26 @@ describe API::DeployKeys do
end
describe 'POST /projects/:id/deploy_keys' do
- it 'should not create an invalid ssh key' do
+ it 'does not create an invalid ssh key' do
post api("/projects/#{project.id}/deploy_keys", admin), { title: 'invalid key' }
expect(response).to have_http_status(400)
expect(json_response['error']).to eq('key is missing')
end
- it 'should not create a key without title' do
+ it 'does not create a key without title' do
post api("/projects/#{project.id}/deploy_keys", admin), key: 'some key'
expect(response).to have_http_status(400)
expect(json_response['error']).to eq('title is missing')
end
- it 'should create new ssh key' do
+ it 'creates new ssh key' do
key_attrs = attributes_for :another_key
expect do
post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
- end.to change{ project.deploy_keys.count }.by(1)
+ end.to change { project.deploy_keys.count }.by(1)
end
it 'returns an existing ssh key when attempting to add a duplicate' do
@@ -117,10 +117,53 @@ describe API::DeployKeys do
end
end
+ describe 'PUT /projects/:id/deploy_keys/:key_id' do
+ let(:private_deploy_key) { create(:another_deploy_key, public: false) }
+ let(:project_private_deploy_key) do
+ create(:deploy_keys_project, project: project, deploy_key: private_deploy_key)
+ end
+
+ it 'updates a public deploy key as admin' do
+ expect do
+ put api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin), { title: 'new title' }
+ end.not_to change(deploy_key, :title)
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'does not update a public deploy key as non admin' do
+ expect do
+ put api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user), { title: 'new title' }
+ end.not_to change(deploy_key, :title)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'does not update a private key with invalid title' do
+ project_private_deploy_key
+
+ expect do
+ put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), { title: '' }
+ end.not_to change(deploy_key, :title)
+
+ expect(response).to have_http_status(400)
+ end
+
+ it 'updates a private ssh key with correct attributes' do
+ project_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
before { deploy_key }
- it 'should delete existing key' do
+ it 'deletes existing key' do
expect do
delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
@@ -128,7 +171,7 @@ describe API::DeployKeys do
end.to change{ project.deploy_keys.count }.by(-1)
end
- it 'should return 404 Not Found with invalid ID' do
+ it 'returns 404 Not Found with invalid ID' do
delete api("/projects/#{project.id}/deploy_keys/404", admin)
expect(response).to have_http_status(404)
@@ -150,7 +193,7 @@ describe API::DeployKeys do
end
context 'when authenticated as non-admin user' do
- it 'should return a 404 error' do
+ it 'returns a 404 error' do
post api("/projects/#{project2.id}/deploy_keys/#{deploy_key.id}/enable", user)
expect(response).to have_http_status(404)
diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb
new file mode 100644
index 00000000000..a19870a95e8
--- /dev/null
+++ b/spec/requests/api/events_spec.rb
@@ -0,0 +1,142 @@
+require 'spec_helper'
+
+describe API::Events, api: true do
+ include ApiHelpers
+ let(:user) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:other_user) { create(:user, username: 'otheruser') }
+ let(:private_project) { create(:empty_project, :private, creator_id: user.id, namespace: user.namespace) }
+ let(:closed_issue) { create(:closed_issue, project: private_project, author: user) }
+ let!(:closed_issue_event) { create(:event, project: private_project, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 12, 30)) }
+
+ describe 'GET /events' do
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get api('/events')
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns users events' do
+ get api('/events?action=closed&target_type=issue&after=2016-12-1&before=2016-12-31', user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ end
+ end
+ end
+
+ describe 'GET /users/:id/events' do
+ context "as a user that cannot see the event's project" do
+ it 'returns no events' do
+ get api("/users/#{user.id}/events", other_user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_empty
+ end
+ end
+
+ context "as a user that can see the event's project" do
+ it 'accepts a username' do
+ get api("/users/#{user.username}/events", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ end
+
+ it 'returns the events' do
+ get api("/users/#{user.id}/events", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ end
+
+ context 'when there are multiple events from different projects' do
+ let(:second_note) { create(:note_on_issue, project: create(:empty_project)) }
+
+ before do
+ second_note.project.add_user(user, :developer)
+
+ [second_note].each do |note|
+ EventCreateService.new.leave_note(note, user)
+ end
+ end
+
+ it 'returns events in the correct order (from newest to oldest)' do
+ get api("/users/#{user.id}/events", user)
+
+ comment_events = json_response.select { |e| e['action_name'] == 'commented on' }
+ close_events = json_response.select { |e| e['action_name'] == 'closed' }
+
+ expect(comment_events[0]['target_id']).to eq(second_note.id)
+ expect(close_events[0]['target_id']).to eq(closed_issue.id)
+ end
+
+ it 'accepts filter parameters' do
+ get api("/users/#{user.id}/events?action=closed&target_type=issue&after=2016-12-1&before=2016-12-31", user)
+
+ expect(json_response.size).to eq(1)
+ expect(json_response[0]['target_id']).to eq(closed_issue.id)
+ end
+ end
+ end
+
+ it 'returns a 404 error if not found' do
+ get api('/users/42/events', user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+ end
+
+ describe 'GET /projects/:id/events' do
+ context 'when unauthenticated ' do
+ it 'returns 404 for private project' do
+ get api("/projects/#{private_project.id}/events")
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns 200 status for a public project' do
+ public_project = create(:empty_project, :public)
+
+ get api("/projects/#{public_project.id}/events")
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ context 'when not permitted to read' do
+ it 'returns 404' do
+ get api("/projects/#{private_project.id}/events", non_member)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns project events' do
+ get api("/projects/#{private_project.id}/events?action=closed&target_type=issue&after=2016-12-1&before=2016-12-31", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ end
+
+ it 'returns 404 if project does not exist' do
+ get api("/projects/1234/events", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
new file mode 100644
index 00000000000..f169e6661d1
--- /dev/null
+++ b/spec/requests/api/features_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe API::Features do
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+
+ describe 'GET /features' do
+ let(:expected_features) do
+ [
+ {
+ 'name' => 'feature_1',
+ 'state' => 'on',
+ 'gates' => [{ 'key' => 'boolean', 'value' => true }]
+ },
+ {
+ 'name' => 'feature_2',
+ 'state' => 'off',
+ 'gates' => [{ 'key' => 'boolean', 'value' => false }]
+ }
+ ]
+ end
+
+ before do
+ Feature.get('feature_1').enable
+ Feature.get('feature_2').disable
+ end
+
+ it 'returns a 401 for anonymous users' do
+ get api('/features')
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns a 403 for users' do
+ get api('/features', user)
+
+ expect(response).to have_http_status(403)
+ end
+
+ it 'returns the feature list for admins' do
+ get api('/features', admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to match_array(expected_features)
+ end
+ end
+
+ describe 'POST /feature' do
+ let(:feature_name) { 'my_feature' }
+ it 'returns a 401 for anonymous users' do
+ post api("/features/#{feature_name}")
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns a 403 for users' do
+ post api("/features/#{feature_name}", user)
+
+ expect(response).to have_http_status(403)
+ end
+
+ it 'creates an enabled feature if passed true' do
+ post api("/features/#{feature_name}", admin), value: 'true'
+
+ expect(response).to have_http_status(201)
+ expect(Feature.get(feature_name)).to be_enabled
+ end
+
+ it 'creates a feature with the given percentage if passed an integer' do
+ post api("/features/#{feature_name}", admin), value: '50'
+
+ expect(response).to have_http_status(201)
+ expect(Feature.get(feature_name).percentage_of_time_value).to be(50)
+ end
+
+ context 'when the feature exists' do
+ let(:feature) { Feature.get(feature_name) }
+
+ before do
+ feature.disable # This also persists the feature on the DB
+ end
+
+ it 'enables the feature if passed true' do
+ post api("/features/#{feature_name}", admin), value: 'true'
+
+ expect(response).to have_http_status(201)
+ expect(feature).to be_enabled
+ end
+
+ context 'with a pre-existing percentage value' do
+ before do
+ feature.enable_percentage_of_time(50)
+ end
+
+ it 'updates the percentage of time if passed an integer' do
+ post api("/features/#{feature_name}", admin), value: '30'
+
+ expect(response).to have_http_status(201)
+ expect(Feature.get(feature_name).percentage_of_time_value).to be(30)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index fa28047d49c..d325c6eff9d 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -258,6 +258,25 @@ describe API::Files do
expect(last_commit.author_name).to eq(user.name)
end
+ it "returns a 400 bad request if update existing file with stale last commit id" do
+ params_with_stale_id = valid_params.merge(last_commit_id: 'stale')
+
+ put api(route(file_path), user), params_with_stale_id
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq('You are attempting to update a file that has changed since you started editing it.')
+ end
+
+ it "updates existing file in project repo with accepts correct last commit id" do
+ last_commit = Gitlab::Git::Commit
+ .last_for_path(project.repository, 'master', URI.unescape(file_path))
+ params_with_correct_id = valid_params.merge(last_commit_id: last_commit.id)
+
+ put api(route(file_path), user), params_with_correct_id
+
+ expect(response).to have_http_status(200)
+ end
+
it "returns a 400 bad request if no params given" do
put api(route(file_path), user)
@@ -329,7 +348,7 @@ describe API::Files do
end
let(:get_params) do
{
- ref: 'master',
+ ref: 'master'
}
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 9fb303be1b5..bb53796cbd7 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -73,7 +73,7 @@ describe API::Groups do
storage_size: 702,
repository_size: 123,
lfs_objects_size: 234,
- build_artifacts_size: 345,
+ build_artifacts_size: 345
}.stringify_keys
exposed_attributes = attributes.dup
exposed_attributes['job_artifacts_size'] = exposed_attributes.delete('build_artifacts_size')
@@ -178,7 +178,7 @@ describe API::Groups do
expect(json_response['path']).to eq(group1.path)
expect(json_response['description']).to eq(group1.description)
expect(json_response['visibility']).to eq(Gitlab::VisibilityLevel.string_level(group1.visibility_level))
- expect(json_response['avatar_url']).to eq(group1.avatar_url)
+ expect(json_response['avatar_url']).to eq(group1.avatar_url(only_path: false))
expect(json_response['web_url']).to eq(group1.web_url)
expect(json_response['request_access_enabled']).to eq(group1.request_access_enabled)
expect(json_response['full_name']).to eq(group1.full_name)
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
new file mode 100644
index 00000000000..85d11deb26f
--- /dev/null
+++ b/spec/requests/api/pipeline_schedules_spec.rb
@@ -0,0 +1,297 @@
+require 'spec_helper'
+
+describe API::PipelineSchedules do
+ set(:developer) { create(:user) }
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+
+ before do
+ project.add_developer(developer)
+ end
+
+ describe 'GET /projects/:id/pipeline_schedules' do
+ context 'authenticated user with valid permissions' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
+
+ before do
+ pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+ end
+
+ it 'returns list of pipeline_schedules' do
+ get api("/projects/#{project.id}/pipeline_schedules", developer)
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response).to match_response_schema('pipeline_schedules')
+ end
+
+ it 'avoids N + 1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ get api("/projects/#{project.id}/pipeline_schedules", developer)
+ end.count
+
+ create_list(:ci_pipeline_schedule, 10, project: project)
+ .each do |pipeline_schedule|
+ create(:user).tap do |user|
+ project.add_developer(user)
+ pipeline_schedule.update_attributes(owner: user)
+ end
+ pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+ end
+
+ expect do
+ get api("/projects/#{project.id}/pipeline_schedules", developer)
+ end.not_to exceed_query_limit(control_count)
+ end
+
+ %w[active inactive].each do |target|
+ context "when scope is #{target}" do
+ before do
+ create(:ci_pipeline_schedule, project: project, active: active?(target))
+ end
+
+ it 'returns matched pipeline schedules' do
+ get api("/projects/#{project.id}/pipeline_schedules", developer), scope: target
+
+ expect(json_response.map{ |r| r['active'] }).to all(eq(active?(target)))
+ end
+ end
+
+ def active?(str)
+ (str == 'active') ? true : false
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer) }
+
+ before do
+ pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'returns pipeline_schedule details' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to match_response_schema('pipeline_schedule')
+ end
+
+ it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
+ get api("/projects/#{project.id}/pipeline_schedules/-5", developer)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not return pipeline_schedules list' do
+ get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipeline_schedules' do
+ let(:params) { attributes_for(:ci_pipeline_schedule) }
+
+ context 'authenticated user with valid permissions' do
+ context 'with required parameters' do
+ it 'creates pipeline_schedule' do
+ expect do
+ post api("/projects/#{project.id}/pipeline_schedules", developer),
+ params
+ end.to change { project.pipeline_schedules.count }.by(1)
+
+ expect(response).to have_http_status(:created)
+ expect(response).to match_response_schema('pipeline_schedule')
+ expect(json_response['description']).to eq(params[:description])
+ expect(json_response['ref']).to eq(params[:ref])
+ expect(json_response['cron']).to eq(params[:cron])
+ expect(json_response['cron_timezone']).to eq(params[:cron_timezone])
+ expect(json_response['owner']['id']).to eq(developer.id)
+ end
+ end
+
+ context 'without required parameters' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules", developer)
+
+ expect(response).to have_http_status(:bad_request)
+ end
+ end
+
+ context 'when cron has validation error' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules", developer),
+ params.merge('cron' => 'invalid-cron')
+
+ expect(response).to have_http_status(:bad_request)
+ expect(json_response['message']).to have_key('cron')
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules", user), params
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not create pipeline_schedule' do
+ post api("/projects/#{project.id}/pipeline_schedules"), params
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
+ let(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'updates cron' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
+ cron: '1 2 3 4 *'
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to match_response_schema('pipeline_schedule')
+ expect(json_response['cron']).to eq('1 2 3 4 *')
+ end
+
+ context 'when cron has validation error' do
+ it 'does not update pipeline_schedule' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
+ cron: 'invalid-cron'
+
+ expect(response).to have_http_status(:bad_request)
+ expect(json_response['message']).to have_key('cron')
+ end
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update pipeline_schedule' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update pipeline_schedule' do
+ put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
+ let(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'updates owner' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", developer)
+
+ expect(response).to have_http_status(:created)
+ expect(response).to match_response_schema('pipeline_schedule')
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update owner' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", user)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update owner' do
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
+ let(:master) { create(:user) }
+
+ let!(:pipeline_schedule) do
+ create(:ci_pipeline_schedule, project: project, owner: developer)
+ end
+
+ before do
+ project.add_master(master)
+ end
+
+ context 'authenticated user with valid permissions' do
+ it 'deletes pipeline_schedule' do
+ expect do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", master)
+ end.to change { project.pipeline_schedules.count }.by(-1)
+
+ expect(response).to have_http_status(:accepted)
+ expect(response).to match_response_schema('pipeline_schedule')
+ end
+
+ it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
+ delete api("/projects/#{project.id}/pipeline_schedules/-5", master)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not delete pipeline_schedule' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
+
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not delete pipeline_schedule' do
+ delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index f9e5316b3de..9e6957e9922 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -7,7 +7,7 @@ describe API::Pipelines do
let!(:pipeline) do
create(:ci_empty_pipeline, project: project, sha: project.commit.id,
- ref: project.default_branch)
+ ref: project.default_branch, user: user)
end
before { project.team << [user, :master] }
@@ -232,20 +232,26 @@ describe API::Pipelines do
context 'when order_by and sort are specified' do
context 'when order_by user_id' do
- let!(:pipeline) { create_list(:ci_pipeline, 2, project: project, user: create(:user)) }
+ before do
+ 3.times do
+ create(:ci_pipeline, project: project, user: create(:user))
+ end
+ end
- it 'sorts as user_id: :asc' do
- get api("/projects/#{project.id}/pipelines", user), order_by: 'user_id', sort: 'asc'
+ context 'when sort parameter is valid' do
+ it 'sorts as user_id: :desc' do
+ get api("/projects/#{project.id}/pipelines", user), order_by: 'user_id', sort: 'desc'
- expect(response).to have_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).not_to be_empty
- pipeline.sort_by { |p| p.user.id }.tap do |sorted_pipeline|
- json_response.each_with_index { |r, i| expect(r['id']).to eq(sorted_pipeline[i].id) }
+ expect(response).to have_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).not_to be_empty
+
+ pipeline_ids = Ci::Pipeline.all.order(user_id: :desc).pluck(:id)
+ expect(json_response.map { |r| r['id'] }).to eq(pipeline_ids)
end
end
- context 'when sort is invalid' do
+ context 'when sort parameter is invalid' do
it 'returns bad_request' do
get api("/projects/#{project.id}/pipelines", user), order_by: 'user_id', sort: 'invalid_sort'
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index aee0e17a153..0f9330b062d 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -60,7 +60,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
expect(json_response['note_events']).to eq(hook.note_events)
- expect(json_response['job_events']).to eq(hook.build_events)
+ expect(json_response['job_events']).to eq(hook.job_events)
expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
@@ -148,7 +148,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
expect(json_response['note_events']).to eq(hook.note_events)
- expect(json_response['job_events']).to eq(hook.build_events)
+ expect(json_response['job_events']).to eq(hook.job_events)
expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 3ab1764f5c3..4d4631322b1 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -36,11 +36,34 @@ describe API::ProjectSnippets do
end
end
+ describe 'GET /projects/:project_id/snippets/:id' do
+ let(:user) { create(:user) }
+ let(:snippet) { create(:project_snippet, :public, project: project) }
+
+ it 'returns snippet json' do
+ get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
+
+ expect(response).to have_http_status(200)
+
+ expect(json_response['title']).to eq(snippet.title)
+ expect(json_response['description']).to eq(snippet.description)
+ expect(json_response['file_name']).to eq(snippet.file_name)
+ end
+
+ it 'returns 404 for invalid snippet id' do
+ get api("/projects/#{project.id}/snippets/1234", user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Not found')
+ end
+ end
+
describe 'POST /projects/:project_id/snippets/' do
let(:params) do
{
title: 'Test Title',
file_name: 'test.rb',
+ description: 'test description',
code: 'puts "hello world"',
visibility: 'public'
}
@@ -52,6 +75,7 @@ describe API::ProjectSnippets do
expect(response).to have_http_status(201)
snippet = ProjectSnippet.find(json_response['id'])
expect(snippet.content).to eq(params[:code])
+ expect(snippet.description).to eq(params[:description])
expect(snippet.title).to eq(params[:title])
expect(snippet.file_name).to eq(params[:file_name])
expect(snippet.visibility_level).to eq(Snippet::PUBLIC)
@@ -106,12 +130,14 @@ describe API::ProjectSnippets do
it 'updates snippet' do
new_content = 'New content'
+ new_description = 'New description'
- put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content
+ put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content, description: new_description
expect(response).to have_http_status(200)
snippet.reload
expect(snippet.content).to eq(new_content)
+ expect(snippet.description).to eq(new_description)
end
it 'returns 404 for invalid snippet id' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index dae437ecb31..86c57204971 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -316,15 +316,15 @@ describe API::Projects do
expect(project.path).to eq('foo_project')
end
- it 'creates new project name and path and returns 201' do
- expect { post api('/projects', user), path: 'foo-Project', name: 'Foo Project' }.
+ it 'creates new project with name and path and returns 201' do
+ expect { post api('/projects', user), path: 'path-project-Foo', name: 'Foo Project' }.
to change { Project.count }.by(1)
expect(response).to have_http_status(201)
project = Project.first
expect(project.name).to eq('Foo Project')
- expect(project.path).to eq('foo-Project')
+ expect(project.path).to eq('path-project-Foo')
end
it 'creates last project before reaching project limit' do
@@ -390,6 +390,14 @@ describe API::Projects do
expect(json_response['visibility']).to eq('private')
end
+ it 'sets tag list to a project' do
+ project = attributes_for(:project, tag_list: %w[tagFirst tagSecond])
+
+ post api('/projects', user), project
+
+ expect(json_response['tag_list']).to eq(%w[tagFirst tagSecond])
+ end
+
it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
post api('/projects', user), project
@@ -462,9 +470,25 @@ describe API::Projects do
before { project }
before { admin }
- it 'creates new project without path and return 201' do
- expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1)
+ it 'creates new project without path but with name and return 201' do
+ expect { post api("/projects/user/#{user.id}", admin), name: 'Foo Project' }.to change {Project.count}.by(1)
expect(response).to have_http_status(201)
+
+ project = Project.first
+
+ expect(project.name).to eq('Foo Project')
+ expect(project.path).to eq('foo-project')
+ end
+
+ it 'creates new project with name and path and returns 201' do
+ expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' }.
+ to change { Project.count }.by(1)
+ expect(response).to have_http_status(201)
+
+ project = Project.first
+
+ expect(project.name).to eq('Foo Project')
+ expect(project.path).to eq('path-project-Foo')
end
it 'responds with 400 on failure and not project' do
@@ -611,6 +635,8 @@ describe API::Projects do
expect(json_response['shared_runners_enabled']).to be_present
expect(json_response['creator_id']).to be_present
expect(json_response['namespace']).to be_present
+ expect(json_response['import_status']).to be_present
+ expect(json_response).to include("import_error")
expect(json_response['avatar_url']).to be_nil
expect(json_response['star_count']).to be_present
expect(json_response['forks_count']).to be_present
@@ -660,7 +686,7 @@ describe API::Projects do
'name' => user.namespace.name,
'path' => user.namespace.path,
'kind' => user.namespace.kind,
- 'full_path' => user.namespace.full_path,
+ 'full_path' => user.namespace.full_path
})
end
@@ -678,6 +704,20 @@ describe API::Projects do
expect(json_response).to include 'statistics'
end
+ it "includes import_error if user can admin project" do
+ get api("/projects/#{project.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to include("import_error")
+ end
+
+ it "does not include import_error if user cannot admin project" do
+ get api("/projects/#{project.id}", user3)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).not_to include("import_error")
+ end
+
describe 'permissions' do
context 'all projects' do
before { project.team << [user, :master] }
@@ -722,64 +762,6 @@ describe API::Projects do
end
end
- describe 'GET /projects/:id/events' do
- shared_examples_for 'project events response' do
- it 'returns the project events' do
- member = create(:user)
- create(:project_member, :developer, user: member, project: project)
- note = create(:note_on_issue, note: 'What an awesome day!', project: project)
- EventCreateService.new.leave_note(note, note.author)
-
- get api("/projects/#{project.id}/events", current_user)
-
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
-
- first_event = json_response.first
- expect(first_event['action_name']).to eq('commented on')
- expect(first_event['note']['body']).to eq('What an awesome day!')
-
- last_event = json_response.last
-
- expect(last_event['action_name']).to eq('joined')
- expect(last_event['project_id'].to_i).to eq(project.id)
- expect(last_event['author_username']).to eq(member.username)
- expect(last_event['author']['name']).to eq(member.name)
- end
- end
-
- context 'when unauthenticated' do
- it_behaves_like 'project events response' do
- let(:project) { create(:empty_project, :public) }
- let(:current_user) { nil }
- end
- end
-
- context 'when authenticated' do
- context 'valid request' do
- it_behaves_like 'project events response' do
- let(:current_user) { user }
- end
- end
-
- it 'returns a 404 error if not found' do
- get api('/projects/42/events', user)
-
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 Project Not Found')
- end
-
- it 'returns a 404 error if user is not a member' do
- other_user = create(:user)
-
- get api("/projects/#{project.id}/events", other_user)
-
- expect(response).to have_http_status(404)
- end
- end
- end
-
describe 'GET /projects/:id/users' do
shared_examples_for 'project users response' do
it 'returns the project users' do
@@ -1440,6 +1422,8 @@ describe API::Projects do
expect(json_response['owner']['id']).to eq(user2.id)
expect(json_response['namespace']['id']).to eq(user2.namespace.id)
expect(json_response['forked_from_project']['id']).to eq(project.id)
+ expect(json_response['import_status']).to eq('scheduled')
+ expect(json_response).to include("import_error")
end
it 'forks if user is admin' do
@@ -1451,6 +1435,8 @@ describe API::Projects do
expect(json_response['owner']['id']).to eq(admin.id)
expect(json_response['namespace']['id']).to eq(admin.namespace.id)
expect(json_response['forked_from_project']['id']).to eq(project.id)
+ expect(json_response['import_status']).to eq('scheduled')
+ expect(json_response).to include("import_error")
end
it 'fails on missing project access for the project to fork' do
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index e429cddcf6a..8741cbd4e80 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -80,11 +80,33 @@ describe API::Snippets do
end
end
+ describe 'GET /snippets/:id' do
+ let(:snippet) { create(:personal_snippet, author: user) }
+
+ it 'returns snippet json' do
+ get api("/snippets/#{snippet.id}", user)
+
+ expect(response).to have_http_status(200)
+
+ expect(json_response['title']).to eq(snippet.title)
+ expect(json_response['description']).to eq(snippet.description)
+ expect(json_response['file_name']).to eq(snippet.file_name)
+ end
+
+ it 'returns 404 for invalid snippet id' do
+ get api("/snippets/1234", user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Not found')
+ end
+ end
+
describe 'POST /snippets/' do
let(:params) do
{
title: 'Test Title',
file_name: 'test.rb',
+ description: 'test description',
content: 'puts "hello world"',
visibility: 'public'
}
@@ -97,6 +119,7 @@ describe API::Snippets do
expect(response).to have_http_status(201)
expect(json_response['title']).to eq(params[:title])
+ expect(json_response['description']).to eq(params[:description])
expect(json_response['file_name']).to eq(params[:file_name])
end
@@ -150,12 +173,14 @@ describe API::Snippets do
it 'updates snippet' do
new_content = 'New content'
+ new_description = 'New description'
- put api("/snippets/#{snippet.id}", user), content: new_content
+ put api("/snippets/#{snippet.id}", user), content: new_content, description: new_description
expect(response).to have_http_status(200)
snippet.reload
expect(snippet.content).to eq(new_content)
+ expect(snippet.description).to eq(new_description)
end
it 'returns 404 for invalid snippet id' do
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index c7b84173570..2eb191d6049 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -32,8 +32,9 @@ describe API::SystemHooks do
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['url']).to eq(hook.url)
- expect(json_response.first['push_events']).to be true
+ expect(json_response.first['push_events']).to be false
expect(json_response.first['tag_push_events']).to be false
+ expect(json_response.first['repository_update_events']).to be true
end
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 4919ad19833..ec51b96c86b 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -287,7 +287,7 @@ describe API::Users do
expect(json_response['message']['projects_limit']).
to eq(['must be greater than or equal to 0'])
expect(json_response['message']['username']).
- to eq([Gitlab::Regex.namespace_regex_message])
+ to eq([Gitlab::PathRegex.namespace_format_message])
end
it "is not available for non admin users" do
@@ -426,9 +426,14 @@ describe API::Users do
expect(user.reload.email).not_to eq('invalid email')
end
- it "is not available for non admin users" do
- put api("/users/#{user.id}", user), attributes_for(:user)
- expect(response).to have_http_status(403)
+ context 'when the current user is not an admin' do
+ it "is not available" do
+ expect do
+ put api("/users/#{user.id}", user), attributes_for(:user)
+ end.not_to change { user.reload.attributes }
+
+ expect(response).to have_http_status(403)
+ end
end
it "returns 404 for non-existing user" do
@@ -459,7 +464,7 @@ describe API::Users do
expect(json_response['message']['projects_limit']).
to eq(['must be greater than or equal to 0'])
expect(json_response['message']['username']).
- to eq([Gitlab::Regex.namespace_regex_message])
+ to eq([Gitlab::PathRegex.namespace_format_message])
end
it 'returns 400 if provider is missing for identity update' do
@@ -649,7 +654,7 @@ describe API::Users do
end
it "returns a 404 for invalid ID" do
- put api("/users/ASDF/emails", admin)
+ get api("/users/ASDF/emails", admin)
expect(response).to have_http_status(404)
end
@@ -702,6 +707,7 @@ describe API::Users do
describe "DELETE /users/:id" do
let!(:namespace) { user.namespace }
+ let!(:issue) { create(:issue, author: user) }
before { admin }
it "deletes user" do
@@ -733,6 +739,25 @@ describe API::Users do
expect(response).to have_http_status(404)
end
+
+ context "hard delete disabled" do
+ it "moves contributions to the ghost user" do
+ Sidekiq::Testing.inline! { delete api("/users/#{user.id}", admin) }
+
+ expect(response).to have_http_status(204)
+ expect(issue.reload).to be_persisted
+ expect(issue.author.ghost?).to be_truthy
+ end
+ end
+
+ context "hard delete enabled" do
+ it "removes contributions" do
+ Sidekiq::Testing.inline! { delete api("/users/#{user.id}?hard_delete=true", admin) }
+
+ expect(response).to have_http_status(204)
+ expect(Issue.exists?(issue.id)).to be_falsy
+ end
+ end
end
describe "GET /user" do
@@ -1110,83 +1135,6 @@ describe API::Users do
end
end
- describe 'GET /users/:id/events' do
- let(:user) { create(:user) }
- let(:project) { create(:empty_project) }
- let(:note) { create(:note_on_issue, note: 'What an awesome day!', project: project) }
-
- before do
- project.add_user(user, :developer)
- EventCreateService.new.leave_note(note, user)
- end
-
- context "as a user than cannot see the event's project" do
- it 'returns no events' do
- other_user = create(:user)
-
- get api("/users/#{user.id}/events", other_user)
-
- expect(response).to have_http_status(200)
- expect(json_response).to be_empty
- end
- end
-
- context "as a user than can see the event's project" do
- context 'joined event' do
- it 'returns the "joined" event' do
- get api("/users/#{user.id}/events", user)
-
- expect(response).to have_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
-
- comment_event = json_response.find { |e| e['action_name'] == 'commented on' }
-
- expect(comment_event['project_id'].to_i).to eq(project.id)
- expect(comment_event['author_username']).to eq(user.username)
- expect(comment_event['note']['id']).to eq(note.id)
- expect(comment_event['note']['body']).to eq('What an awesome day!')
-
- joined_event = json_response.find { |e| e['action_name'] == 'joined' }
-
- expect(joined_event['project_id'].to_i).to eq(project.id)
- expect(joined_event['author_username']).to eq(user.username)
- expect(joined_event['author']['name']).to eq(user.name)
- end
- end
-
- context 'when there are multiple events from different projects' do
- let(:second_note) { create(:note_on_issue, project: create(:empty_project)) }
- let(:third_note) { create(:note_on_issue, project: project) }
-
- before do
- second_note.project.add_user(user, :developer)
-
- [second_note, third_note].each do |note|
- EventCreateService.new.leave_note(note, user)
- end
- end
-
- it 'returns events in the correct order (from newest to oldest)' do
- get api("/users/#{user.id}/events", user)
-
- comment_events = json_response.select { |e| e['action_name'] == 'commented on' }
-
- expect(comment_events[0]['target_id']).to eq(third_note.id)
- expect(comment_events[1]['target_id']).to eq(second_note.id)
- expect(comment_events[2]['target_id']).to eq(note.id)
- end
- end
- end
-
- it 'returns a 404 error if not found' do
- get api('/users/42/events', user)
-
- expect(response).to have_http_status(404)
- expect(json_response['message']).to eq('404 User Not Found')
- end
- end
-
context "user activities", :redis do
let!(:old_active_user) { create(:user, last_activity_on: Time.utc(2000, 1, 1)) }
let!(:newly_active_user) { create(:user, last_activity_on: 2.days.ago.midday) }
diff --git a/spec/requests/api/v3/branches_spec.rb b/spec/requests/api/v3/branches_spec.rb
index 72f8fbe71fb..c88f7788697 100644
--- a/spec/requests/api/v3/branches_spec.rb
+++ b/spec/requests/api/v3/branches_spec.rb
@@ -47,19 +47,6 @@ describe API::V3::Branches do
delete v3_api("/projects/#{project.id}/repository/branches/foobar", user)
expect(response).to have_http_status(404)
end
-
- it "removes protected branch" do
- create(:protected_branch, project: project, name: branch_name)
- delete v3_api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
- expect(response).to have_http_status(405)
- expect(json_response['message']).to eq('Protected branch cant be removed')
- end
-
- it "does not remove HEAD branch" do
- delete v3_api("/projects/#{project.id}/repository/branches/master", user)
- expect(response).to have_http_status(405)
- expect(json_response['message']).to eq('Cannot remove HEAD branch')
- end
end
describe "DELETE /projects/:id/repository/merged_branches" do
diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb
index 386f60065ad..4a4a5dc5c7c 100644
--- a/spec/requests/api/v3/commits_spec.rb
+++ b/spec/requests/api/v3/commits_spec.rb
@@ -386,7 +386,7 @@ describe API::V3::Commits do
end
it "returns status for CI" do
- pipeline = project.ensure_pipeline('master', project.repository.commit.sha)
+ pipeline = project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha)
pipeline.update(status: 'success')
get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
@@ -396,7 +396,7 @@ describe API::V3::Commits do
end
it "returns status for CI when pipeline is created" do
- project.ensure_pipeline('master', project.repository.commit.sha)
+ project.pipelines.create(source: :push, ref: 'master', sha: project.repository.commit.sha)
get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
diff --git a/spec/requests/api/v3/deploy_keys_spec.rb b/spec/requests/api/v3/deploy_keys_spec.rb
index b61b2b618a6..94f4d93a8dc 100644
--- a/spec/requests/api/v3/deploy_keys_spec.rb
+++ b/spec/requests/api/v3/deploy_keys_spec.rb
@@ -105,6 +105,15 @@ describe API::V3::DeployKeys do
expect(response).to have_http_status(201)
end
+
+ it 'accepts can_push parameter' do
+ key_attrs = attributes_for :write_access_key
+
+ post v3_api("/projects/#{project.id}/#{path}", admin), key_attrs
+
+ expect(response).to have_http_status(201)
+ expect(json_response['can_push']).to eq(true)
+ end
end
describe "DELETE /projects/:id/#{path}/:key_id" do
diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb
index 5bcbb441979..378ca1720ff 100644
--- a/spec/requests/api/v3/files_spec.rb
+++ b/spec/requests/api/v3/files_spec.rb
@@ -53,7 +53,7 @@ describe API::V3::Files do
let(:params) do
{
file_path: 'app/models/application.rb',
- ref: 'master',
+ ref: 'master'
}
end
@@ -263,7 +263,7 @@ describe API::V3::Files do
let(:get_params) do
{
file_path: file_path,
- ref: 'master',
+ ref: 'master'
}
end
diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb
index 2f6f1bad0b8..98e8c954909 100644
--- a/spec/requests/api/v3/groups_spec.rb
+++ b/spec/requests/api/v3/groups_spec.rb
@@ -69,7 +69,7 @@ describe API::V3::Groups do
storage_size: 702,
repository_size: 123,
lfs_objects_size: 234,
- build_artifacts_size: 345,
+ build_artifacts_size: 345
}.stringify_keys
project1.statistics.update!(attributes)
@@ -176,7 +176,7 @@ describe API::V3::Groups do
expect(json_response['path']).to eq(group1.path)
expect(json_response['description']).to eq(group1.description)
expect(json_response['visibility_level']).to eq(group1.visibility_level)
- expect(json_response['avatar_url']).to eq(group1.avatar_url)
+ expect(json_response['avatar_url']).to eq(group1.avatar_url(only_path: false))
expect(json_response['web_url']).to eq(group1.web_url)
expect(json_response['request_access_enabled']).to eq(group1.request_access_enabled)
expect(json_response['full_name']).to eq(group1.full_name)
diff --git a/spec/requests/api/v3/project_hooks_spec.rb b/spec/requests/api/v3/project_hooks_spec.rb
index a3a4c77d09d..1969d1c7f2b 100644
--- a/spec/requests/api/v3/project_hooks_spec.rb
+++ b/spec/requests/api/v3/project_hooks_spec.rb
@@ -58,7 +58,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
expect(json_response['note_events']).to eq(hook.note_events)
- expect(json_response['build_events']).to eq(hook.build_events)
+ expect(json_response['build_events']).to eq(hook.job_events)
expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
@@ -143,7 +143,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
expect(json_response['note_events']).to eq(hook.note_events)
- expect(json_response['build_events']).to eq(hook.build_events)
+ expect(json_response['build_events']).to eq(hook.job_events)
expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index 5503882609f..47cca4275af 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -165,7 +165,7 @@ describe API::V3::Projects do
expect(json_response).to satisfy do |response|
response.one? do |entry|
- entry.has_key?('permissions') &&
+ entry.key?('permissions') &&
entry['name'] == project.name &&
entry['owner']['username'] == user.username
end
@@ -226,7 +226,7 @@ describe API::V3::Projects do
storage_size: 702,
repository_size: 123,
lfs_objects_size: 234,
- build_artifacts_size: 345,
+ build_artifacts_size: 345
}
project4.statistics.update!(attributes)
@@ -704,7 +704,7 @@ describe API::V3::Projects do
'name' => user.namespace.name,
'path' => user.namespace.path,
'kind' => user.namespace.kind,
- 'full_path' => user.namespace.full_path,
+ 'full_path' => user.namespace.full_path
})
end
diff --git a/spec/requests/api/v3/system_hooks_spec.rb b/spec/requests/api/v3/system_hooks_spec.rb
index 72c7d14b8ba..ae427541abb 100644
--- a/spec/requests/api/v3/system_hooks_spec.rb
+++ b/spec/requests/api/v3/system_hooks_spec.rb
@@ -31,8 +31,9 @@ describe API::V3::SystemHooks do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['url']).to eq(hook.url)
- expect(json_response.first['push_events']).to be true
+ expect(json_response.first['push_events']).to be false
expect(json_response.first['tag_push_events']).to be false
+ expect(json_response.first['repository_update_events']).to be true
end
end
end
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index 63d6d3001ac..83673864fe7 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -42,6 +42,7 @@ describe API::Variables do
expect(response).to have_http_status(200)
expect(json_response['value']).to eq(variable.value)
+ expect(json_response['protected']).to eq(variable.protected?)
end
it 'responds with 404 Not Found if requesting non-existing variable' do
@@ -72,12 +73,13 @@ describe API::Variables do
context 'authorized user with proper permissions' do
it 'creates variable' do
expect do
- post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
+ post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true
end.to change{project.variables.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
expect(json_response['value']).to eq('VALUE_2')
+ expect(json_response['protected']).to be_truthy
end
it 'does not allow to duplicate variable key' do
@@ -112,13 +114,14 @@ describe API::Variables do
initial_variable = project.variables.first
value_before = initial_variable.value
- put api("/projects/#{project.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP'
+ put api("/projects/#{project.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP', protected: true
updated_variable = project.variables.first
expect(response).to have_http_status(200)
expect(value_before).to eq(variable.value)
expect(updated_variable.value).to eq('VALUE_1_UP')
+ expect(updated_variable).to be_protected
end
it 'responds with 404 Not Found if requesting non-existing variable' do
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 108f73bb965..286de277ae7 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -185,7 +185,7 @@ describe Ci::API::Builds do
{ "key" => "CI_PIPELINE_TRIGGERED", "value" => "true", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
- { "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false },
+ { "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false }
)
end
end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 6ca3ef18fe6..f018b48ceb2 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -5,76 +5,217 @@ describe 'Git HTTP requests', lib: true do
include WorkhorseHelpers
include UserActivitiesHelpers
- it "gives WWW-Authenticate hints" do
- clone_get('doesnt/exist.git')
+ shared_examples 'pulls require Basic HTTP Authentication' do
+ context "when no credentials are provided" do
+ it "responds to downloads with status 401 Unauthorized (no project existence information leak)" do
+ download(path) do |response|
+ expect(response).to have_http_status(:unauthorized)
+ expect(response.header['WWW-Authenticate']).to start_with('Basic ')
+ end
+ end
+ end
- expect(response.header['WWW-Authenticate']).to start_with('Basic ')
- end
+ context "when only username is provided" do
+ it "responds to downloads with status 401 Unauthorized" do
+ download(path, user: user.username) do |response|
+ expect(response).to have_http_status(:unauthorized)
+ expect(response.header['WWW-Authenticate']).to start_with('Basic ')
+ end
+ end
+ end
- describe "User with no identities" do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository, path: 'project.git-project') }
+ context "when username and password are provided" do
+ context "when authentication fails" do
+ it "responds to downloads with status 401 Unauthorized" do
+ download(path, user: user.username, password: "wrong-password") do |response|
+ expect(response).to have_http_status(:unauthorized)
+ expect(response.header['WWW-Authenticate']).to start_with('Basic ')
+ end
+ end
+ end
- context "when the project doesn't exist" do
- context "when no authentication is provided" do
- it "responds with status 401 (no project existence information leak)" do
- download('doesnt/exist.git') do |response|
- expect(response).to have_http_status(401)
+ context "when authentication succeeds" do
+ it "does not respond to downloads with status 401 Unauthorized" do
+ download(path, user: user.username, password: user.password) do |response|
+ expect(response).not_to have_http_status(:unauthorized)
+ expect(response.header['WWW-Authenticate']).to be_nil
end
end
end
+ end
+ end
- context "when username and password are provided" do
- context "when authentication fails" do
- it "responds with status 401" do
- download('doesnt/exist.git', user: user.username, password: "nope") do |response|
- expect(response).to have_http_status(401)
- end
+ shared_examples 'pushes require Basic HTTP Authentication' do
+ context "when no credentials are provided" do
+ it "responds to uploads with status 401 Unauthorized (no project existence information leak)" do
+ upload(path) do |response|
+ expect(response).to have_http_status(:unauthorized)
+ expect(response.header['WWW-Authenticate']).to start_with('Basic ')
+ end
+ end
+ end
+
+ context "when only username is provided" do
+ it "responds to uploads with status 401 Unauthorized" do
+ upload(path, user: user.username) do |response|
+ expect(response).to have_http_status(:unauthorized)
+ expect(response.header['WWW-Authenticate']).to start_with('Basic ')
+ end
+ end
+ end
+
+ context "when username and password are provided" do
+ context "when authentication fails" do
+ it "responds to uploads with status 401 Unauthorized" do
+ upload(path, user: user.username, password: "wrong-password") do |response|
+ expect(response).to have_http_status(:unauthorized)
+ expect(response.header['WWW-Authenticate']).to start_with('Basic ')
end
end
+ end
- context "when authentication succeeds" do
- it "responds with status 404" do
- download('/doesnt/exist.git', user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(404)
- end
+ context "when authentication succeeds" do
+ it "does not respond to uploads with status 401 Unauthorized" do
+ upload(path, user: user.username, password: user.password) do |response|
+ expect(response).not_to have_http_status(:unauthorized)
+ expect(response.header['WWW-Authenticate']).to be_nil
end
end
end
end
+ end
- context "when the Wiki for a project exists" do
- it "responds with the right project" do
- wiki = ProjectWiki.new(project)
- project.update_attribute(:visibility_level, Project::PUBLIC)
+ shared_examples_for 'pulls are allowed' do
+ it do
+ download(path, env) do |response|
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+ end
+ end
- download("/#{wiki.repository.path_with_namespace}.git") do |response|
- json_body = ActiveSupport::JSON.decode(response.body)
+ shared_examples_for 'pushes are allowed' do
+ it do
+ upload(path, env) do |response|
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+ end
+ end
- expect(response).to have_http_status(200)
- expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ describe "User with no identities" do
+ let(:user) { create(:user) }
+
+ context "when the project doesn't exist" do
+ let(:path) { 'doesnt/exist.git' }
+
+ it_behaves_like 'pulls require Basic HTTP Authentication'
+ it_behaves_like 'pushes require Basic HTTP Authentication'
+
+ context 'when authenticated' do
+ it 'rejects downloads and uploads with 404 Not Found' do
+ download_or_upload(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(:not_found)
+ end
end
end
+ end
+
+ context "when requesting the Wiki" do
+ let(:wiki) { ProjectWiki.new(project) }
+ let(:path) { "/#{wiki.repository.path_with_namespace}.git" }
+
+ context "when the project is public" do
+ let(:project) { create(:project, :repository, :public, :wiki_enabled) }
+
+ it_behaves_like 'pushes require Basic HTTP Authentication'
+
+ context 'when unauthenticated' do
+ let(:env) { {} }
- context 'but the repo is disabled' do
- let(:project) { create(:project, :repository_disabled, :wiki_enabled) }
- let(:wiki) { ProjectWiki.new(project) }
- let(:path) { "/#{wiki.repository.path_with_namespace}.git" }
+ it_behaves_like 'pulls are allowed'
- before do
- project.team << [user, :developer]
+ it "responds to pulls with the wiki's repo" do
+ download(path) do |response|
+ json_body = ActiveSupport::JSON.decode(response.body)
+
+ expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
+ end
+ end
end
- it 'allows clones' do
- download(path, user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(200)
+ context 'when authenticated' do
+ let(:env) { { user: user.username, password: user.password } }
+
+ context 'and as a developer on the team' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ context 'but the repo is disabled' do
+ let(:project) { create(:project, :repository, :public, :repository_disabled, :wiki_enabled) }
+
+ it_behaves_like 'pulls are allowed'
+ it_behaves_like 'pushes are allowed'
+ end
+ end
+
+ context 'and not on the team' do
+ it_behaves_like 'pulls are allowed'
+
+ it 'rejects pushes with 403 Forbidden' do
+ upload(path, env) do |response|
+ expect(response).to have_http_status(:forbidden)
+ expect(response.body).to eq(git_access_wiki_error(:write_to_wiki))
+ end
+ end
end
end
+ end
- it 'allows pushes' do
- upload(path, user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(200)
+ context "when the project is private" do
+ let(:project) { create(:project, :repository, :private, :wiki_enabled) }
+
+ it_behaves_like 'pulls require Basic HTTP Authentication'
+ it_behaves_like 'pushes require Basic HTTP Authentication'
+
+ context 'when authenticated' do
+ context 'and as a developer on the team' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ context 'but the repo is disabled' do
+ let(:project) { create(:project, :repository, :private, :repository_disabled, :wiki_enabled) }
+
+ it 'allows clones' do
+ download(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(:ok)
+ end
+ end
+
+ it 'pushes are allowed' do
+ upload(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(:ok)
+ end
+ end
+ end
+ end
+
+ context 'and not on the team' do
+ it 'rejects clones with 404 Not Found' do
+ download(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(:not_found)
+ expect(response.body).to eq(git_access_error(:project_not_found))
+ end
+ end
+
+ it 'rejects pushes with 404 Not Found' do
+ upload(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(:not_found)
+ expect(response.body).to eq(git_access_error(:project_not_found))
+ end
+ end
end
end
end
@@ -84,49 +225,60 @@ describe 'Git HTTP requests', lib: true do
let(:path) { "#{project.path_with_namespace}.git" }
context "when the project is public" do
- before do
- project.update_attribute(:visibility_level, Project::PUBLIC)
- end
+ let(:project) { create(:project, :repository, :public) }
- it "downloads get status 200" do
- download(path, {}) do |response|
- expect(response).to have_http_status(200)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- end
- end
+ it_behaves_like 'pushes require Basic HTTP Authentication'
- it "uploads get status 401" do
- upload(path, {}) do |response|
- expect(response).to have_http_status(401)
- end
+ context 'when not authenticated' do
+ let(:env) { {} }
+
+ it_behaves_like 'pulls are allowed'
end
- context "with correct credentials" do
+ context "when authenticated" do
let(:env) { { user: user.username, password: user.password } }
- it "uploads get status 403" do
- upload(path, env) do |response|
- expect(response).to have_http_status(403)
+ context 'as a developer on the team' do
+ before do
+ project.team << [user, :developer]
end
- end
- context 'but git-receive-pack is disabled' do
- it "responds with status 404" do
- allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
+ it_behaves_like 'pulls are allowed'
+ it_behaves_like 'pushes are allowed'
- upload(path, env) do |response|
- expect(response).to have_http_status(403)
+ context 'but git-receive-pack over HTTP is disabled in config' do
+ before do
+ allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
+ end
+
+ it 'rejects pushes with 403 Forbidden' do
+ upload(path, env) do |response|
+ expect(response).to have_http_status(:forbidden)
+ expect(response.body).to eq(git_access_error(:receive_pack_disabled_over_http))
+ end
+ end
+ end
+
+ context 'but git-upload-pack over HTTP is disabled in config' do
+ it "rejects pushes with 403 Forbidden" do
+ allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
+
+ download(path, env) do |response|
+ expect(response).to have_http_status(:forbidden)
+ expect(response.body).to eq(git_access_error(:upload_pack_disabled_over_http))
+ end
end
end
end
- end
- context 'but git-upload-pack is disabled' do
- it "responds with status 404" do
- allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
+ context 'and not a member of the team' do
+ it_behaves_like 'pulls are allowed'
- download(path, {}) do |response|
- expect(response).to have_http_status(404)
+ it 'rejects pushes with 403 Forbidden' do
+ upload(path, env) do |response|
+ expect(response).to have_http_status(:forbidden)
+ expect(response.body).to eq(change_access_error(:push_code))
+ end
end
end
end
@@ -141,66 +293,41 @@ describe 'Git HTTP requests', lib: true do
context 'when the repo is public' do
context 'but the repo is disabled' do
- it 'does not allow to clone the repo' do
- project = create(:project, :public, :repository_disabled)
+ let(:project) { create(:project, :public, :repository, :repository_disabled) }
+ let(:path) { "#{project.path_with_namespace}.git" }
+ let(:env) { {} }
- download("#{project.path_with_namespace}.git", {}) do |response|
- expect(response).to have_http_status(:unauthorized)
- end
- end
+ it_behaves_like 'pulls require Basic HTTP Authentication'
+ it_behaves_like 'pushes require Basic HTTP Authentication'
end
context 'but the repo is enabled' do
- it 'allows to clone the repo' do
- project = create(:project, :public, :repository_enabled)
+ let(:project) { create(:project, :public, :repository, :repository_enabled) }
+ let(:path) { "#{project.path_with_namespace}.git" }
+ let(:env) { {} }
- download("#{project.path_with_namespace}.git", {}) do |response|
- expect(response).to have_http_status(:ok)
- end
- end
+ it_behaves_like 'pulls are allowed'
end
context 'but only project members are allowed' do
- it 'does not allow to clone the repo' do
- project = create(:project, :public, :repository_private)
+ let(:project) { create(:project, :public, :repository, :repository_private) }
- download("#{project.path_with_namespace}.git", {}) do |response|
- expect(response).to have_http_status(:unauthorized)
- end
- end
+ it_behaves_like 'pulls require Basic HTTP Authentication'
+ it_behaves_like 'pushes require Basic HTTP Authentication'
end
end
end
context "when the project is private" do
- before do
- project.update_attribute(:visibility_level, Project::PRIVATE)
- end
-
- context "when no authentication is provided" do
- it "responds with status 401 to downloads" do
- download(path, {}) do |response|
- expect(response).to have_http_status(401)
- end
- end
+ let(:project) { create(:project, :repository, :private) }
- it "responds with status 401 to uploads" do
- upload(path, {}) do |response|
- expect(response).to have_http_status(401)
- end
- end
- end
+ it_behaves_like 'pulls require Basic HTTP Authentication'
+ it_behaves_like 'pushes require Basic HTTP Authentication'
context "when username and password are provided" do
let(:env) { { user: user.username, password: 'nope' } }
context "when authentication fails" do
- it "responds with status 401" do
- download(path, env) do |response|
- expect(response).to have_http_status(401)
- end
- end
-
context "when the user is IP banned" do
it "responds with status 401" do
expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true)
@@ -208,7 +335,7 @@ describe 'Git HTTP requests', lib: true do
clone_get(path, env)
- expect(response).to have_http_status(401)
+ expect(response).to have_http_status(:unauthorized)
end
end
end
@@ -222,37 +349,39 @@ describe 'Git HTTP requests', lib: true do
end
context "when the user is blocked" do
- it "responds with status 401" do
+ it "rejects pulls with 401 Unauthorized" do
user.block
project.team << [user, :master]
download(path, env) do |response|
- expect(response).to have_http_status(401)
+ expect(response).to have_http_status(:unauthorized)
end
end
- it "responds with status 401 for unknown projects (no project existence information leak)" do
+ it "rejects pulls with 401 Unauthorized for unknown projects (no project existence information leak)" do
user.block
download('doesnt/exist.git', env) do |response|
- expect(response).to have_http_status(401)
+ expect(response).to have_http_status(:unauthorized)
end
end
end
context "when the user isn't blocked" do
- it "downloads get status 200" do
- expect(Rack::Attack::Allow2Ban).to receive(:reset)
-
- clone_get(path, env)
+ it "resets the IP in Rack Attack on download" do
+ expect(Rack::Attack::Allow2Ban).to receive(:reset).twice
- expect(response).to have_http_status(200)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ download(path, env) do
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
end
- it "uploads get status 200" do
- upload(path, env) do |response|
- expect(response).to have_http_status(200)
+ it "resets the IP in Rack Attack on upload" do
+ expect(Rack::Attack::Allow2Ban).to receive(:reset).twice
+
+ upload(path, env) do
+ expect(response).to have_http_status(:ok)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
end
@@ -272,56 +401,43 @@ describe 'Git HTTP requests', lib: true do
@token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api")
end
- it "downloads get status 200" do
- clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
-
- expect(response).to have_http_status(200)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- end
-
- it "uploads get status 200" do
- push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
+ let(:path) { "#{project.path_with_namespace}.git" }
+ let(:env) { { user: 'oauth2', password: @token.token } }
- expect(response).to have_http_status(200)
- end
+ it_behaves_like 'pulls are allowed'
+ it_behaves_like 'pushes are allowed'
end
context 'when user has 2FA enabled' do
let(:user) { create(:user, :two_factor) }
let(:access_token) { create(:personal_access_token, user: user) }
+ let(:path) { "#{project.path_with_namespace}.git" }
before do
project.team << [user, :master]
end
context 'when username and password are provided' do
- it 'rejects the clone attempt' do
- download("#{project.path_with_namespace}.git", user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(401)
+ it 'rejects pulls with 2FA error message' do
+ download(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(:unauthorized)
expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
end
end
it 'rejects the push attempt' do
- upload("#{project.path_with_namespace}.git", user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(401)
+ upload(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(:unauthorized)
expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
end
end
end
context 'when username and personal access token are provided' do
- it 'allows clones' do
- download("#{project.path_with_namespace}.git", user: user.username, password: access_token.token) do |response|
- expect(response).to have_http_status(200)
- end
- end
+ let(:env) { { user: user.username, password: access_token.token } }
- it 'allows pushes' do
- upload("#{project.path_with_namespace}.git", user: user.username, password: access_token.token) do |response|
- expect(response).to have_http_status(200)
- end
- end
+ it_behaves_like 'pulls are allowed'
+ it_behaves_like 'pushes are allowed'
end
end
@@ -357,15 +473,15 @@ describe 'Git HTTP requests', lib: true do
end
context "when the user doesn't have access to the project" do
- it "downloads get status 404" do
+ it "pulls get status 404" do
download(path, user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(:not_found)
end
end
it "uploads get status 404" do
upload(path, user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(:not_found)
end
end
end
@@ -373,28 +489,41 @@ describe 'Git HTTP requests', lib: true do
end
context "when a gitlab ci token is provided" do
+ let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, :running) }
- let(:project) { build.project }
let(:other_project) { create(:empty_project) }
- context 'when build created by system is authenticated' do
- it "downloads get status 200" do
- clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
-
- expect(response).to have_http_status(200)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- end
-
- it "uploads get status 401 (no project existence information leak)" do
- push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+ before do
+ build.update!(project: project) # can't associate it on factory create
+ end
- expect(response).to have_http_status(401)
+ context 'when build created by system is authenticated' do
+ let(:path) { "#{project.path_with_namespace}.git" }
+ let(:env) { { user: 'gitlab-ci-token', password: build.token } }
+
+ it_behaves_like 'pulls are allowed'
+
+ # A non-401 here is not an information leak since the system is
+ # "authenticated" as CI using the correct token. It does not have
+ # push access, so pushes should be rejected as forbidden, and giving
+ # a reason is fine.
+ #
+ # We know for sure it is not an information leak since pulls using
+ # the build token must be allowed.
+ it "rejects pushes with 403 Forbidden" do
+ push_get(path, env)
+
+ expect(response).to have_http_status(:forbidden)
+ expect(response.body).to eq(git_access_error(:upload))
end
- it "downloads from other project get status 404" do
- clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+ # We are "authenticated" as CI using a valid token here. But we are
+ # not authorized to see any other project, so return "not found".
+ it "rejects pulls for other project with 404 Not Found" do
+ clone_get("#{other_project.path_with_namespace}.git", env)
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(:not_found)
+ expect(response.body).to eq(git_access_error(:project_not_found))
end
end
@@ -405,31 +534,27 @@ describe 'Git HTTP requests', lib: true do
end
shared_examples 'can download code only' do
- it 'downloads get status 200' do
- allow_any_instance_of(Repository).
- to receive(:exists?).and_return(true)
-
- clone_get "#{project.path_with_namespace}.git",
- user: 'gitlab-ci-token', password: build.token
+ let(:path) { "#{project.path_with_namespace}.git" }
+ let(:env) { { user: 'gitlab-ci-token', password: build.token } }
- expect(response).to have_http_status(200)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- end
+ it_behaves_like 'pulls are allowed'
- it 'downloads from non-existing repository and gets 403' do
- allow_any_instance_of(Repository).
- to receive(:exists?).and_return(false)
+ context 'when the repo does not exist' do
+ let(:project) { create(:empty_project) }
- clone_get "#{project.path_with_namespace}.git",
- user: 'gitlab-ci-token', password: build.token
+ it 'rejects pulls with 403 Forbidden' do
+ clone_get path, env
- expect(response).to have_http_status(403)
+ expect(response).to have_http_status(:forbidden)
+ expect(response.body).to eq(git_access_error(:no_repo))
+ end
end
- it 'uploads get status 403' do
- push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+ it 'rejects pushes with 403 Forbidden' do
+ push_get path, env
- expect(response).to have_http_status(401)
+ expect(response).to have_http_status(:forbidden)
+ expect(response.body).to eq(git_access_error(:upload))
end
end
@@ -441,7 +566,7 @@ describe 'Git HTTP requests', lib: true do
it 'downloads from other project get status 403' do
clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
- expect(response).to have_http_status(403)
+ expect(response).to have_http_status(:forbidden)
end
end
@@ -453,91 +578,93 @@ describe 'Git HTTP requests', lib: true do
it 'downloads from other project get status 404' do
clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(:not_found)
end
end
end
end
end
- end
- context "when the project path doesn't end in .git" do
- context "GET info/refs" do
- let(:path) { "/#{project.path_with_namespace}/info/refs" }
+ context "when the project path doesn't end in .git" do
+ let(:project) { create(:project, :repository, :public, path: 'project.git-project') }
+
+ context "GET info/refs" do
+ let(:path) { "/#{project.path_with_namespace}/info/refs" }
- context "when no params are added" do
- before { get path }
+ context "when no params are added" do
+ before { get path }
- it "redirects to the .git suffix version" do
- expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs")
+ it "redirects to the .git suffix version" do
+ expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs")
+ end
end
- end
- context "when the upload-pack service is requested" do
- let(:params) { { service: 'git-upload-pack' } }
- before { get path, params }
+ context "when the upload-pack service is requested" do
+ let(:params) { { service: 'git-upload-pack' } }
+ before { get path, params }
- it "redirects to the .git suffix version" do
- expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
+ it "redirects to the .git suffix version" do
+ expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
+ end
end
- end
- context "when the receive-pack service is requested" do
- let(:params) { { service: 'git-receive-pack' } }
- before { get path, params }
+ context "when the receive-pack service is requested" do
+ let(:params) { { service: 'git-receive-pack' } }
+ before { get path, params }
- it "redirects to the .git suffix version" do
- expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
+ it "redirects to the .git suffix version" do
+ expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
+ end
end
- end
- context "when the params are anything else" do
- let(:params) { { service: 'git-implode-pack' } }
- before { get path, params }
+ context "when the params are anything else" do
+ let(:params) { { service: 'git-implode-pack' } }
+ before { get path, params }
- it "redirects to the sign-in page" do
- expect(response).to redirect_to(new_user_session_path)
+ it "redirects to the sign-in page" do
+ expect(response).to redirect_to(new_user_session_path)
+ end
end
end
- end
- context "POST git-upload-pack" do
- it "fails to find a route" do
- expect { clone_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
+ context "POST git-upload-pack" do
+ it "fails to find a route" do
+ expect { clone_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
+ end
end
- end
- context "POST git-receive-pack" do
- it "failes to find a route" do
- expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
+ context "POST git-receive-pack" do
+ it "failes to find a route" do
+ expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
+ end
end
end
- end
- context "retrieving an info/refs file" do
- before { project.update_attribute(:visibility_level, Project::PUBLIC) }
+ context "retrieving an info/refs file" do
+ let(:project) { create(:project, :repository, :public) }
+
+ context "when the file exists" do
+ before do
+ # Provide a dummy file in its place
+ allow_any_instance_of(Repository).to receive(:blob_at).and_call_original
+ allow_any_instance_of(Repository).to receive(:blob_at).with('b83d6e391c22777fca1ed3012fce84f633d7fed0', 'info/refs') do
+ Gitlab::Git::Blob.find(project.repository, 'master', 'bar/branch-test.txt')
+ end
- context "when the file exists" do
- before do
- # Provide a dummy file in its place
- allow_any_instance_of(Repository).to receive(:blob_at).and_call_original
- allow_any_instance_of(Repository).to receive(:blob_at).with('b83d6e391c22777fca1ed3012fce84f633d7fed0', 'info/refs') do
- Gitlab::Git::Blob.find(project.repository, 'master', 'bar/branch-test.txt')
+ get "/#{project.path_with_namespace}/blob/master/info/refs"
end
- get "/#{project.path_with_namespace}/blob/master/info/refs"
+ it "returns the file" do
+ expect(response).to have_http_status(:ok)
+ end
end
- it "returns the file" do
- expect(response).to have_http_status(200)
- end
- end
+ context "when the file does not exist" do
+ before { get "/#{project.path_with_namespace}/blob/master/info/refs" }
- context "when the file does not exist" do
- before { get "/#{project.path_with_namespace}/blob/master/info/refs" }
-
- it "returns not found" do
- expect(response).to have_http_status(404)
+ it "returns not found" do
+ expect(response).to have_http_status(:not_found)
+ end
end
end
end
@@ -546,6 +673,7 @@ describe 'Git HTTP requests', lib: true do
describe "User with LDAP identity" do
let(:user) { create(:omniauth_user, extern_uid: dn) }
let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
+ let(:path) { 'doesnt/exist.git' }
before do
allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
@@ -553,44 +681,36 @@ describe 'Git HTTP requests', lib: true do
allow(Gitlab::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user)
end
- context "when authentication fails" do
- context "when no authentication is provided" do
- it "responds with status 401" do
- download('doesnt/exist.git') do |response|
- expect(response).to have_http_status(401)
- end
- end
- end
-
- context "when username and invalid password are provided" do
- it "responds with status 401" do
- download('doesnt/exist.git', user: user.username, password: "nope") do |response|
- expect(response).to have_http_status(401)
- end
- end
- end
- end
+ it_behaves_like 'pulls require Basic HTTP Authentication'
+ it_behaves_like 'pushes require Basic HTTP Authentication'
context "when authentication succeeds" do
context "when the project doesn't exist" do
- it "responds with status 404" do
- download('/doesnt/exist.git', user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(404)
+ it "responds with status 404 Not Found" do
+ download(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(:not_found)
end
end
end
context "when the project exists" do
- let(:project) { create(:project, path: 'project.git-project') }
+ let(:project) { create(:project, :repository) }
+ let(:path) { "#{project.full_path}.git" }
+ let(:env) { { user: user.username, password: user.password } }
- before do
- project.team << [user, :master]
- end
+ context 'and the user is on the team' do
+ before do
+ project.team << [user, :master]
+ end
- it "responds with status 200" do
- clone_get(path, user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(200)
+ it "responds with status 200" do
+ clone_get(path, env) do |response|
+ expect(response).to have_http_status(200)
+ end
end
+
+ it_behaves_like 'pulls are allowed'
+ it_behaves_like 'pushes are allowed'
end
end
end
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index a3e7844b2f3..e056353fa6f 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -41,6 +41,19 @@ describe JwtController do
it { expect(response).to have_http_status(401) }
end
+
+ context 'using personal access tokens' do
+ let(:user) { create(:user) }
+ let(:pat) { create(:personal_access_token, user: user, scopes: ['read_registry']) }
+ let(:headers) { { authorization: credentials('personal_access_token', pat.token) } }
+
+ subject! { get '/jwt/auth', parameters, headers }
+
+ it 'authenticates correctly' do
+ expect(response).to have_http_status(200)
+ expect(service_class).to have_received(:new).with(nil, user, parameters)
+ end
+ end
end
context 'using User login' do
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 5d495bc9e7d..697b150ab34 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -425,7 +425,7 @@ describe 'Git LFS API and storage' do
'size' => sample_size,
'error' => {
'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ 'message' => "Object does not exist on the server or you don't have permissions to access it"
}
}
]
@@ -456,7 +456,7 @@ describe 'Git LFS API and storage' do
'size' => 1575078,
'error' => {
'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ 'message' => "Object does not exist on the server or you don't have permissions to access it"
}
}
]
@@ -493,7 +493,7 @@ describe 'Git LFS API and storage' do
'size' => 1575078,
'error' => {
'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ 'message' => "Object does not exist on the server or you don't have permissions to access it"
}
},
{
@@ -759,8 +759,8 @@ describe 'Git LFS API and storage' do
context 'tries to push to own project' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
- it 'responds with 401' do
- expect(response).to have_http_status(401)
+ it 'responds with 403 (not 404 because project is public)' do
+ expect(response).to have_http_status(403)
end
end
@@ -769,8 +769,9 @@ describe 'Git LFS API and storage' do
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
- it 'responds with 401' do
- expect(response).to have_http_status(401)
+ # I'm not sure what this tests that is different from the previous test
+ it 'responds with 403 (not 404 because project is public)' do
+ expect(response).to have_http_status(403)
end
end
end
@@ -778,8 +779,8 @@ describe 'Git LFS API and storage' do
context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) }
- it 'responds with 401' do
- expect(response).to have_http_status(401)
+ it 'responds with 403 (not 404 because project is public)' do
+ expect(response).to have_http_status(403)
end
end
end
@@ -979,8 +980,8 @@ describe 'Git LFS API and storage' do
put_authorize
end
- it 'responds with 401' do
- expect(response).to have_http_status(401)
+ it 'responds with 403 (not 404 because the build user can read the project)' do
+ expect(response).to have_http_status(403)
end
end
@@ -993,8 +994,8 @@ describe 'Git LFS API and storage' do
put_authorize
end
- it 'responds with 401' do
- expect(response).to have_http_status(401)
+ it 'responds with 404 (do not leak non-public project existence)' do
+ expect(response).to have_http_status(404)
end
end
end
@@ -1006,8 +1007,8 @@ describe 'Git LFS API and storage' do
put_authorize
end
- it 'responds with 401' do
- expect(response).to have_http_status(401)
+ it 'responds with 404 (do not leak non-public project existence)' do
+ expect(response).to have_http_status(404)
end
end
end
@@ -1079,8 +1080,8 @@ describe 'Git LFS API and storage' do
context 'tries to push to own project' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
- it 'responds with 401' do
- expect(response).to have_http_status(401)
+ it 'responds with 403 (not 404 because project is public)' do
+ expect(response).to have_http_status(403)
end
end
@@ -1089,8 +1090,9 @@ describe 'Git LFS API and storage' do
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
- it 'responds with 401' do
- expect(response).to have_http_status(401)
+ # I'm not sure what this tests that is different from the previous test
+ it 'responds with 403 (not 404 because project is public)' do
+ expect(response).to have_http_status(403)
end
end
end
@@ -1098,8 +1100,8 @@ describe 'Git LFS API and storage' do
context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) }
- it 'responds with 401' do
- expect(response).to have_http_status(401)
+ it 'responds with 403 (not 404 because project is public)' do
+ expect(response).to have_http_status(403)
end
end
end
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index 75d8fc92a43..05176c3beaa 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -61,7 +61,7 @@ describe 'OpenID Connect requests' do
email: private_email.email,
public_email: public_email.email,
website_url: 'https://example.com',
- avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png"),
+ avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png")
)
end
@@ -79,7 +79,7 @@ describe 'OpenID Connect requests' do
'email_verified' => true,
'website' => 'https://example.com',
'profile' => 'http://localhost/alice',
- 'picture' => "http://localhost/uploads/system/user/avatar/#{user.id}/dk.png",
+ 'picture' => "http://localhost/uploads/user/avatar/#{user.id}/dk.png"
})
end
end
@@ -98,7 +98,7 @@ describe 'OpenID Connect requests' do
expect(@payload['sub']).to eq hashed_subject
end
- it 'includes the time of the last authentication' do
+ it 'includes the time of the last authentication', :redis do
expect(@payload['auth_time']).to eq user.current_sign_in_at.to_i
end
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index 33940f70b1c..d4d3c9478a0 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -9,8 +9,6 @@ describe 'cycle analytics events', api: true do
before do
project.team << [user, :developer]
- allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
-
3.times do |count|
Timecop.freeze(Time.now + count.days) do
create_cycle
@@ -121,9 +119,9 @@ describe 'cycle analytics events', api: true do
def create_cycle
milestone = create(:milestone, project: project)
issue.update(milestone: milestone)
- mr = create_merge_request_closing_issue(issue)
+ mr = create_merge_request_closing_issue(issue, commit_message: "References #{issue.to_reference}")
- pipeline = create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha)
+ pipeline = create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr)
pipeline.run
create(:ci_build, pipeline: pipeline, status: :success, author: user)
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index e5fc0b676af..179fc9733ad 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -103,6 +103,18 @@ describe Admin::HooksController, "routing" do
end
end
+# admin_hook_hook_log_retry GET /admin/hooks/:hook_id/hook_logs/:id/retry(.:format) admin/hook_logs#retry
+# admin_hook_hook_log GET /admin/hooks/:hook_id/hook_logs/:id(.:format) admin/hook_logs#show
+describe Admin::HookLogsController, 'routing' do
+ it 'to #retry' do
+ expect(get('/admin/hooks/1/hook_logs/1/retry')).to route_to('admin/hook_logs#retry', hook_id: '1', id: '1')
+ end
+
+ it 'to #show' do
+ expect(get('/admin/hooks/1/hook_logs/1')).to route_to('admin/hook_logs#show', hook_id: '1', id: '1')
+ end
+end
+
# admin_logs GET /admin/logs(.:format) admin/logs#show
describe Admin::LogsController, "routing" do
it "to #show" do
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index a391c046f92..0a6778ae2ef 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -201,10 +201,12 @@ describe 'project routing' do
# POST /:project_id/deploy_keys(.:format) deploy_keys#create
# new_project_deploy_key GET /:project_id/deploy_keys/new(.:format) deploy_keys#new
# project_deploy_key GET /:project_id/deploy_keys/:id(.:format) deploy_keys#show
+ # edit_project_deploy_key GET /:project_id/deploy_keys/:id/edit(.:format) deploy_keys#edit
+ # project_deploy_key PATCH /:project_id/deploy_keys/:id(.:format) deploy_keys#update
# DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy
describe Projects::DeployKeysController, 'routing' do
it_behaves_like 'RESTful project resources' do
- let(:actions) { [:index, :new, :create] }
+ let(:actions) { [:index, :new, :create, :edit, :update] }
let(:controller) { 'deploy_keys' }
end
end
@@ -349,6 +351,18 @@ describe 'project routing' do
end
end
+ # retry_namespace_project_hook_hook_log GET /:project_id/hooks/:hook_id/hook_logs/:id/retry(.:format) projects/hook_logs#retry
+ # namespace_project_hook_hook_log GET /:project_id/hooks/:hook_id/hook_logs/:id(.:format) projects/hook_logs#show
+ describe Projects::HookLogsController, 'routing' do
+ it 'to #retry' do
+ expect(get('/gitlab/gitlabhq/hooks/1/hook_logs/1/retry')).to route_to('projects/hook_logs#retry', namespace_id: 'gitlab', project_id: 'gitlabhq', hook_id: '1', id: '1')
+ end
+
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/hooks/1/hook_logs/1')).to route_to('projects/hook_logs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', hook_id: '1', id: '1')
+ end
+ end
+
# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/}
describe Projects::CommitController, 'routing' do
it 'to #show' do
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 9f6defe1450..a62af13cf0c 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -151,6 +151,10 @@ describe ProfilesController, "routing" do
expect(put("/profile/reset_private_token")).to route_to('profiles#reset_private_token')
end
+ it "to #reset_rss_token" do
+ expect(put("/profile/reset_rss_token")).to route_to('profiles#reset_rss_token')
+ end
+
it "to #show" do
expect(get("/profile")).to route_to('profiles#show')
end
@@ -249,17 +253,34 @@ describe RootController, 'routing' do
end
end
-# new_user_session GET /users/sign_in(.:format) devise/sessions#new
-# user_session POST /users/sign_in(.:format) devise/sessions#create
-# destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
-# user_omniauth_authorize /users/auth/:provider(.:format) omniauth_callbacks#passthru
-# user_omniauth_callback /users/auth/:action/callback(.:format) omniauth_callbacks#(?-mix:(?!))
-# user_password POST /users/password(.:format) devise/passwords#create
-# new_user_password GET /users/password/new(.:format) devise/passwords#new
-# edit_user_password GET /users/password/edit(.:format) devise/passwords#edit
-# PUT /users/password(.:format) devise/passwords#update
describe "Authentication", "routing" do
- # pending
+ it "GET /users/sign_in" do
+ expect(get("/users/sign_in")).to route_to('sessions#new')
+ end
+
+ it "POST /users/sign_in" do
+ expect(post("/users/sign_in")).to route_to('sessions#create')
+ end
+
+ it "DELETE /users/sign_out" do
+ expect(delete("/users/sign_out")).to route_to('sessions#destroy')
+ end
+
+ it "POST /users/password" do
+ expect(post("/users/password")).to route_to('passwords#create')
+ end
+
+ it "GET /users/password/new" do
+ expect(get("/users/password/new")).to route_to('passwords#new')
+ end
+
+ it "GET /users/password/edit" do
+ expect(get("/users/password/edit")).to route_to('passwords#edit')
+ end
+
+ it "PUT /users/password" do
+ expect(put("/users/password")).to route_to('passwords#update')
+ end
end
describe "Groups", "routing" do
diff --git a/spec/rubocop/cop/activerecord_serialize_spec.rb b/spec/rubocop/cop/activerecord_serialize_spec.rb
new file mode 100644
index 00000000000..5bd7e5fa926
--- /dev/null
+++ b/spec/rubocop/cop/activerecord_serialize_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/activerecord_serialize'
+
+describe RuboCop::Cop::ActiverecordSerialize do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'inside the app/models directory' do
+ it 'registers an offense when serialize is used' do
+ allow(cop).to receive(:in_model?).and_return(true)
+
+ inspect_source(cop, 'serialize :foo')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+ end
+
+ context 'outside the app/models directory' do
+ it 'does nothing' do
+ allow(cop).to receive(:in_model?).and_return(false)
+
+ inspect_source(cop, 'serialize :foo')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
new file mode 100644
index 00000000000..968dcd6232e
--- /dev/null
+++ b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/migration/update_column_in_batches'
+
+describe RuboCop::Cop::Migration::UpdateColumnInBatches do
+ let(:cop) { described_class.new }
+ let(:tmp_rails_root) { Rails.root.join('tmp', 'rails_root') }
+ let(:migration_code) do
+ <<-END
+ def up
+ update_column_in_batches(:projects, :name, "foo") do |table, query|
+ query.where(table[:name].eq(nil))
+ end
+ end
+ END
+ end
+
+ before do
+ allow(cop).to receive(:rails_root).and_return(tmp_rails_root)
+ end
+ after do
+ FileUtils.rm_rf(tmp_rails_root)
+ end
+
+ context 'outside of a migration' do
+ it 'does not register any offenses' do
+ inspect_source(cop, migration_code)
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ let(:spec_filepath) { tmp_rails_root.join('spec', 'migrations', 'my_super_migration_spec.rb') }
+
+ shared_context 'with a migration file' do
+ before do
+ FileUtils.mkdir_p(File.dirname(migration_filepath))
+ @migration_file = File.new(migration_filepath, 'w+')
+ end
+ after do
+ @migration_file.close
+ end
+ end
+
+ shared_examples 'a migration file with no spec file' do
+ include_context 'with a migration file'
+
+ let(:relative_spec_filepath) { Pathname.new(spec_filepath).relative_path_from(tmp_rails_root) }
+
+ it 'registers an offense when using update_column_in_batches' do
+ inspect_source(cop, migration_code, @migration_file)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([2])
+ expect(cop.offenses.first.message).
+ to include("`#{relative_spec_filepath}`")
+ end
+ end
+ end
+
+ shared_examples 'a migration file with a spec file' do
+ include_context 'with a migration file'
+
+ before do
+ FileUtils.mkdir_p(File.dirname(spec_filepath))
+ @spec_file = File.new(spec_filepath, 'w+')
+ end
+ after do
+ @spec_file.close
+ end
+
+ it 'does not register any offenses' do
+ inspect_source(cop, migration_code, @migration_file)
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'in a migration' do
+ let(:migration_filepath) { tmp_rails_root.join('db', 'migrate', '20121220064453_my_super_migration.rb') }
+
+ it_behaves_like 'a migration file with no spec file'
+ it_behaves_like 'a migration file with a spec file'
+ end
+
+ context 'in a post migration' do
+ let(:migration_filepath) { tmp_rails_root.join('db', 'post_migrate', '20121220064453_my_super_migration.rb') }
+
+ it_behaves_like 'a migration file with no spec file'
+ it_behaves_like 'a migration file with a spec file'
+ end
+end
diff --git a/spec/rubocop/cop/polymorphic_associations_spec.rb b/spec/rubocop/cop/polymorphic_associations_spec.rb
new file mode 100644
index 00000000000..49959aa6419
--- /dev/null
+++ b/spec/rubocop/cop/polymorphic_associations_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/polymorphic_associations'
+
+describe RuboCop::Cop::PolymorphicAssociations do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'inside the app/models directory' do
+ it 'registers an offense when polymorphic: true is used' do
+ allow(cop).to receive(:in_model?).and_return(true)
+
+ inspect_source(cop, 'belongs_to :foo, polymorphic: true')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+ end
+
+ context 'outside the app/models directory' do
+ it 'does nothing' do
+ allow(cop).to receive(:in_model?).and_return(false)
+
+ inspect_source(cop, 'belongs_to :foo, polymorphic: true')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/redirect_with_status_spec.rb b/spec/rubocop/cop/redirect_with_status_spec.rb
new file mode 100644
index 00000000000..5ad63567f84
--- /dev/null
+++ b/spec/rubocop/cop/redirect_with_status_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../rubocop/cop/redirect_with_status'
+
+describe RuboCop::Cop::RedirectWithStatus do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+ let(:controller_fixture_without_status) do
+ %q(
+ class UserController < ApplicationController
+ def show
+ user = User.find(params[:id])
+ redirect_to user_path if user.name == 'John Wick'
+ end
+
+ def destroy
+ user = User.find(params[:id])
+
+ if user.destroy
+ redirect_to root_path
+ else
+ render :show
+ end
+ end
+ end
+ )
+ end
+
+ let(:controller_fixture_with_status) do
+ %q(
+ class UserController < ApplicationController
+ def show
+ user = User.find(params[:id])
+ redirect_to user_path if user.name == 'John Wick'
+ end
+
+ def destroy
+ user = User.find(params[:id])
+
+ if user.destroy
+ redirect_to root_path, status: 302
+ else
+ render :show
+ end
+ end
+ end
+ )
+ end
+
+ context 'in controller' do
+ before do
+ allow(cop).to receive(:in_controller?).and_return(true)
+ end
+
+ it 'registers an offense when a "destroy" action uses "redirect_to" without "status"' do
+ inspect_source(cop, controller_fixture_without_status)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([12]) # 'redirect_to' is located on 12th line in controller_fixture.
+ expect(cop.highlights).to eq(['redirect_to'])
+ end
+ end
+
+ it 'does not register an offense when a "destroy" action uses "redirect_to" with "status"' do
+ inspect_source(cop, controller_fixture_with_status)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'outside of controller' do
+ it 'registers no offense' do
+ inspect_source(cop, controller_fixture_without_status)
+ inspect_source(cop, controller_fixture_with_status)
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/serializers/analytics_issue_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb
index 68086216ba9..75d606d5eb3 100644
--- a/spec/serializers/analytics_issue_entity_spec.rb
+++ b/spec/serializers/analytics_issue_entity_spec.rb
@@ -9,7 +9,7 @@ describe AnalyticsIssueEntity do
iid: "1",
id: "1",
created_at: "2016-11-12 15:04:02.948604",
- author: user,
+ author: user
}
end
diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb
index ba24cf8e481..7c14c198a74 100644
--- a/spec/serializers/analytics_issue_serializer_spec.rb
+++ b/spec/serializers/analytics_issue_serializer_spec.rb
@@ -16,7 +16,7 @@ describe AnalyticsIssueSerializer do
iid: "1",
id: "1",
created_at: "2016-11-12 15:04:02.948604",
- author: user,
+ author: user
}
end
diff --git a/spec/serializers/build_action_entity_spec.rb b/spec/serializers/build_action_entity_spec.rb
index 059deba5416..15720d86583 100644
--- a/spec/serializers/build_action_entity_spec.rb
+++ b/spec/serializers/build_action_entity_spec.rb
@@ -1,26 +1,26 @@
require 'spec_helper'
describe BuildActionEntity do
- let(:build) { create(:ci_build, name: 'test_build') }
+ let(:job) { create(:ci_build, name: 'test_job') }
let(:request) { double('request') }
let(:entity) do
- described_class.new(build, request: spy('request'))
+ described_class.new(job, request: spy('request'))
end
describe '#as_json' do
subject { entity.as_json }
- it 'contains original build name' do
- expect(subject[:name]).to eq 'test_build'
+ it 'contains original job name' do
+ expect(subject[:name]).to eq 'test_job'
end
it 'contains path to the action play' do
- expect(subject[:path]).to include "builds/#{build.id}/play"
+ expect(subject[:path]).to include "jobs/#{job.id}/play"
end
it 'contains whether it is playable' do
- expect(subject[:playable]).to eq build.playable?
+ expect(subject[:playable]).to eq job.playable?
end
end
end
diff --git a/spec/serializers/build_artifact_entity_spec.rb b/spec/serializers/build_artifact_entity_spec.rb
index 2fc60aa9de6..ad0d3d3839e 100644
--- a/spec/serializers/build_artifact_entity_spec.rb
+++ b/spec/serializers/build_artifact_entity_spec.rb
@@ -1,22 +1,32 @@
require 'spec_helper'
describe BuildArtifactEntity do
- let(:build) { create(:ci_build, name: 'test:build') }
+ let(:job) { create(:ci_build, name: 'test:job', artifacts_expire_at: 1.hour.from_now) }
let(:entity) do
- described_class.new(build, request: double)
+ described_class.new(job, request: double)
end
describe '#as_json' do
subject { entity.as_json }
- it 'contains build name' do
- expect(subject[:name]).to eq 'test:build'
+ it 'contains job name' do
+ expect(subject[:name]).to eq 'test:job'
end
- it 'contains path to the artifacts' do
+ it 'exposes information about expiration of artifacts' do
+ expect(subject).to include(:expired, :expire_at)
+ end
+
+ it 'contains paths to the artifacts' do
expect(subject[:path])
- .to include "builds/#{build.id}/artifacts/download"
+ .to include "jobs/#{job.id}/artifacts/download"
+
+ expect(subject[:keep_path])
+ .to include "jobs/#{job.id}/artifacts/keep"
+
+ expect(subject[:browse_path])
+ .to include "jobs/#{job.id}/artifacts/browse"
end
end
end
diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb
new file mode 100644
index 00000000000..e2511e8968c
--- /dev/null
+++ b/spec/serializers/build_details_entity_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe BuildDetailsEntity do
+ set(:user) { create(:admin) }
+
+ it 'inherits from BuildEntity' do
+ expect(described_class).to be < BuildEntity
+ end
+
+ describe '#as_json' do
+ let(:project) { create(:project, :repository) }
+ let!(:build) { create(:ci_build, :failed, project: project) }
+ let(:request) { double('request') }
+ let(:entity) { described_class.new(build, request: request, current_user: user, project: project) }
+ subject { entity.as_json }
+
+ before do
+ allow(request).to receive(:current_user).and_return(user)
+ end
+
+ context 'when the user has access to issues and merge requests' do
+ let!(:merge_request) do
+ create(:merge_request, source_project: project, source_branch: build.ref)
+ end
+
+ before do
+ allow(build).to receive(:merge_request).and_return(merge_request)
+ end
+
+ it 'contains the needed key value pairs' do
+ expect(subject).to include(:coverage, :erased_at, :duration)
+ expect(subject).to include(:artifacts, :runner, :pipeline)
+ expect(subject).to include(:raw_path, :merge_request)
+ expect(subject).to include(:new_issue_path)
+ end
+
+ it 'exposes details of the merge request' do
+ expect(subject[:merge_request]).to include(:iid, :path)
+ end
+
+ context 'when the build has been erased' do
+ let!(:build) { create(:ci_build, :erasable, project: project) }
+
+ it 'exposes the user whom erased the build' do
+ expect(subject).to include(:erase_path)
+ end
+ end
+
+ context 'when the build has been erased' do
+ let!(:build) { create(:ci_build, erased_at: Time.now, project: project, erased_by: user) }
+
+ it 'exposes the user whom erased the build' do
+ expect(subject).to include(:erased_by)
+ end
+ end
+ end
+
+ context 'when the user can only read the build' do
+ let(:user) { create(:user) }
+
+ it "won't display the paths to issues and merge requests" do
+ expect(subject['new_issue_path']).to be_nil
+ expect(subject['merge_request_path']).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/build_entity_spec.rb
index b5eb84ae43b..46d43a80ef7 100644
--- a/spec/serializers/build_entity_spec.rb
+++ b/spec/serializers/build_entity_spec.rb
@@ -2,7 +2,8 @@ require 'spec_helper'
describe BuildEntity do
let(:user) { create(:user) }
- let(:build) { create(:ci_build) }
+ let(:build) { create(:ci_build, :failed) }
+ let(:project) { build.project }
let(:request) { double('request') }
before do
@@ -17,6 +18,7 @@ describe BuildEntity do
it 'contains paths to build page and retry action' do
expect(subject).to include(:build_path, :retry_path)
+ expect(subject[:retry_path]).not_to be_nil
end
it 'does not contain sensitive information' do
@@ -52,7 +54,10 @@ describe BuildEntity do
context 'when user is allowed to trigger action' do
before do
- build.project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
end
it 'contains path to play action' do
diff --git a/spec/serializers/deploy_key_entity_spec.rb b/spec/serializers/deploy_key_entity_spec.rb
index e73fbe190ca..ed89fccc3d0 100644
--- a/spec/serializers/deploy_key_entity_spec.rb
+++ b/spec/serializers/deploy_key_entity_spec.rb
@@ -12,27 +12,44 @@ describe DeployKeyEntity do
let(:entity) { described_class.new(deploy_key, user: user) }
- it 'returns deploy keys with projects a user can read' do
- expected_result = {
- id: deploy_key.id,
- 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,
- projects: [
- {
- id: project.id,
- name: project.name,
- full_path: namespace_project_path(project.namespace, project),
- full_name: project.full_name
- }
- ]
- }
-
- expect(entity.as_json).to eq(expected_result)
+ describe 'returns deploy keys with projects a user can read' do
+ let(:expected_result) do
+ {
+ id: deploy_key.id,
+ 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: [
+ {
+ id: project.id,
+ name: project.name,
+ full_path: namespace_project_path(project.namespace, project),
+ full_name: project.full_name
+ }
+ ]
+ }
+ end
+
+ it { expect(entity.as_json).to eq(expected_result) }
+ end
+
+ describe 'returns can_edit true if user is a master of project' do
+ before do
+ project.add_master(user)
+ end
+
+ it { expect(entity.as_json).to include(can_edit: true) }
+ end
+
+ describe 'returns can_edit true if a user admin' do
+ let(:user) { create(:user, :admin) }
+
+ it { expect(entity.as_json).to include(can_edit: true) }
end
end
diff --git a/spec/serializers/merge_request_entity_spec.rb b/spec/serializers/merge_request_entity_spec.rb
index bb6e83ae4bd..d38433c2365 100644
--- a/spec/serializers/merge_request_entity_spec.rb
+++ b/spec/serializers/merge_request_entity_spec.rb
@@ -26,7 +26,7 @@ describe MergeRequestEntity do
pipeline = build_stubbed(:ci_pipeline)
allow(resource).to receive(:head_pipeline).and_return(pipeline)
- pipeline_payload = PipelineEntity
+ pipeline_payload = PipelineDetailsEntity
.represent(pipeline, request: req)
.as_json
@@ -65,6 +65,23 @@ describe MergeRequestEntity do
.to eq(resource.merge_commit_message(include_description: true))
end
+ describe 'new_blob_path' do
+ context 'when user can push to project' do
+ it 'returns path' do
+ project.add_developer(user)
+
+ expect(subject[:new_blob_path])
+ .to eq("/#{resource.project.full_path}/new/#{resource.source_branch}")
+ end
+ end
+
+ context 'when user cannot push to project' do
+ it 'returns nil' do
+ expect(subject[:new_blob_path]).to be_nil
+ end
+ end
+ end
+
describe 'diff_head_sha' do
before do
allow(resource).to receive(:diff_head_sha) { 'sha' }
diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb
new file mode 100644
index 00000000000..03cc5ae9b63
--- /dev/null
+++ b/spec/serializers/pipeline_details_entity_spec.rb
@@ -0,0 +1,120 @@
+require 'spec_helper'
+
+describe PipelineDetailsEntity do
+ set(:user) { create(:user) }
+ let(:request) { double('request') }
+
+ it 'inherrits from PipelineEntity' do
+ expect(described_class).to be < PipelineEntity
+ end
+
+ before do
+ allow(request).to receive(:current_user).and_return(user)
+ end
+
+ let(:entity) do
+ described_class.represent(pipeline, request: request)
+ end
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ context 'when pipeline is empty' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ it 'contains details' do
+ expect(subject).to include :details
+ expect(subject[:details])
+ .to include :duration, :finished_at
+ expect(subject[:details])
+ .to include :stages, :artifacts, :manual_actions
+ expect(subject[:details][:status]).to include :icon, :favicon, :text, :label
+ end
+
+ it 'contains flags' do
+ expect(subject).to include :flags
+ expect(subject[:flags])
+ .to include :latest, :stuck,
+ :yaml_errors, :retryable, :cancelable
+ end
+ end
+
+ context 'when pipeline is retryable' do
+ let(:project) { create(:empty_project) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, status: :success, project: project)
+ end
+
+ before do
+ create(:ci_build, :failed, pipeline: pipeline)
+ end
+
+ context 'user has ability to retry pipeline' do
+ before { project.team << [user, :developer] }
+
+ it 'retryable flag is true' do
+ expect(subject[:flags][:retryable]).to eq true
+ end
+ end
+
+ context 'user does not have ability to retry pipeline' do
+ it 'retryable flag is false' do
+ expect(subject[:flags][:retryable]).to eq false
+ end
+ end
+ end
+
+ context 'when pipeline is cancelable' do
+ let(:project) { create(:empty_project) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, status: :running, project: project)
+ end
+
+ before do
+ create(:ci_build, :pending, pipeline: pipeline)
+ end
+
+ context 'user has ability to cancel pipeline' do
+ before { project.add_developer(user) }
+
+ it 'cancelable flag is true' do
+ expect(subject[:flags][:cancelable]).to eq true
+ end
+ end
+
+ context 'user does not have ability to cancel pipeline' do
+ it 'cancelable flag is false' do
+ expect(subject[:flags][:cancelable]).to eq false
+ end
+ end
+ end
+
+ context 'when pipeline has YAML errors' do
+ let(:pipeline) do
+ create(:ci_pipeline, config: { rspec: { invalid: :value } })
+ end
+
+ it 'contains information about error' do
+ expect(subject[:yaml_errors]).to be_present
+ end
+
+ it 'contains flag that indicates there are errors' do
+ expect(subject[:flags][:yaml_errors]).to be true
+ end
+ end
+
+ context 'when pipeline does not have YAML errors' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ it 'does not contain field that normally holds an error' do
+ expect(subject).not_to have_key(:yaml_errors)
+ end
+
+ it 'contains flag that indicates there are no errors' do
+ expect(subject[:flags][:yaml_errors]).to be false
+ end
+ end
+ end
+end
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index d2482ac434b..a059c2cc736 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe PipelineEntity do
- let(:user) { create(:user) }
+ set(:user) { create(:user) }
let(:request) { double('request') }
before do
@@ -19,7 +19,7 @@ describe PipelineEntity do
let(:pipeline) { create(:ci_empty_pipeline) }
it 'contains required fields' do
- expect(subject).to include :id, :user, :path, :coverage
+ expect(subject).to include :id, :user, :path, :coverage, :source
expect(subject).to include :ref, :commit
expect(subject).to include :updated_at, :created_at
end
@@ -28,15 +28,13 @@ describe PipelineEntity do
expect(subject).to include :details
expect(subject[:details])
.to include :duration, :finished_at
- expect(subject[:details])
- .to include :stages, :artifacts, :manual_actions
expect(subject[:details][:status]).to include :icon, :favicon, :text, :label
end
it 'contains flags' do
expect(subject).to include :flags
expect(subject[:flags])
- .to include :latest, :triggered, :stuck,
+ .to include :latest, :stuck,
:yaml_errors, :retryable, :cancelable
end
end
@@ -55,20 +53,12 @@ describe PipelineEntity do
context 'user has ability to retry pipeline' do
before { project.team << [user, :developer] }
- it 'retryable flag is true' do
- expect(subject[:flags][:retryable]).to eq true
- end
-
it 'contains retry path' do
expect(subject[:retry_path]).to be_present
end
end
context 'user does not have ability to retry pipeline' do
- it 'retryable flag is false' do
- expect(subject[:flags][:retryable]).to eq false
- end
-
it 'does not contain retry path' do
expect(subject).not_to have_key(:retry_path)
end
@@ -87,11 +77,7 @@ describe PipelineEntity do
end
context 'user has ability to cancel pipeline' do
- before { project.team << [user, :developer] }
-
- it 'cancelable flag is true' do
- expect(subject[:flags][:cancelable]).to eq true
- end
+ before { project.add_developer(user) }
it 'contains cancel path' do
expect(subject[:cancel_path]).to be_present
@@ -99,42 +85,12 @@ describe PipelineEntity do
end
context 'user does not have ability to cancel pipeline' do
- it 'cancelable flag is false' do
- expect(subject[:flags][:cancelable]).to eq false
- end
-
it 'does not contain cancel path' do
expect(subject).not_to have_key(:cancel_path)
end
end
end
- context 'when pipeline has YAML errors' do
- let(:pipeline) do
- create(:ci_pipeline, config: { rspec: { invalid: :value } })
- end
-
- it 'contains flag that indicates there are errors' do
- expect(subject[:flags][:yaml_errors]).to be true
- end
-
- it 'contains information about error' do
- expect(subject[:yaml_errors]).to be_present
- end
- end
-
- context 'when pipeline does not have YAML errors' do
- let(:pipeline) { create(:ci_empty_pipeline) }
-
- it 'contains flag that indicates there are no errors' do
- expect(subject[:flags][:yaml_errors]).to be false
- end
-
- it 'does not contain field that normally holds an error' do
- expect(subject).not_to have_key(:yaml_errors)
- end
- end
-
context 'when pipeline ref is empty' do
let(:pipeline) { create(:ci_empty_pipeline) }
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index f2426db6d81..088f24eb180 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -113,7 +113,7 @@ describe PipelineSerializer do
it "verifies number of queries" do
recorded = ActiveRecord::QueryRecorder.new { subject }
- expect(recorded.count).to be_within(1).of(58)
+ expect(recorded.count).to be_within(1).of(60)
expect(recorded.cached_count).to eq(0)
end
diff --git a/spec/serializers/runner_entity_spec.rb b/spec/serializers/runner_entity_spec.rb
new file mode 100644
index 00000000000..4f25a8dcfa0
--- /dev/null
+++ b/spec/serializers/runner_entity_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe RunnerEntity do
+ let(:runner) { create(:ci_runner, :specific) }
+ let(:entity) { described_class.new(runner, request: request, current_user: user) }
+ let(:request) { double('request') }
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:admin) }
+
+ before do
+ allow(request).to receive(:current_user).and_return(user)
+ allow(request).to receive(:project).and_return(project)
+ end
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ it 'contains required fields' do
+ expect(subject).to include(:id, :description)
+ expect(subject).to include(:edit_path)
+ end
+ end
+end
diff --git a/spec/serializers/user_entity_spec.rb b/spec/serializers/user_entity_spec.rb
index c5d11cbcf5e..cd778e49107 100644
--- a/spec/serializers/user_entity_spec.rb
+++ b/spec/serializers/user_entity_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe UserEntity do
+ include Gitlab::Routing
+
let(:entity) { described_class.new(user) }
let(:user) { create(:user) }
subject { entity.as_json }
@@ -20,4 +22,8 @@ describe UserEntity do
it 'does not expose 2FA OTPs' do
expect(subject).not_to include(/otp/)
end
+
+ it 'exposes user path' do
+ expect(subject[:path]).to eq user_path(user)
+ end
end
diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb
index a8555f5b4a0..effa4633d13 100644
--- a/spec/services/boards/create_service_spec.rb
+++ b/spec/services/boards/create_service_spec.rb
@@ -14,8 +14,9 @@ describe Boards::CreateService, services: true do
it 'creates the default lists' do
board = service.execute
- expect(board.lists.size).to eq 1
- expect(board.lists.first).to be_closed
+ expect(board.lists.size).to eq 2
+ expect(board.lists.first).to be_backlog
+ expect(board.lists.last).to be_closed
end
end
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index c982031c791..a1e220c2322 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -13,6 +13,7 @@ describe Boards::Issues::ListService, services: true do
let(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
let(:p3) { create(:label, title: 'P3', project: project, priority: 3) }
+ let!(:backlog) { create(:backlog_list, board: board) }
let!(:list1) { create(:list, board: board, label: development, position: 0) }
let!(:list2) { create(:list, board: board, label: testing, position: 1) }
let!(:closed) { create(:closed_list, board: board) }
@@ -53,12 +54,20 @@ describe Boards::Issues::ListService, services: true do
expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1]
end
+ it 'returns opened issues when listing issues from Backlog' do
+ params = { board_id: board.id, id: backlog.id }
+
+ issues = described_class.new(project, user, params).execute
+
+ expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1]
+ end
+
it 'returns closed issues when listing issues from Closed' do
params = { board_id: board.id, id: closed.id }
issues = described_class.new(project, user, params).execute
- expect(issues).to eq [closed_issue4, closed_issue2, closed_issue5, closed_issue3, closed_issue1]
+ expect(issues).to eq [closed_issue4, closed_issue2, closed_issue3, closed_issue1]
end
it 'returns opened issues that have label list applied when listing issues from a label list' do
diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb
index ab9fb1bc914..68140759600 100644
--- a/spec/services/boards/lists/list_service_spec.rb
+++ b/spec/services/boards/lists/list_service_spec.rb
@@ -1,16 +1,33 @@
require 'spec_helper'
describe Boards::Lists::ListService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:board) { create(:board, project: project) }
+ let(:label) { create(:label, project: project) }
+ let!(:list) { create(:list, board: board, label: label) }
+ let(:service) { described_class.new(project, double) }
+
describe '#execute' do
- it "returns board's lists" do
- project = create(:empty_project)
- board = create(:board, project: project)
- label = create(:label, project: project)
- list = create(:list, board: board, label: label)
+ context 'when the board has a backlog list' do
+ let!(:backlog_list) { create(:backlog_list, board: board) }
+
+ it 'does not create a backlog list' do
+ expect { service.execute(board) }.not_to change(board.lists, :count)
+ end
+
+ it "returns board's lists" do
+ expect(service.execute(board)).to eq [backlog_list, list, board.closed_list]
+ end
+ end
- service = described_class.new(project, double)
+ context 'when the board does not have a backlog list' do
+ it 'creates a backlog list' do
+ expect { service.execute(board) }.to change(board.lists, :count).by(1)
+ end
- expect(service.execute(board)).to eq [list, board.closed_list]
+ it "returns board's lists" do
+ expect(service.execute(board)).to eq [board.backlog_list, list, board.closed_list]
+ end
end
end
end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index fa5014cee07..e9c2b865b47 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::CreatePipelineService, services: true do
+describe Ci::CreatePipelineService, :services do
let(:project) { create(:project, :repository) }
let(:user) { create(:admin) }
@@ -9,13 +9,13 @@ describe Ci::CreatePipelineService, services: true do
end
describe '#execute' do
- def execute_service(after: project.commit.id, message: 'Message', ref: 'refs/heads/master')
+ def execute_service(source: :push, after: project.commit.id, message: 'Message', ref: 'refs/heads/master')
params = { ref: ref,
before: '00000000',
after: after,
commits: [{ message: message }] }
- described_class.new(project, user, params).execute
+ described_class.new(project, user, params).execute(source)
end
context 'valid params' do
@@ -27,12 +27,64 @@ describe Ci::CreatePipelineService, services: true do
)
end
- it { expect(pipeline).to be_kind_of(Ci::Pipeline) }
- it { expect(pipeline).to be_valid }
- it { expect(pipeline).to eq(project.pipelines.last) }
- it { expect(pipeline).to have_attributes(user: user) }
- it { expect(pipeline).to have_attributes(status: 'pending') }
- it { expect(pipeline.builds.first).to be_kind_of(Ci::Build) }
+ it 'creates a pipeline' do
+ expect(pipeline).to be_kind_of(Ci::Pipeline)
+ expect(pipeline).to be_valid
+ expect(pipeline).to be_persisted
+ expect(pipeline).to be_push
+ expect(pipeline).to eq(project.pipelines.last)
+ expect(pipeline).to have_attributes(user: user)
+ expect(pipeline).to have_attributes(status: 'pending')
+ expect(pipeline.builds.first).to be_kind_of(Ci::Build)
+ end
+
+ context 'when merge requests already exist for this source branch' do
+ it 'updates head pipeline of each merge request' do
+ merge_request_1 = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
+ merge_request_2 = create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project)
+
+ head_pipeline = pipeline
+
+ expect(merge_request_1.reload.head_pipeline).to eq(head_pipeline)
+ expect(merge_request_2.reload.head_pipeline).to eq(head_pipeline)
+ end
+
+ context 'when there is no pipeline for source branch' do
+ it "does not update merge request head pipeline" do
+ merge_request = create(:merge_request, source_branch: 'other_branch', target_branch: "branch_1", source_project: project)
+
+ head_pipeline = pipeline
+
+ expect(merge_request.reload.head_pipeline).not_to eq(head_pipeline)
+ end
+ end
+
+ context 'when merge request target project is different from source project' do
+ let!(:target_project) { create(:project) }
+ let!(:forked_project_link) { create(:forked_project_link, forked_to_project: project, forked_from_project: target_project) }
+
+ it 'updates head pipeline for merge request' do
+ merge_request =
+ create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project, target_project: target_project)
+
+ head_pipeline = pipeline
+
+ expect(merge_request.reload.head_pipeline).to eq(head_pipeline)
+ end
+ end
+
+ context 'when the pipeline is not the latest for the branch' do
+ it 'does not update merge request head pipeline' do
+ merge_request = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
+
+ allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(false)
+
+ pipeline
+
+ expect(merge_request.reload.head_pipeline).to be_nil
+ end
+ end
+ end
context 'auto-cancel enabled' do
before do
@@ -245,5 +297,20 @@ describe Ci::CreatePipelineService, services: true do
expect(Environment.find_by(name: "review/master")).not_to be_nil
end
end
+
+ context 'when environment with invalid name' do
+ before do
+ config = YAML.dump(deploy: { environment: { name: 'name,with,commas' }, script: 'ls' })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'does not create an environment' do
+ expect do
+ result = execute_service
+
+ expect(result).to be_persisted
+ end.not_to change { Environment.count }
+ end
+ end
end
end
diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb
index 5a20102872a..f2956262f4b 100644
--- a/spec/services/ci/create_trigger_request_service_spec.rb
+++ b/spec/services/ci/create_trigger_request_service_spec.rb
@@ -16,6 +16,7 @@ describe Ci::CreateTriggerRequestService, services: true do
context 'without owner' do
it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
+ it { expect(subject.pipeline).to be_trigger }
it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
end
@@ -25,6 +26,7 @@ describe Ci::CreateTriggerRequestService, services: true do
it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
+ it { expect(subject.pipeline).to be_trigger }
it { expect(subject.pipeline.user).to eq(owner) }
it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
it { expect(subject.builds.first.user).to eq(owner) }
diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb
index d6f9fa42045..ea211de1f82 100644
--- a/spec/services/ci/play_build_service_spec.rb
+++ b/spec/services/ci/play_build_service_spec.rb
@@ -13,8 +13,11 @@ describe Ci::PlayBuildService, '#execute', :services do
context 'when project does not have repository yet' do
let(:project) { create(:empty_project) }
- it 'allows user with master role to play build' do
- project.add_master(user)
+ it 'allows user to play build if protected branch rules are met' do
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
service.execute(build)
@@ -45,7 +48,10 @@ describe Ci::PlayBuildService, '#execute', :services do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
it 'enqueues the build' do
@@ -64,7 +70,10 @@ describe Ci::PlayBuildService, '#execute', :services do
let(:build) { create(:ci_build, when: :manual, pipeline: pipeline) }
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
it 'duplicates the build' do
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index fc5de5d069a..1557cb3c938 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -333,10 +333,11 @@ describe Ci::ProcessPipelineService, '#execute', :services do
context 'when pipeline is promoted sequentially up to the end' do
before do
- # We are using create(:empty_project), and users has to be master in
- # order to execute manual action when repository does not exist.
+ # Users need ability to merge into a branch in order to trigger
+ # protected manual actions.
#
- project.add_master(user)
+ create(:protected_branch, :developers_can_merge,
+ name: 'master', project: project)
end
it 'properly processes entire pipeline' do
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index 7254e6b357a..ef9927c5969 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -25,12 +25,24 @@ describe Ci::RetryBuildService, :services do
user_id auto_canceled_by_id retried].freeze
shared_examples 'build duplication' do
+ let(:stage) do
+ # TODO, we still do not have factory for new stages, we will need to
+ # switch existing factory to persist stages, instead of using LegacyStage
+ #
+ Ci::Stage.create!(project: project, pipeline: pipeline, name: 'test')
+ end
+
let(:build) do
create(:ci_build, :failed, :artifacts_expired, :erased,
:queued, :coverage, :tags, :allowed_to_fail, :on_tag,
- :teardown_environment, :triggered, :trace,
- description: 'some build', pipeline: pipeline,
- auto_canceled_by: create(:ci_empty_pipeline))
+ :triggered, :trace, :teardown_environment,
+ description: 'my-job', stage: 'test', pipeline: pipeline,
+ auto_canceled_by: create(:ci_empty_pipeline)) do |build|
+ ##
+ # TODO, workaround for FactoryGirl limitation when having both
+ # stage (text) and stage_id (integer) columns in the table.
+ build.stage_id = stage.id
+ end
end
describe 'clone accessors' do
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index d941d56c0d8..3e860203063 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -6,9 +6,12 @@ describe Ci::RetryPipelineService, '#execute', :services do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:service) { described_class.new(project, user) }
- context 'when user has ability to modify pipeline' do
+ context 'when user has full ability to modify pipeline' do
before do
- project.add_master(user)
+ project.add_developer(user)
+
+ create(:protected_branch, :developers_can_merge,
+ name: pipeline.ref, project: project)
end
context 'when there are already retried jobs present' do
diff --git a/spec/services/cohorts_service_spec.rb b/spec/services/cohorts_service_spec.rb
index 1e99442fdcb..77595d7ba2d 100644
--- a/spec/services/cohorts_service_spec.rb
+++ b/spec/services/cohorts_service_spec.rb
@@ -89,7 +89,7 @@ describe CohortsService do
activity_months: [{ total: 2, percentage: 100 }],
total: 2,
inactive: 1
- },
+ }
]
expect(described_class.new.execute).to eq(months_included: 12,
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index a883705bd45..5398b5c3f7e 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -1,156 +1,117 @@
require 'spec_helper'
describe CreateDeploymentService, services: true do
- let(:project) { create(:empty_project) }
let(:user) { create(:user) }
+ let(:options) { nil }
+
+ let(:job) do
+ create(:ci_build,
+ ref: 'master',
+ tag: false,
+ environment: 'production',
+ options: { environment: options })
+ end
- let(:service) { described_class.new(project, user, params) }
+ let(:project) { job.project }
- describe '#execute' do
- let(:options) { nil }
- let(:params) do
- {
- environment: 'production',
- ref: 'master',
- tag: false,
- sha: '97de212e80737a608d939f648d959671fb0a0142',
- options: options
- }
- end
+ let!(:environment) do
+ create(:environment, project: project, name: 'production')
+ end
- subject { service.execute }
+ let(:service) { described_class.new(job) }
- context 'when no environments exist' do
- it 'does create a new environment' do
- expect { subject }.to change { Environment.count }.by(1)
- end
+ describe '#execute' do
+ subject { service.execute }
- it 'does create a deployment' do
+ context 'when environment exists' do
+ it 'creates a deployment' do
expect(subject).to be_persisted
end
end
- context 'when environment exist' do
- let!(:environment) { create(:environment, project: project, name: 'production') }
-
- it 'does not create a new environment' do
- expect { subject }.not_to change { Environment.count }
- end
+ context 'when environment does not exist' do
+ let(:environment) {}
- it 'does create a deployment' do
- expect(subject).to be_persisted
+ it 'does not create a deployment' do
+ expect do
+ expect(subject).to be_nil
+ end.not_to change { Deployment.count }
end
+ end
- context 'and start action is defined' do
- let(:options) { { action: 'start' } }
+ context 'when start action is defined' do
+ let(:options) { { action: 'start' } }
- context 'and environment is stopped' do
- before do
- environment.stop
- end
+ context 'and environment is stopped' do
+ before do
+ environment.stop
+ end
- it 'makes environment available' do
- subject
+ it 'makes environment available' do
+ subject
- expect(environment.reload).to be_available
- end
+ expect(environment.reload).to be_available
+ end
- it 'does create a deployment' do
- expect(subject).to be_persisted
- end
+ it 'creates a deployment' do
+ expect(subject).to be_persisted
end
end
+ end
- context 'and stop action is defined' do
- let(:options) { { action: 'stop' } }
-
- context 'and environment is available' do
- before do
- environment.start
- end
-
- it 'makes environment stopped' do
- subject
-
- expect(environment.reload).to be_stopped
- end
+ context 'when stop action is defined' do
+ let(:options) { { action: 'stop' } }
- it 'does not create a deployment' do
- expect(subject).to be_nil
- end
+ context 'and environment is available' do
+ before do
+ environment.start
end
- end
- end
- context 'for environment with invalid name' do
- let(:params) do
- {
- environment: 'name,with,commas',
- ref: 'master',
- tag: false,
- sha: '97de212e80737a608d939f648d959671fb0a0142'
- }
- end
+ it 'makes environment stopped' do
+ subject
- it 'does not create a new environment' do
- expect { subject }.not_to change { Environment.count }
- end
+ expect(environment.reload).to be_stopped
+ end
- it 'does not create a deployment' do
- expect(subject).to be_nil
+ it 'does not create a deployment' do
+ expect(subject).to be_nil
+ end
end
end
context 'when variables are used' do
- let(:params) do
- {
- environment: 'review-apps/$CI_COMMIT_REF_NAME',
- ref: 'master',
- tag: false,
- sha: '97de212e80737a608d939f648d959671fb0a0142',
- options: {
- name: 'review-apps/$CI_COMMIT_REF_NAME',
- url: 'http://$CI_COMMIT_REF_NAME.review-apps.gitlab.com'
- },
- variables: [
- { key: 'CI_COMMIT_REF_NAME', value: 'feature-review-apps' }
- ]
- }
+ let(:options) do
+ { name: 'review-apps/$CI_COMMIT_REF_NAME',
+ url: 'http://$CI_COMMIT_REF_NAME.review-apps.gitlab.com' }
end
- it 'does create a new environment' do
- expect { subject }.to change { Environment.count }.by(1)
-
- expect(subject.environment.name).to eq('review-apps/feature-review-apps')
- expect(subject.environment.external_url).to eq('http://feature-review-apps.review-apps.gitlab.com')
+ before do
+ environment.update(name: 'review-apps/master')
+ job.update(environment: 'review-apps/$CI_COMMIT_REF_NAME')
end
- it 'does create a new deployment' do
+ it 'creates a new deployment' do
expect(subject).to be_persisted
end
- context 'and environment exist' do
- let!(:environment) { create(:environment, project: project, name: 'review-apps/feature-review-apps') }
-
- it 'does not create a new environment' do
- expect { subject }.not_to change { Environment.count }
- end
-
- it 'updates external url' do
- subject
+ it 'does not create a new environment' do
+ expect { subject }.not_to change { Environment.count }
+ end
- expect(subject.environment.name).to eq('review-apps/feature-review-apps')
- expect(subject.environment.external_url).to eq('http://feature-review-apps.review-apps.gitlab.com')
- end
+ it 'updates external url' do
+ subject
- it 'does create a new deployment' do
- expect(subject).to be_persisted
- end
+ expect(subject.environment.name).to eq('review-apps/master')
+ expect(subject.environment.external_url).to eq('http://master.review-apps.gitlab.com')
end
end
context 'when project was removed' do
- let(:project) { nil }
+ let(:environment) {}
+
+ before do
+ job.update(project: nil)
+ end
it 'does not create deployment or environment' do
expect { subject }.not_to raise_error
@@ -162,34 +123,26 @@ describe CreateDeploymentService, services: true do
end
describe 'processing of builds' do
- let(:environment) { nil }
-
- shared_examples 'does not create environment and deployment' do
- it 'does not create a new environment' do
- expect { subject }.not_to change { Environment.count }
- end
-
+ shared_examples 'does not create deployment' do
it 'does not create a new deployment' do
expect { subject }.not_to change { Deployment.count }
end
it 'does not call a service' do
expect_any_instance_of(described_class).not_to receive(:execute)
+
subject
end
end
- shared_examples 'does create environment and deployment' do
- it 'does create a new environment' do
- expect { subject }.to change { Environment.count }.by(1)
- end
-
- it 'does create a new deployment' do
+ shared_examples 'creates deployment' do
+ it 'creates a new deployment' do
expect { subject }.to change { Deployment.count }.by(1)
end
- it 'does call a service' do
+ it 'calls a service' do
expect_any_instance_of(described_class).to receive(:execute)
+
subject
end
@@ -199,7 +152,7 @@ describe CreateDeploymentService, services: true do
expect(Deployment.last.deployable).to eq(deployable)
end
- it 'create environment has URL set' do
+ it 'updates environment URL' do
subject
expect(Deployment.last.environment.external_url).not_to be_nil
@@ -207,41 +160,39 @@ describe CreateDeploymentService, services: true do
end
context 'without environment specified' do
- let(:build) { create(:ci_build, project: project) }
+ let(:job) { create(:ci_build) }
- it_behaves_like 'does not create environment and deployment' do
- subject { build.success }
+ it_behaves_like 'does not create deployment' do
+ subject { job.success }
end
end
context 'when environment is specified' do
- let(:pipeline) { create(:ci_pipeline, project: project) }
- let(:build) { create(:ci_build, pipeline: pipeline, environment: 'production', options: options) }
+ let(:deployable) { job }
+
let(:options) do
{ environment: { name: 'production', url: 'http://gitlab.com' } }
end
- context 'when build succeeds' do
- it_behaves_like 'does create environment and deployment' do
- let(:deployable) { build }
-
- subject { build.success }
+ context 'when job succeeds' do
+ it_behaves_like 'creates deployment' do
+ subject { job.success }
end
end
- context 'when build fails' do
- it_behaves_like 'does not create environment and deployment' do
- subject { build.drop }
+ context 'when job fails' do
+ it_behaves_like 'does not create deployment' do
+ subject { job.drop }
end
end
- context 'when build is retried' do
- it_behaves_like 'does create environment and deployment' do
+ context 'when job is retried' do
+ it_behaves_like 'creates deployment' do
before do
project.add_developer(user)
end
- let(:deployable) { Ci::Build.retry(build, user) }
+ let(:deployable) { Ci::Build.retry(job, user) }
subject { deployable.success }
end
@@ -250,15 +201,6 @@ describe CreateDeploymentService, services: true do
end
describe "merge request metrics" do
- let(:params) do
- {
- environment: 'production',
- ref: 'master',
- tag: false,
- sha: '97de212e80737a608d939f648d959671fb0a0142b',
- }
- end
-
let(:merge_request) { create(:merge_request, target_branch: 'master', source_branch: 'feature', source_project: project) }
context "while updating the 'first_deployed_to_production_at' time" do
@@ -273,8 +215,8 @@ describe CreateDeploymentService, services: true do
end
it "doesn't set the time if the deploy's environment is not 'production'" do
- staging_params = params.merge(environment: 'staging')
- service = described_class.new(project, user, staging_params)
+ job.update(environment: 'staging')
+ service = described_class.new(job)
service.execute
expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_nil
@@ -298,7 +240,7 @@ describe CreateDeploymentService, services: true do
expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time)
# Current deploy
- service = described_class.new(project, user, params)
+ service = described_class.new(job)
Timecop.freeze(time + 12.hours) { service.execute }
expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time)
@@ -318,7 +260,7 @@ describe CreateDeploymentService, services: true do
expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_nil
# Current deploy
- service = described_class.new(project, user, params)
+ service = described_class.new(job)
Timecop.freeze(time + 12.hours) { service.execute }
expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_nil
diff --git a/spec/services/delete_merged_branches_service_spec.rb b/spec/services/delete_merged_branches_service_spec.rb
index 7b921f606f8..cae74df9c90 100644
--- a/spec/services/delete_merged_branches_service_spec.rb
+++ b/spec/services/delete_merged_branches_service_spec.rb
@@ -6,33 +6,22 @@ describe DeleteMergedBranchesService, services: true do
let(:project) { create(:project, :repository) }
context '#execute' do
- context 'unprotected branches' do
- before do
- service.execute
- end
+ it 'deletes a branch that was merged' do
+ service.execute
- it 'deletes a branch that was merged' do
- expect(project.repository.branch_names).not_to include('improve/awesome')
- end
+ expect(project.repository.branch_names).not_to include('improve/awesome')
+ end
- it 'keeps branch that is unmerged' do
- expect(project.repository.branch_names).to include('feature')
- end
+ it 'keeps branch that is unmerged' do
+ service.execute
- it 'keeps "master"' do
- expect(project.repository.branch_names).to include('master')
- end
+ expect(project.repository.branch_names).to include('feature')
end
- context 'protected branches' do
- before do
- create(:protected_branch, name: 'improve/awesome', project: project)
- service.execute
- end
+ it 'keeps "master"' do
+ service.execute
- it 'keeps protected branch' do
- expect(project.repository.branch_names).to include('improve/awesome')
- end
+ expect(project.repository.branch_names).to include('master')
end
context 'user without rights' do
diff --git a/spec/services/notes/diff_position_update_service_spec.rb b/spec/services/discussions/update_diff_position_service_spec.rb
index d73ae51fbc3..177e32e13bd 100644
--- a/spec/services/notes/diff_position_update_service_spec.rb
+++ b/spec/services/discussions/update_diff_position_service_spec.rb
@@ -1,7 +1,8 @@
require 'spec_helper'
-describe Notes::DiffPositionUpdateService, services: true do
+describe Discussions::UpdateDiffPositionService, services: true do
let(:project) { create(:project, :repository) }
+ let(:current_user) { project.owner }
let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") }
let(:modify_commit) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e") }
let(:edit_commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") }
@@ -25,7 +26,7 @@ describe Notes::DiffPositionUpdateService, services: true do
subject do
described_class.new(
project,
- nil,
+ current_user,
old_diff_refs: old_diff_refs,
new_diff_refs: new_diff_refs,
paths: [path]
@@ -137,7 +138,7 @@ describe Notes::DiffPositionUpdateService, services: true do
# .. ..
describe "#execute" do
- let(:note) { create(:diff_note_on_merge_request, project: project, position: old_position) }
+ let(:discussion) { create(:diff_note_on_merge_request, project: project, position: old_position).to_discussion }
let(:old_position) do
Gitlab::Diff::Position.new(
@@ -153,11 +154,11 @@ describe Notes::DiffPositionUpdateService, services: true do
let(:line) { 16 }
it "updates the position" do
- subject.execute(note)
+ subject.execute(discussion)
- expect(note.original_position).to eq(old_position)
- expect(note.position).not_to eq(old_position)
- expect(note.position.new_line).to eq(22)
+ expect(discussion.original_position).to eq(old_position)
+ expect(discussion.position).not_to eq(old_position)
+ expect(discussion.position.new_line).to eq(22)
end
end
@@ -165,10 +166,27 @@ describe Notes::DiffPositionUpdateService, services: true do
let(:line) { 9 }
it "doesn't update the position" do
- subject.execute(note)
+ subject.execute(discussion)
- expect(note.original_position).to eq(old_position)
- expect(note.position).to eq(old_position)
+ expect(discussion.original_position).to eq(old_position)
+ expect(discussion.position).to eq(old_position)
+ end
+
+ it 'sets the change position' do
+ subject.execute(discussion)
+
+ change_position = discussion.change_position
+ expect(change_position.start_sha).to eq(old_diff_refs.head_sha)
+ expect(change_position.head_sha).to eq(new_diff_refs.head_sha)
+ expect(change_position.old_line).to eq(9)
+ expect(change_position.new_line).to be_nil
+ end
+
+ it 'creates a system discussion' do
+ expect(SystemNoteService).to receive(:diff_discussion_outdated).with(
+ discussion, project, current_user, instance_of(Gitlab::Diff::Position))
+
+ subject.execute(discussion)
end
end
end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 0477cac6677..bcd1fb64ab9 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -131,6 +131,19 @@ describe GitPushService, services: true do
end
end
+ describe "Pipelines" do
+ subject { execute_service(project, user, @oldrev, @newrev, @ref) }
+
+ before do
+ stub_ci_pipeline_to_return_yaml_file
+ end
+
+ it "creates a new pipeline" do
+ expect{ subject }.to change{ Ci::Pipeline.count }
+ expect(Ci::Pipeline.last).to be_push
+ end
+ end
+
describe "Push Event" do
before do
service = execute_service(project, user, @oldrev, @newrev, @ref )
@@ -436,6 +449,7 @@ describe GitPushService, services: true do
author_name: commit_author.name,
author_email: commit_author.email
})
+ allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
allow(project.repository).to receive_messages(commits_between: [closing_commit])
end
@@ -584,7 +598,7 @@ describe GitPushService, services: true do
commit = double(:commit)
diff = double(:diff, new_path: 'README.md')
- expect(commit).to receive(:raw_diffs).with(deltas_only: true).
+ expect(commit).to receive(:raw_deltas).
and_return([diff])
service.push_commits = [commit]
@@ -622,12 +636,21 @@ describe GitPushService, services: true do
it 'only schedules a limited number of commits' do
allow(service).to receive(:push_commits).
- and_return(Array.new(1000, double(:commit, to_hash: {})))
+ and_return(Array.new(1000, double(:commit, to_hash: {}, matches_cross_reference_regex?: true)))
expect(ProcessCommitWorker).to receive(:perform_async).exactly(100).times
service.process_commit_messages
end
+
+ it "skips commits which don't include cross-references" do
+ allow(service).to receive(:push_commits).
+ and_return([double(:commit, to_hash: {}, matches_cross_reference_regex?: false)])
+
+ expect(ProcessCommitWorker).not_to receive(:perform_async)
+
+ service.process_commit_messages
+ end
end
def execute_service(project, user, oldrev, newrev, ref)
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index b73beb3f6fc..1fdcb420a8b 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -30,6 +30,20 @@ describe GitTagPushService, services: true do
end
end
+ describe "Pipelines" do
+ subject { service.execute }
+
+ before do
+ stub_ci_pipeline_to_return_yaml_file
+ project.team << [user, :developer]
+ end
+
+ it "creates a new pipeline" do
+ expect{ subject }.to change{ Ci::Pipeline.count }
+ expect(Ci::Pipeline.last).to be_push
+ end
+ end
+
describe "Git Tag Push Data" do
subject { @push_data }
let(:tag) { project.repository.find_tag(tag_name) }
diff --git a/spec/services/gravatar_service_spec.rb b/spec/services/gravatar_service_spec.rb
new file mode 100644
index 00000000000..8c4ad8c7a3e
--- /dev/null
+++ b/spec/services/gravatar_service_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe GravatarService, service: true do
+ describe '#execute' do
+ let(:url) { 'http://example.com/avatar?hash=%{hash}&size=%{size}&email=%{email}&username=%{username}' }
+
+ before do
+ allow(Gitlab.config.gravatar).to receive(:plain_url).and_return(url)
+ end
+
+ it 'replaces the placeholders' do
+ avatar_url = described_class.new.execute('user@example.com', 100, 2, username: 'user')
+
+ expect(avatar_url).to include("hash=#{Digest::MD5.hexdigest('user@example.com')}")
+ expect(avatar_url).to include("size=200")
+ expect(avatar_url).to include("email=user%40example.com")
+ expect(avatar_url).to include("username=user")
+ end
+ end
+end
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index 8fd56214752..eb9b1670c71 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -72,7 +72,7 @@ describe Issuable::BulkUpdateService, services: true do
end
context "when the new assignee ID is #{IssuableFinder::NONE}" do
- it "unassigns the issues" do
+ it 'unassigns the issues' do
expect { bulk_update(merge_request, assignee_id: IssuableFinder::NONE) }
.to change { merge_request.reload.assignee }.to(nil)
end
@@ -163,7 +163,7 @@ describe Issuable::BulkUpdateService, services: true do
{
label_ids: labels.map(&:id),
add_label_ids: add_labels.map(&:id),
- remove_label_ids: remove_labels.map(&:id),
+ remove_label_ids: remove_labels.map(&:id)
}
end
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index 55d635235b0..bed25fe7ccf 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -136,7 +136,7 @@ describe Issues::BuildService, services: true do
user,
title: 'Issue #1',
description: 'Issue description',
- milestone_id: milestone.id,
+ milestone_id: milestone.id
).execute
expect(issue.title).to eq('Issue #1')
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 51840531711..be0e829880e 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -41,6 +41,12 @@ describe Issues::CloseService, services: true do
service.execute(issue)
end
+
+ it 'invalidates counter cache for assignees' do
+ expect_any_instance_of(User).to receive(:invalidate_issue_cache_counts)
+
+ service.execute(issue)
+ end
end
describe '#close_issue' do
@@ -51,8 +57,10 @@ describe Issues::CloseService, services: true do
end
end
- it { expect(issue).to be_valid }
- it { expect(issue).to be_closed }
+ it 'closes the issue' do
+ expect(issue).to be_valid
+ expect(issue).to be_closed
+ end
it 'sends email to user2 about assign of new issue' do
email = ActionMailer::Base.deliveries.last
@@ -96,9 +104,11 @@ describe Issues::CloseService, services: true do
described_class.new(project, user).close_issue(issue)
end
- it { expect(issue).to be_valid }
- it { expect(issue).to be_opened }
- it { expect(todo.reload).to be_pending }
+ it 'closes the issue' do
+ expect(issue).to be_valid
+ expect(issue).to be_opened
+ expect(todo.reload).to be_pending
+ end
end
end
end
diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb
index 93a8270fd16..391ecad303a 100644
--- a/spec/services/issues/reopen_service_spec.rb
+++ b/spec/services/issues/reopen_service_spec.rb
@@ -27,6 +27,13 @@ describe Issues::ReopenService, services: true do
project.team << [user, :master]
end
+ it 'invalidates counter cache for assignees' do
+ issue.assignees << user
+ expect_any_instance_of(User).to receive(:invalidate_issue_cache_counts)
+
+ described_class.new(project, user).execute(issue)
+ end
+
context 'when issue is not confidential' do
it 'executes issue hooks' do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb
index c3b4c2176ee..86f218dec12 100644
--- a/spec/services/issues/resolve_discussions_spec.rb
+++ b/spec/services/issues/resolve_discussions_spec.rb
@@ -77,7 +77,7 @@ describe Issues::ResolveDiscussions, services: true do
_second_discussion = Discussion.new([create(:diff_note_on_merge_request, :resolved,
noteable: merge_request,
project: merge_request.target_project,
- line_number: 15,
+ line_number: 15
)])
service = DummyService.new(
project,
diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb
index 0670ac2faa2..5ce8e17976b 100644
--- a/spec/services/members/create_service_spec.rb
+++ b/spec/services/members/create_service_spec.rb
@@ -11,7 +11,7 @@ describe Members::CreateService, services: true do
params = { user_ids: project_user.id.to_s, access_level: Gitlab::Access::GUEST }
result = described_class.new(project, user, params).execute
- expect(result).to be_truthy
+ expect(result[:status]).to eq(:success)
expect(project.users).to include project_user
end
@@ -19,7 +19,19 @@ describe Members::CreateService, services: true do
params = { user_ids: '', access_level: Gitlab::Access::GUEST }
result = described_class.new(project, user, params).execute
- expect(result).to be_falsey
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to be_present
+ expect(project.users).not_to include project_user
+ end
+
+ it 'limits the number of users to 100' do
+ user_ids = 1.upto(101).to_a.join(',')
+ params = { user_ids: user_ids, access_level: Gitlab::Access::GUEST }
+
+ result = described_class.new(project, user, params).execute
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to be_present
expect(project.users).not_to include project_user
end
end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index d55a7657c0e..154f30aac3b 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -15,6 +15,8 @@ describe MergeRequests::CloseService, services: true do
end
describe '#execute' do
+ it_behaves_like 'cache counters invalidator'
+
context 'valid params' do
let(:service) { described_class.new(project, user, {}) }
diff --git a/spec/services/merge_requests/conflicts/resolve_service_spec.rb b/spec/services/merge_requests/conflicts/resolve_service_spec.rb
index 19e8d5cc5f1..c77e6e9cd50 100644
--- a/spec/services/merge_requests/conflicts/resolve_service_spec.rb
+++ b/spec/services/merge_requests/conflicts/resolve_service_spec.rb
@@ -26,6 +26,10 @@ describe MergeRequests::Conflicts::ResolveService do
describe '#execute' do
let(:service) { described_class.new(merge_request) }
+ def blob_content(project, ref, path)
+ project.repository.blob_at(ref, path).data
+ end
+
context 'with section params' do
let(:params) do
{
@@ -66,6 +70,35 @@ describe MergeRequests::Conflicts::ResolveService do
end
end
+ context 'when some files have trailing newlines' do
+ let!(:source_head) do
+ branch = 'conflict-resolvable'
+ path = 'files/ruby/popen.rb'
+ popen_content = blob_content(project, branch, path)
+
+ project.repository.update_file(
+ user,
+ path,
+ popen_content.chomp("\n"),
+ message: 'Remove trailing newline from popen.rb',
+ branch_name: branch
+ )
+ end
+
+ before do
+ service.execute(user, params)
+ end
+
+ it 'preserves trailing newlines from our side of the conflicts' do
+ head_sha = merge_request.source_branch_head.sha
+ popen_content = blob_content(project, head_sha, 'files/ruby/popen.rb')
+ regex_content = blob_content(project, head_sha, 'files/ruby/regex.rb')
+
+ expect(popen_content).not_to end_with("\n")
+ expect(regex_content).to end_with("\n")
+ end
+ end
+
context 'when the source project is a fork and does not contain the HEAD of the target branch' do
let!(:target_head) do
project.repository.create_file(
@@ -142,10 +175,13 @@ describe MergeRequests::Conflicts::ResolveService do
end
it 'sets the content to the content given' do
- blob = merge_request.source_project.repository.blob_at(merge_request.source_branch_head.sha,
- 'files/ruby/popen.rb')
+ blob = blob_content(
+ merge_request.source_project,
+ merge_request.source_branch_head.sha,
+ 'files/ruby/popen.rb'
+ )
- expect(blob.data).to eq(popen_content)
+ expect(blob).to eq(popen_content)
end
end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 41752f1a01a..2963f62cc7d 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -27,10 +27,12 @@ describe MergeRequests::CreateService, services: true do
@merge_request = service.execute
end
- it { expect(@merge_request).to be_valid }
- it { expect(@merge_request.title).to eq('Awesome merge_request') }
- it { expect(@merge_request.assignee).to be_nil }
- it { expect(@merge_request.merge_params['force_remove_source_branch']).to eq('1') }
+ it 'creates an MR' do
+ expect(@merge_request).to be_valid
+ expect(@merge_request.title).to eq('Awesome merge_request')
+ expect(@merge_request.assignee).to be_nil
+ expect(@merge_request.merge_params['force_remove_source_branch']).to eq('1')
+ end
it 'executes hooks with default action' do
expect(service).to have_received(:execute_hooks).with(@merge_request)
@@ -73,6 +75,37 @@ describe MergeRequests::CreateService, services: true do
expect(Todo.where(attributes).count).to eq 1
end
end
+
+ context 'when head pipelines already exist for merge request source branch' do
+ let(:sha) { project.commit(opts[:source_branch]).id }
+ let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: sha) }
+ let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: sha) }
+ let!(:pipeline_3) { create(:ci_pipeline, project: project, ref: "other_branch", project_id: project.id) }
+
+ before do
+ project.merge_requests.
+ where(source_branch: opts[:source_branch], target_branch: opts[:target_branch]).
+ destroy_all
+ end
+
+ it 'sets head pipeline' do
+ merge_request = service.execute
+
+ expect(merge_request.head_pipeline).to eq(pipeline_2)
+ expect(merge_request).to be_persisted
+ end
+
+ context 'when merge request head commit sha does not match pipeline sha' do
+ it 'sets the head pipeline correctly' do
+ pipeline_2.update(sha: 1234)
+
+ merge_request = service.execute
+
+ expect(merge_request.head_pipeline).to eq(pipeline_1)
+ expect(merge_request).to be_persisted
+ end
+ end
+ end
end
it_behaves_like 'new issuable record that supports slash commands' do
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 769b3193275..f17db70faf6 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
@@ -79,7 +79,8 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
context 'when triggered by pipeline with valid ref and sha' do
let(:triggering_pipeline) do
create(:ci_pipeline, project: project, ref: merge_request_ref,
- sha: merge_request_head, status: 'success')
+ sha: merge_request_head, status: 'success',
+ head_pipeline_of: mr_merge_if_green_enabled)
end
it "merges all merge requests with merge when the pipeline succeeds enabled" do
@@ -121,7 +122,8 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
let(:conflict_pipeline) do
create(:ci_pipeline, project: project, ref: mr_conflict.source_branch,
- sha: mr_conflict.diff_head_sha, status: 'success')
+ sha: mr_conflict.diff_head_sha, status: 'success',
+ head_pipeline_of: mr_conflict)
end
it 'does not merge the merge request' do
diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb
new file mode 100644
index 00000000000..a20b32eda5f
--- /dev/null
+++ b/spec/services/merge_requests/post_merge_service_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe MergeRequests::PostMergeService, services: true do
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request, assignee: user) }
+ let(:project) { merge_request.project }
+
+ before do
+ project.team << [user, :master]
+ end
+
+ describe '#execute' do
+ it_behaves_like 'cache counters invalidator'
+ end
+end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 03215a4624a..1f109eab268 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -348,7 +348,7 @@ describe MergeRequests::RefreshService, services: true do
title: 'fixup! Fix issue',
work_in_progress?: true,
to_reference: 'ccccccc'
- ),
+ )
])
refresh_service.execute(@oldrev, @newrev, 'refs/heads/wip')
reload_mrs
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index a99d4eac9bd..b6d4db2f922 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -14,6 +14,8 @@ describe MergeRequests::ReopenService, services: true do
end
describe '#execute' do
+ it_behaves_like 'cache counters invalidator'
+
context 'valid params' do
let(:service) { described_class.new(project, user, {}) }
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 2bd5c3531cb..d371fc68312 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -59,14 +59,16 @@ describe MergeRequests::UpdateService, services: true do
end
end
- it { expect(@merge_request).to be_valid }
- it { expect(@merge_request.title).to eq('New title') }
- it { expect(@merge_request.assignee).to eq(user2) }
- it { expect(@merge_request).to be_closed }
- it { expect(@merge_request.labels.count).to eq(1) }
- it { expect(@merge_request.labels.first.title).to eq(label.name) }
- it { expect(@merge_request.target_branch).to eq('target') }
- it { expect(@merge_request.merge_params['force_remove_source_branch']).to eq('1') }
+ it 'mathces base expectations' do
+ expect(@merge_request).to be_valid
+ expect(@merge_request.title).to eq('New title')
+ expect(@merge_request.assignee).to eq(user2)
+ expect(@merge_request).to be_closed
+ expect(@merge_request.labels.count).to eq(1)
+ expect(@merge_request.labels.first.title).to eq(label.name)
+ expect(@merge_request.target_branch).to eq('target')
+ expect(@merge_request.merge_params['force_remove_source_branch']).to eq('1')
+ end
it 'executes hooks with update action' do
expect(service).to have_received(:execute_hooks).
@@ -148,9 +150,11 @@ describe MergeRequests::UpdateService, services: true do
end
end
- it { expect(@merge_request).to be_valid }
- it { expect(@merge_request.state).to eq('merged') }
- it { expect(@merge_request.merge_error).to be_nil }
+ it 'merges the MR' do
+ expect(@merge_request).to be_valid
+ expect(@merge_request.state).to eq('merged')
+ expect(@merge_request.merge_error).to be_nil
+ end
end
context 'with finished pipeline' do
@@ -167,17 +171,22 @@ describe MergeRequests::UpdateService, services: true do
end
end
- it { expect(@merge_request).to be_valid }
- it { expect(@merge_request.state).to eq('merged') }
+ it 'merges the MR' do
+ expect(@merge_request).to be_valid
+ expect(@merge_request.state).to eq('merged')
+ end
end
context 'with active pipeline' do
before do
service_mock = double
- create(:ci_pipeline_with_one_job,
+ create(
+ :ci_pipeline_with_one_job,
project: project,
- ref: merge_request.source_branch,
- sha: merge_request.diff_head_sha)
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha,
+ head_pipeline_of: merge_request
+ )
expect(MergeRequests::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user).
and_return(service_mock)
@@ -200,8 +209,10 @@ describe MergeRequests::UpdateService, services: true do
end
end
- it { expect(@merge_request.state).to eq('opened') }
- it { expect(@merge_request.merge_error).not_to be_nil }
+ it 'does not merge the MR' do
+ expect(@merge_request.state).to eq('opened')
+ expect(@merge_request.merge_error).not_to be_nil
+ end
end
context 'MR can not be merged when note sha != MR sha' do
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 74f96b97909..de3bbc6b6a1 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -350,7 +350,7 @@ describe NotificationService, services: true do
create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_participant),
create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_mentioned),
create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_disabled),
- create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_note_author),
+ create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_note_author)
]
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 033e6ecd18c..40298dcb723 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -115,7 +115,7 @@ describe Projects::CreateService, '#execute', services: true do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
opts.merge!(
- visibility_level: Gitlab::VisibilityLevel.options['Public']
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC
)
end
@@ -161,15 +161,13 @@ describe Projects::CreateService, '#execute', services: true do
end
context 'when a bad service template is created' do
- before do
- create(:service, type: 'DroneCiService', project: nil, template: true, active: true)
- end
-
it 'reports an error in the imported project' do
opts[:import_url] = 'http://www.gitlab.com/gitlab-org/gitlab-ce'
+ create(:service, type: 'DroneCiService', project: nil, template: true, active: true)
+
project = create_project(user, opts)
- expect(project.errors.full_messages_for(:base).first).to match /Unable to save project. Error: Unable to save DroneCiService/
+ expect(project.errors.full_messages_for(:base).first).to match(/Unable to save project. Error: Unable to save DroneCiService/)
expect(project.services.count).to eq 0
end
end
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index f8eb34f2ef4..0df81f3abcb 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -3,8 +3,8 @@ require 'spec_helper'
describe Projects::ForkService, services: true do
describe 'fork by user' do
before do
- @from_namespace = create(:namespace)
- @from_user = create(:user, namespace: @from_namespace )
+ @from_user = create(:user)
+ @from_namespace = @from_user.namespace
avatar = fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")
@from_project = create(:project,
:repository,
@@ -13,8 +13,8 @@ describe Projects::ForkService, services: true do
star_count: 107,
avatar: avatar,
description: 'wow such project')
- @to_namespace = create(:namespace)
- @to_user = create(:user, namespace: @to_namespace)
+ @to_user = create(:user)
+ @to_namespace = @to_user.namespace
@from_project.add_user(@to_user, :developer)
end
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index 852a4ac852f..44db299812f 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -186,7 +186,7 @@ describe Projects::ImportService, services: true do
}
)
- allow(Gitlab.config.omniauth).to receive(:providers).and_return([provider])
+ stub_omniauth_setting(providers: [provider])
end
end
end
diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb
index d524c9aff17..0657b7e93fe 100644
--- a/spec/services/projects/participants_service_spec.rb
+++ b/spec/services/projects/participants_service_spec.rb
@@ -6,7 +6,6 @@ describe Projects::ParticipantsService, services: true do
let(:project) { create(:empty_project, :public) }
let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png')) }
let(:user) { create(:user) }
- let(:base_url) { Settings.send(:build_base_gitlab_url) }
let!(:group_member) { create(:group_member, group: group, user: user) }
it 'should return an url for the avatar' do
@@ -14,7 +13,7 @@ describe Projects::ParticipantsService, services: true do
groups = participants.groups
expect(groups.size).to eq 1
- expect(groups.first[:avatar_url]).to eq "#{base_url}/uploads/system/group/avatar/#{group.id}/dk.png"
+ expect(groups.first[:avatar_url]).to eq("/uploads/group/avatar/#{group.id}/dk.png")
end
it 'should return an url for the avatar with relative url' do
@@ -25,7 +24,7 @@ describe Projects::ParticipantsService, services: true do
groups = participants.groups
expect(groups.size).to eq 1
- expect(groups.first[:avatar_url]).to eq "#{base_url}/gitlab/uploads/system/group/avatar/#{group.id}/dk.png"
+ expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/group/avatar/#{group.id}/dk.png")
end
end
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 29ccce59c53..b957517c715 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -26,6 +26,7 @@ describe Projects::TransferService, services: true do
it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) }
+ it { expect(project.errors.messages[:new_namespace].first).to eq 'Please select a new namespace for your project.' }
end
context 'disallow transfering of project with tags' do
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 2112f1cf9ea..5cf989105d0 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -26,6 +26,15 @@ describe SearchService, services: true do
expect(project).to eq accessible_project
end
+
+ it 'returns the project for guests' do
+ search_project = create :empty_project
+ search_project.add_guest(user)
+
+ project = SearchService.new(user, project_id: search_project.id).project
+
+ expect(project).to eq search_project
+ end
end
context 'when the project is not accessible' do
diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb
index e5e400ee281..4db491fd5f3 100644
--- a/spec/services/slash_commands/interpret_service_spec.rb
+++ b/spec/services/slash_commands/interpret_service_spec.rb
@@ -384,7 +384,7 @@ describe SlashCommands::InterpretService, services: true do
it 'fetches assignee and populates assignee_id if content contains /assign' do
_, updates = service.execute(content, issue)
- expect(updates[:assignee_ids]).to match_array([developer.id, developer2.id])
+ expect(updates[:assignee_ids]).to match_array([developer.id])
end
end
diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb
new file mode 100644
index 00000000000..63a1e78f274
--- /dev/null
+++ b/spec/services/submit_usage_ping_service_spec.rb
@@ -0,0 +1,101 @@
+require 'spec_helper'
+
+describe SubmitUsagePingService do
+ context 'when usage ping is disabled' do
+ before do
+ stub_application_setting(usage_ping_enabled: false)
+ end
+
+ it 'does not run' do
+ expect(HTTParty).not_to receive(:post)
+
+ result = subject.execute
+
+ expect(result).to eq false
+ end
+ end
+
+ context 'when usage ping is enabled' do
+ before do
+ stub_application_setting(usage_ping_enabled: true)
+ end
+
+ it 'sends a POST request' do
+ response = stub_response(without_conv_index_params)
+
+ subject.execute
+
+ expect(response).to have_been_requested
+ end
+
+ it 'refreshes usage data statistics before submitting' do
+ stub_response(without_conv_index_params)
+
+ expect(Gitlab::UsageData).to receive(:to_json)
+ .with(force_refresh: true)
+ .and_call_original
+
+ subject.execute
+ end
+
+ it 'saves conversational development index data from the response' do
+ stub_response(with_conv_index_params)
+
+ expect { subject.execute }
+ .to change { ConversationalDevelopmentIndex::Metric.count }
+ .by(1)
+
+ expect(ConversationalDevelopmentIndex::Metric.last.leader_issues).to eq 10.2
+ end
+ end
+
+ def without_conv_index_params
+ {
+ conv_index: {}
+ }
+ end
+
+ def with_conv_index_params
+ {
+ conv_index: {
+ leader_issues: 10.2,
+ instance_issues: 3.2,
+
+ leader_notes: 25.3,
+ instance_notes: 23.2,
+
+ leader_milestones: 16.2,
+ instance_milestones: 5.5,
+
+ leader_boards: 5.2,
+ instance_boards: 3.2,
+
+ leader_merge_requests: 5.2,
+ instance_merge_requests: 3.2,
+
+ leader_ci_pipelines: 25.1,
+ instance_ci_pipelines: 21.3,
+
+ leader_environments: 3.3,
+ instance_environments: 2.2,
+
+ leader_deployments: 41.3,
+ instance_deployments: 15.2,
+
+ leader_projects_prometheus_active: 0.31,
+ instance_projects_prometheus_active: 0.30,
+
+ leader_service_desk_issues: 15.8,
+ instance_service_desk_issues: 15.1
+ }
+ }
+ end
+
+ def stub_response(body)
+ stub_request(:post, 'https://version.gitlab.com/usage_data').
+ to_return(
+ headers: { 'Content-Type' => 'application/json' },
+ body: body.to_json
+ )
+ end
+end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 7a9cd7553b1..c499b1bb343 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -733,6 +733,26 @@ describe SystemNoteService, services: true do
jira_service_settings
end
+ def cross_reference(type, link_exists = false)
+ noteable = type == 'commit' ? commit : merge_request
+
+ links = []
+ if link_exists
+ url = if type == 'commit'
+ "#{Settings.gitlab.base_url}/#{project.namespace.path}/#{project.path}/commit/#{commit.id}"
+ 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!)
+ end
+
+ allow(JIRA::Resource::Remotelink).to receive(:all).and_return(links)
+
+ described_class.cross_reference(jira_issue, noteable, author)
+ end
+
noteable_types = %w(merge_requests commit)
noteable_types.each do |type|
@@ -740,24 +760,39 @@ describe SystemNoteService, services: true do
it "blocks cross reference when #{type.underscore}_events is false" do
jira_tracker.update("#{type}_events" => false)
- noteable = type == "commit" ? commit : merge_request
- result = described_class.cross_reference(jira_issue, noteable, author)
-
- expect(result).to eq("Events for #{noteable.class.to_s.underscore.humanize.pluralize.downcase} are disabled.")
+ expect(cross_reference(type)).to eq("Events for #{type.pluralize.humanize.downcase} are disabled.")
end
it "blocks cross reference when #{type.underscore}_events is true" do
jira_tracker.update("#{type}_events" => true)
- noteable = type == "commit" ? commit : merge_request
- result = described_class.cross_reference(jira_issue, noteable, author)
+ expect(cross_reference(type)).to eq(success_message)
+ end
+ end
- expect(result).to eq(success_message)
+ context 'when a new cross reference is created' do
+ it 'creates a new comment and remote link' do
+ cross_reference(type)
+
+ expect(WebMock).to have_requested(:post, jira_api_comment_url(jira_issue))
+ expect(WebMock).to have_requested(:post, jira_api_remote_link_url(jira_issue))
+ end
+ end
+
+ context 'when a link exists' do
+ it 'updates a link but does not create a new comment' do
+ expect(WebMock).not_to have_requested(:post, jira_api_comment_url(jira_issue))
+
+ cross_reference(type, true)
end
end
end
describe "new reference" do
+ before do
+ allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
+ end
+
context 'for commits' do
it "creates comment" do
result = described_class.cross_reference(jira_issue, commit, author)
@@ -837,6 +872,7 @@ describe SystemNoteService, services: true do
describe "existing reference" do
before do
+ allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
message = "[#{author.name}|http://localhost/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\n'#{commit.title.chomp}'"
allow_any_instance_of(JIRA::Resource::Issue).to receive(:comments).and_return([OpenStruct.new(body: message)])
end
@@ -1034,4 +1070,35 @@ describe SystemNoteService, services: true do
expect(subject.note).to eq 'resolved all discussions'
end
end
+
+ describe '.diff_discussion_outdated' do
+ let(:discussion) { create(:diff_note_on_merge_request).to_discussion }
+ let(:merge_request) { discussion.noteable }
+ let(:project) { merge_request.source_project }
+ let(:change_position) { discussion.position }
+
+ def reloaded_merge_request
+ MergeRequest.find(merge_request.id)
+ end
+
+ subject { described_class.diff_discussion_outdated(discussion, project, author, change_position) }
+
+ it_behaves_like 'a system note' do
+ let(:expected_noteable) { discussion.first_note.noteable }
+ let(:action) { 'outdated' }
+ end
+
+ it 'creates a new note in the discussion' do
+ # we need to completely rebuild the merge request object, or the `@discussions` on the merge request are not reloaded.
+ expect { subject }.to change { reloaded_merge_request.discussions.first.notes.size }.by(1)
+ end
+
+ it 'links to the diff in the system note' do
+ expect(subject.note).to include('version 1')
+
+ diff_id = merge_request.merge_request_diff.id
+ line_code = change_position.line_code(project.repository)
+ expect(subject.note).to include(diffs_namespace_project_merge_request_url(project.namespace, project, merge_request, diff_id: diff_id, anchor: line_code))
+ end
+ end
end
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index de37a61e388..5409f67c091 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -147,16 +147,22 @@ describe Users::DestroyService, services: true do
end
context "migrating associated records" do
+ let!(:issue) { create(:issue, author: user) }
+
it 'delegates to the `MigrateToGhostUser` service to move associated records to the ghost user' do
- expect_any_instance_of(Users::MigrateToGhostUserService).to receive(:execute).once
+ expect_any_instance_of(Users::MigrateToGhostUserService).to receive(:execute).once.and_call_original
service.execute(user)
+
+ expect(issue.reload.author).to be_ghost
end
it 'does not run `MigrateToGhostUser` if hard_delete option is given' do
expect_any_instance_of(Users::MigrateToGhostUserService).not_to receive(:execute)
service.execute(user, hard_delete: true)
+
+ expect(Issue.exists?(issue.id)).to be_falsy
end
end
end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
new file mode 100644
index 00000000000..b5abc46e80c
--- /dev/null
+++ b/spec/services/web_hook_service_spec.rb
@@ -0,0 +1,137 @@
+require 'spec_helper'
+
+describe WebHookService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:project_hook) { create(:project_hook) }
+ let(:headers) do
+ {
+ 'Content-Type' => 'application/json',
+ 'X-Gitlab-Event' => 'Push Hook'
+ }
+ end
+ let(:data) do
+ { before: 'oldrev', after: 'newrev', ref: 'ref' }
+ end
+ let(:service_instance) { WebHookService.new(project_hook, data, 'push_hooks') }
+
+ describe '#execute' do
+ before(:each) do
+ project.hooks << [project_hook]
+
+ WebMock.stub_request(:post, project_hook.url)
+ end
+
+ context 'when token is defined' do
+ let(:project_hook) { create(:project_hook, :token) }
+
+ it 'POSTs to the webhook URL' do
+ service_instance.execute
+ expect(WebMock).to have_requested(:post, project_hook.url).with(
+ headers: headers.merge({ 'X-Gitlab-Token' => project_hook.token })
+ ).once
+ end
+ end
+
+ it 'POSTs to the webhook URL' do
+ service_instance.execute
+ expect(WebMock).to have_requested(:post, project_hook.url).with(
+ headers: headers
+ ).once
+ end
+
+ it 'POSTs the data as JSON' do
+ service_instance.execute
+ expect(WebMock).to have_requested(:post, project_hook.url).with(
+ headers: headers
+ ).once
+ end
+
+ it 'catches exceptions' do
+ WebMock.stub_request(:post, project_hook.url).to_raise(StandardError.new('Some error'))
+
+ expect { service_instance.execute }.to raise_error(StandardError)
+ end
+
+ it 'handles exceptions' do
+ exceptions = [SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout]
+ exceptions.each do |exception_class|
+ exception = exception_class.new('Exception message')
+
+ WebMock.stub_request(:post, project_hook.url).to_raise(exception)
+ expect(service_instance.execute).to eq([nil, exception.message])
+ expect { service_instance.execute }.not_to raise_error
+ end
+ end
+
+ it 'handles 200 status code' do
+ WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')
+
+ expect(service_instance.execute).to eq([200, 'Success'])
+ end
+
+ it 'handles 2xx status codes' do
+ WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: 'Success')
+
+ expect(service_instance.execute).to eq([201, 'Success'])
+ end
+
+ context 'execution logging' do
+ let(:hook_log) { project_hook.web_hook_logs.last }
+
+ context 'with success' do
+ before do
+ WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')
+ service_instance.execute
+ end
+
+ it 'log successful execution' do
+ expect(hook_log.trigger).to eq('push_hooks')
+ expect(hook_log.url).to eq(project_hook.url)
+ expect(hook_log.request_headers).to eq(headers)
+ expect(hook_log.response_body).to eq('Success')
+ expect(hook_log.response_status).to eq('200')
+ expect(hook_log.execution_duration).to be > 0
+ expect(hook_log.internal_error_message).to be_nil
+ end
+ end
+
+ context 'with exception' do
+ before do
+ WebMock.stub_request(:post, project_hook.url).to_raise(SocketError.new('Some HTTP Post error'))
+ service_instance.execute
+ end
+
+ it 'log failed execution' do
+ expect(hook_log.trigger).to eq('push_hooks')
+ expect(hook_log.url).to eq(project_hook.url)
+ expect(hook_log.request_headers).to eq(headers)
+ expect(hook_log.response_body).to eq('')
+ expect(hook_log.response_status).to eq('internal error')
+ expect(hook_log.execution_duration).to be > 0
+ expect(hook_log.internal_error_message).to eq('Some HTTP Post error')
+ end
+ end
+
+ context 'should not log ServiceHooks' do
+ let(:service_hook) { create(:service_hook) }
+ let(:service_instance) { WebHookService.new(service_hook, data, 'service_hook') }
+
+ before do
+ WebMock.stub_request(:post, service_hook.url).to_return(status: 200, body: 'Success')
+ end
+
+ it { expect { service_instance.execute }.not_to change(WebHookLog, :count) }
+ end
+ end
+ end
+
+ describe '#async_execute' do
+ let(:system_hook) { create(:system_hook) }
+
+ it 'enqueue WebHookWorker' do
+ expect(Sidekiq::Client).to receive(:enqueue).with(WebHookWorker, project_hook.id, data, 'push_hooks')
+
+ WebHookService.new(project_hook, data, 'push_hooks').async_execute
+ end
+ end
+end
diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb
index 5341ba3d261..054e28ae7b0 100644
--- a/spec/services/wiki_pages/create_service_spec.rb
+++ b/spec/services/wiki_pages/create_service_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe WikiPages::CreateService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
+
let(:opts) do
{
title: 'Title',
@@ -10,27 +11,28 @@ describe WikiPages::CreateService, services: true do
format: 'markdown'
}
end
- let(:service) { described_class.new(project, user, opts) }
+
+ subject(:service) { described_class.new(project, user, opts) }
+
+ before do
+ project.add_developer(user)
+ end
describe '#execute' do
- context "valid params" do
- before do
- allow(service).to receive(:execute_hooks)
- project.add_master(user)
- end
-
- subject { service.execute }
-
- it 'creates a valid wiki page' do
- is_expected.to be_valid
- expect(subject.title).to eq(opts[:title])
- expect(subject.content).to eq(opts[:content])
- expect(subject.format).to eq(opts[:format].to_sym)
- end
-
- it 'executes webhooks' do
- expect(service).to have_received(:execute_hooks).once.with(subject, 'create')
- end
+ it 'creates wiki page with valid attributes' do
+ page = service.execute
+
+ expect(page).to be_valid
+ expect(page.title).to eq(opts[:title])
+ expect(page.content).to eq(opts[:content])
+ expect(page.format).to eq(opts[:format].to_sym)
+ end
+
+ it 'executes webhooks' do
+ expect(service).to receive(:execute_hooks).once
+ .with(instance_of(WikiPage), 'create')
+
+ service.execute
end
end
end
diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb
index a4b9a390fe2..920be4d4c8a 100644
--- a/spec/services/wiki_pages/destroy_service_spec.rb
+++ b/spec/services/wiki_pages/destroy_service_spec.rb
@@ -3,19 +3,20 @@ require 'spec_helper'
describe WikiPages::DestroyService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
- let(:wiki_page) { create(:wiki_page) }
- let(:service) { described_class.new(project, user) }
+ let(:page) { create(:wiki_page) }
- describe '#execute' do
- before do
- allow(service).to receive(:execute_hooks)
- project.add_master(user)
- end
+ subject(:service) { described_class.new(project, user) }
+ before do
+ project.add_developer(user)
+ end
+
+ describe '#execute' do
it 'executes webhooks' do
- service.execute(wiki_page)
+ expect(service).to receive(:execute_hooks).once
+ .with(instance_of(WikiPage), 'delete')
- expect(service).to have_received(:execute_hooks).once.with(wiki_page, 'delete')
+ service.execute(page)
end
end
end
diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb
index 2bccca764d7..5e36ea4cf94 100644
--- a/spec/services/wiki_pages/update_service_spec.rb
+++ b/spec/services/wiki_pages/update_service_spec.rb
@@ -3,7 +3,8 @@ require 'spec_helper'
describe WikiPages::UpdateService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
- let(:wiki_page) { create(:wiki_page) }
+ let(:page) { create(:wiki_page) }
+
let(:opts) do
{
content: 'New content for wiki page',
@@ -11,27 +12,28 @@ describe WikiPages::UpdateService, services: true do
message: 'New wiki message'
}
end
- let(:service) { described_class.new(project, user, opts) }
+
+ subject(:service) { described_class.new(project, user, opts) }
+
+ before do
+ project.add_developer(user)
+ end
describe '#execute' do
- context "valid params" do
- before do
- allow(service).to receive(:execute_hooks)
- project.add_master(user)
- end
-
- subject { service.execute(wiki_page) }
-
- it 'updates the wiki page' do
- is_expected.to be_valid
- expect(subject.content).to eq(opts[:content])
- expect(subject.format).to eq(opts[:format].to_sym)
- expect(subject.message).to eq(opts[:message])
- end
-
- it 'executes webhooks' do
- expect(service).to have_received(:execute_hooks).once.with(subject, 'update')
- end
+ it 'updates the wiki page' do
+ updated_page = service.execute(page)
+
+ expect(updated_page).to be_valid
+ expect(updated_page.message).to eq(opts[:message])
+ expect(updated_page.content).to eq(opts[:content])
+ expect(updated_page.format).to eq(opts[:format].to_sym)
+ end
+
+ it 'executes webhooks' do
+ expect(service).to receive(:execute_hooks).once
+ .with(instance_of(WikiPage), 'update')
+
+ service.execute(page)
end
end
end
diff --git a/spec/sidekiq/cron/job_gem_dependency_spec.rb b/spec/sidekiq/cron/job_gem_dependency_spec.rb
new file mode 100644
index 00000000000..2e30cf025b0
--- /dev/null
+++ b/spec/sidekiq/cron/job_gem_dependency_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Sidekiq::Cron::Job do
+ describe 'cron jobs' do
+ context 'when rufus-scheduler depends on ZoTime or EoTime' do
+ before do
+ described_class
+ .create(name: 'TestCronWorker',
+ cron: Settings.cron_jobs[:pipeline_schedule_worker]['cron'],
+ class: Settings.cron_jobs[:pipeline_schedule_worker]['job_class'])
+ end
+
+ it 'does not get "Rufus::Scheduler::ZoTime/EtOrbi::EoTime into an exact number"' do
+ expect { described_class.all.first.should_enque?(Time.now) }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index c126641c4b9..8b8fbf6e862 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -3,6 +3,7 @@ SimpleCovEnv.start!
ENV["RAILS_ENV"] ||= 'test'
ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true'
+# ENV['prometheus_multiproc_dir'] = 'tmp/prometheus_multiproc_dir_test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
@@ -10,7 +11,7 @@ require 'shoulda/matchers'
require 'rspec/retry'
rspec_profiling_is_configured =
- ENV['RSPEC_PROFILING_POSTGRES_URL'] ||
+ ENV['RSPEC_PROFILING_POSTGRES_URL'].present? ||
ENV['RSPEC_PROFILING']
branch_can_be_profiled =
ENV['GITLAB_DATABASE'] == 'postgresql' &&
@@ -26,6 +27,9 @@ if ENV['CI'] && !ENV['NO_KNAPSACK']
Knapsack::Adapters::RSpecAdapter.bind
end
+# require rainbow gem String monkeypatch, so we can test SystemChecks
+require 'rainbow/ext/string'
+
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
@@ -44,7 +48,6 @@ RSpec.configure do |config|
config.include LoginHelpers, type: :feature
config.include SearchHelpers, type: :feature
config.include WaitForRequests, :js
- config.include WaitForAjax, :js
config.include StubConfiguration
config.include EmailHelpers, type: :mailer
config.include TestEnv
@@ -53,6 +56,7 @@ RSpec.configure do |config|
config.include StubGitlabCalls
config.include StubGitlabData
config.include ApiHelpers, :api
+ config.include MigrationsHelpers, :migration
config.infer_spec_type_from_file_location!
@@ -94,6 +98,17 @@ RSpec.configure do |config|
Sidekiq.redis(&:flushall)
end
+ config.around(:example, :migration) do |example|
+ begin
+ ActiveRecord::Migrator
+ .migrate(migrations_paths, previous_migration.version)
+
+ example.run
+ ensure
+ ActiveRecord::Migrator.migrate(migrations_paths)
+ end
+ end
+
config.around(:each, :nested_groups) do |example|
example.run if Group.supports_nested_groups?
end
diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb
index c59b30c772d..d6b40db09ce 100644
--- a/spec/support/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb
@@ -209,9 +209,13 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
context 'user has chosen a namespace and name for the project' do
- let(:test_namespace) { create(:namespace, name: 'test_namespace', owner: user) }
+ let(:test_namespace) { create(:group, name: 'test_namespace') }
let(:test_name) { 'test_name' }
+ before do
+ test_namespace.add_owner(user)
+ end
+
it 'takes the selected namespace and name' do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider).
@@ -230,10 +234,14 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
context 'user has chosen an existing nested namespace and name for the project' do
- let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
- let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) }
+ let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
+ let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' }
+ before do
+ nested_namespace.add_owner(user)
+ end
+
it 'takes the selected namespace and name' do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider).
@@ -276,7 +284,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
- let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
+ let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
it 'takes the selected namespace and name' do
expect(Gitlab::GithubImport::ProjectCreator).
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
index 8ad042f5e3b..6e1eb5c678d 100644
--- a/spec/support/cycle_analytics_helpers.rb
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -20,7 +20,7 @@ module CycleAnalyticsHelpers
ref: 'refs/heads/master').execute
end
- def create_merge_request_closing_issue(issue, message: nil, source_branch: nil)
+ def create_merge_request_closing_issue(issue, message: nil, source_branch: nil, commit_message: 'commit message')
if !source_branch || project.repository.commit(source_branch).blank?
source_branch = generate(:branch)
project.repository.add_branch(user, source_branch, 'master')
@@ -30,7 +30,7 @@ module CycleAnalyticsHelpers
user,
generate(:branch),
'content',
- message: 'commit message',
+ message: commit_message,
branch_name: source_branch)
project.repository.commit(sha)
@@ -51,12 +51,43 @@ module CycleAnalyticsHelpers
end
def deploy_master(environment: 'production')
- CreateDeploymentService.new(project, user, {
- environment: environment,
- ref: 'master',
- tag: false,
- sha: project.repository.commit('master').sha
- }).execute
+ dummy_job =
+ case environment
+ when 'production'
+ dummy_production_job
+ when 'staging'
+ dummy_staging_job
+ else
+ raise ArgumentError
+ end
+
+ CreateDeploymentService.new(dummy_job).execute
+ end
+
+ def dummy_production_job
+ @dummy_job ||= new_dummy_job('production')
+ end
+
+ def dummy_staging_job
+ @dummy_job ||= new_dummy_job('staging')
+ end
+
+ def dummy_pipeline
+ @dummy_pipeline ||=
+ Ci::Pipeline.new(sha: project.repository.commit('master').sha)
+ end
+
+ def new_dummy_job(environment)
+ project.environments.find_or_create_by(name: environment)
+
+ Ci::Build.new(
+ project: project,
+ user: user,
+ environment: environment,
+ ref: 'master',
+ tag: false,
+ name: 'dummy',
+ pipeline: dummy_pipeline)
end
end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index 6f31828b825..7f5769209bb 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -19,6 +19,10 @@ RSpec.configure do |config|
DatabaseCleaner.strategy = :truncation
end
+ config.before(:each, :migration) do
+ DatabaseCleaner.strategy = :truncation
+ end
+
config.before(:each) do
DatabaseCleaner.start
end
diff --git a/spec/support/dropzone_helper.rb b/spec/support/dropzone_helper.rb
index 984ec7d2741..02fdeb08afe 100644
--- a/spec/support/dropzone_helper.rb
+++ b/spec/support/dropzone_helper.rb
@@ -6,32 +6,52 @@ module DropzoneHelper
# Dropzone events to perform the actual upload.
#
# This method waits for the upload to complete before returning.
- def dropzone_file(file_path)
+ # max_file_size is an optional parameter.
+ # If it's not 0, then it used in dropzone.maxFilesize parameter.
+ # wait_for_queuecomplete is an optional parameter.
+ # If it's 'false', then the helper will NOT wait for backend response
+ # It lets to test behaviors while AJAX is processing.
+ def dropzone_file(files, max_file_size = 0, wait_for_queuecomplete = true)
# Generate a fake file input that Capybara can attach to
page.execute_script <<-JS.strip_heredoc
+ $('#fakeFileInput').remove();
var fakeFileInput = window.$('<input/>').attr(
- {id: 'fakeFileInput', type: 'file'}
+ {id: 'fakeFileInput', type: 'file', multiple: true}
).appendTo('body');
window._dropzoneComplete = false;
JS
- # Attach the file to the fake input selector with Capybara
- attach_file('fakeFileInput', file_path)
+ # Attach files to the fake input selector with Capybara
+ attach_file('fakeFileInput', files)
# Manually trigger a Dropzone "drop" event with the fake input's file list
page.execute_script <<-JS.strip_heredoc
- var fileList = [$('#fakeFileInput')[0].files[0]];
- var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
-
var dropzone = $('.div-dropzone')[0].dropzone;
+ dropzone.options.autoProcessQueue = false;
+
+ if (#{max_file_size} > 0) {
+ dropzone.options.maxFilesize = #{max_file_size};
+ }
+
dropzone.on('queuecomplete', function() {
window._dropzoneComplete = true;
});
- dropzone.listeners[0].events.drop(e);
+
+ var fileList = [$('#fakeFileInput')[0].files];
+
+ $.map(fileList, function(file){
+ var e = jQuery.Event('drop', { dataTransfer : { files : file } });
+
+ dropzone.listeners[0].events.drop(e);
+ });
+
+ dropzone.processQueue();
JS
- # Wait until Dropzone's fired `queuecomplete`
- loop until page.evaluate_script('window._dropzoneComplete === true')
+ if wait_for_queuecomplete
+ # Wait until Dropzone's fired `queuecomplete`
+ loop until page.evaluate_script('window._dropzoneComplete === true')
+ end
end
end
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index ad46b163cd6..fa82dc5e9f9 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -22,7 +22,7 @@ shared_examples 'issuable record that supports slash commands in its description
after do
# Ensure all outstanding Ajax requests are complete to avoid database deadlocks
- wait_for_ajax
+ wait_for_requests
end
describe "new #{issuable_type}", js: true do
@@ -58,7 +58,7 @@ shared_examples 'issuable record that supports slash commands in its description
expect(page).not_to have_content '/label ~bug'
expect(page).not_to have_content '/milestone %"ASAP"'
- wait_for_ajax
+ wait_for_requests
issuable.reload
note = issuable.notes.user.first
diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb
new file mode 100644
index 00000000000..0d80c95e826
--- /dev/null
+++ b/spec/support/features/reportable_note_shared_examples.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+shared_examples 'reportable note' do
+ include NotesHelper
+
+ let(:comment) { find("##{ActionView::RecordIdentifier.dom_id(note)}") }
+ let(:more_actions_selector) { '.more-actions.dropdown' }
+ let(:abuse_report_path) { new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) }
+
+ it 'has a `More actions` dropdown' do
+ expect(comment).to have_selector(more_actions_selector)
+ end
+
+ it 'dropdown has Edit, Report and Delete links' do
+ dropdown = comment.find(more_actions_selector)
+
+ dropdown.click
+ dropdown.find('.dropdown-menu li', match: :first)
+
+ expect(dropdown).to have_button('Edit comment')
+ expect(dropdown).to have_link('Report as abuse', href: abuse_report_path)
+ expect(dropdown).to have_link('Delete comment', href: note_url(note, project))
+ end
+
+ it 'Report button links to a report page' do
+ dropdown = comment.find(more_actions_selector)
+
+ dropdown.click
+ dropdown.find('.dropdown-menu li', match: :first)
+
+ dropdown.click_link('Report as abuse')
+
+ expect(find('#user_name')['value']).to match(note.author.username)
+ expect(find('#abuse_report_message')['value']).to match(noteable_note_url(note))
+ end
+end
diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb
index 9a3b0a731ad..1cbb4134995 100644
--- a/spec/support/features/rss_shared_examples.rb
+++ b/spec/support/features/rss_shared_examples.rb
@@ -1,23 +1,23 @@
-shared_examples "an autodiscoverable RSS feed with current_user's private token" do
- it "has an RSS autodiscovery link tag with current_user's private token" do
- expect(page).to have_css("link[type*='atom+xml'][href*='private_token=#{Thread.current[:current_user].private_token}']", visible: false)
+shared_examples "an autodiscoverable RSS feed with current_user's RSS token" do
+ it "has an RSS autodiscovery link tag with current_user's RSS token" do
+ expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{Thread.current[:current_user].rss_token}']", visible: false)
end
end
-shared_examples "it has an RSS button with current_user's private token" do
- it "shows the RSS button with current_user's private token" do
- expect(page).to have_css("a:has(.fa-rss)[href*='private_token=#{Thread.current[:current_user].private_token}']")
+shared_examples "it has an RSS button with current_user's RSS token" do
+ it "shows the RSS button with current_user's RSS token" do
+ expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{Thread.current[:current_user].rss_token}']")
end
end
-shared_examples "an autodiscoverable RSS feed without a private token" do
- it "has an RSS autodiscovery link tag without a private token" do
- expect(page).to have_css("link[type*='atom+xml']:not([href*='private_token'])", visible: false)
+shared_examples "an autodiscoverable RSS feed without an RSS token" do
+ it "has an RSS autodiscovery link tag without an RSS token" do
+ expect(page).to have_css("link[type*='atom+xml']:not([href*='rss_token'])", visible: false)
end
end
-shared_examples "it has an RSS button without a private token" do
- it "shows the RSS button without a private token" do
- expect(page).to have_css("a:has(.fa-rss):not([href*='private_token'])")
+shared_examples "it has an RSS button without an RSS token" do
+ it "shows the RSS button without an RSS token" do
+ expect(page).to have_css("a:has(.fa-rss):not([href*='rss_token'])")
end
end
diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/filtered_search_helpers.rb
index 36be0bb6bf8..37cc308e613 100644
--- a/spec/support/filtered_search_helpers.rb
+++ b/spec/support/filtered_search_helpers.rb
@@ -73,11 +73,11 @@ module FilteredSearchHelpers
end
def remove_recent_searches
- execute_script('window.localStorage.removeItem(\'issue-recent-searches\');')
+ execute_script('window.localStorage.clear();')
end
- def set_recent_searches(input)
- execute_script("window.localStorage.setItem('issue-recent-searches', '#{input}');")
+ def set_recent_searches(key, input)
+ execute_script("window.localStorage.setItem('#{key}', '#{input}');")
end
def wait_for_filtered_search(text)
diff --git a/spec/support/generate-seed-repo-rb b/spec/support/generate-seed-repo-rb
new file mode 100755
index 00000000000..7335f74c0e9
--- /dev/null
+++ b/spec/support/generate-seed-repo-rb
@@ -0,0 +1,162 @@
+#!/usr/bin/env ruby
+#
+# # generate-seed-repo-rb
+#
+# This script generates the seed_repo.rb file used by lib/gitlab/git
+# tests. The seed_repo.rb file needs to be updated anytime there is a
+# Git push to https://gitlab.com/gitlab-org/gitlab-git-test.
+#
+# Usage:
+#
+# ./spec/support/generate-seed-repo-rb > spec/support/seed_repo.rb
+#
+#
+
+require 'erb'
+require 'tempfile'
+
+SOURCE = 'https://gitlab.com/gitlab-org/gitlab-git-test.git'.freeze
+SCRIPT_NAME = 'generate-seed-repo-rb'.freeze
+REPO_NAME = 'gitlab-git-test.git'.freeze
+
+def main
+ Dir.mktmpdir do |dir|
+ 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)
+ end
+end
+
+def capture!(cmd, dir)
+ output = IO.popen(cmd, 'r', chdir: dir) { |io| io.read }
+ raise "command failed with #{$?}: #{cmd.join(' ')}" unless $?.success?
+ output.chomp
+end
+
+main
+
+__END__
+# This file is generated by <%= SCRIPT_NAME %>. Do not edit this file manually.
+#
+# Seed repo:
+<%= capture!(%w{git log --format=#\ %H\ %s}, repo) %>
+
+module SeedRepo
+ module BigCommit
+ ID = "913c66a37b4a45b9769037c55c2d238bd0942d2e".freeze
+ PARENT_ID = "cfe32cf61b73a0d5e9f13e774abde7ff789b1660".freeze
+ MESSAGE = "Files, encoding and much more".freeze
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze
+ FILES_COUNT = 2
+ end
+
+ module Commit
+ ID = "570e7b2abdd848b95f2f578043fc23bd6f6fd24d".freeze
+ PARENT_ID = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9".freeze
+ MESSAGE = "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n".freeze
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze
+ FILES = ["files/ruby/popen.rb", "files/ruby/regex.rb"].freeze
+ FILES_COUNT = 2
+ C_FILE_PATH = "files/ruby".freeze
+ C_FILES = ["popen.rb", "regex.rb", "version_info.rb"].freeze
+ BLOB_FILE = %{%h3= @key.title\n%hr\n%pre= @key.key\n.actions\n = link_to 'Remove', @key, :confirm => 'Are you sure?', :method => :delete, :class => \"btn danger delete-key\"\n\n\n}.freeze
+ BLOB_FILE_PATH = "app/views/keys/show.html.haml".freeze
+ end
+
+ module EmptyCommit
+ ID = "b0e52af38d7ea43cf41d8a6f2471351ac036d6c9".freeze
+ PARENT_ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d".freeze
+ MESSAGE = "Empty commit".freeze
+ AUTHOR_FULL_NAME = "Rémy Coutable".freeze
+ FILES = [].freeze
+ FILES_COUNT = FILES.count
+ end
+
+ module EncodingCommit
+ ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d".freeze
+ PARENT_ID = "66028349a123e695b589e09a36634d976edcc5e8".freeze
+ MESSAGE = "Add ISO-8859-encoded file".freeze
+ AUTHOR_FULL_NAME = "Stan Hu".freeze
+ FILES = ["encoding/iso8859.txt"].freeze
+ FILES_COUNT = FILES.count
+ end
+
+ module FirstCommit
+ ID = "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863".freeze
+ PARENT_ID = nil
+ MESSAGE = "Initial commit".freeze
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze
+ FILES = ["LICENSE", ".gitignore", "README.md"].freeze
+ FILES_COUNT = 3
+ end
+
+ module LastCommit
+ ID = <%= capture!(%w[git show -s --format=%H HEAD], repo).inspect %>.freeze
+ PARENT_ID = <%= capture!(%w[git show -s --format=%P HEAD], repo).split.last.inspect %>.freeze
+ MESSAGE = <%= capture!(%w[git show -s --format=%s HEAD], repo).inspect %>.freeze
+ AUTHOR_FULL_NAME = <%= capture!(%w[git show -s --format=%an HEAD], repo).inspect %>.freeze
+ FILES = <%=
+ parents = capture!(%w[git show -s --format=%P HEAD], repo).split
+ merge_base = parents.size > 1 ? capture!(%w[git merge-base] + parents, repo) : parents.first
+ capture!( %W[git diff --name-only #{merge_base}..HEAD --], repo).split("\n").inspect
+ %>.freeze
+ FILES_COUNT = FILES.count
+ end
+
+ module Repo
+ HEAD = "master".freeze
+ BRANCHES = %w[
+<%= capture!(%W[git for-each-ref --format=#{' ' * 3}%(refname:strip=2) refs/heads/], repo) %>
+ ].freeze
+ TAGS = %w[
+<%= capture!(%W[git for-each-ref --format=#{' ' * 3}%(refname:strip=2) refs/tags/], repo) %>
+ ].freeze
+ end
+
+ module RubyBlob
+ ID = "7e3e39ebb9b2bf433b4ad17313770fbe4051649c".freeze
+ NAME = "popen.rb".freeze
+ CONTENT = <<-eos.freeze
+require 'fileutils'
+require 'open3'
+
+module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ raise RuntimeError, "System commands must be given as an array of strings"
+ end
+
+ path ||= Dir.pwd
+
+ vars = {
+ "PWD" => path
+ }
+
+ options = {
+ chdir: path
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
+ end
+end
+ eos
+ end
+end
diff --git a/spec/support/git_http_helpers.rb b/spec/support/git_http_helpers.rb
index 46b686fce94..b8289e6c5f1 100644
--- a/spec/support/git_http_helpers.rb
+++ b/spec/support/git_http_helpers.rb
@@ -35,9 +35,14 @@ module GitHttpHelpers
yield response
end
+ def download_or_upload(*args, &block)
+ download(*args, &block)
+ upload(*args, &block)
+ end
+
def auth_env(user, password, spnego_request_token)
env = workhorse_internal_api_request_header
- if user && password
+ if user
env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password)
elsif spnego_request_token
env['HTTP_AUTHORIZATION'] = "Negotiate #{::Base64.strict_encode64('opaque_request_token')}"
@@ -45,4 +50,19 @@ module GitHttpHelpers
env
end
+
+ def git_access_error(error_key)
+ message = Gitlab::GitAccess::ERROR_MESSAGES[error_key]
+ message || raise("GitAccess error message key '#{error_key}' not found")
+ end
+
+ def git_access_wiki_error(error_key)
+ message = Gitlab::GitAccessWiki::ERROR_MESSAGES[error_key]
+ message || raise("GitAccessWiki error message key '#{error_key}' not found")
+ end
+
+ def change_access_error(error_key)
+ message = Gitlab::Checks::ChangeAccess::ERROR_MESSAGES[error_key]
+ message || raise("ChangeAccess error message key '#{error_key}' not found")
+ end
end
diff --git a/spec/support/gitaly.rb b/spec/support/gitaly.rb
index 7aca902fc61..2bf159002a0 100644
--- a/spec/support/gitaly.rb
+++ b/spec/support/gitaly.rb
@@ -1,6 +1,7 @@
if Gitlab::GitalyClient.enabled?
RSpec.configure do |config|
- config.before(:each) do
+ config.before(:each) do |example|
+ next if example.metadata[:skip_gitaly_mock]
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
end
end
diff --git a/spec/support/helpers/key_generator_helper.rb b/spec/support/helpers/key_generator_helper.rb
new file mode 100644
index 00000000000..b1c289ffef7
--- /dev/null
+++ b/spec/support/helpers/key_generator_helper.rb
@@ -0,0 +1,41 @@
+module Spec
+ module Support
+ module Helpers
+ class KeyGeneratorHelper
+ # The components in a openssh .pub / known_host RSA public key.
+ RSA_COMPONENTS = ['ssh-rsa', :e, :n].freeze
+
+ attr_reader :size
+
+ def initialize(size = 2048)
+ @size = size
+ end
+
+ def generate
+ key = OpenSSL::PKey::RSA.generate(size)
+ components = RSA_COMPONENTS.map do |component|
+ key.respond_to?(component) ? encode_mpi(key.public_send(component)) : component
+ end
+
+ # Ruby tries to be helpful and adds new lines every 60 bytes :(
+ 'ssh-rsa ' + [pack_pubkey_components(components)].pack('m').delete("\n")
+ end
+
+ private
+
+ # Encodes an openssh-mpi-encoded integer.
+ def encode_mpi(n)
+ chars, n = [], n.to_i
+ chars << (n & 0xff) && n >>= 8 while n != 0
+ chars << 0 if chars.empty? || chars.last >= 0x80
+ chars.reverse.pack('C*')
+ end
+
+ # Packs string components into an openssh-encoded pubkey.
+ def pack_pubkey_components(strings)
+ (strings.map { |s| [s.length].pack('N') }).zip(strings).flatten.join
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/note_interaction_helpers.rb b/spec/support/helpers/note_interaction_helpers.rb
new file mode 100644
index 00000000000..551c759133c
--- /dev/null
+++ b/spec/support/helpers/note_interaction_helpers.rb
@@ -0,0 +1,8 @@
+module NoteInteractionHelpers
+ def open_more_actions_dropdown(note)
+ note_element = find("#note_#{note.id}")
+
+ note_element.find('.more-actions').click
+ note_element.find('.more-actions .dropdown-menu li', match: :first)
+ end
+end
diff --git a/spec/support/import_spec_helper.rb b/spec/support/import_spec_helper.rb
index 6710962f082..d4eced724fa 100644
--- a/spec/support/import_spec_helper.rb
+++ b/spec/support/import_spec_helper.rb
@@ -28,6 +28,6 @@ module ImportSpecHelper
app_id: 'asd123',
app_secret: 'asd123'
)
- allow(Gitlab.config.omniauth).to receive(:providers).and_return([provider])
+ stub_omniauth_setting(providers: [provider])
end
end
diff --git a/spec/support/issuable_shared_examples.rb b/spec/support/issuable_shared_examples.rb
new file mode 100644
index 00000000000..03011535351
--- /dev/null
+++ b/spec/support/issuable_shared_examples.rb
@@ -0,0 +1,7 @@
+shared_examples 'cache counters invalidator' do
+ it 'invalidates counter cache for assignees' do
+ expect_any_instance_of(User).to receive(:invalidate_merge_request_cache_counts)
+
+ described_class.new(project, user, {}).execute(merge_request)
+ end
+end
diff --git a/spec/support/javascript_fixtures_helpers.rb b/spec/support/javascript_fixtures_helpers.rb
index a982b159b48..aace4b3adee 100644
--- a/spec/support/javascript_fixtures_helpers.rb
+++ b/spec/support/javascript_fixtures_helpers.rb
@@ -48,7 +48,7 @@ module JavaScriptFixturesHelpers
link_tags = doc.css('link')
link_tags.remove
- scripts = doc.css("script:not([type='text/template'])")
+ scripts = doc.css("script:not([type='text/template']):not([type='text/x-template'])")
scripts.remove
fixture = doc.to_html
diff --git a/spec/support/kubernetes_helpers.rb b/spec/support/kubernetes_helpers.rb
index b5ed71ba3be..9280fad4ace 100644
--- a/spec/support/kubernetes_helpers.rb
+++ b/spec/support/kubernetes_helpers.rb
@@ -5,7 +5,7 @@ module KubernetesHelpers
{
"kind" => "APIResourceList",
"resources" => [
- { "name" => "pods", "namespaced" => true, "kind" => "Pod" },
+ { "name" => "pods", "namespaced" => true, "kind" => "Pod" }
]
}
end
@@ -22,13 +22,13 @@ module KubernetesHelpers
"metadata" => {
"name" => "kube-pod",
"creationTimestamp" => "2016-11-25T19:55:19Z",
- "labels" => { "app" => app },
+ "labels" => { "app" => app }
},
"spec" => {
"containers" => [
{ "name" => "container-0" },
- { "name" => "container-1" },
- ],
+ { "name" => "container-1" }
+ ]
},
"status" => { "phase" => "Running" }
}
@@ -41,7 +41,7 @@ module KubernetesHelpers
containers.map do |container|
terminal = {
selectors: { pod: pod_name, container: container['name'] },
- url: container_exec_url(service.api_url, service.namespace, pod_name, container['name']),
+ url: container_exec_url(service.api_url, service.actual_namespace, pod_name, container['name']),
subprotocols: ['channel.k8s.io'],
headers: { 'Authorization' => ["Bearer #{service.token}"] },
created_at: DateTime.parse(pod['metadata']['creationTimestamp']),
diff --git a/spec/support/matchers/execute_check.rb b/spec/support/matchers/execute_check.rb
new file mode 100644
index 00000000000..7232fad52fb
--- /dev/null
+++ b/spec/support/matchers/execute_check.rb
@@ -0,0 +1,23 @@
+RSpec::Matchers.define :execute_check do |expected|
+ match do |actual|
+ expect(actual).to eq(SystemCheck)
+ expect(actual).to receive(:run) do |*args|
+ expect(args[1]).to include(expected)
+ end
+ end
+
+ match_when_negated do |actual|
+ expect(actual).to eq(SystemCheck)
+ expect(actual).to receive(:run) do |*args|
+ expect(args[1]).not_to include(expected)
+ end
+ end
+
+ failure_message do |actual|
+ 'This matcher must be used with SystemCheck' unless actual == SystemCheck
+ end
+
+ failure_message_when_negated do |actual|
+ 'This matcher must be used with SystemCheck' unless actual == SystemCheck
+ end
+end
diff --git a/spec/support/matchers/gitaly_matchers.rb b/spec/support/matchers/gitaly_matchers.rb
index 65dbc01f6e4..ed14bcec9f2 100644
--- a/spec/support/matchers/gitaly_matchers.rb
+++ b/spec/support/matchers/gitaly_matchers.rb
@@ -1,3 +1,9 @@
RSpec::Matchers.define :gitaly_request_with_repo_path do |path|
match { |actual| actual.repository.path == path }
end
+
+RSpec::Matchers.define :gitaly_request_with_params do |params|
+ match do |actual|
+ params.reduce(true) { |r, (key, val)| r && actual.send(key) == val }
+ end
+end
diff --git a/spec/support/migrations_helpers.rb b/spec/support/migrations_helpers.rb
new file mode 100644
index 00000000000..91fbb4eaf48
--- /dev/null
+++ b/spec/support/migrations_helpers.rb
@@ -0,0 +1,29 @@
+module MigrationsHelpers
+ def table(name)
+ Class.new(ActiveRecord::Base) { self.table_name = name }
+ end
+
+ def migrations_paths
+ ActiveRecord::Migrator.migrations_paths
+ end
+
+ def table_exists?(name)
+ ActiveRecord::Base.connection.table_exists?(name)
+ end
+
+ def migrations
+ ActiveRecord::Migrator.migrations(migrations_paths)
+ end
+
+ def previous_migration
+ migrations.each_cons(2) do |previous, migration|
+ break previous if migration.name == described_class.name
+ end
+ end
+
+ def migrate!
+ ActiveRecord::Migrator.up(migrations_paths) do |migration|
+ migration.name == described_class.name
+ end
+ end
+end
diff --git a/spec/support/prometheus_helpers.rb b/spec/support/prometheus_helpers.rb
index 51987c7767d..55c11abe3f7 100644
--- a/spec/support/prometheus_helpers.rb
+++ b/spec/support/prometheus_helpers.rb
@@ -1,10 +1,16 @@
module PrometheusHelpers
def prometheus_memory_query(environment_slug)
- %{(sum(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / count(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"})) /1024/1024}
+ %{avg(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / 2^20}
end
def prometheus_cpu_query(environment_slug)
- %{sum(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}) * 100}
+ %{avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) * 100}
+ end
+
+ def prometheus_ping_url(prometheus_query)
+ query = { query: prometheus_query }.to_query
+
+ "https://prometheus.example.com/api/v1/query?#{query}"
end
def prometheus_ping_url(prometheus_query)
diff --git a/spec/features/protected_branches/access_control_ce_spec.rb b/spec/support/protected_branches/access_control_ce_shared_examples.rb
index d30e7947106..287d6bb13c3 100644
--- a/spec/features/protected_branches/access_control_ce_spec.rb
+++ b/spec/support/protected_branches/access_control_ce_shared_examples.rb
@@ -31,14 +31,14 @@ RSpec.shared_examples "protected branches > access control > CE" do
within(".protected-branches-list") do
find(".js-allowed-to-push").click
-
+
within('.js-allowed-to-push-container') do
expect(first("li")).to have_content("Roles")
click_on access_type_name
end
end
- wait_for_ajax
+ wait_for_requests
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(access_type_id)
end
@@ -83,7 +83,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
end
end
- wait_for_ajax
+ wait_for_requests
expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to include(access_type_id)
end
diff --git a/spec/features/protected_tags/access_control_ce_spec.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb
index 12622cd548a..1d11512ef82 100644
--- a/spec/features/protected_tags/access_control_ce_spec.rb
+++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb
@@ -39,7 +39,7 @@ RSpec.shared_examples "protected tags > access control > CE" do
end
end
- wait_for_ajax
+ wait_for_requests
expect(ProtectedTag.last.create_access_levels.map(&:access_level)).to include(access_type_id)
end
diff --git a/spec/support/rake_helpers.rb b/spec/support/rake_helpers.rb
index 4a8158ed79b..5cb415111d2 100644
--- a/spec/support/rake_helpers.rb
+++ b/spec/support/rake_helpers.rb
@@ -7,4 +7,9 @@ module RakeHelpers
def stub_warn_user_is_not_gitlab
allow_any_instance_of(Object).to receive(:warn_user_is_not_gitlab)
end
+
+ def silence_output
+ allow($stdout).to receive(:puts)
+ allow($stdout).to receive(:print)
+ end
end
diff --git a/spec/support/repo_helpers.rb b/spec/support/repo_helpers.rb
index e9d5c7b12ae..3c6956cf5e0 100644
--- a/spec/support/repo_helpers.rb
+++ b/spec/support/repo_helpers.rb
@@ -92,11 +92,11 @@ eos
changes = [
{
line_code: 'a5cc2925ca8258af241be7e5b0381edf30266302_20_20',
- file_path: '.gitignore',
+ file_path: '.gitignore'
},
{
line_code: '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44_4_6',
- file_path: '.gitmodules',
+ file_path: '.gitmodules'
}
]
diff --git a/spec/support/seed_repo.rb b/spec/support/seed_repo.rb
index 99a500bbbb1..cfe7fc980a8 100644
--- a/spec/support/seed_repo.rb
+++ b/spec/support/seed_repo.rb
@@ -1,4 +1,8 @@
+# This file is generated by generate-seed-repo-rb. Do not edit this file manually.
+#
# Seed repo:
+# 4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6 Merge branch 'master' into 'master'
+# 0e1b353b348f8477bdbec1ef47087171c5032cd9 adds an executable with different permissions
# 0e50ec4d3c7ce42ab74dda1d422cb2cbffe1e326 Merge branch 'lfs_pointers' into 'master'
# 33bcff41c232a11727ac6d660bd4b0c2ba86d63d Add valid and invalid lfs pointers
# 732401c65e924df81435deb12891ef570167d2e2 Update year in license file
@@ -94,7 +98,12 @@ module SeedRepo
master
merge-test
].freeze
- TAGS = %w[v1.0.0 v1.1.0 v1.2.0 v1.2.1].freeze
+ TAGS = %w[
+ v1.0.0
+ v1.1.0
+ v1.2.0
+ v1.2.1
+ ].freeze
end
module RubyBlob
diff --git a/spec/support/snippets_shared_examples.rb b/spec/support/snippets_shared_examples.rb
index 57dfff3471f..85f0facd5c3 100644
--- a/spec/support/snippets_shared_examples.rb
+++ b/spec/support/snippets_shared_examples.rb
@@ -7,7 +7,7 @@ RSpec.shared_examples 'paginated snippets' do |remote: false|
context 'clicking on the link to the second page' do
before do
click_link('2')
- wait_for_ajax if remote
+ wait_for_requests if remote
end
it 'shows the remaining snippets' do
diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb
index 444adcc1906..b39a23bd18a 100644
--- a/spec/support/stub_configuration.rb
+++ b/spec/support/stub_configuration.rb
@@ -25,6 +25,10 @@ module StubConfiguration
allow(Gitlab.config.mattermost).to receive_messages(messages)
end
+ def stub_omniauth_setting(messages)
+ allow(Gitlab.config.omniauth).to receive_messages(messages)
+ end
+
private
# Modifies stubbed messages to also stub possible predicate versions
diff --git a/spec/support/target_branch_helpers.rb b/spec/support/target_branch_helpers.rb
index 3ee8f0f657e..01d1c53fe6c 100644
--- a/spec/support/target_branch_helpers.rb
+++ b/spec/support/target_branch_helpers.rb
@@ -1,7 +1,7 @@
module TargetBranchHelpers
def select_branch(name)
first('button.js-target-branch').click
- wait_for_ajax
+ wait_for_requests
all('a[data-group="Branches"]').find do |el|
el.text == name
end.click
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 9bf9dc5d4b2..3f472e59c49 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -40,8 +40,8 @@ module TestEnv
'wip' => 'b9238ee',
'csv' => '3dd0896',
'v1.1.0' => 'b83d6e3',
- 'add-ipython-files' => '6d85bb69',
- 'add-pdf-file' => 'e774ebd3'
+ 'add-ipython-files' => '93ee732',
+ 'add-pdf-file' => 'e774ebd'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
@@ -54,6 +54,8 @@ module TestEnv
'conflict-resolvable-fork' => '404fa3f'
}.freeze
+ TMP_TEST_PATH = Rails.root.join('tmp', 'tests', '**')
+
# Test environment
#
# See gitlab.yml.example test section for paths
@@ -98,9 +100,7 @@ module TestEnv
#
# Keeps gitlab-shell and gitlab-test
def clean_test_path
- tmp_test_path = Rails.root.join('tmp', 'tests', '**')
-
- Dir[tmp_test_path].each do |entry|
+ Dir[TMP_TEST_PATH].each do |entry|
unless File.basename(entry) =~ /\A(gitaly|gitlab-(shell|test|test_bare|test-fork|test-fork_bare))\z/
FileUtils.rm_rf(entry)
end
@@ -111,6 +111,14 @@ module TestEnv
FileUtils.mkdir_p(pages_path)
end
+ def clean_gitlab_test_path
+ Dir[TMP_TEST_PATH].each do |entry|
+ if File.basename(entry) =~ /\A(gitlab-(test|test_bare|test-fork|test-fork_bare))\z/
+ FileUtils.rm_rf(entry)
+ end
+ end
+ end
+
def setup_gitlab_shell
unless File.directory?(Gitlab.config.gitlab_shell.path)
unless system('rake', 'gitlab:shell:install')
@@ -123,7 +131,7 @@ module TestEnv
socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '')
gitaly_dir = File.dirname(socket_path)
- unless File.directory?(gitaly_dir) || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")
+ unless !gitaly_needs_update?(gitaly_dir) || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")
raise "Can't clone gitaly"
end
@@ -155,14 +163,14 @@ module TestEnv
FORKED_BRANCH_SHA)
end
- def setup_repo(repo_path, repo_path_bare, repo_name, branch_sha)
+ def setup_repo(repo_path, repo_path_bare, repo_name, refs)
clone_url = "https://gitlab.com/gitlab-org/#{repo_name}.git"
unless File.directory?(repo_path)
system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path}))
end
- set_repo_refs(repo_path, branch_sha)
+ set_repo_refs(repo_path, refs)
unless File.directory?(repo_path_bare)
# We must copy bare repositories because we will push to them.
@@ -170,13 +178,12 @@ module TestEnv
end
end
- def copy_repo(project)
- base_repo_path = File.expand_path(factory_repo_path_bare)
+ def copy_repo(project, bare_repo:, refs:)
target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.full_path}.git")
FileUtils.mkdir_p(target_repo_path)
- FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
+ FileUtils.cp_r("#{File.expand_path(bare_repo)}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
- set_repo_refs(target_repo_path, BRANCH_SHA)
+ set_repo_refs(target_repo_path, refs)
end
def repos_path
@@ -191,15 +198,6 @@ module TestEnv
Gitlab.config.pages.path
end
- def copy_forked_repo_with_submodules(project)
- base_repo_path = File.expand_path(forked_repo_path_bare)
- target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.full_path}.git")
- FileUtils.mkdir_p(target_repo_path)
- FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
- FileUtils.chmod_R 0755, target_repo_path
- set_repo_refs(target_repo_path, FORKED_BRANCH_SHA)
- end
-
# When no cached assets exist, manually hit the root path to create them
#
# Otherwise they'd be created by the first test, often timing out and
@@ -211,16 +209,20 @@ module TestEnv
Capybara.current_session.visit '/'
end
+ def factory_repo_path_bare
+ "#{factory_repo_path}_bare"
+ end
+
+ def forked_repo_path_bare
+ "#{forked_repo_path}_bare"
+ end
+
private
def factory_repo_path
@factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name)
end
- def factory_repo_path_bare
- "#{factory_repo_path}_bare"
- end
-
def factory_repo_name
'gitlab-test'
end
@@ -229,10 +231,6 @@ module TestEnv
@forked_repo_path ||= Rails.root.join('tmp', 'tests', forked_repo_name)
end
- def forked_repo_path_bare
- "#{forked_repo_path}_bare"
- end
-
def forked_repo_name
'gitlab-test-fork'
end
@@ -244,19 +242,33 @@ module TestEnv
end
def set_repo_refs(repo_path, branch_sha)
- instructions = branch_sha.map {|branch, sha| "update refs/heads/#{branch}\x00#{sha}\x00" }.join("\x00") << "\x00"
+ instructions = branch_sha.map { |branch, sha| "update refs/heads/#{branch}\x00#{sha}\x00" }.join("\x00") << "\x00"
update_refs = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z)
reset = proc do
- IO.popen(update_refs, "w") {|io| io.write(instructions) }
- $?.success?
+ Dir.chdir(repo_path) do
+ IO.popen(update_refs, "w") { |io| io.write(instructions) }
+ $?.success?
+ end
end
- Dir.chdir(repo_path) do
- # Try to reset without fetching to avoid using the network.
- unless reset.call
- raise 'Could not fetch test seed repository.' unless system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
- raise 'The fetched test seed does not contain the required revision.' unless reset.call
- end
+ # Try to reset without fetching to avoid using the network.
+ unless reset.call
+ raise 'Could not fetch test seed repository.' unless system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} fetch origin))
+
+ # Before we used Git clone's --mirror option, bare repos could end up
+ # with missing refs, clearing them and retrying should fix the issue.
+ cleanup && clean_gitlab_test_path && init unless reset.call
end
end
+
+ def gitaly_needs_update?(gitaly_dir)
+ gitaly_version = File.read(File.join(gitaly_dir, 'VERSION')).strip
+
+ # Notice that this will always yield true when using branch versions
+ # (`=branch_name`), but that actually makes sure the server is always based
+ # on the latest branch revision.
+ gitaly_version != Gitlab::GitalyClient.expected_server_version
+ rescue Errno::ENOENT
+ true
+ end
end
diff --git a/spec/support/time_tracking_shared_examples.rb b/spec/support/time_tracking_shared_examples.rb
index 84ef46ffa27..b407b8097d2 100644
--- a/spec/support/time_tracking_shared_examples.rb
+++ b/spec/support/time_tracking_shared_examples.rb
@@ -8,7 +8,7 @@ shared_examples 'issuable time tracker' do
it 'updates the sidebar component when estimate is added' do
submit_time('/estimate 3w 1d 1h')
- wait_for_ajax
+ wait_for_requests
page.within '.time-tracking-estimate-only-pane' do
expect(page).to have_content '3w 1d 1h'
end
@@ -17,7 +17,7 @@ shared_examples 'issuable time tracker' do
it 'updates the sidebar component when spent is added' do
submit_time('/spend 3w 1d 1h')
- wait_for_ajax
+ wait_for_requests
page.within '.time-tracking-spend-only-pane' do
expect(page).to have_content '3w 1d 1h'
end
@@ -27,7 +27,7 @@ shared_examples 'issuable time tracker' do
submit_time('/estimate 3w 1d 1h')
submit_time('/spend 3w 1d 1h')
- wait_for_ajax
+ wait_for_requests
page.within '.time-tracking-comparison-pane' do
expect(page).to have_content '3w 1d 1h'
end
@@ -81,5 +81,5 @@ end
def submit_time(slash_command)
fill_in 'note[note]', with: slash_command
find('.js-comment-submit-button').trigger('click')
- wait_for_ajax
+ wait_for_requests
end
diff --git a/spec/support/wait_for_ajax.rb b/spec/support/wait_for_ajax.rb
deleted file mode 100644
index 508de2ee8e1..00000000000
--- a/spec/support/wait_for_ajax.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-module WaitForAjax
- def wait_for_ajax
- Timeout.timeout(Capybara.default_max_wait_time) do
- loop until finished_all_ajax_requests?
- end
- end
-
- def finished_all_ajax_requests?
- return true unless javascript_test?
- return true if page.evaluate_script('typeof jQuery === "undefined"')
-
- page.evaluate_script('jQuery.active').zero?
- end
-
- def javascript_test?
- Capybara.current_driver == Capybara.javascript_driver
- end
-end
diff --git a/spec/support/wait_for_requests.rb b/spec/support/wait_for_requests.rb
index d41e83ae128..05ec9026141 100644
--- a/spec/support/wait_for_requests.rb
+++ b/spec/support/wait_for_requests.rb
@@ -1,21 +1,31 @@
-require_relative './wait_for_ajax'
-require_relative './wait_for_vue_resource'
+require_relative './wait_for_requests'
module WaitForRequests
extend self
- include WaitForAjax
- include WaitForVueResource
# This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests
- def wait_for_requests_complete
+ def block_and_wait_for_requests_complete
Gitlab::Testing::RequestBlockerMiddleware.block_requests!
- wait_for('pending AJAX requests complete') do
+ wait_for('pending requests complete') do
Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero?
end
ensure
Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
end
+ def wait_for_requests
+ wait_for('JS requests') { finished_all_requests? }
+ end
+
+ private
+
+ def finished_all_requests?
+ return true unless javascript_test?
+
+ finished_all_ajax_requests? &&
+ finished_all_vue_resource_requests?
+ end
+
# Waits until the passed block returns true
def wait_for(condition_name, max_wait_time: Capybara.default_max_wait_time, polling_interval: 0.01)
wait_until = Time.now + max_wait_time.seconds
@@ -28,10 +38,24 @@ module WaitForRequests
end
end
end
+
+ def finished_all_vue_resource_requests?
+ page.evaluate_script('window.activeVueResources || 0').zero?
+ end
+
+ def finished_all_ajax_requests?
+ return true if page.evaluate_script('typeof jQuery === "undefined"')
+
+ page.evaluate_script('jQuery.active').zero?
+ end
+
+ def javascript_test?
+ Capybara.current_driver == Capybara.javascript_driver
+ end
end
RSpec.configure do |config|
config.after(:each, :js) do
- wait_for_requests_complete
+ block_and_wait_for_requests_complete
end
end
diff --git a/spec/support/workhorse_helpers.rb b/spec/support/workhorse_helpers.rb
index 47673cd4c3a..ef1f9f68671 100644
--- a/spec/support/workhorse_helpers.rb
+++ b/spec/support/workhorse_helpers.rb
@@ -9,7 +9,7 @@ module WorkhorseHelpers
header = split_header.join(':')
[
type,
- JSON.parse(Base64.urlsafe_decode64(header)),
+ JSON.parse(Base64.urlsafe_decode64(header))
]
end
end
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index aaf998a546f..4a636decafd 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -80,28 +80,28 @@ describe 'gitlab:gitaly namespace rake task' do
it 'prints storage configuration in a TOML format' do
config = {
'default' => { 'path' => '/path/to/default' },
- 'nfs_01' => { 'path' => '/path/to/nfs_01' },
+ 'nfs_01' => { 'path' => '/path/to/nfs_01' }
}
allow(Gitlab.config.repositories).to receive(:storages).and_return(config)
- orig_stdout = $stdout
- $stdout = StringIO.new
-
- header = ''
+ expected_output = ''
Timecop.freeze do
- header = <<~TOML
+ expected_output = <<~TOML
# Gitaly storage configuration generated from #{Gitlab.config.source} on #{Time.current.to_s(:long)}
# This is in TOML format suitable for use in Gitaly's config.toml file.
+ [[storage]]
+ name = "default"
+ path = "/path/to/default"
+ [[storage]]
+ name = "nfs_01"
+ path = "/path/to/nfs_01"
TOML
- run_rake_task('gitlab:gitaly:storage_config')
end
- output = $stdout.string
- $stdout = orig_stdout
-
- expect(output).to include(header)
+ expect { run_rake_task('gitlab:gitaly:storage_config')}.
+ to output(expected_output).to_stdout
- parsed_output = TOML.parse(output)
+ parsed_output = TOML.parse(expected_output)
config.each do |name, params|
expect(parsed_output['storage']).to include({ 'name' => name, 'path' => params['path'] })
end
diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb
index 19036c7677c..b84137eb365 100644
--- a/spec/tasks/tokens_spec.rb
+++ b/spec/tasks/tokens_spec.rb
@@ -18,4 +18,10 @@ describe 'tokens rake tasks' do
expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token }
end
end
+
+ describe 'reset_all_rss task' do
+ it 'invokes create_hooks task' do
+ expect { run_rake_task('tokens:reset_all_rss') }.to change { user.reload.rss_token }
+ end
+ end
end
diff --git a/spec/uploaders/artifact_uploader_spec.rb b/spec/uploaders/artifact_uploader_spec.rb
new file mode 100644
index 00000000000..24e2e3a9f0e
--- /dev/null
+++ b/spec/uploaders/artifact_uploader_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+describe ArtifactUploader do
+ let(:job) { create(:ci_build) }
+ let(:uploader) { described_class.new(job, :artifacts_file) }
+ let(:path) { Gitlab.config.artifacts.path }
+
+ describe '.local_artifacts_store' do
+ subject { described_class.local_artifacts_store }
+
+ it "delegate to artifacts path" do
+ expect(Gitlab.config.artifacts).to receive(:path)
+
+ subject
+ end
+ end
+
+ describe '.artifacts_upload_path' do
+ subject { described_class.artifacts_upload_path }
+
+ it { is_expected.to start_with(path) }
+ it { is_expected.to end_with('tmp/uploads/') }
+ end
+
+ describe '#store_dir' do
+ subject { uploader.store_dir }
+
+ it { is_expected.to start_with(path) }
+ it { is_expected.to end_with("#{job.project_id}/#{job.id}") }
+ end
+
+ describe '#cache_dir' do
+ subject { uploader.cache_dir }
+
+ it { is_expected.to start_with(path) }
+ it { is_expected.to end_with('tmp/cache') }
+ end
+end
diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb
new file mode 100644
index 00000000000..896cb410ed5
--- /dev/null
+++ b/spec/uploaders/file_mover_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe FileMover do
+ let(:filename) { 'banana_sample.gif' }
+ let(:file) { fixture_file_upload(Rails.root.join('spec', 'fixtures', filename)) }
+ let(:temp_description) do
+ 'test ![banana_sample](/uploads/temp/secret55/banana_sample.gif) same ![banana_sample]'\
+ '(/uploads/temp/secret55/banana_sample.gif)'
+ end
+ let(:temp_file_path) { File.join('secret55', filename).to_s }
+ let(:file_path) { File.join('uploads', 'personal_snippet', snippet.id.to_s, 'secret55', filename).to_s }
+
+ let(:snippet) { create(:personal_snippet, description: temp_description) }
+
+ subject { described_class.new(file_path, snippet).execute }
+
+ describe '#execute' do
+ before do
+ expect(FileUtils).to receive(:mkdir_p).with(a_string_including(File.dirname(file_path)))
+ expect(FileUtils).to receive(:move).with(a_string_including(temp_file_path), a_string_including(file_path))
+ allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:exists?).and_return(true)
+ allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:size).and_return(10)
+ end
+
+ context 'when move and field update successful' do
+ it 'updates the description correctly' do
+ subject
+
+ expect(snippet.reload.description)
+ .to eq(
+ "test ![banana_sample](/uploads/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"\
+ " same ![banana_sample](/uploads/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"
+ )
+ end
+
+ it 'creates a new update record' do
+ expect { subject }.to change { Upload.count }.by(1)
+ end
+ end
+
+ context 'when update_markdown fails' do
+ before do
+ expect(FileUtils).to receive(:move).with(a_string_including(file_path), a_string_including(temp_file_path))
+ end
+
+ subject { described_class.new(file_path, snippet, :non_existing_field).execute }
+
+ it 'does not update the description' do
+ subject
+
+ expect(snippet.reload.description)
+ .to eq(
+ "test ![banana_sample](/uploads/temp/secret55/banana_sample.gif)"\
+ " same ![banana_sample](/uploads/temp/secret55/banana_sample.gif)"
+ )
+ end
+
+ it 'does not create a new update record' do
+ expect { subject }.not_to change { Upload.count }
+ end
+ end
+ end
+end
diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb
new file mode 100644
index 00000000000..78e9d9cf46c
--- /dev/null
+++ b/spec/uploaders/gitlab_uploader_spec.rb
@@ -0,0 +1,56 @@
+require 'rails_helper'
+require 'carrierwave/storage/fog'
+
+describe GitlabUploader do
+ let(:uploader_class) { Class.new(described_class) }
+
+ subject { uploader_class.new }
+
+ describe '#file_storage?' do
+ context 'when file storage is used' do
+ before do
+ uploader_class.storage(:file)
+ end
+
+ it { is_expected.to be_file_storage }
+ end
+
+ context 'when is remote storage' do
+ before do
+ uploader_class.storage(:fog)
+ end
+
+ it { is_expected.not_to be_file_storage }
+ end
+ end
+
+ describe '#file_cache_storage?' do
+ context 'when file storage is used' do
+ before do
+ uploader_class.cache_storage(:file)
+ end
+
+ it { is_expected.to be_file_cache_storage }
+ end
+
+ context 'when is remote storage' do
+ before do
+ uploader_class.cache_storage(:fog)
+ end
+
+ it { is_expected.not_to be_file_cache_storage }
+ end
+ end
+
+ describe '#move_to_cache' do
+ it 'is true' do
+ expect(subject.move_to_cache).to eq(true)
+ end
+ end
+
+ describe '#move_to_store' do
+ it 'is true' do
+ expect(subject.move_to_store).to eq(true)
+ end
+ end
+end
diff --git a/spec/uploaders/lfs_object_uploader_spec.rb b/spec/uploaders/lfs_object_uploader_spec.rb
new file mode 100644
index 00000000000..c3b72e7d677
--- /dev/null
+++ b/spec/uploaders/lfs_object_uploader_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe LfsObjectUploader do
+ let(:uploader) { described_class.new(build_stubbed(:empty_project)) }
+
+ describe '#cache!' do
+ it 'caches the file in the cache directory' do
+ # One to get the work dir, the other to remove it
+ expect(uploader).to receive(:workfile_path).exactly(2).times.and_call_original
+ expect(FileUtils).to receive(:mv).with(anything, /^#{uploader.work_dir}/).and_call_original
+ expect(FileUtils).to receive(:mv).with(/^#{uploader.work_dir}/, /^#{uploader.cache_dir}/).and_call_original
+
+ fixture = Rails.root.join('spec', 'fixtures', 'rails_sample.jpg')
+ uploader.cache!(fixture_file_upload(fixture))
+
+ expect(uploader.file.path).to start_with(uploader.cache_dir)
+ end
+ end
+
+ describe '#move_to_cache' do
+ it 'is true' do
+ expect(uploader.move_to_cache).to eq(true)
+ end
+ end
+
+ describe '#move_to_store' do
+ it 'is true' do
+ expect(uploader.move_to_store).to eq(true)
+ end
+ end
+end
diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb
index 5c26e334a6e..bb32ee62ccb 100644
--- a/spec/uploaders/records_uploads_spec.rb
+++ b/spec/uploaders/records_uploads_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
describe RecordsUploads do
- let(:uploader) do
+ let!(:uploader) do
class RecordsUploadsExampleUploader < GitlabUploader
include RecordsUploads
@@ -57,6 +57,13 @@ describe RecordsUploads do
uploader.store!(upload_fixture('rails_sample.jpg'))
end
+ it 'does not create an Upload record if model is missing' do
+ expect_any_instance_of(RecordsUploadsExampleUploader).to receive(:model).and_return(nil)
+ expect(Upload).not_to receive(:record).with(uploader)
+
+ uploader.store!(upload_fixture('rails_sample.jpg'))
+ end
+
it 'it destroys Upload records at the same path before recording' do
existing = Upload.create!(
path: File.join('uploads', 'rails_sample.jpg'),
diff --git a/spec/validators/dynamic_path_validator_spec.rb b/spec/validators/dynamic_path_validator_spec.rb
index 03e23781d1b..8acd2743f2c 100644
--- a/spec/validators/dynamic_path_validator_spec.rb
+++ b/spec/validators/dynamic_path_validator_spec.rb
@@ -3,6 +3,27 @@ require 'spec_helper'
describe DynamicPathValidator do
let(:validator) { described_class.new(attributes: [:path]) }
+ def expect_handles_invalid_utf8
+ expect { yield('\255invalid') }.to be_falsey
+ end
+
+ describe '.valid_user_path' do
+ it 'handles invalid utf8' do
+ expect(described_class.valid_user_path?("a\0weird\255path")).to be_falsey
+ end
+ end
+
+ describe '.valid_group_path' do
+ it 'handles invalid utf8' do
+ expect(described_class.valid_group_path?("a\0weird\255path")).to be_falsey
+ end
+ end
+
+ describe '.valid_project_path' do
+ it 'handles invalid utf8' do
+ expect(described_class.valid_project_path?("a\0weird\255path")).to be_falsey
+ end
+
describe '#path_valid_for_record?' do
context 'for project' do
it 'calls valid_project_path?' do
@@ -15,31 +36,31 @@ describe DynamicPathValidator do
end
context 'for group' do
- it 'calls valid_namespace_path?' do
+ it 'calls valid_group_path?' do
group = build(:group, :nested, path: 'activity')
- expect(described_class).to receive(:valid_namespace_path?).with(group.full_path).and_call_original
+ expect(described_class).to receive(:valid_group_path?).with(group.full_path).and_call_original
expect(validator.path_valid_for_record?(group, 'activity')).to be_falsey
end
end
context 'for user' do
- it 'calls valid_namespace_path?' do
+ it 'calls valid_user_path?' do
user = build(:user, username: 'activity')
- expect(described_class).to receive(:valid_namespace_path?).with(user.full_path).and_call_original
+ expect(described_class).to receive(:valid_user_path?).with(user.full_path).and_call_original
expect(validator.path_valid_for_record?(user, 'activity')).to be_truthy
end
end
context 'for user namespace' do
- it 'calls valid_namespace_path?' do
+ it 'calls valid_user_path?' do
user = create(:user, username: 'activity')
namespace = user.namespace
- expect(described_class).to receive(:valid_namespace_path?).with(namespace.full_path).and_call_original
+ expect(described_class).to receive(:valid_user_path?).with(namespace.full_path).and_call_original
expect(validator.path_valid_for_record?(namespace, 'activity')).to be_truthy
end
@@ -52,7 +73,7 @@ describe DynamicPathValidator do
validator.validate_each(group, :path, "Path with spaces, and comma's!")
- expect(group.errors[:path]).to include(Gitlab::Regex.namespace_regex_message)
+ expect(group.errors[:path]).to include(Gitlab::PathRegex.namespace_format_message)
end
it 'adds a message when the path is not in the correct format' do
diff --git a/spec/views/ci/status/_badge.html.haml_spec.rb b/spec/views/ci/status/_badge.html.haml_spec.rb
index c62450fb8e2..72323da2838 100644
--- a/spec/views/ci/status/_badge.html.haml_spec.rb
+++ b/spec/views/ci/status/_badge.html.haml_spec.rb
@@ -16,7 +16,7 @@ describe 'ci/status/_badge', :view do
end
it 'has link to build details page' do
- details_path = namespace_project_build_path(
+ details_path = namespace_project_job_path(
project.namespace, project, build)
render_status(build)
diff --git a/spec/views/projects/_last_commit.html.haml_spec.rb b/spec/views/projects/_last_commit.html.haml_spec.rb
deleted file mode 100644
index eea1695b171..00000000000
--- a/spec/views/projects/_last_commit.html.haml_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'spec_helper'
-
-describe 'projects/_last_commit', :view do
- let(:project) { create(:project, :repository) }
-
- context 'when there is a pipeline present for the commit' do
- context 'when pipeline is blocked' do
- let!(:pipeline) do
- create(:ci_pipeline, :blocked, project: project,
- sha: project.commit.id)
- end
-
- it 'shows correct pipeline badge' do
- render 'projects/last_commit', commit: project.commit,
- project: project,
- ref: :master
-
- expect(rendered).to have_text "blocked #{project.commit.short_id}"
- end
- end
- end
-end
diff --git a/spec/views/projects/blob/_viewer.html.haml_spec.rb b/spec/views/projects/blob/_viewer.html.haml_spec.rb
index 501f90c5f9a..bbd7f98fa8d 100644
--- a/spec/views/projects/blob/_viewer.html.haml_spec.rb
+++ b/spec/views/projects/blob/_viewer.html.haml_spec.rb
@@ -10,9 +10,9 @@ describe 'projects/blob/_viewer.html.haml', :view do
include BlobViewer::Rich
self.partial_name = 'text'
- self.max_size = 1.megabyte
- self.absolute_max_size = 5.megabytes
- self.client_side = false
+ self.collapse_limit = 1.megabyte
+ self.size_limit = 5.megabytes
+ self.load_async = true
end
end
@@ -35,9 +35,9 @@ describe 'projects/blob/_viewer.html.haml', :view do
render partial: 'projects/blob/viewer', locals: { viewer: viewer }
end
- context 'when the viewer is server side' do
+ context 'when the viewer is loaded asynchronously' do
before do
- viewer_class.client_side = false
+ viewer_class.load_async = true
end
context 'when there is no render error' do
@@ -47,10 +47,10 @@ describe 'projects/blob/_viewer.html.haml', :view do
expect(rendered).to have_css('.blob-viewer[data-url]')
end
- it 'displays a spinner' do
+ it 'renders the loading indicator' do
render_view
- expect(rendered).to have_css('i[aria-label="Loading content"]')
+ expect(view).to render_template('projects/blob/viewers/_loading')
end
end
@@ -65,9 +65,9 @@ describe 'projects/blob/_viewer.html.haml', :view do
end
end
- context 'when the viewer is client side' do
+ context 'when the viewer is loaded synchronously' do
before do
- viewer_class.client_side = true
+ viewer_class.load_async = false
end
context 'when there is no render error' do
diff --git a/spec/views/projects/builds/_build.html.haml_spec.rb b/spec/views/projects/jobs/_build.html.haml_spec.rb
index 751482cac42..1d58891036e 100644
--- a/spec/views/projects/builds/_build.html.haml_spec.rb
+++ b/spec/views/projects/jobs/_build.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/ci/builds/_build' do
+describe 'projects/ci/jobs/_build' do
include Devise::Test::ControllerHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/builds/_generic_commit_status.html.haml_spec.rb b/spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb
index dc2ffc9dc47..dc2ffc9dc47 100644
--- a/spec/views/projects/builds/_generic_commit_status.html.haml_spec.rb
+++ b/spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb
diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb
index 0f39df0f250..8f2822f5dc5 100644
--- a/spec/views/projects/builds/show.html.haml_spec.rb
+++ b/spec/views/projects/jobs/show.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/builds/show', :view do
+describe 'projects/jobs/show', :view do
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, pipeline: pipeline) }
@@ -278,7 +278,7 @@ describe 'projects/builds/show', :view do
it 'links to issues/new with the title and description filled in' do
title = "Build Failed ##{build.id}"
- build_url = namespace_project_build_url(project.namespace, project, build)
+ build_url = namespace_project_job_url(project.namespace, project, build)
href = new_namespace_project_issue_path(
project.namespace,
project,
diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb
index 900f8d4732f..33eba3e6d3d 100644
--- a/spec/views/projects/tree/show.html.haml_spec.rb
+++ b/spec/views/projects/tree/show.html.haml_spec.rb
@@ -21,17 +21,17 @@ describe 'projects/tree/show' do
let(:tree) { repository.tree(commit.id, path) }
before do
+ assign(:id, File.join(ref, path))
assign(:ref, ref)
- assign(:commit, commit)
- assign(:id, commit.id)
- assign(:tree, tree)
assign(:path, path)
+ assign(:last_commit, commit)
+ assign(:tree, tree)
end
it 'displays correctly' do
render
expect(rendered).to have_css('.js-project-refs-dropdown .dropdown-toggle-text', text: ref)
- expect(rendered).to have_css('.readme-holder .file-content', text: ref)
+ expect(rendered).to have_css('.readme-holder')
end
end
end
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 7a590f64e3c..8c5303b61cc 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -105,7 +105,7 @@ describe GitGarbageCollectWorker do
author: Gitlab::Git.committer_hash(email: 'foo@bar', name: 'baz'),
committer: Gitlab::Git.committer_hash(email: 'foo@bar', name: 'baz'),
tree: old_commit.tree,
- parents: [old_commit],
+ parents: [old_commit]
)
GitOperationService.new(nil, project.repository).send(
:update_ref,
diff --git a/spec/workers/gitlab_usage_ping_worker_spec.rb b/spec/workers/gitlab_usage_ping_worker_spec.rb
index 26241044533..49b4e04dc7c 100644
--- a/spec/workers/gitlab_usage_ping_worker_spec.rb
+++ b/spec/workers/gitlab_usage_ping_worker_spec.rb
@@ -3,21 +3,11 @@ require 'spec_helper'
describe GitlabUsagePingWorker do
subject { described_class.new }
- it "sends POST request" do
- stub_application_setting(usage_ping_enabled: true)
+ it 'delegates to SubmitUsagePingService' do
+ allow(subject).to receive(:try_obtain_lease).and_return(true)
- stub_request(:post, "https://version.gitlab.com/usage_data").
- to_return(status: 200, body: '', headers: {})
- expect(Gitlab::UsageData).to receive(:to_json).with({ force_refresh: true }).and_call_original
- expect(subject).to receive(:try_obtain_lease).and_return(true)
+ expect_any_instance_of(SubmitUsagePingService).to receive(:execute)
- expect(subject.perform.response.code.to_i).to eq(200)
- end
-
- it "does not run if usage ping is disabled" do
- stub_application_setting(usage_ping_enabled: false)
-
- expect(subject).not_to receive(:try_obtain_lease)
- expect(subject).not_to receive(:perform)
+ subject.perform
end
end
diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb
new file mode 100644
index 00000000000..8533b7b85e9
--- /dev/null
+++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe NamespacelessProjectDestroyWorker do
+ subject { described_class.new }
+
+ before do
+ # Stub after_save callbacks that will fail when Project has no namespace
+ allow_any_instance_of(Project).to receive(:ensure_dir_exist).and_return(nil)
+ allow_any_instance_of(Project).to receive(:update_project_statistics).and_return(nil)
+ end
+
+ describe '#perform' do
+ context 'project has namespace' do
+ it 'does not do anything' do
+ project = create(:empty_project)
+
+ subject.perform(project.id)
+
+ expect(Project.unscoped.all).to include(project)
+ end
+ end
+
+ context 'project has no namespace' do
+ let!(:project) do
+ project = build(:empty_project, namespace_id: nil)
+ project.save(validate: false)
+ project
+ end
+
+ context 'project not a fork of another project' do
+ it "truncates the project's team" do
+ expect_any_instance_of(ProjectTeam).to receive(:truncate)
+
+ subject.perform(project.id)
+ end
+
+ it 'deletes the project' do
+ subject.perform(project.id)
+
+ expect(Project.unscoped.all).not_to include(project)
+ end
+
+ it 'does not call unlink_fork' do
+ is_expected.not_to receive(:unlink_fork)
+
+ subject.perform(project.id)
+ end
+
+ it 'does not do anything in Project#remove_pages method' do
+ expect(Gitlab::PagesTransfer).not_to receive(:new)
+
+ subject.perform(project.id)
+ end
+ end
+
+ context 'project forked from another' do
+ let!(:parent_project) { create(:empty_project) }
+
+ before do
+ create(:forked_project_link, forked_to_project: project, forked_from_project: parent_project)
+ end
+
+ it 'closes open merge requests' do
+ merge_request = create(:merge_request, source_project: project, target_project: parent_project)
+
+ subject.perform(project.id)
+
+ expect(merge_request.reload).to be_closed
+ end
+
+ it 'destroys the link' do
+ subject.perform(project.id)
+
+ expect(parent_project.forked_project_links).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb
index 5dbc0da95c2..ef71125c0b6 100644
--- a/spec/workers/pipeline_metrics_worker_spec.rb
+++ b/spec/workers/pipeline_metrics_worker_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe PipelineMetricsWorker do
let(:project) { create(:project, :repository) }
- let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref, head_pipeline: pipeline) }
let(:pipeline) do
create(:ci_empty_pipeline,
diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb
index 91d5a16993f..14ed8b7811e 100644
--- a/spec/workers/pipeline_schedule_worker_spec.rb
+++ b/spec/workers/pipeline_schedule_worker_spec.rb
@@ -11,40 +11,54 @@ describe PipelineScheduleWorker do
end
before do
- project.add_master(user)
-
stub_ci_pipeline_to_return_yaml_file
- end
- context 'when there is a scheduled pipeline within next_run_at' do
- let(:next_run_at) { 2.days.ago }
+ pipeline_schedule.update_column(:next_run_at, 1.day.ago)
+ end
+ context 'when the schedule is runnable by the user' do
before do
- pipeline_schedule.update_column(:next_run_at, next_run_at)
+ project.add_master(user)
end
- it 'creates a new pipeline' do
- expect { subject }.to change { project.pipelines.count }.by(1)
- end
+ context 'when there is a scheduled pipeline within next_run_at' do
+ it 'creates a new pipeline' do
+ expect{ subject }.to change { project.pipelines.count }.by(1)
+ expect(Ci::Pipeline.last).to be_schedule
+ end
- it 'updates the next_run_at field' do
- subject
+ it 'updates the next_run_at field' do
+ subject
+
+ expect(pipeline_schedule.reload.next_run_at).to be > Time.now
+ end
- expect(pipeline_schedule.reload.next_run_at).to be > Time.now
+ it 'sets the schedule on the pipeline' do
+ subject
+
+ expect(project.pipelines.last.pipeline_schedule).to eq(pipeline_schedule)
+ end
end
- it 'sets the schedule on the pipeline' do
- subject
- expect(project.pipelines.last.pipeline_schedule).to eq(pipeline_schedule)
+ context 'inactive schedule' do
+ before do
+ pipeline_schedule.deactivate!
+ end
+
+ it 'does not creates a new pipeline' do
+ expect { subject }.not_to change { project.pipelines.count }
+ end
end
end
- context 'inactive schedule' do
- before do
- pipeline_schedule.update(active: false)
+ context 'when the schedule is not runnable by the user' do
+ it 'deactivates the schedule' do
+ subject
+
+ expect(pipeline_schedule.reload.active).to be_falsy
end
- it 'does not creates a new pipeline' do
+ it 'does not schedule a pipeline' do
expect { subject }.not_to change { project.pipelines.count }
end
end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 0260416dbe2..f4bc63bcc6a 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -4,13 +4,16 @@ describe PostReceive do
let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
- let(:project) { create(:project, :repository) }
let(:project_identifier) { "project-#{project.id}" }
let(:key) { create(:key, user: project.owner) }
let(:key_id) { key.shell_id }
- context "as a resque worker" do
- it "reponds to #perform" do
+ let(:project) do
+ create(:project, :repository, auto_cancel_pending_pipelines: 'disabled')
+ end
+
+ context "as a sidekiq worker" do
+ it "responds to #perform" do
expect(described_class.new).to respond_to(:perform)
end
end
@@ -93,6 +96,27 @@ describe PostReceive do
end
end
+ describe '#process_repository_update' do
+ let(:changes) {'123456 789012 refs/heads/tést'}
+ let(:fake_hook_data) do
+ { event_name: 'repository_update' }
+ end
+
+ before do
+ allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner)
+ allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data)
+ # silence hooks so we can isolate
+ allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true)
+ allow(subject).to receive(:process_project_changes).and_return(true)
+ end
+
+ it 'calls SystemHooksService' do
+ expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(fake_hook_data, :repository_update_hooks).and_return(true)
+
+ subject.perform(pwd(project), key_id, base64_changes)
+ end
+ end
+
context "webhook" do
it "fetches the correct project" do
expect(Project).to receive(:find_by).with(id: project.id.to_s)
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 9afe2e610b9..4e036285e8c 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -31,6 +31,18 @@ describe ProcessCommitWorker do
worker.perform(project.id, user.id, commit.to_hash)
end
+
+ context 'when commit already exists in upstream project' do
+ let(:forked) { create(:project, :public) }
+
+ it 'does not process commit message' do
+ create(:forked_project_link, forked_to_project: forked, forked_from_project: project)
+
+ expect(worker).not_to receive(:process_commit_message)
+
+ worker.perform(forked.id, user.id, forked.commit.to_hash)
+ end
+ end
end
describe '#process_commit_message' do
diff --git a/spec/workers/remove_old_web_hook_logs_worker_spec.rb b/spec/workers/remove_old_web_hook_logs_worker_spec.rb
new file mode 100644
index 00000000000..6d26ba5dfa0
--- /dev/null
+++ b/spec/workers/remove_old_web_hook_logs_worker_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe RemoveOldWebHookLogsWorker do
+ subject { described_class.new }
+
+ describe '#perform' do
+ let!(:week_old_record) { create(:web_hook_log, created_at: Time.now - 1.week) }
+ let!(:three_days_old_record) { create(:web_hook_log, created_at: Time.now - 3.days) }
+ let!(:one_day_old_record) { create(:web_hook_log, created_at: Time.now - 1.day) }
+
+ it 'removes web hook logs older than 2 days' do
+ subject.perform
+
+ expect(WebHookLog.all).to include(one_day_old_record)
+ expect(WebHookLog.all).not_to include(week_old_record, three_days_old_record)
+ end
+ end
+end
diff --git a/spec/workers/repository_check/clear_worker_spec.rb b/spec/workers/repository_check/clear_worker_spec.rb
index a3b70c74787..3b1a64c5057 100644
--- a/spec/workers/repository_check/clear_worker_spec.rb
+++ b/spec/workers/repository_check/clear_worker_spec.rb
@@ -5,7 +5,7 @@ describe RepositoryCheck::ClearWorker do
project = create(:empty_project)
project.update_columns(
last_repository_check_failed: true,
- last_repository_check_at: Time.now,
+ last_repository_check_at: Time.now
)
described_class.new.perform
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 5e1cb74c7fc..6ea5569b438 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe RepositoryForkWorker do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository, :import_scheduled) }
let(:fork_project) { create(:project, :repository, forked_from_project: project) }
let(:shell) { Gitlab::Shell.new }
@@ -46,15 +46,27 @@ describe RepositoryForkWorker do
end
it "handles bad fork" do
+ source_path = project.full_path
+ target_path = fork_project.namespace.full_path
+ error_message = "Unable to fork project #{project.id} for repository #{source_path} -> #{target_path}"
+
expect(shell).to receive(:fork_repository).and_return(false)
- expect(subject.logger).to receive(:error)
+ expect do
+ subject.perform(project.id, '/test/path', source_path, target_path)
+ end.to raise_error(RepositoryForkWorker::ForkError, error_message)
+ end
- subject.perform(
- project.id,
- '/test/path',
- project.full_path,
- fork_project.namespace.full_path)
+ it 'handles unexpected error' do
+ source_path = project.full_path
+ target_path = fork_project.namespace.full_path
+
+ allow_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_raise(RuntimeError)
+
+ expect do
+ subject.perform(project.id, '/test/path', source_path, target_path)
+ end.to raise_error(RepositoryForkWorker::ForkError)
+ expect(project.reload.import_status).to eq('failed')
end
end
end
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index 5a2c0671dac..9c277c501f1 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe RepositoryImportWorker do
- let(:project) { create(:empty_project) }
+ let(:project) { create(:empty_project, :import_scheduled) }
subject { described_class.new }
@@ -21,15 +21,26 @@ describe RepositoryImportWorker do
context 'when the import has failed' do
it 'hide the credentials that were used in the import URL' do
error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found }
- expect_any_instance_of(Projects::ImportService).to receive(:execute).
- and_return({ status: :error, message: error })
- allow(subject).to receive(:jid).and_return('123')
- subject.perform(project.id)
+ expect_any_instance_of(Projects::ImportService).to receive(:execute).and_return({ status: :error, message: error })
+ allow(subject).to receive(:jid).and_return('123')
- expect(project.reload.import_error).to include("https://*****:*****@test.com/root/repoC.git/")
+ expect do
+ subject.perform(project.id)
+ end.to raise_error(RepositoryImportWorker::ImportError, error)
expect(project.reload.import_jid).not_to be_nil
end
end
+
+ context 'with unexpected error' do
+ it 'marks import as failed' do
+ allow_any_instance_of(Projects::ImportService).to receive(:execute).and_raise(RuntimeError)
+
+ expect do
+ subject.perform(project.id)
+ end.to raise_error(RepositoryImportWorker::ImportError)
+ expect(project.reload.import_status).to eq('failed')
+ end
+ end
end
end