summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb2
-rw-r--r--spec/controllers/application_controller_spec.rb92
-rw-r--r--spec/controllers/boards/issues_controller_spec.rb2
-rw-r--r--spec/controllers/boards/lists_controller_spec.rb18
-rw-r--r--spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb12
-rw-r--r--spec/controllers/concerns/internal_redirect_spec.rb25
-rw-r--r--spec/controllers/graphql_controller_spec.rb69
-rw-r--r--spec/controllers/groups/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/groups/shared_projects_controller_spec.rb11
-rw-r--r--spec/controllers/import/gitlab_projects_controller_spec.rb2
-rw-r--r--spec/controllers/import/google_code_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/projects/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb19
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb8
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb24
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb35
-rw-r--r--spec/controllers/projects/pipeline_schedules_controller_spec.rb16
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb111
-rw-r--r--spec/controllers/projects_controller_spec.rb16
-rw-r--r--spec/controllers/registrations_controller_spec.rb21
-rw-r--r--spec/controllers/search_controller_spec.rb2
-rw-r--r--spec/controllers/sessions_controller_spec.rb10
-rw-r--r--spec/controllers/uploads_controller_spec.rb101
-rw-r--r--spec/controllers/users/terms_controller_spec.rb22
-rw-r--r--spec/factories/lfs_objects.rb2
-rw-r--r--spec/factories/merge_requests.rb6
-rw-r--r--spec/factories/notes.rb4
-rw-r--r--spec/factories/project_auto_devops.rb9
-rw-r--r--spec/factories/projects.rb12
-rw-r--r--spec/factories/term_agreements.rb8
-rw-r--r--spec/features/admin/admin_appearance_spec.rb20
-rw-r--r--spec/features/admin/admin_settings_spec.rb2
-rw-r--r--spec/features/admin/admin_uses_repository_checks_spec.rb2
-rw-r--r--spec/features/boards/boards_spec.rb2
-rw-r--r--spec/features/commits_spec.rb2
-rw-r--r--spec/features/ics/dashboard_issues_spec.rb26
-rw-r--r--spec/features/ics/group_issues_spec.rb26
-rw-r--r--spec/features/ics/project_issues_spec.rb26
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb51
-rw-r--r--spec/features/issues/form_spec.rb3
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb40
-rw-r--r--spec/features/labels_hierarchy_spec.rb4
-rw-r--r--spec/features/markdown/copy_as_gfm_spec.rb9
-rw-r--r--spec/features/markdown/markdown_spec.rb235
-rw-r--r--spec/features/merge_request/maintainer_edits_fork_spec.rb2
-rw-r--r--spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb (renamed from spec/features/merge_request/user_allows_a_maintainer_to_push_spec.rb)22
-rw-r--r--spec/features/merge_request/user_creates_image_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_posts_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb4
-rw-r--r--spec/features/projects/deploy_keys_spec.rb3
-rw-r--r--spec/features/projects/diffs/diff_show_spec.rb3
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb6
-rw-r--r--spec/features/projects/issues/user_comments_on_issue_spec.rb7
-rw-r--r--spec/features/projects/jobs/permissions_spec.rb3
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb2
-rw-r--r--spec/features/projects/jobs_spec.rb2
-rw-r--r--spec/features/projects/labels/subscription_spec.rb6
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb8
-rw-r--r--spec/features/projects/labels/user_removes_labels_spec.rb20
-rw-r--r--spec/features/projects/pages_spec.rb4
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb10
-rw-r--r--spec/features/projects/settings/user_manages_group_links_spec.rb2
-rw-r--r--spec/features/projects/settings/user_manages_project_members_spec.rb2
-rw-r--r--spec/features/protected_branches_spec.rb4
-rw-r--r--spec/features/runners_spec.rb12
-rw-r--r--spec/features/tags/master_deletes_tag_spec.rb4
-rw-r--r--spec/features/task_lists_spec.rb45
-rw-r--r--spec/features/users/signup_spec.rb21
-rw-r--r--spec/features/users/terms_spec.rb137
-rw-r--r--spec/finders/group_members_finder_spec.rb12
-rw-r--r--spec/finders/members_finder_spec.rb12
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_basic.json17
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json2
-rw-r--r--spec/fixtures/api/schemas/list.json2
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/commit/with_stats.json14
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/commits_with_stats.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/merge_requests.json1
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/milestones.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/snippets.json1
-rw-r--r--spec/fixtures/markdown.md.erb10
-rw-r--r--spec/graphql/gitlab_schema_spec.rb33
-rw-r--r--spec/graphql/resolvers/merge_request_resolver_spec.rb45
-rw-r--r--spec/graphql/resolvers/project_resolver_spec.rb32
-rw-r--r--spec/graphql/types/project_type_spec.rb14
-rw-r--r--spec/graphql/types/query_type_spec.rb23
-rw-r--r--spec/graphql/types/time_type_spec.rb16
-rw-r--r--spec/helpers/emails_helper_spec.rb4
-rw-r--r--spec/helpers/groups_helper_spec.rb8
-rw-r--r--spec/helpers/markup_helper_spec.rb2
-rw-r--r--spec/helpers/page_layout_helper_spec.rb17
-rw-r--r--spec/helpers/preferences_helper_spec.rb4
-rw-r--r--spec/helpers/projects_helper_spec.rb58
-rw-r--r--spec/helpers/storage_helper_spec.rb24
-rw-r--r--spec/initializers/artifacts_direct_upload_support_spec.rb71
-rw-r--r--spec/initializers/direct_upload_support_spec.rb90
-rw-r--r--spec/initializers/fog_google_https_private_urls_spec.rb12
-rw-r--r--spec/javascripts/blob/viewer/index_spec.js4
-rw-r--r--spec/javascripts/boards/board_card_spec.js2
-rw-r--r--spec/javascripts/boards/boards_store_spec.js2
-rw-r--r--spec/javascripts/boards/issue_card_spec.js2
-rw-r--r--spec/javascripts/boards/issue_spec.js2
-rw-r--r--spec/javascripts/boards/list_spec.js2
-rw-r--r--spec/javascripts/boards/modal_store_spec.js2
-rw-r--r--spec/javascripts/bootstrap_linked_tabs_spec.js2
-rw-r--r--spec/javascripts/clusters/stores/clusters_store_spec.js2
-rw-r--r--spec/javascripts/commits_spec.js2
-rw-r--r--spec/javascripts/create_merge_request_dropdown_spec.js67
-rw-r--r--spec/javascripts/datetime_utility_spec.js23
-rw-r--r--spec/javascripts/environments/environments_app_spec.js2
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js2
-rw-r--r--spec/javascripts/fixtures/images/green_box.pngbin0 -> 1306 bytes
-rw-r--r--spec/javascripts/fixtures/images/red_box.pngbin0 -> 1305 bytes
-rw-r--r--spec/javascripts/gl_field_errors_spec.js4
-rw-r--r--spec/javascripts/helpers/user_mock_data_helper.js2
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/list_item_spec.js17
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/list_spec.js5
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js2
-rw-r--r--spec/javascripts/ide/components/jobs/detail/description_spec.js28
-rw-r--r--spec/javascripts/ide/components/jobs/detail/scroll_button_spec.js59
-rw-r--r--spec/javascripts/ide/components/jobs/detail_spec.js185
-rw-r--r--spec/javascripts/ide/components/jobs/item_spec.js10
-rw-r--r--spec/javascripts/ide/components/merge_requests/dropdown_spec.js47
-rw-r--r--spec/javascripts/ide/components/merge_requests/item_spec.js61
-rw-r--r--spec/javascripts/ide/components/merge_requests/list_spec.js126
-rw-r--r--spec/javascripts/ide/components/pipelines/list_spec.js11
-rw-r--r--spec/javascripts/ide/components/repo_commit_section_spec.js34
-rw-r--r--spec/javascripts/ide/components/repo_editor_spec.js17
-rw-r--r--spec/javascripts/ide/lib/common/model_manager_spec.js10
-rw-r--r--spec/javascripts/ide/lib/common/model_spec.js18
-rw-r--r--spec/javascripts/ide/lib/decorations/controller_spec.js18
-rw-r--r--spec/javascripts/ide/lib/diff/controller_spec.js25
-rw-r--r--spec/javascripts/ide/lib/editor_spec.js44
-rw-r--r--spec/javascripts/ide/mock_data.js4
-rw-r--r--spec/javascripts/ide/monaco_loader_spec.js15
-rw-r--r--spec/javascripts/ide/stores/actions/file_spec.js4
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js99
-rw-r--r--spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js96
-rw-r--r--spec/javascripts/ide/stores/modules/merge_requests/mutations_spec.js19
-rw-r--r--spec/javascripts/ide/stores/modules/pipelines/actions_spec.js152
-rw-r--r--spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js49
-rw-r--r--spec/javascripts/ide/stores/mutations/file_spec.js47
-rw-r--r--spec/javascripts/ide/stores/utils_spec.js56
-rw-r--r--spec/javascripts/importer_status_spec.js20
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js4
-rw-r--r--spec/javascripts/jobs/header_spec.js7
-rw-r--r--spec/javascripts/jobs/mock_data.js4
-rw-r--r--spec/javascripts/labels_select_spec.js4
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js51
-rw-r--r--spec/javascripts/lib/utils/mock_data.js5
-rw-r--r--spec/javascripts/lib/utils/url_utility_spec.js29
-rw-r--r--spec/javascripts/monitoring/graph_spec.js10
-rw-r--r--spec/javascripts/notes/components/comment_form_spec.js2
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js4
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js4
-rw-r--r--spec/javascripts/notes/components/note_form_spec.js2
-rw-r--r--spec/javascripts/notes/mock_data.js578
-rw-r--r--spec/javascripts/notes/stores/collapse_utils_spec.js46
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js19
-rw-r--r--spec/javascripts/notes_spec.js4
-rw-r--r--spec/javascripts/pipelines/graph/mock_data.js18
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js2
-rw-r--r--spec/javascripts/profile/account/components/update_username_spec.js3
-rw-r--r--spec/javascripts/settings_panels_spec.js4
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js2
-rw-r--r--spec/javascripts/test_bundle.js3
-rw-r--r--spec/javascripts/test_constants.js3
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js8
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js12
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js23
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js7
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js13
-rw-r--r--spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js10
-rw-r--r--spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js70
-rw-r--r--spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js185
-rw-r--r--spec/javascripts/vue_shared/components/gl_modal_spec.js41
-rw-r--r--spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js13
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js2
-rw-r--r--spec/lib/backup/files_spec.rb4
-rw-r--r--spec/lib/backup/manager_spec.rb7
-rw-r--r--spec/lib/backup/repository_spec.rb4
-rw-r--r--spec/lib/banzai/filter/blockquote_fence_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/image_lazy_load_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/markdown_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb10
-rw-r--r--spec/lib/gitlab/auth/user_auth_finders_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb59
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/bare_repository_import/importer_spec.rb7
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb6
-rw-r--r--spec/lib/gitlab/checks/change_access_spec.rb4
-rw-r--r--spec/lib/gitlab/checks/force_push_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/preloader_spec.rb47
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb2
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb143
-rw-r--r--spec/lib/gitlab/database_spec.rb9
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb117
-rw-r--r--spec/lib/gitlab/favicon_spec.rb52
-rw-r--r--spec/lib/gitlab/git/blame_spec.rb10
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb14
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb4
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb27
-rw-r--r--spec/lib/gitlab/git/committer_with_hooks_spec.rb216
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb6
-rw-r--r--spec/lib/gitlab/git/gitlab_projects_spec.rb112
-rw-r--r--spec/lib/gitlab/git/hook_spec.rb32
-rw-r--r--spec/lib/gitlab/git/hooks_service_spec.rb8
-rw-r--r--spec/lib/gitlab/git/index_spec.rb7
-rw-r--r--spec/lib/gitlab/git/pre_receive_error_spec.rb9
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb179
-rw-r--r--spec/lib/gitlab/git/rev_list_spec.rb14
-rw-r--r--spec/lib/gitlab/git/wiki_spec.rb20
-rw-r--r--spec/lib/gitlab/git_access_spec.rb48
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb4
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_importer_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb23
-rw-r--r--spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb94
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb97
-rw-r--r--spec/lib/gitlab/github_import/importer/repository_importer_spec.rb3
-rw-r--r--spec/lib/gitlab/github_import/sequential_importer_spec.rb1
-rw-r--r--spec/lib/gitlab/gpg_spec.rb13
-rw-r--r--spec/lib/gitlab/hashed_storage/migrator_spec.rb75
-rw-r--r--spec/lib/gitlab/i18n/metadata_entry_spec.rb6
-rw-r--r--spec/lib/gitlab/i18n/po_linter_spec.rb163
-rw-r--r--spec/lib/gitlab/i18n/translation_entry_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/avatar_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/fork_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/merge_request_parser_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/import_export/uploads_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_sources_spec.rb19
-rw-r--r--spec/lib/gitlab/legacy_github_import/project_creator_spec.rb40
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb12
-rw-r--r--spec/lib/gitlab/profiler_spec.rb45
-rw-r--r--spec/lib/gitlab/quick_actions/extractor_spec.rb16
-rw-r--r--spec/lib/gitlab/shell_spec.rb56
-rw-r--r--spec/lib/gitlab/sql/cte_spec.rb42
-rw-r--r--spec/lib/gitlab/sql/glob_spec.rb3
-rw-r--r--spec/lib/gitlab/themes_spec.rb6
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb46
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb25
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb2
-rw-r--r--spec/lib/gitlab/user_access_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/override_spec.rb170
-rw-r--r--spec/lib/gitlab/verify/job_artifacts_spec.rb29
-rw-r--r--spec/lib/gitlab/verify/lfs_objects_spec.rb25
-rw-r--r--spec/lib/gitlab/verify/uploads_spec.rb27
-rw-r--r--spec/lib/microsoft_teams/notifier_spec.rb2
-rw-r--r--spec/lib/object_storage/direct_upload_spec.rb168
-rw-r--r--spec/mailers/notify_spec.rb56
-rw-r--r--spec/migrations/active_record/schema_spec.rb6
-rw-r--r--spec/migrations/change_default_value_for_dsa_key_restriction_spec.rb33
-rw-r--r--spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb33
-rw-r--r--spec/migrations/migrate_process_commit_worker_jobs_spec.rb6
-rw-r--r--spec/migrations/remove_soft_removed_objects_spec.rb36
-rw-r--r--spec/migrations/schedule_to_archive_legacy_traces_spec.rb45
-rw-r--r--spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb8
-rw-r--r--spec/models/application_setting/term_spec.rb37
-rw-r--r--spec/models/application_setting_spec.rb3
-rw-r--r--spec/models/ci/build_spec.rb95
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb8
-rw-r--r--spec/models/ci/group_spec.rb51
-rw-r--r--spec/models/ci/job_artifact_spec.rb4
-rw-r--r--spec/models/ci/pipeline_spec.rb120
-rw-r--r--spec/models/ci/runner_spec.rb26
-rw-r--r--spec/models/ci/stage_spec.rb142
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb10
-rw-r--r--spec/models/group_spec.rb26
-rw-r--r--spec/models/merge_request_spec.rb40
-rw-r--r--spec/models/namespace_spec.rb10
-rw-r--r--spec/models/notification_recipient_spec.rb44
-rw-r--r--spec/models/project_auto_devops_spec.rb121
-rw-r--r--spec/models/project_services/jira_service_spec.rb19
-rw-r--r--spec/models/project_services/microsoft_teams_service_spec.rb7
-rw-r--r--spec/models/project_spec.rb104
-rw-r--r--spec/models/project_team_spec.rb4
-rw-r--r--spec/models/project_wiki_spec.rb6
-rw-r--r--spec/models/remote_mirror_spec.rb11
-rw-r--r--spec/models/repository_spec.rb79
-rw-r--r--spec/models/term_agreement_spec.rb9
-rw-r--r--spec/models/user_spec.rb2
-rw-r--r--spec/policies/ci/build_policy_spec.rb2
-rw-r--r--spec/policies/ci/pipeline_policy_spec.rb2
-rw-r--r--spec/policies/project_policy_spec.rb2
-rw-r--r--spec/requests/api/avatar_spec.rb106
-rw-r--r--spec/requests/api/commits_spec.rb43
-rw-r--r--spec/requests/api/events_spec.rb4
-rw-r--r--spec/requests/api/graphql/project_query_spec.rb88
-rw-r--r--spec/requests/api/groups_spec.rb49
-rw-r--r--spec/requests/api/internal_spec.rb18
-rw-r--r--spec/requests/api/issues_spec.rb8
-rw-r--r--spec/requests/api/jobs_spec.rb12
-rw-r--r--spec/requests/api/merge_requests_spec.rb12
-rw-r--r--spec/requests/api/pipelines_spec.rb60
-rw-r--r--spec/requests/api/project_import_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb4
-rw-r--r--spec/requests/api/repositories_spec.rb9
-rw-r--r--spec/requests/api/runner_spec.rb39
-rw-r--r--spec/requests/api/settings_spec.rb4
-rw-r--r--spec/requests/api/snippets_spec.rb3
-rw-r--r--spec/requests/api/tags_spec.rb5
-rw-r--r--spec/requests/api/users_spec.rb75
-rw-r--r--spec/requests/git_http_spec.rb17
-rw-r--r--spec/requests/lfs_http_spec.rb1
-rw-r--r--spec/requests/openid_connect_spec.rb2
-rw-r--r--spec/routing/api_routing_spec.rb31
-rw-r--r--spec/routing/project_routing_spec.rb20
-rw-r--r--spec/serializers/build_serializer_spec.rb4
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb15
-rw-r--r--spec/serializers/status_entity_spec.rb12
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb4
-rw-r--r--spec/services/git_tag_push_service_spec.rb4
-rw-r--r--spec/services/lfs/unlock_file_service_spec.rb4
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb11
-rw-r--r--spec/services/merge_requests/ff_merge_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb2
-rw-r--r--spec/services/merge_requests/squash_service_spec.rb4
-rw-r--r--spec/services/merge_requests/update_service_spec.rb12
-rw-r--r--spec/services/notification_recipient_service_spec.rb36
-rw-r--r--spec/services/notification_service_spec.rb188
-rw-r--r--spec/services/pages_service_spec.rb53
-rw-r--r--spec/services/projects/after_import_service_spec.rb8
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb15
-rw-r--r--spec/services/projects/create_service_spec.rb5
-rw-r--r--spec/services/projects/destroy_service_spec.rb6
-rw-r--r--spec/services/projects/fork_service_spec.rb2
-rw-r--r--spec/services/projects/gitlab_projects_import_service_spec.rb2
-rw-r--r--spec/services/projects/hashed_storage/migrate_repository_service_spec.rb6
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb72
-rw-r--r--spec/services/projects/import_service_spec.rb72
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb102
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_service_spec.rb69
-rw-r--r--spec/services/projects/lfs_pointers/lfs_import_service_spec.rb146
-rw-r--r--spec/services/projects/lfs_pointers/lfs_link_service_spec.rb33
-rw-r--r--spec/services/projects/participants_service_spec.rb2
-rw-r--r--spec/services/projects/transfer_service_spec.rb14
-rw-r--r--spec/services/projects/update_pages_service_spec.rb12
-rw-r--r--spec/services/projects/update_service_spec.rb8
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb18
-rw-r--r--spec/services/system_note_service_spec.rb9
-rw-r--r--spec/services/tags/create_service_spec.rb2
-rw-r--r--spec/services/test_hooks/project_service_spec.rb10
-rw-r--r--spec/services/upload_service_spec.rb10
-rw-r--r--spec/spec_helper.rb18
-rw-r--r--spec/support/api/scopes/read_user_shared_examples.rb10
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_examples.rb9
-rw-r--r--spec/support/features/reportable_note_shared_examples.rb4
-rw-r--r--spec/support/gitaly.rb2
-rw-r--r--spec/support/helpers/assets_helpers.rb15
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb6
-rw-r--r--spec/support/helpers/features/notes_helpers.rb2
-rw-r--r--spec/support/helpers/graphql_helpers.rb99
-rw-r--r--spec/support/helpers/migrations_helpers.rb4
-rw-r--r--spec/support/helpers/query_recorder.rb7
-rw-r--r--spec/support/helpers/stub_object_storage.rb12
-rw-r--r--spec/support/helpers/test_env.rb10
-rw-r--r--spec/support/matchers/email_matchers.rb5
-rw-r--r--spec/support/matchers/exceed_query_limit.rb65
-rw-r--r--spec/support/matchers/graphql_matchers.rb46
-rw-r--r--spec/support/rspec.rb2
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb36
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/notify_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/graphql_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb81
-rw-r--r--spec/support/shared_examples/uploaders/object_storage_shared_examples.rb10
-rw-r--r--spec/support/shoulda/matchers/rails_shim.rb27
-rw-r--r--spec/support/trace/trace_helpers.rb27
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb19
-rw-r--r--spec/tasks/gitlab/gitaly_rake_spec.rb4
-rw-r--r--spec/tasks/gitlab/shell_rake_spec.rb8
-rw-r--r--spec/tasks/gitlab/storage_rake_spec.rb45
-rw-r--r--spec/uploaders/attachment_uploader_spec.rb2
-rw-r--r--spec/uploaders/file_mover_spec.rb2
-rw-r--r--spec/uploaders/file_uploader_spec.rb2
-rw-r--r--spec/uploaders/gitlab_uploader_spec.rb2
-rw-r--r--spec/uploaders/job_artifact_uploader_spec.rb8
-rw-r--r--spec/uploaders/legacy_artifact_uploader_spec.rb3
-rw-r--r--spec/uploaders/namespace_file_uploader_spec.rb2
-rw-r--r--spec/uploaders/object_storage_spec.rb179
-rw-r--r--spec/uploaders/personal_file_uploader_spec.rb2
-rw-r--r--spec/uploaders/records_uploads_spec.rb2
-rw-r--r--spec/uploaders/uploader_helper_spec.rb2
-rw-r--r--spec/uploaders/workers/object_storage/background_move_worker_spec.rb6
-rw-r--r--spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb51
-rw-r--r--spec/validators/url_validator_spec.rb53
-rw-r--r--spec/views/errors/access_denied.html.haml_spec.rb7
-rw-r--r--spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb8
-rw-r--r--spec/workers/ci/archive_traces_cron_worker_spec.rb59
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb2
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb125
-rw-r--r--spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb28
-rw-r--r--spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb2
-rw-r--r--spec/workers/project_destroy_worker_spec.rb6
-rw-r--r--spec/workers/repository_check/single_repository_worker_spec.rb8
-rw-r--r--spec/workers/repository_fork_worker_spec.rb18
-rw-r--r--spec/workers/repository_import_worker_spec.rb15
-rw-r--r--spec/workers/repository_remove_remote_worker_spec.rb4
-rw-r--r--spec/workers/storage_migrator_worker_spec.rb25
-rw-r--r--spec/workers/stuck_ci_jobs_worker_spec.rb4
409 files changed, 8848 insertions, 2212 deletions
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index b4fc2aa326f..9d10d725ff3 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -73,7 +73,7 @@ describe Admin::ApplicationSettingsController do
end
it 'updates the restricted_visibility_levels when empty array is passed' do
- put :update, application_setting: { restricted_visibility_levels: [] }
+ put :update, application_setting: { restricted_visibility_levels: [""] }
expect(response).to redirect_to(admin_application_settings_path)
expect(ApplicationSetting.current.restricted_visibility_levels).to be_empty
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index b048da1991c..74f362fd7fc 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
require 'spec_helper'
describe ApplicationController do
@@ -457,6 +458,8 @@ describe ApplicationController do
end
context 'for sessionless users' do
+ render_views
+
before do
sign_out user
end
@@ -467,6 +470,14 @@ describe ApplicationController do
expect(response).to have_gitlab_http_status(403)
end
+ it 'renders the error message when the format was html' do
+ get :index,
+ private_token: create(:personal_access_token, user: user).token,
+ format: :html
+
+ expect(response.body).to have_content /accept the terms of service/i
+ end
+
it 'renders a 200 when the sessionless user accepted the terms' do
accept_terms(user)
@@ -477,4 +488,85 @@ describe ApplicationController do
end
end
end
+
+ describe '#append_info_to_payload' do
+ controller(described_class) do
+ attr_reader :last_payload
+
+ def index
+ render text: 'authenticated'
+ end
+
+ def append_info_to_payload(payload)
+ super
+
+ @last_payload = payload
+ end
+ end
+
+ it 'does not log errors with a 200 response' do
+ get :index
+
+ expect(controller.last_payload.has_key?(:response)).to be_falsey
+ end
+
+ context '422 errors' do
+ it 'logs a response with a string' do
+ response = spy(ActionDispatch::Response, status: 422, body: 'Hello world', content_type: 'application/json', cookies: {})
+ allow(controller).to receive(:response).and_return(response)
+ get :index
+
+ expect(controller.last_payload[:response]).to eq('Hello world')
+ end
+
+ it 'logs a response with an array' do
+ body = ['I want', 'my hat back']
+ response = spy(ActionDispatch::Response, status: 422, body: body, content_type: 'application/json', cookies: {})
+ allow(controller).to receive(:response).and_return(response)
+ get :index
+
+ expect(controller.last_payload[:response]).to eq(body)
+ end
+
+ it 'does not log a string with an empty body' do
+ response = spy(ActionDispatch::Response, status: 422, body: nil, content_type: 'application/json', cookies: {})
+ allow(controller).to receive(:response).and_return(response)
+ get :index
+
+ expect(controller.last_payload.has_key?(:response)).to be_falsey
+ end
+
+ it 'does not log an HTML body' do
+ response = spy(ActionDispatch::Response, status: 422, body: 'This is a test', content_type: 'application/html', cookies: {})
+ allow(controller).to receive(:response).and_return(response)
+ get :index
+
+ expect(controller.last_payload.has_key?(:response)).to be_falsey
+ end
+ end
+ end
+
+ describe '#access_denied' do
+ controller(described_class) do
+ def index
+ access_denied!(params[:message])
+ end
+ end
+
+ before do
+ sign_in user
+ end
+
+ it 'renders a 404 without a message' do
+ get :index
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'renders a 403 when a message is passed to access denied' do
+ get :index, message: 'None shall pass'
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
end
diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb
index dcb0faffbd4..e47ff8661a2 100644
--- a/spec/controllers/boards/issues_controller_spec.rb
+++ b/spec/controllers/boards/issues_controller_spec.rb
@@ -18,7 +18,7 @@ describe Boards::IssuesController do
end
describe 'GET index', :request_store do
- let(:johndoe) { create(:user, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) }
+ let(:johndoe) { create(:user, avatar: fixture_file_upload(File.join('spec/fixtures/dk.png'))) }
context 'with invalid board id' do
it 'returns a not found 404 response' do
diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb
index 71d45a22d91..57ccbf1d6b5 100644
--- a/spec/controllers/boards/lists_controller_spec.rb
+++ b/spec/controllers/boards/lists_controller_spec.rb
@@ -156,12 +156,18 @@ describe Boards::ListsController do
def move(user:, board:, list:, position:)
sign_in(user)
- patch :update, namespace_id: project.namespace.to_param,
- project_id: project,
- board_id: board.to_param,
- id: list.to_param,
- list: { position: position },
- format: :json
+ params = { namespace_id: project.namespace.to_param,
+ project_id: project,
+ board_id: board.to_param,
+ id: list.to_param,
+ list: { position: position },
+ format: :json }
+
+ if Gitlab.rails5?
+ patch :update, params: params, as: :json
+ else
+ patch :update, params
+ end
end
end
diff --git a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb
index 27f558e1b5d..d20471ef603 100644
--- a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb
+++ b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb
@@ -43,13 +43,13 @@ describe ControllerWithCrossProjectAccessCheck do
end
end
- it 'renders a 404 with trying to access a cross project page' do
+ it 'renders a 403 with trying to access a cross project page' do
message = "This page is unavailable because you are not allowed to read "\
"information across multiple projects."
get :index
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(403)
expect(response.body).to match(/#{message}/)
end
@@ -119,7 +119,7 @@ describe ControllerWithCrossProjectAccessCheck do
get :index
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(403)
end
it 'is executed when the `unless` condition returns true' do
@@ -127,19 +127,19 @@ describe ControllerWithCrossProjectAccessCheck do
get :index
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(403)
end
it 'does not skip the check on an action that is not skipped' do
get :show, id: 'hello'
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(403)
end
it 'does not skip the check on an action that was not defined to skip' do
get :edit, id: 'hello'
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(403)
end
end
end
diff --git a/spec/controllers/concerns/internal_redirect_spec.rb b/spec/controllers/concerns/internal_redirect_spec.rb
index a0ee13b2352..7e23b56356e 100644
--- a/spec/controllers/concerns/internal_redirect_spec.rb
+++ b/spec/controllers/concerns/internal_redirect_spec.rb
@@ -54,6 +54,31 @@ describe InternalRedirect do
end
end
+ describe '#sanitize_redirect' do
+ let(:valid_path) { '/hello/world?hello=world' }
+ let(:valid_url) { "http://test.host#{valid_path}" }
+
+ it 'returns `nil` for invalid paths' do
+ invalid_path = '//not/valid'
+
+ expect(controller.sanitize_redirect(invalid_path)).to eq nil
+ end
+
+ it 'returns `nil` for invalid urls' do
+ input = 'http://test.host:3000/invalid'
+
+ expect(controller.sanitize_redirect(input)).to eq nil
+ end
+
+ it 'returns input for valid paths' do
+ expect(controller.sanitize_redirect(valid_path)).to eq valid_path
+ end
+
+ it 'returns path for valid urls' do
+ expect(controller.sanitize_redirect(valid_url)).to eq valid_path
+ end
+ end
+
describe '#host_allowed?' do
it 'allows uris with the same host and port' do
expect(controller.host_allowed?(URI('http://test.host/test'))).to be(true)
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
new file mode 100644
index 00000000000..1449036e148
--- /dev/null
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe GraphqlController do
+ describe 'execute' do
+ let(:user) { nil }
+
+ before do
+ sign_in(user) if user
+
+ run_test_query!
+ end
+
+ subject { query_response }
+
+ context 'graphql is disabled by feature flag' do
+ let(:user) { nil }
+
+ before do
+ stub_feature_flags(graphql: false)
+ end
+
+ it 'returns 404' do
+ run_test_query!
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'signed out' do
+ let(:user) { nil }
+
+ it 'runs the query with current_user: nil' do
+ is_expected.to eq('echo' => 'nil says: test success')
+ end
+ end
+
+ context 'signed in' do
+ let(:user) { create(:user, username: 'Simon') }
+
+ it 'runs the query with current_user set' do
+ is_expected.to eq('echo' => '"Simon" says: test success')
+ end
+ end
+
+ context 'invalid variables' do
+ it 'returns an error' do
+ run_test_query!(variables: "This is not JSON")
+
+ expect(response).to have_gitlab_http_status(422)
+ expect(json_response['errors'].first['message']).not_to be_nil
+ end
+ end
+ end
+
+ # Chosen to exercise all the moving parts in GraphqlController#execute
+ def run_test_query!(variables: { 'text' => 'test success' })
+ query = <<~QUERY
+ query Echo($text: String) {
+ echo(text: $text)
+ }
+ QUERY
+
+ post :execute, query: query, operationName: 'Echo', variables: variables
+ end
+
+ def query_response
+ json_response['data']
+ end
+end
diff --git a/spec/controllers/groups/avatars_controller_spec.rb b/spec/controllers/groups/avatars_controller_spec.rb
index 506aeee7d2a..7feecd0c380 100644
--- a/spec/controllers/groups/avatars_controller_spec.rb
+++ b/spec/controllers/groups/avatars_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Groups::AvatarsController do
let(:user) { create(:user) }
- let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let(:group) { create(:group, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
before do
group.add_owner(user)
diff --git a/spec/controllers/groups/shared_projects_controller_spec.rb b/spec/controllers/groups/shared_projects_controller_spec.rb
index d8fa41abb18..003c8c262e7 100644
--- a/spec/controllers/groups/shared_projects_controller_spec.rb
+++ b/spec/controllers/groups/shared_projects_controller_spec.rb
@@ -38,7 +38,7 @@ describe Groups::SharedProjectsController do
end
it 'allows filtering shared projects' do
- project = create(:project, :archived, namespace: user.namespace, name: "Searching for")
+ project = create(:project, namespace: user.namespace, name: "Searching for")
share_project(project)
get_shared_projects(filter: 'search')
@@ -55,5 +55,14 @@ describe Groups::SharedProjectsController do
expect(json_project_ids).to eq([second_project.id, shared_project.id])
end
+
+ it 'does not include archived projects' do
+ archived_project = create(:project, :archived, namespace: user.namespace)
+ share_project(archived_project)
+
+ get_shared_projects
+
+ expect(json_project_ids).to contain_exactly(shared_project.id)
+ end
end
end
diff --git a/spec/controllers/import/gitlab_projects_controller_spec.rb b/spec/controllers/import/gitlab_projects_controller_spec.rb
index 8759d3c0b97..d624659bce9 100644
--- a/spec/controllers/import/gitlab_projects_controller_spec.rb
+++ b/spec/controllers/import/gitlab_projects_controller_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Import::GitlabProjectsController do
set(:namespace) { create(:namespace) }
set(:user) { namespace.owner }
- let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+ let(:file) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
before do
sign_in(user)
diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb
index 4241db6e771..0763492d88a 100644
--- a/spec/controllers/import/google_code_controller_spec.rb
+++ b/spec/controllers/import/google_code_controller_spec.rb
@@ -4,7 +4,7 @@ describe Import::GoogleCodeController do
include ImportSpecHelper
let(:user) { create(:user) }
- let(:dump_file) { fixture_file_upload(Rails.root + 'spec/fixtures/GoogleCodeProjectHosting.json', 'application/json') }
+ let(:dump_file) { fixture_file_upload('spec/fixtures/GoogleCodeProjectHosting.json', 'application/json') }
before do
sign_in(user)
diff --git a/spec/controllers/profiles/avatars_controller_spec.rb b/spec/controllers/profiles/avatars_controller_spec.rb
index 4fa0462ccdf..909709e1103 100644
--- a/spec/controllers/profiles/avatars_controller_spec.rb
+++ b/spec/controllers/profiles/avatars_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Profiles::AvatarsController do
- let(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png")) }
+ let(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png")) }
before do
sign_in(user)
diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb
index 6a41c4d23ea..acfa2730d94 100644
--- a/spec/controllers/projects/avatars_controller_spec.rb
+++ b/spec/controllers/projects/avatars_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Projects::AvatarsController do
- let(:project) { create(:project, :repository, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let(:project) { create(:project, :repository, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
let(:user) { create(:user) }
before do
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 00a7df6ccc8..9e696e9cb29 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -55,6 +55,25 @@ describe Projects::BlobController do
expect(json_response).to have_key 'raw_path'
end
end
+
+ context "with viewer=none" do
+ let(:id) { 'master/README.md' }
+
+ before do
+ get(:show,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id,
+ format: :json,
+ viewer: 'none')
+ end
+
+ it do
+ expect(response).to be_ok
+ expect(json_response).not_to have_key 'html'
+ expect(json_response).to have_key 'raw_path'
+ end
+ end
end
context 'with tree path' do
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 011843baffc..812833cc86b 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -29,7 +29,7 @@ describe Projects::ImportsController do
context 'when import is in progress' do
before do
- project.update_attribute(:import_status, :started)
+ project.update_attributes(import_status: :started)
end
it 'renders template' do
@@ -47,7 +47,7 @@ describe Projects::ImportsController do
context 'when import failed' do
before do
- project.update_attribute(:import_status, :failed)
+ project.update_attributes(import_status: :failed)
end
it 'redirects to new_namespace_project_import_path' do
@@ -59,7 +59,7 @@ describe Projects::ImportsController do
context 'when import finished' do
before do
- project.update_attribute(:import_status, :finished)
+ project.update_attributes(import_status: :finished)
end
context 'when project is a fork' do
@@ -108,7 +108,7 @@ describe Projects::ImportsController do
context 'when import never happened' do
before do
- project.update_attribute(:import_status, :none)
+ project.update_attributes(import_status: :none)
end
it 'redirects to namespace_project_path' do
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index a08fcea27a5..06c8a432561 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -265,7 +265,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
- expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.ico"
+ expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.png"
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 6e8de6db9c3..a412e74581d 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -80,6 +80,16 @@ describe Projects::MergeRequestsController do
))
end
end
+
+ context "that is invalid" do
+ let(:merge_request) { create(:invalid_merge_request, target_project: project, source_project: project) }
+
+ it "renders merge request page" do
+ go(format: :html)
+
+ expect(response).to be_success
+ end
+ end
end
describe 'as json' do
@@ -106,6 +116,16 @@ describe Projects::MergeRequestsController do
expect(response).to match_response_schema('entities/merge_request_widget')
end
end
+
+ context "that is invalid" do
+ let(:merge_request) { create(:invalid_merge_request, target_project: project, source_project: project) }
+
+ it "renders merge request page" do
+ go(format: :json)
+
+ expect(response).to be_success
+ end
+ end
end
describe "as diff" do
@@ -214,7 +234,7 @@ describe Projects::MergeRequestsController do
body = JSON.parse(response.body)
expect(body['assignee'].keys)
- .to match_array(%w(name username avatar_url))
+ .to match_array(%w(name username avatar_url id state web_url))
end
end
@@ -681,7 +701,7 @@ describe Projects::MergeRequestsController do
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
- expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.ico"
+ expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.png"
end
end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 548c5ef36e7..02b30f9bc6d 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -57,19 +57,36 @@ describe Projects::MilestonesController do
context "as json" do
let!(:group) { create(:group, :public) }
let!(:group_milestone) { create(:milestone, group: group) }
- let!(:group_member) { create(:group_member, group: group, user: user) }
- before do
- project.update(namespace: group)
- get :index, namespace_id: project.namespace.id, project_id: project.id, format: :json
+ context 'with a single group ancestor' do
+ before do
+ project.update(namespace: group)
+ get :index, namespace_id: project.namespace.id, project_id: project.id, format: :json
+ end
+
+ it "queries projects milestones and groups milestones" do
+ milestones = assigns(:milestones)
+
+ expect(milestones.count).to eq(2)
+ expect(milestones).to match_array([milestone, group_milestone])
+ end
end
- it "queries projects milestones and groups milestones" do
- milestones = assigns(:milestones)
+ context 'with nested groups', :nested_groups do
+ let!(:subgroup) { create(:group, :public, parent: group) }
+ let!(:subgroup_milestone) { create(:milestone, group: subgroup) }
+
+ before do
+ project.update(namespace: subgroup)
+ get :index, namespace_id: project.namespace.id, project_id: project.id, format: :json
+ end
+
+ it "queries projects milestones and all ancestors milestones" do
+ milestones = assigns(:milestones)
- expect(milestones.count).to eq(2)
- expect(milestones.where(project_id: nil).first).to eq(group_milestone)
- expect(milestones.where(group_id: nil).first).to eq(milestone)
+ expect(milestones.count).to eq(3)
+ expect(milestones).to match_array([milestone, group_milestone, subgroup_milestone])
+ end
end
end
end
diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
index 3506305f755..4cdaa54e0bc 100644
--- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb
+++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
@@ -310,9 +310,19 @@ describe Projects::PipelineSchedulesController do
end
def go
- put :update, namespace_id: project.namespace.to_param,
- project_id: project, id: pipeline_schedule,
- schedule: schedule
+ if Gitlab.rails5?
+ put :update, params: { namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: pipeline_schedule,
+ schedule: schedule },
+ as: :html
+
+ else
+ put :update, namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: pipeline_schedule,
+ schedule: schedule
+ end
end
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 9e7bc20a6d1..9618a8417ec 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -17,44 +17,103 @@ describe Projects::PipelinesController do
describe 'GET index.json' do
before do
- %w(pending running created success).each_with_index do |status, index|
- sha = project.commit("HEAD~#{index}")
- create(:ci_empty_pipeline, status: status, project: project, sha: sha)
+ %w(pending running success failed canceled).each_with_index do |status, index|
+ create_pipeline(status, project.commit("HEAD~#{index}"))
end
end
- subject do
- get :index, namespace_id: project.namespace, project_id: project, format: :json
+ context 'when using persisted stages', :request_store do
+ before do
+ stub_feature_flags(ci_pipeline_persisted_stages: true)
+ end
+
+ it 'returns serialized pipelines', :request_store do
+ queries = ActiveRecord::QueryRecorder.new do
+ get_pipelines_index_json
+ end
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('pipeline')
+
+ expect(json_response).to include('pipelines')
+ expect(json_response['pipelines'].count).to eq 5
+ expect(json_response['count']['all']).to eq '5'
+ expect(json_response['count']['running']).to eq '1'
+ expect(json_response['count']['pending']).to eq '1'
+ expect(json_response['count']['finished']).to eq '3'
+
+ json_response.dig('pipelines', 0, 'details', 'stages').tap do |stages|
+ expect(stages.count).to eq 3
+ end
+
+ expect(queries.count).to be
+ end
end
- it 'returns JSON with serialized pipelines' do
- subject
+ context 'when using legacy stages', :request_store do
+ before do
+ stub_feature_flags(ci_pipeline_persisted_stages: false)
+ end
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('pipeline')
+ it 'returns JSON with serialized pipelines', :request_store do
+ queries = ActiveRecord::QueryRecorder.new do
+ get_pipelines_index_json
+ end
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('pipeline')
- expect(json_response).to include('pipelines')
- expect(json_response['pipelines'].count).to eq 4
- expect(json_response['count']['all']).to eq '4'
- expect(json_response['count']['running']).to eq '1'
- expect(json_response['count']['pending']).to eq '1'
- expect(json_response['count']['finished']).to eq '1'
+ expect(json_response).to include('pipelines')
+ expect(json_response['pipelines'].count).to eq 5
+ expect(json_response['count']['all']).to eq '5'
+ expect(json_response['count']['running']).to eq '1'
+ expect(json_response['count']['pending']).to eq '1'
+ expect(json_response['count']['finished']).to eq '3'
+
+ json_response.dig('pipelines', 0, 'details', 'stages').tap do |stages|
+ expect(stages.count).to eq 3
+ end
+
+ expect(queries.count).to be_within(3).of(30)
+ end
end
it 'does not include coverage data for the pipelines' do
- subject
+ get_pipelines_index_json
expect(json_response['pipelines'][0]).not_to include('coverage')
end
context 'when performing gitaly calls', :request_store do
it 'limits the Gitaly requests' do
- expect { subject }.to change { Gitlab::GitalyClient.get_request_count }.by(3)
+ expect { get_pipelines_index_json }
+ .to change { Gitlab::GitalyClient.get_request_count }.by(2)
end
end
+
+ def get_pipelines_index_json
+ get :index, namespace_id: project.namespace,
+ project_id: project,
+ format: :json
+ end
+
+ def create_pipeline(status, sha)
+ pipeline = create(:ci_empty_pipeline, status: status,
+ project: project,
+ sha: sha)
+
+ create_build(pipeline, 'build', 1, 'build')
+ create_build(pipeline, 'test', 2, 'test')
+ create_build(pipeline, 'deploy', 3, 'deploy')
+ end
+
+ def create_build(pipeline, stage, stage_idx, name)
+ status = %w[created running pending success failed canceled].sample
+ create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name, status: status)
+ end
end
- describe 'GET show JSON' do
+ describe 'GET show.json' do
let(:pipeline) { create(:ci_pipeline_with_one_job, project: project) }
it 'returns the pipeline' do
@@ -67,6 +126,14 @@ describe Projects::PipelinesController do
end
context 'when the pipeline has multiple stages and groups', :request_store do
+ let(:project) { create(:project, :repository) }
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project,
+ user: user,
+ sha: project.commit.id)
+ end
+
before do
create_build('build', 0, 'build')
create_build('test', 1, 'rspec 0')
@@ -74,11 +141,6 @@ describe Projects::PipelinesController do
create_build('post deploy', 3, 'pages 0')
end
- let(:project) { create(:project, :repository) }
- 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
@@ -90,6 +152,7 @@ describe Projects::PipelinesController do
create_build('post deploy', 3, 'pages 2')
new_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count
+
expect(new_count).to be_within(12).of(control_count)
end
end
@@ -190,7 +253,7 @@ describe Projects::PipelinesController do
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
- expect(json_response['favicon']).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico")
+ expect(json_response['favicon']).to match_asset_path("/assets/ci_favicons/#{status.favicon}.png")
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 994da3cd159..705b30f0130 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -6,8 +6,8 @@ describe ProjectsController do
let(:project) { create(:project) }
let(:public_project) { create(:project, :public) }
let(:user) { create(:user) }
- let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
- let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+ let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
+ let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
describe 'GET new' do
context 'with an authenticated user' do
@@ -296,16 +296,22 @@ describe ProjectsController do
shared_examples_for 'updating a project' do
context 'when only renaming a project path' do
it "sets the repository to the right path after a rename" do
- original_repository_path = project.repository.path
+ original_repository_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.path
+ end
expect { update_project path: 'renamed_path' }
.to change { project.reload.path }
expect(project.path).to include 'renamed_path'
+ assign_repository_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ assigns(:repository).path
+ end
+
if project.hashed_storage?(:repository)
- expect(assigns(:repository).path).to eq(original_repository_path)
+ expect(assign_repository_path).to eq(original_repository_path)
else
- expect(assigns(:repository).path).to include(project.path)
+ expect(assign_repository_path).to include(project.path)
end
expect(response).to have_gitlab_http_status(302)
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index 346944fd5b0..898f3863008 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe RegistrationsController do
+ include TermsHelper
+
describe '#create' do
let(:user_params) { { user: { name: 'new_user', username: 'new_username', email: 'new@user.com', password: 'Any_password' } } }
@@ -67,6 +69,25 @@ describe RegistrationsController do
expect(flash[:notice]).to include 'Welcome! You have signed up successfully.'
end
end
+
+ context 'when terms are enforced' do
+ before do
+ enforce_terms
+ end
+
+ it 'redirects back with a notice when the checkbox was not checked' do
+ post :create, user_params
+
+ expect(flash[:alert]).to match /you must accept our terms/i
+ end
+
+ it 'creates the user with agreement when terms are accepted' do
+ post :create, user_params.merge(terms_opt_in: '1')
+
+ expect(subject.current_user).to be_present
+ expect(subject.current_user.terms_accepted?).to be(true)
+ end
+ end
end
describe '#destroy' do
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index 30c06ddf744..416a09e1684 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -32,7 +32,7 @@ describe SearchController do
it 'still blocks searches without a project_id' do
get :show, search: 'hello'
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(403)
end
it 'allows searches with a project_id' do
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 555b186fe31..2b61e0d4a85 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -257,15 +257,15 @@ describe SessionsController do
end
end
- describe '#new' do
+ describe "#new" do
before do
set_devise_mapping(context: @request)
end
- it 'redirects correctly for referer on same host with params' do
- search_path = '/search?search=seed_project'
- allow(controller.request).to receive(:referer)
- .and_return('http://%{host}%{path}' % { host: 'test.host', path: search_path })
+ it "redirects correctly for referer on same host with params" do
+ host = "test.host"
+ search_path = "/search?search=seed_project"
+ request.headers[:HTTP_REFERER] = "http://#{host}#{search_path}"
get(:new, redirect_to_referer: :yes)
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 376b229ffc9..eb94d395a9e 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -6,13 +6,13 @@ shared_examples 'content not cached without revalidation' do
end
describe UploadsController do
- let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
describe 'POST create' do
let(:model) { 'personal_snippet' }
let(:snippet) { create(:personal_snippet, :public) }
- let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
- let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+ let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
+ let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
context 'when a user does not have permissions to upload a file' do
it "returns 401 when the user is not logged in" do
@@ -136,7 +136,7 @@ describe UploadsController do
context 'for PNG files' do
it 'returns Content-Disposition: inline' do
note = create(:note, :with_attachment, project: project)
- get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
expect(response['Content-Disposition']).to start_with('inline;')
end
@@ -145,7 +145,7 @@ describe UploadsController do
context 'for SVG files' do
it 'returns Content-Disposition: attachment' do
note = create(:note, :with_svg_attachment, project: project)
- get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.svg'
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'unsanitized.svg'
expect(response['Content-Disposition']).to start_with('attachment;')
end
@@ -164,7 +164,7 @@ describe UploadsController do
end
it "redirects to the sign in page" do
- get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png"
+ get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png"
expect(response).to redirect_to(new_user_session_path)
end
@@ -172,14 +172,14 @@ describe UploadsController do
context "when the user isn't blocked" do
it "responds with status 200" do
- get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png"
+ get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'image.png'
+ get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png'
response
end
@@ -189,14 +189,14 @@ describe UploadsController do
context "when not signed in" do
it "responds with status 200" do
- get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png"
+ get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'image.png'
+ get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png'
response
end
@@ -205,7 +205,7 @@ describe UploadsController do
end
context "when viewing a project avatar" do
- let!(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let!(:project) { create(:project, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
context "when the project is public" do
before do
@@ -214,14 +214,14 @@ describe UploadsController do
context "when not signed in" do
it "responds with status 200" do
- get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
+ get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png'
+ get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png'
response
end
@@ -234,14 +234,14 @@ describe UploadsController do
end
it "responds with status 200" do
- get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
+ get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png'
+ get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png'
response
end
@@ -256,7 +256,7 @@ describe UploadsController do
context "when not signed in" do
it "redirects to the sign in page" do
- get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
+ get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
expect(response).to redirect_to(new_user_session_path)
end
@@ -279,7 +279,7 @@ describe UploadsController do
end
it "redirects to the sign in page" do
- get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
+ get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
expect(response).to redirect_to(new_user_session_path)
end
@@ -287,14 +287,14 @@ describe UploadsController do
context "when the user isn't blocked" do
it "responds with status 200" do
- get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
+ get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png'
+ get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png'
response
end
@@ -304,7 +304,7 @@ describe UploadsController do
context "when the user doesn't have access to the project" do
it "responds with status 404" do
- get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
+ get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(404)
end
@@ -314,19 +314,19 @@ describe UploadsController do
end
context "when viewing a group avatar" do
- let!(:group) { create(:group, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let!(:group) { create(:group, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
context "when the group is public" do
context "when not signed in" do
it "responds with status 200" do
- get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
+ get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png'
+ get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png'
response
end
@@ -339,14 +339,14 @@ describe UploadsController do
end
it "responds with status 200" do
- get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
+ get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png'
+ get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png'
response
end
@@ -375,7 +375,7 @@ describe UploadsController do
end
it "redirects to the sign in page" do
- get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
+ get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
expect(response).to redirect_to(new_user_session_path)
end
@@ -383,14 +383,14 @@ describe UploadsController do
context "when the user isn't blocked" do
it "responds with status 200" do
- get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
+ get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png'
+ get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png'
response
end
@@ -400,7 +400,7 @@ describe UploadsController do
context "when the user doesn't have access to the project" do
it "responds with status 404" do
- get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
+ get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(404)
end
@@ -420,14 +420,14 @@ describe UploadsController do
context "when not signed in" do
it "responds with status 200" do
- get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
+ get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
response
end
@@ -440,14 +440,14 @@ describe UploadsController do
end
it "responds with status 200" do
- get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
+ get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
response
end
@@ -462,7 +462,7 @@ describe UploadsController do
context "when not signed in" do
it "redirects to the sign in page" do
- get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
+ get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
expect(response).to redirect_to(new_user_session_path)
end
@@ -485,7 +485,7 @@ describe UploadsController do
end
it "redirects to the sign in page" do
- get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
+ get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
expect(response).to redirect_to(new_user_session_path)
end
@@ -493,14 +493,14 @@ describe UploadsController do
context "when the user isn't blocked" do
it "responds with status 200" do
- get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
+ get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
response
end
@@ -510,7 +510,7 @@ describe UploadsController do
context "when the user doesn't have access to the project" do
it "responds with status 404" do
- get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
+ get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
expect(response).to have_gitlab_http_status(404)
end
@@ -521,7 +521,7 @@ describe UploadsController do
context 'Appearance' do
context 'when viewing a custom header logo' do
- let!(:appearance) { create :appearance, header_logo: fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'), 'image/png') }
+ let!(:appearance) { create :appearance, header_logo: fixture_file_upload('spec/fixtures/dk.png', 'image/png') }
context 'when not signed in' do
it 'responds with status 200' do
@@ -541,7 +541,7 @@ describe UploadsController do
end
context 'when viewing a custom logo' do
- let!(:appearance) { create :appearance, logo: fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'), 'image/png') }
+ let!(:appearance) { create :appearance, logo: fixture_file_upload('spec/fixtures/dk.png', 'image/png') }
context 'when not signed in' do
it 'responds with status 200' do
@@ -560,5 +560,26 @@ describe UploadsController do
end
end
end
+
+ context 'original filename or a version filename must match' do
+ let!(:appearance) { create :appearance, favicon: fixture_file_upload('spec/fixtures/dk.png', 'image/png') }
+
+ context 'has a valid filename on the original file' do
+ it 'successfully returns the file' do
+ get :show, model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'dk.png'
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['Content-Disposition']).to end_with 'filename="dk.png"'
+ end
+ end
+
+ context 'has an invalid filename on the original file' do
+ it 'returns a 404' do
+ get :show, model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'bogus.png'
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
end
end
diff --git a/spec/controllers/users/terms_controller_spec.rb b/spec/controllers/users/terms_controller_spec.rb
index a744463413c..0d77e91a67d 100644
--- a/spec/controllers/users/terms_controller_spec.rb
+++ b/spec/controllers/users/terms_controller_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe Users::TermsController do
+ include TermsHelper
let(:user) { create(:user) }
let(:term) { create(:term) }
@@ -15,10 +16,25 @@ describe Users::TermsController do
expect(response).to have_gitlab_http_status(:redirect)
end
- it 'shows terms when they exist' do
- term
+ context 'when terms exist' do
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ term
+ end
+
+ it 'shows terms when they exist' do
+ get :index
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
- expect(response).to have_gitlab_http_status(:success)
+ it 'shows a message when the user already accepted the terms' do
+ accept_terms(user)
+
+ get :index
+
+ expect(controller).to set_flash.now[:notice].to(/already accepted/)
+ end
end
end
diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb
index eaf3a4ed497..c909471bb55 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -7,7 +7,7 @@ FactoryBot.define do
end
trait :with_file do
- file { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") }
+ file { fixture_file_upload("spec/fixtures/dk.png", "`/png") }
end
# The uniqueness constraint means we can't use the correct OID for all LFS
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index fab0ec22450..3441ce1b8cb 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -54,6 +54,11 @@ FactoryBot.define do
state :opened
end
+ trait :invalid do
+ source_branch "feature_one"
+ target_branch "feature_two"
+ end
+
trait :locked do
state :locked
end
@@ -98,6 +103,7 @@ FactoryBot.define do
factory :merged_merge_request, traits: [:merged]
factory :closed_merge_request, traits: [:closed]
factory :reopened_merge_request, traits: [:opened]
+ factory :invalid_merge_request, traits: [:invalid]
factory :merge_request_with_diffs, traits: [:with_diffs]
factory :merge_request_with_diff_notes do
after(:create) do |mr|
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 40f3fa7d69b..9fdc3e616a6 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -130,11 +130,11 @@ FactoryBot.define do
end
trait :with_attachment do
- attachment { fixture_file_upload(Rails.root.join( "spec/fixtures/dk.png"), "image/png") }
+ attachment { fixture_file_upload("spec/fixtures/dk.png", "image/png") }
end
trait :with_svg_attachment do
- attachment { fixture_file_upload(Rails.root.join("spec/fixtures/unsanitized.svg"), "image/svg+xml") }
+ attachment { fixture_file_upload("spec/fixtures/unsanitized.svg", "image/svg+xml") }
end
transient do
diff --git a/spec/factories/project_auto_devops.rb b/spec/factories/project_auto_devops.rb
index 5ce1988c76f..b77f702f9e1 100644
--- a/spec/factories/project_auto_devops.rb
+++ b/spec/factories/project_auto_devops.rb
@@ -3,5 +3,14 @@ FactoryBot.define do
project
enabled true
domain "example.com"
+ deploy_strategy :continuous
+
+ trait :manual do
+ deploy_strategy :manual
+ end
+
+ trait :disabled do
+ enabled false
+ end
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 16e025618a6..f6b05bac0e8 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -151,11 +151,6 @@ FactoryBot.define do
trait :empty_repo do
after(:create) do |project|
raise "Failed to create repository!" unless project.create_repository
-
- # We delete hooks so that gitlab-shell will not try to authenticate with
- # an API that isn't running
- project.gitlab_shell.rm_directory(project.repository_storage,
- File.join("#{project.disk_path}.git", 'hooks'))
end
end
@@ -180,13 +175,6 @@ FactoryBot.define do
trait :wiki_repo do
after(:create) do |project|
raise 'Failed to create wiki repository!' unless project.create_wiki
-
- # We delete hooks so that gitlab-shell will not try to authenticate with
- # an API that isn't running
- project.gitlab_shell.rm_directory(
- project.repository_storage,
- File.join("#{project.wiki.repository.disk_path}.git", "hooks")
- )
end
end
diff --git a/spec/factories/term_agreements.rb b/spec/factories/term_agreements.rb
index 557599e663d..3c4eebd0196 100644
--- a/spec/factories/term_agreements.rb
+++ b/spec/factories/term_agreements.rb
@@ -3,4 +3,12 @@ FactoryBot.define do
term
user
end
+
+ trait :declined do
+ accepted false
+ end
+
+ trait :accepted do
+ accepted true
+ end
end
diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb
index d91dcf76191..a5e0ac592b9 100644
--- a/spec/features/admin/admin_appearance_spec.rb
+++ b/spec/features/admin/admin_appearance_spec.rb
@@ -76,6 +76,26 @@ feature 'Admin Appearance' do
expect(page).not_to have_css(header_logo_selector)
end
+ scenario 'Favicon' do
+ sign_in(create(:admin))
+ visit admin_appearances_path
+
+ attach_file(:appearance_favicon, logo_fixture)
+ click_button 'Save'
+
+ expect(page).to have_css('.appearance-light-logo-preview')
+
+ click_link 'Remove favicon'
+
+ expect(page).not_to have_css('.appearance-light-logo-preview')
+
+ # allowed file types
+ attach_file(:appearance_favicon, Rails.root.join('spec', 'fixtures', 'sanitized.svg'))
+ click_button 'Save'
+
+ expect(page).to have_content 'Favicon You are not allowed to upload "svg" files, allowed types: png, ico'
+ end
+
def expect_custom_sign_in_appearance(appearance)
expect(page).to have_content appearance.title
expect(page).to have_content appearance.description
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index dc025d82937..e7aca94db66 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -94,7 +94,7 @@ feature 'Admin updates settings' do
accept_terms(admin)
page.within('.as-terms') do
- check 'Require all users to accept Terms of Service when they access GitLab.'
+ check 'Require all users to accept Terms of Service and Privacy Policy when they access GitLab.'
fill_in 'Terms of Service Agreement', with: 'Be nice!'
click_button 'Save changes'
end
diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb
index 90cf5a53787..7371a494d36 100644
--- a/spec/features/admin/admin_uses_repository_checks_spec.rb
+++ b/spec/features/admin/admin_uses_repository_checks_spec.rb
@@ -28,7 +28,7 @@ feature 'Admin uses repository checks' do
visit_admin_project_page(project)
page.within('.alert') do
- expect(page.text).to match(/Last repository check \(.* ago\) failed/)
+ expect(page.text).to match(/Last repository check \(just now\) failed/)
end
end
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index e414345ac23..f6e0dee28c6 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -150,7 +150,7 @@ describe 'Issue Boards', :js do
click_button 'Add list'
wait_for_requests
- find('.dropdown-menu-close').click
+ find('.js-new-board-list').click
page.within(find('.board:nth-child(2)')) do
accept_confirm { find('.board-delete').click }
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 62a2ec55b00..87fa3f60826 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -47,7 +47,7 @@ describe 'Commits' do
context 'commit status is Ci Build' do
let!(:build) { create(:ci_build, pipeline: pipeline) }
- let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
+ let(:artifacts_file) { fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
context 'when logged as developer' do
before do
diff --git a/spec/features/ics/dashboard_issues_spec.rb b/spec/features/ics/dashboard_issues_spec.rb
index 5d6cd44ad1c..90d02f7e40f 100644
--- a/spec/features/ics/dashboard_issues_spec.rb
+++ b/spec/features/ics/dashboard_issues_spec.rb
@@ -11,13 +11,25 @@ describe 'Dashboard Issues Calendar Feed' do
end
context 'when authenticated' do
- it 'renders calendar feed' do
- sign_in user
- visit issues_dashboard_path(:ics)
+ context 'with no referer' do
+ it 'renders calendar feed' do
+ sign_in user
+ visit issues_dashboard_path(:ics)
- expect(response_headers['Content-Type']).to have_content('text/calendar')
- expect(response_headers['Content-Disposition']).to have_content('inline')
- expect(body).to have_text('BEGIN:VCALENDAR')
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
+
+ context 'with GitLab as the referer' do
+ it 'renders calendar feed as text/plain' do
+ sign_in user
+ page.driver.header('Referer', issues_dashboard_url(host: Settings.gitlab.base_url))
+ visit issues_dashboard_path(:ics)
+
+ expect(response_headers['Content-Type']).to have_content('text/plain')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
end
end
@@ -28,7 +40,6 @@ describe 'Dashboard Issues Calendar Feed' do
visit issues_dashboard_path(:ics, private_token: personal_access_token.token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
- expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
@@ -38,7 +49,6 @@ describe 'Dashboard Issues Calendar Feed' do
visit issues_dashboard_path(:ics, feed_token: user.feed_token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
- expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
diff --git a/spec/features/ics/group_issues_spec.rb b/spec/features/ics/group_issues_spec.rb
index 0a049be2ffe..24de5b4b7c6 100644
--- a/spec/features/ics/group_issues_spec.rb
+++ b/spec/features/ics/group_issues_spec.rb
@@ -13,13 +13,25 @@ describe 'Group Issues Calendar Feed' do
end
context 'when authenticated' do
- it 'renders calendar feed' do
- sign_in user
- visit issues_group_path(group, :ics)
+ context 'with no referer' do
+ it 'renders calendar feed' do
+ sign_in user
+ visit issues_group_path(group, :ics)
- expect(response_headers['Content-Type']).to have_content('text/calendar')
- expect(response_headers['Content-Disposition']).to have_content('inline')
- expect(body).to have_text('BEGIN:VCALENDAR')
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
+
+ context 'with GitLab as the referer' do
+ it 'renders calendar feed as text/plain' do
+ sign_in user
+ page.driver.header('Referer', issues_group_url(group, host: Settings.gitlab.base_url))
+ visit issues_group_path(group, :ics)
+
+ expect(response_headers['Content-Type']).to have_content('text/plain')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
end
end
@@ -30,7 +42,6 @@ describe 'Group Issues Calendar Feed' do
visit issues_group_path(group, :ics, private_token: personal_access_token.token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
- expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
@@ -40,7 +51,6 @@ describe 'Group Issues Calendar Feed' do
visit issues_group_path(group, :ics, feed_token: user.feed_token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
- expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
diff --git a/spec/features/ics/project_issues_spec.rb b/spec/features/ics/project_issues_spec.rb
index b99e9607f1d..2ca3d52a5be 100644
--- a/spec/features/ics/project_issues_spec.rb
+++ b/spec/features/ics/project_issues_spec.rb
@@ -12,13 +12,25 @@ describe 'Project Issues Calendar Feed' do
end
context 'when authenticated' do
- it 'renders calendar feed' do
- sign_in user
- visit project_issues_path(project, :ics)
+ context 'with no referer' do
+ it 'renders calendar feed' do
+ sign_in user
+ visit project_issues_path(project, :ics)
- expect(response_headers['Content-Type']).to have_content('text/calendar')
- expect(response_headers['Content-Disposition']).to have_content('inline')
- expect(body).to have_text('BEGIN:VCALENDAR')
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
+
+ context 'with GitLab as the referer' do
+ it 'renders calendar feed as text/plain' do
+ sign_in user
+ page.driver.header('Referer', project_issues_url(project, host: Settings.gitlab.base_url))
+ visit project_issues_path(project, :ics)
+
+ expect(response_headers['Content-Type']).to have_content('text/plain')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
end
end
@@ -29,7 +41,6 @@ describe 'Project Issues Calendar Feed' do
visit project_issues_path(project, :ics, private_token: personal_access_token.token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
- expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
@@ -39,7 +50,6 @@ describe 'Project Issues Calendar Feed' do
visit project_issues_path(project, :ics, feed_token: user.feed_token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
- expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index cbd0949c192..c8115db9212 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -31,7 +31,7 @@ describe 'Dropdown assignee', :js do
describe 'behavior' do
it 'opens when the search bar has assignee:' do
- filtered_search.set('assignee:')
+ input_filtered_search('assignee:', submit: false, extra_space: false)
expect(page).to have_css(js_dropdown_assignee, visible: true)
end
@@ -44,6 +44,7 @@ describe 'Dropdown assignee', :js do
it 'should show loading indicator when opened' do
slow_requests do
+ # We aren't using `input_filtered_search` because we want to see the loading indicator
filtered_search.set('assignee:')
expect(page).to have_css('#js-dropdown-assignee .filter-dropdown-loading', visible: true)
@@ -51,19 +52,19 @@ describe 'Dropdown assignee', :js do
end
it 'should hide loading indicator when loaded' do
- filtered_search.set('assignee:')
+ input_filtered_search('assignee:', submit: false, extra_space: false)
expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading')
end
it 'should load all the assignees when opened' do
- filtered_search.set('assignee:')
+ input_filtered_search('assignee:', submit: false, extra_space: false)
expect(dropdown_assignee_size).to eq(4)
end
it 'shows current user at top of dropdown' do
- filtered_search.set('assignee:')
+ input_filtered_search('assignee:', submit: false, extra_space: false)
expect(filter_dropdown.first('.filter-dropdown-item')).to have_content(user.name)
end
@@ -71,7 +72,7 @@ describe 'Dropdown assignee', :js do
describe 'filtering' do
before do
- filtered_search.set('assignee:')
+ input_filtered_search('assignee:', submit: false, extra_space: false)
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name)
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
@@ -79,23 +80,21 @@ describe 'Dropdown assignee', :js do
end
it 'filters by name' do
- filtered_search.send_keys('j')
+ input_filtered_search('jac', submit: false, extra_space: false)
- expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name)
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user.name)
end
it 'filters by case insensitive name' do
- filtered_search.send_keys('J')
+ input_filtered_search('JAC', submit: false, extra_space: false)
- expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name)
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user.name)
end
it 'filters by username with symbol' do
- filtered_search.send_keys('@ot')
+ input_filtered_search('@ott', submit: false, extra_space: false)
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
@@ -103,7 +102,7 @@ describe 'Dropdown assignee', :js do
end
it 'filters by case insensitive username with symbol' do
- filtered_search.send_keys('@OT')
+ input_filtered_search('@OTT', submit: false, extra_space: false)
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
@@ -111,7 +110,9 @@ describe 'Dropdown assignee', :js do
end
it 'filters by username without symbol' do
- filtered_search.send_keys('ot')
+ input_filtered_search('ott', submit: false, extra_space: false)
+
+ wait_for_requests
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
@@ -119,7 +120,9 @@ describe 'Dropdown assignee', :js do
end
it 'filters by case insensitive username without symbol' do
- filtered_search.send_keys('OT')
+ input_filtered_search('OTT', submit: false, extra_space: false)
+
+ wait_for_requests
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
@@ -129,7 +132,7 @@ describe 'Dropdown assignee', :js do
describe 'selecting from dropdown' do
before do
- filtered_search.set('assignee:')
+ input_filtered_search('assignee:', submit: false, extra_space: false)
end
it 'fills in the assignee username when the assignee has not been filtered' do
@@ -143,7 +146,7 @@ describe 'Dropdown assignee', :js do
end
it 'fills in the assignee username when the assignee has been filtered' do
- filtered_search.send_keys('roo')
+ input_filtered_search('roo', submit: false, extra_space: false)
click_assignee(user.name)
wait_for_requests
@@ -165,7 +168,7 @@ describe 'Dropdown assignee', :js do
describe 'selecting from dropdown without Ajax call' do
before do
Gitlab::Testing::RequestBlockerMiddleware.block_requests!
- filtered_search.set('assignee:')
+ input_filtered_search('assignee:', submit: false, extra_space: false)
end
after do
@@ -183,31 +186,31 @@ describe 'Dropdown assignee', :js do
describe 'input has existing content' do
it 'opens assignee dropdown with existing search term' do
- filtered_search.set('searchTerm assignee:')
+ input_filtered_search('searchTerm assignee:', submit: false, extra_space: false)
expect(page).to have_css(js_dropdown_assignee, visible: true)
end
it 'opens assignee dropdown with existing author' do
- filtered_search.set('author:@user assignee:')
+ input_filtered_search('author:@user assignee:', submit: false, extra_space: false)
expect(page).to have_css(js_dropdown_assignee, visible: true)
end
it 'opens assignee dropdown with existing label' do
- filtered_search.set('label:~bug assignee:')
+ input_filtered_search('label:~bug assignee:', submit: false, extra_space: false)
expect(page).to have_css(js_dropdown_assignee, visible: true)
end
it 'opens assignee dropdown with existing milestone' do
- filtered_search.set('milestone:%v1.0 assignee:')
+ input_filtered_search('milestone:%v1.0 assignee:', submit: false, extra_space: false)
expect(page).to have_css(js_dropdown_assignee, visible: true)
end
it 'opens assignee dropdown with existing my-reaction' do
- filtered_search.set('my-reaction:star assignee:')
+ input_filtered_search('my-reaction:star assignee:', submit: false, extra_space: false)
expect(page).to have_css(js_dropdown_assignee, visible: true)
end
@@ -215,8 +218,7 @@ describe 'Dropdown assignee', :js do
describe 'caching requests' do
it 'caches requests after the first load' do
- filtered_search.set('assignee')
- filtered_search.send_keys(':')
+ input_filtered_search('assignee:', submit: false, extra_space: false)
initial_size = dropdown_assignee_size
expect(initial_size).to be > 0
@@ -224,8 +226,7 @@ describe 'Dropdown assignee', :js do
new_user = create(:user)
project.add_master(new_user)
find('.filtered-search-box .clear-search').click
- filtered_search.set('assignee')
- filtered_search.send_keys(':')
+ input_filtered_search('assignee:', submit: false, extra_space: false)
expect(dropdown_assignee_size).to eq(initial_size)
end
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 4625a50b8d9..2cb3ae08b0e 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -143,6 +143,9 @@ describe 'New/edit issue', :js do
click_link label.title
click_link label2.title
end
+
+ find('.js-issuable-form-dropdown.js-label-select').click
+
page.within '.js-label-select' do
expect(page).to have_content label.title
end
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index fd0aa6cf3a3..17818beb947 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -153,6 +153,42 @@ feature 'Issues > User uses quick actions', :js do
end
end
+ describe 'make issue confidential' do
+ let(:issue) { create(:issue, project: project) }
+ let(:original_issue) { create(:issue, project: project) }
+
+ context 'when the current user can update issues' do
+ it 'does not create a note, and marks the issue as confidential' do
+ add_note("/confidential")
+
+ expect(page).not_to have_content "/confidential"
+ expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "made the issue confidential"
+
+ expect(issue.reload).to be_confidential
+ end
+ end
+
+ context 'when the current user cannot update the issue' do
+ let(:guest) { create(:user) }
+ before do
+ project.add_guest(guest)
+ gitlab_sign_out
+ sign_in(guest)
+ visit project_issue_path(project, issue)
+ end
+
+ it 'does not create a note, and does not mark the issue as confidential' do
+ add_note("/confidential")
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(page).not_to have_content "made the issue confidential"
+
+ expect(issue.reload).not_to be_confidential
+ end
+ end
+ end
+
describe 'move the issue to another project' do
let(:issue) { create(:issue, project: project) }
@@ -190,7 +226,9 @@ feature 'Issues > User uses quick actions', :js do
it 'does not move the issue' do
add_note("/move #{project_unauthorized.full_path}")
- expect(page).not_to have_content 'Commands applied'
+ wait_for_requests
+
+ expect(page).to have_content 'Commands applied'
expect(issue.reload).to be_open
end
end
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index 4700ada1aae..5573148f8bc 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -34,7 +34,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do
wait_for_requests
- expect(page).to have_selector('span.badge', text: label.title)
+ expect(page).to have_selector('.badge', text: label.title)
end
end
@@ -45,7 +45,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do
wait_for_requests
- expect(page).not_to have_selector('span.badge', text: child_group_label.title)
+ expect(page).not_to have_selector('.badge', text: child_group_label.title)
end
end
diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb
index 4d897f09b57..05228e27963 100644
--- a/spec/features/markdown/copy_as_gfm_spec.rb
+++ b/spec/features/markdown/copy_as_gfm_spec.rb
@@ -502,6 +502,13 @@ describe 'Copy as GFM', :js do
1. Numbered lists
GFM
+ # list item followed by an HR
+ <<-GFM.strip_heredoc,
+ - list item
+
+ -----
+ GFM
+
'# Heading',
'## Heading',
'### Heading',
@@ -515,8 +522,6 @@ describe 'Copy as GFM', :js do
'~~Strikethrough~~',
- '2^2',
-
'-----',
# table
diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb
index f13d78d24e3..cac8a5068ec 100644
--- a/spec/features/markdown/markdown_spec.rb
+++ b/spec/features/markdown/markdown_spec.rb
@@ -24,7 +24,7 @@ require 'erb'
#
# See the MarkdownFeature class for setup details.
-describe 'GitLab Markdown' do
+describe 'GitLab Markdown', :aggregate_failures do
include Capybara::Node::Matchers
include MarkupHelper
include MarkdownMatchers
@@ -44,112 +44,106 @@ describe 'GitLab Markdown' do
# Shared behavior that all pipelines should exhibit
shared_examples 'all pipelines' do
- describe 'Redcarpet extensions' do
- it 'does not parse emphasis inside of words' do
+ it 'includes extensions' do
+ aggregate_failures 'does not parse emphasis inside of words' do
expect(doc.to_html).not_to match('foo<em>bar</em>baz')
end
- it 'parses table Markdown' do
- aggregate_failures do
- expect(doc).to have_selector('th:contains("Header")')
- expect(doc).to have_selector('th:contains("Row")')
- expect(doc).to have_selector('th:contains("Example")')
- end
+ aggregate_failures 'parses table Markdown' do
+ expect(doc).to have_selector('th:contains("Header")')
+ expect(doc).to have_selector('th:contains("Row")')
+ expect(doc).to have_selector('th:contains("Example")')
end
- it 'allows Markdown in tables' do
+ aggregate_failures 'allows Markdown in tables' do
expect(doc.at_css('td:contains("Baz")').children.to_html)
.to eq '<strong>Baz</strong>'
end
- it 'parses fenced code blocks' do
- aggregate_failures do
- expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.c')
- expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.python')
- end
+ aggregate_failures 'parses fenced code blocks' do
+ expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.c')
+ expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.python')
end
- it 'parses mermaid code block' do
- aggregate_failures do
- expect(doc).to have_selector('pre[lang=mermaid] > code.js-render-mermaid')
- end
+ aggregate_failures 'parses mermaid code block' do
+ expect(doc).to have_selector('pre[lang=mermaid] > code.js-render-mermaid')
end
- it 'parses strikethroughs' do
+ aggregate_failures 'parses strikethroughs' do
expect(doc).to have_selector(%{del:contains("and this text doesn't")})
end
-
- it 'parses superscript' do
- expect(doc).to have_selector('sup', count: 2)
- end
end
- describe 'SanitizationFilter' do
- it 'permits b elements' do
+ it 'includes SanitizationFilter' do
+ aggregate_failures 'permits b elements' do
expect(doc).to have_selector('b:contains("b tag")')
end
- it 'permits em elements' do
+ aggregate_failures 'permits em elements' do
expect(doc).to have_selector('em:contains("em tag")')
end
- it 'permits code elements' do
+ aggregate_failures 'permits code elements' do
expect(doc).to have_selector('code:contains("code tag")')
end
- it 'permits kbd elements' do
+ aggregate_failures 'permits kbd elements' do
expect(doc).to have_selector('kbd:contains("s")')
end
- it 'permits strike elements' do
+ aggregate_failures 'permits strike elements' do
expect(doc).to have_selector('strike:contains(Emoji)')
end
- it 'permits img elements' do
+ aggregate_failures 'permits img elements' do
expect(doc).to have_selector('img[data-src*="smile.png"]')
end
- it 'permits br elements' do
+ aggregate_failures 'permits br elements' do
expect(doc).to have_selector('br')
end
- it 'permits hr elements' do
+ aggregate_failures 'permits hr elements' do
expect(doc).to have_selector('hr')
end
- it 'permits span elements' do
+ aggregate_failures 'permits span elements' do
expect(doc).to have_selector('span:contains("span tag")')
end
- it 'permits details elements' do
+ aggregate_failures 'permits details elements' do
expect(doc).to have_selector('details:contains("Hiding the details")')
end
- it 'permits summary elements' do
+ aggregate_failures 'permits summary elements' do
expect(doc).to have_selector('details summary:contains("collapsible")')
end
- it 'permits style attribute in th elements' do
- aggregate_failures do
- expect(doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center'
- expect(doc.at_css('th:contains("Row")')['style']).to eq 'text-align: right'
- expect(doc.at_css('th:contains("Example")')['style']).to eq 'text-align: left'
- end
+ aggregate_failures 'permits align attribute in th elements' do
+ expect(doc.at_css('th:contains("Header")')['align']).to eq 'center'
+ expect(doc.at_css('th:contains("Row")')['align']).to eq 'right'
+ expect(doc.at_css('th:contains("Example")')['align']).to eq 'left'
end
- it 'permits style attribute in td elements' do
- aggregate_failures do
- expect(doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center'
- expect(doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right'
- expect(doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left'
- end
+ aggregate_failures 'permits align attribute in td elements' do
+ expect(doc.at_css('td:contains("Foo")')['align']).to eq 'center'
+ expect(doc.at_css('td:contains("Bar")')['align']).to eq 'right'
+ expect(doc.at_css('td:contains("Baz")')['align']).to eq 'left'
+ end
+
+ aggregate_failures 'permits superscript elements' do
+ expect(doc).to have_selector('sup', count: 2)
+ end
+
+ aggregate_failures 'permits subscript elements' do
+ expect(doc).to have_selector('sub', count: 3)
end
- it 'removes `rel` attribute from links' do
+ aggregate_failures 'removes `rel` attribute from links' do
expect(doc).not_to have_selector('a[rel="bookmark"]')
end
- it "removes `href` from `a` elements if it's fishy" do
+ aggregate_failures "removes `href` from `a` elements if it's fishy" do
expect(doc).not_to have_selector('a[href*="javascript"]')
end
end
@@ -176,26 +170,26 @@ describe 'GitLab Markdown' do
end
end
- describe 'ExternalLinkFilter' do
- it 'adds nofollow to external link' do
+ it 'includes ExternalLinkFilter' do
+ aggregate_failures 'adds nofollow to external link' do
link = doc.at_css('a:contains("Google")')
expect(link.attr('rel')).to include('nofollow')
end
- it 'adds noreferrer to external link' do
+ aggregate_failures 'adds noreferrer to external link' do
link = doc.at_css('a:contains("Google")')
expect(link.attr('rel')).to include('noreferrer')
end
- it 'adds _blank to target attribute for external links' do
+ aggregate_failures 'adds _blank to target attribute for external links' do
link = doc.at_css('a:contains("Google")')
expect(link.attr('target')).to match('_blank')
end
- it 'ignores internal link' do
+ aggregate_failures 'ignores internal link' do
link = doc.at_css('a:contains("GitLab Root")')
expect(link.attr('rel')).not_to match 'nofollow'
@@ -219,24 +213,24 @@ describe 'GitLab Markdown' do
it_behaves_like 'all pipelines'
- it 'includes RelativeLinkFilter' do
- expect(doc).to parse_relative_links
- end
+ it 'includes custom filters' do
+ aggregate_failures 'RelativeLinkFilter' do
+ expect(doc).to parse_relative_links
+ end
- it 'includes EmojiFilter' do
- expect(doc).to parse_emoji
- end
+ aggregate_failures 'EmojiFilter' do
+ expect(doc).to parse_emoji
+ end
- it 'includes TableOfContentsFilter' do
- expect(doc).to create_header_links
- end
+ aggregate_failures 'TableOfContentsFilter' do
+ expect(doc).to create_header_links
+ end
- it 'includes AutolinkFilter' do
- expect(doc).to create_autolinks
- end
+ aggregate_failures 'AutolinkFilter' do
+ expect(doc).to create_autolinks
+ end
- it 'includes all reference filters' do
- aggregate_failures do
+ aggregate_failures 'all reference filters' do
expect(doc).to reference_users
expect(doc).to reference_issues
expect(doc).to reference_merge_requests
@@ -246,22 +240,22 @@ describe 'GitLab Markdown' do
expect(doc).to reference_labels
expect(doc).to reference_milestones
end
- end
- it 'includes TaskListFilter' do
- expect(doc).to parse_task_lists
- end
+ aggregate_failures 'TaskListFilter' do
+ expect(doc).to parse_task_lists
+ end
- it 'includes InlineDiffFilter' do
- expect(doc).to parse_inline_diffs
- end
+ aggregate_failures 'InlineDiffFilter' do
+ expect(doc).to parse_inline_diffs
+ end
- it 'includes VideoLinkFilter' do
- expect(doc).to parse_video_links
- end
+ aggregate_failures 'VideoLinkFilter' do
+ expect(doc).to parse_video_links
+ end
- it 'includes ColorFilter' do
- expect(doc).to parse_colors
+ aggregate_failures 'ColorFilter' do
+ expect(doc).to parse_colors
+ end
end
end
@@ -280,24 +274,24 @@ describe 'GitLab Markdown' do
it_behaves_like 'all pipelines'
- it 'includes RelativeLinkFilter' do
- expect(doc).not_to parse_relative_links
- end
+ it 'includes custom filters' do
+ aggregate_failures 'RelativeLinkFilter' do
+ expect(doc).not_to parse_relative_links
+ end
- it 'includes EmojiFilter' do
- expect(doc).to parse_emoji
- end
+ aggregate_failures 'EmojiFilter' do
+ expect(doc).to parse_emoji
+ end
- it 'includes TableOfContentsFilter' do
- expect(doc).to create_header_links
- end
+ aggregate_failures 'TableOfContentsFilter' do
+ expect(doc).to create_header_links
+ end
- it 'includes AutolinkFilter' do
- expect(doc).to create_autolinks
- end
+ aggregate_failures 'AutolinkFilter' do
+ expect(doc).to create_autolinks
+ end
- it 'includes all reference filters' do
- aggregate_failures do
+ aggregate_failures 'all reference filters' do
expect(doc).to reference_users
expect(doc).to reference_issues
expect(doc).to reference_merge_requests
@@ -307,26 +301,51 @@ describe 'GitLab Markdown' do
expect(doc).to reference_labels
expect(doc).to reference_milestones
end
- end
- it 'includes TaskListFilter' do
- expect(doc).to parse_task_lists
- end
+ aggregate_failures 'TaskListFilter' do
+ expect(doc).to parse_task_lists
+ end
- it 'includes GollumTagsFilter' do
- expect(doc).to parse_gollum_tags
- end
+ aggregate_failures 'GollumTagsFilter' do
+ expect(doc).to parse_gollum_tags
+ end
+
+ aggregate_failures 'InlineDiffFilter' do
+ expect(doc).to parse_inline_diffs
+ end
- it 'includes InlineDiffFilter' do
- expect(doc).to parse_inline_diffs
+ aggregate_failures 'VideoLinkFilter' do
+ expect(doc).to parse_video_links
+ end
+
+ aggregate_failures 'ColorFilter' do
+ expect(doc).to parse_colors
+ end
end
+ end
- it 'includes VideoLinkFilter' do
- expect(doc).to parse_video_links
+ context 'Redcarpet documents' do
+ before do
+ allow_any_instance_of(Banzai::Filter::MarkdownFilter).to receive(:engine).and_return('Redcarpet')
+ @html = markdown(@feat.raw_markdown)
end
- it 'includes ColorFilter' do
- expect(doc).to parse_colors
+ it 'processes certain elements differently' do
+ aggregate_failures 'parses superscript' do
+ expect(doc).to have_selector('sup', count: 3)
+ end
+
+ aggregate_failures 'permits style attribute in th elements' do
+ expect(doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center'
+ expect(doc.at_css('th:contains("Row")')['style']).to eq 'text-align: right'
+ expect(doc.at_css('th:contains("Example")')['style']).to eq 'text-align: left'
+ end
+
+ aggregate_failures 'permits style attribute in td elements' do
+ expect(doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center'
+ expect(doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right'
+ expect(doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left'
+ end
end
end
diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb
index a3323da1b1f..1808d0c0a0c 100644
--- a/spec/features/merge_request/maintainer_edits_fork_spec.rb
+++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb
@@ -14,7 +14,7 @@ describe 'a maintainer edits files on a source-branch of an MR from a fork', :js
source_branch: 'fix',
target_branch: 'master',
author: author,
- allow_maintainer_to_push: true)
+ allow_collaboration: true)
end
before do
diff --git a/spec/features/merge_request/user_allows_a_maintainer_to_push_spec.rb b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb
index eb41d7de8ed..0af37d76539 100644
--- a/spec/features/merge_request/user_allows_a_maintainer_to_push_spec.rb
+++ b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'create a merge request that allows maintainers to push', :js do
+describe 'create a merge request, allowing commits from members who can merge to the target branch', :js do
include ProjectForksHelper
let(:user) { create(:user) }
let(:target_project) { create(:project, :public, :repository) }
@@ -21,16 +21,16 @@ describe 'create a merge request that allows maintainers to push', :js do
sign_in(user)
end
- it 'allows setting maintainer push possible' do
+ it 'allows setting possible' do
visit_new_merge_request
- check 'Allow edits from maintainers'
+ check 'Allow commits from members who can merge to the target branch'
click_button 'Submit merge request'
wait_for_requests
- expect(page).to have_content('Allows edits from maintainers')
+ expect(page).to have_content('Allows commits from members who can merge to the target branch')
end
it 'shows a message when one of the projects is private' do
@@ -57,12 +57,12 @@ describe 'create a merge request that allows maintainers to push', :js do
visit_new_merge_request
- expect(page).not_to have_content('Allows edits from maintainers')
+ expect(page).not_to have_content('Allows commits from members who can merge to the target branch')
end
end
- context 'when a maintainer tries to edit the option' do
- let(:maintainer) { create(:user) }
+ context 'when a member who can merge tries to edit the option' do
+ let(:member) { create(:user) }
let(:merge_request) do
create(:merge_request,
source_project: source_project,
@@ -71,15 +71,15 @@ describe 'create a merge request that allows maintainers to push', :js do
end
before do
- target_project.add_master(maintainer)
+ target_project.add_master(member)
- sign_in(maintainer)
+ sign_in(member)
end
- it 'it hides the option from maintainers' do
+ it 'it hides the option from members' do
visit edit_project_merge_request_path(target_project, merge_request)
- expect(page).not_to have_content('Allows edits from maintainers')
+ expect(page).not_to have_content('Allows commits from members who can merge to the target branch')
end
end
end
diff --git a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
index 7c4fd25bb39..25c408516d1 100644
--- a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
@@ -12,7 +12,7 @@ feature 'Merge request > User creates image diff notes', :js do
# Stub helper to return any blob file as image from public app folder.
# This is necessary to run this specs since we don't display repo images in capybara.
allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_url).and_return('/apple-touch-icon.png')
- allow_any_instance_of(DiffHelper).to receive(:diff_file_old_blob_raw_url).and_return('/favicon.ico')
+ allow_any_instance_of(DiffHelper).to receive(:diff_file_old_blob_raw_url).and_return('/favicon.png')
end
context 'create commit diff notes' do
diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb
index b54addce993..3bd9f5e2298 100644
--- a/spec/features/merge_request/user_posts_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_notes_spec.rb
@@ -139,7 +139,7 @@ describe 'Merge request > User posts notes', :js do
page.within("#note_#{note.id}") do
is_expected.to have_css('.note_edited_ago')
expect(find('.note_edited_ago').text)
- .to match(/less than a minute ago/)
+ .to match(/just now/)
end
end
end
diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
index fd1629746ef..d3104b448e0 100644
--- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
@@ -23,8 +23,8 @@ describe 'Merge request < User sees mini pipeline graph', :js do
end
context 'as json' do
- let(:artifacts_file1) { fixture_file_upload(Rails.root.join('spec/fixtures/banana_sample.gif'), 'image/gif') }
- let(:artifacts_file2) { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'), 'image/png') }
+ let(:artifacts_file1) { fixture_file_upload(File.join('spec/fixtures/banana_sample.gif'), 'image/gif') }
+ let(:artifacts_file2) { fixture_file_upload(File.join('spec/fixtures/dk.png'), 'image/png') }
before do
create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file1)
diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb
index 43a23c42f83..1552a3512dd 100644
--- a/spec/features/projects/deploy_keys_spec.rb
+++ b/spec/features/projects/deploy_keys_spec.rb
@@ -22,7 +22,8 @@ describe 'Project deploy keys', :js do
accept_confirm { find('.ic-remove').click() }
- expect(page).not_to have_selector('.fa-spinner', count: 0)
+ wait_for_requests
+
expect(page).to have_selector('.deploy-key', count: 0)
end
end
diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb
index c1307ab640f..9bfcb1e816a 100644
--- a/spec/features/projects/diffs/diff_show_spec.rb
+++ b/spec/features/projects/diffs/diff_show_spec.rb
@@ -166,8 +166,7 @@ feature 'Diff file viewer', :js do
context 'expanding the diff' do
before do
- # We can't use `click_link` because the "link" doesn't have an `href`.
- find('a.click-to-expand').click
+ click_button 'Click to expand it.'
wait_for_requests
end
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 60fe30bd898..d0912e645bc 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -87,11 +87,13 @@ feature 'Import/Export - project import integration test', :js do
def wiki_exists?(project)
wiki = ProjectWiki.new(project)
- File.exist?(wiki.repository.path_to_repo) && !wiki.repository.empty?
+ wiki.repository.exists? && !wiki.repository.empty?
end
def project_hook_exists?(project)
- Gitlab::Git::Hook.new('post-receive', project.repository.raw_repository).exists?
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ Gitlab::Git::Hook.new('post-receive', project.repository.raw_repository).exists?
+ end
end
def click_import_project_tab
diff --git a/spec/features/projects/issues/user_comments_on_issue_spec.rb b/spec/features/projects/issues/user_comments_on_issue_spec.rb
index c45fdc7642f..353f487485d 100644
--- a/spec/features/projects/issues/user_comments_on_issue_spec.rb
+++ b/spec/features/projects/issues/user_comments_on_issue_spec.rb
@@ -31,11 +31,14 @@ describe "User comments on issue", :js do
end
it "adds comment with code block" do
- comment = "```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```"
+ code_block_content = "Command [1]: /usr/local/bin/git , see [text](doc/text)"
+ comment = "```\n#{code_block_content}\n```"
add_note(comment)
- expect(page).to have_content(comment)
+ wait_for_requests
+
+ expect(page.find('pre code').text).to eq code_block_content
end
end
diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb
index 31abadf9bd6..e9588daf37d 100644
--- a/spec/features/projects/jobs/permissions_spec.rb
+++ b/spec/features/projects/jobs/permissions_spec.rb
@@ -88,8 +88,7 @@ describe 'Project Jobs Permissions' do
describe 'artifacts page' do
context 'when recent job has artifacts available' do
before do
- artifacts = Rails.root.join('spec/fixtures/ci_build_artifacts.zip')
- archive = fixture_file_upload(artifacts, 'application/zip')
+ archive = fixture_file_upload('spec/fixtures/ci_build_artifacts.zip')
job.update_attributes(legacy_artifacts_file: archive)
end
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index bff5bbe99af..ce0b38b7239 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -32,8 +32,6 @@ describe 'User browses a job', :js do
page.within('.erased') do
expect(page).to have_content('Job has been erased')
end
-
- expect(build.project.running_or_pending_build_count).to eq(build.project.builds.running_or_pending.count(:all))
end
context 'with a failed job' do
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 9d1c4cbad8b..d2aaf60e72c 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -11,7 +11,7 @@ feature 'Jobs', :clean_gitlab_redis_shared_state do
let(:job2) { create(:ci_build) }
let(:artifacts_file) do
- fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif')
+ fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif')
end
before do
diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb
index 70e8d436dcb..fafd338e448 100644
--- a/spec/features/projects/labels/subscription_spec.rb
+++ b/spec/features/projects/labels/subscription_spec.rb
@@ -36,7 +36,7 @@ feature 'Labels subscription' do
within "#group_label_#{feature.id}" do
expect(page).not_to have_button 'Unsubscribe'
- click_link_on_dropdown('Group level')
+ click_link_on_dropdown('Subscribe at group level')
expect(page).not_to have_selector('.dropdown-group-label')
expect(page).to have_button 'Unsubscribe'
@@ -45,7 +45,7 @@ feature 'Labels subscription' do
expect(page).to have_selector('.dropdown-group-label')
- click_link_on_dropdown('Project level')
+ click_link_on_dropdown('Subscribe at project level')
expect(page).not_to have_selector('.dropdown-group-label')
expect(page).to have_button 'Unsubscribe'
@@ -68,7 +68,7 @@ feature 'Labels subscription' do
find('.dropdown-group-label').click
page.within('.dropdown-group-label') do
- find('a.js-subscribe-button', text: text).click
+ find('.js-subscribe-button', text: text).click
end
end
end
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index ae8b1364ec7..359381c391c 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -102,16 +102,16 @@ feature 'Prioritize labels' do
drag_to(selector: '.label-list-item', from_index: 1, to_index: 2)
page.within('.prioritized-labels') do
- expect(first('li')).to have_content('feature')
- expect(page.all('li').last).to have_content('bug')
+ expect(first('.label-list-item')).to have_content('feature')
+ expect(page.all('.label-list-item').last).to have_content('bug')
end
refresh
wait_for_requests
page.within('.prioritized-labels') do
- expect(first('li')).to have_content('feature')
- expect(page.all('li').last).to have_content('bug')
+ expect(first('.label-list-item')).to have_content('feature')
+ expect(page.all('.label-list-item').last).to have_content('bug')
end
end
diff --git a/spec/features/projects/labels/user_removes_labels_spec.rb b/spec/features/projects/labels/user_removes_labels_spec.rb
index f4fda6de465..efa74015c6e 100644
--- a/spec/features/projects/labels/user_removes_labels_spec.rb
+++ b/spec/features/projects/labels/user_removes_labels_spec.rb
@@ -17,8 +17,9 @@ describe "User removes labels" do
end
it "removes label" do
- page.within(".labels") do
+ page.within(".other-labels") do
page.first(".label-list-item") do
+ first('.js-label-options-dropdown').click
first(".remove-row").click
first(:link, "Delete label").click
end
@@ -36,17 +37,16 @@ describe "User removes labels" do
end
it "removes all labels" do
- page.within(".labels") do
- loop do
- li = page.first(".label-list-item")
- break unless li
+ loop do
+ li = page.first(".label-list-item")
+ break unless li
- li.click_link("Delete")
- click_link("Delete label")
- end
-
- expect(page).to have_content("Generate a default set of labels").and have_content("New label")
+ li.find('.js-label-options-dropdown').click
+ li.click_button("Delete")
+ click_link("Delete label")
end
+
+ expect(page).to have_content("Generate a default set of labels").and have_content("New label")
end
end
end
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index bdd49f731c7..a2899ec0f48 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -314,8 +314,8 @@ feature 'Pages' do
project: project,
pipeline: pipeline,
ref: 'HEAD',
- legacy_artifacts_file: fixture_file_upload(Rails.root.join('spec/fixtures/pages.zip')),
- legacy_artifacts_metadata: fixture_file_upload(Rails.root.join('spec/fixtures/pages.zip.meta'))
+ legacy_artifacts_file: fixture_file_upload(File.join('spec/fixtures/pages.zip')),
+ legacy_artifacts_metadata: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta'))
)
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 35776a5f23b..ecc7cf84138 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -344,6 +344,16 @@ describe 'Pipeline', :js do
it 'shows build failure logs' do
expect(page).to have_content('4 examples, 1 failure')
end
+
+ it 'shows the failure reason' do
+ expect(page).to have_content('There is an unknown failure, please try again')
+ end
+
+ it 'shows retry button for failed build' do
+ page.within(find('.build-failures', match: :first)) do
+ expect(page).to have_link('Retry')
+ end
+ end
end
context 'when missing build logs' do
diff --git a/spec/features/projects/settings/user_manages_group_links_spec.rb b/spec/features/projects/settings/user_manages_group_links_spec.rb
index fdf42797091..92ce2ca83c7 100644
--- a/spec/features/projects/settings/user_manages_group_links_spec.rb
+++ b/spec/features/projects/settings/user_manages_group_links_spec.rb
@@ -30,7 +30,7 @@ describe 'Projects > Settings > User manages group links' do
click_link('Share with group')
select2(group_market.id, from: '#link_group_id')
- select('Master', from: 'link_group_access')
+ select('Maintainer', from: 'link_group_access')
click_button('Share')
diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb
index 8af95522165..d3003753ae6 100644
--- a/spec/features/projects/settings/user_manages_project_members_spec.rb
+++ b/spec/features/projects/settings/user_manages_project_members_spec.rb
@@ -62,7 +62,7 @@ describe 'Projects > Settings > User manages project members' do
page.within('.project-members-groups') do
expect(page).to have_content('OpenSource')
- expect(first('.group_member')).to have_content('Master')
+ expect(first('.group_member')).to have_content('Maintainer')
end
end
end
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 0c28a853b54..4c0f9971425 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -72,7 +72,7 @@ feature 'Protected Branches', :js do
click_link 'No one'
find(".js-allowed-to-push").click
wait_for_requests
- click_link 'Developers + Masters'
+ click_link 'Developers + Maintainers'
end
visit project_protected_branches_path(project)
@@ -82,7 +82,7 @@ feature 'Protected Branches', :js do
expect(page.find(".dropdown-toggle-text")).to have_content("No one")
end
page.within(".js-allowed-to-push") do
- expect(page.find(".dropdown-toggle-text")).to have_content("Developers + Masters")
+ expect(page.find(".dropdown-toggle-text")).to have_content("Developers + Maintainers")
end
end
end
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index 9ce7d538004..fe0b03a7e00 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -184,7 +184,7 @@ feature 'Runners' do
given(:group) { create :group }
- context 'as project and group master' do
+ context 'as project and group maintainer' do
background do
group.add_master(user)
end
@@ -197,13 +197,13 @@ feature 'Runners' do
expect(page).to have_content 'This group does not provide any group Runners yet'
- expect(page).to have_content 'Group masters can register group runners in the Group CI/CD settings'
- expect(page).not_to have_content 'Ask your group master to setup a group Runner'
+ expect(page).to have_content 'Group maintainers can register group runners in the Group CI/CD settings'
+ expect(page).not_to have_content 'Ask your group maintainer to setup a group Runner'
end
end
end
- context 'as project master' do
+ context 'as project maintainer' do
context 'project without a group' do
given(:project) { create :project }
@@ -223,8 +223,8 @@ feature 'Runners' do
expect(page).to have_content 'This group does not provide any group Runners yet.'
- expect(page).not_to have_content 'Group masters can register group runners in the Group CI/CD settings'
- expect(page).to have_content 'Ask your group master to setup a group Runner.'
+ expect(page).not_to have_content 'Group maintainers can register group runners in the Group CI/CD settings'
+ expect(page).to have_content 'Ask your group maintainer to setup a group Runner.'
end
end
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
index c0b4fa52526..9981bfa4609 100644
--- a/spec/features/tags/master_deletes_tag_spec.rb
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -38,7 +38,7 @@ feature 'Master deletes tag' do
context 'when Gitaly operation_user_delete_tag feature is enabled' do
before do
allow_any_instance_of(Gitlab::GitalyClient::OperationService).to receive(:rm_tag)
- .and_raise(Gitlab::Git::HooksService::PreReceiveError, 'Do not delete tags')
+ .and_raise(Gitlab::Git::PreReceiveError, 'Do not delete tags')
end
scenario 'shows the error message' do
@@ -51,7 +51,7 @@ feature 'Master deletes tag' do
context 'when Gitaly operation_user_delete_tag feature is disabled', :skip_gitaly_mock do
before do
allow_any_instance_of(Gitlab::Git::HooksService).to receive(:execute)
- .and_raise(Gitlab::Git::HooksService::PreReceiveError, 'Do not delete tags')
+ .and_raise(Gitlab::Git::PreReceiveError, 'Do not delete tags')
end
scenario 'shows the error message' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 2dc3c5e3927..f37d8998045 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -36,7 +36,7 @@ feature 'Task Lists' do
MARKDOWN
end
- let(:nested_tasks_markdown) do
+ let(:nested_tasks_markdown_redcarpet) do
<<-EOT.strip_heredoc
- [ ] Task a
- [x] Task a.1
@@ -49,6 +49,19 @@ feature 'Task Lists' do
EOT
end
+ let(:nested_tasks_markdown) do
+ <<-EOT.strip_heredoc
+ - [ ] Task a
+ - [x] Task a.1
+ - [ ] Task a.2
+ - [ ] Task b
+
+ 1. [ ] Task 1
+ 1. [ ] Task 1.1
+ 1. [x] Task 1.2
+ EOT
+ end
+
before do
Warden.test_mode!
@@ -141,13 +154,11 @@ feature 'Task Lists' do
end
end
- describe 'nested tasks', :js do
- let(:issue) { create(:issue, description: nested_tasks_markdown, author: user, project: project) }
-
+ shared_examples 'shared nested tasks' do
before do
+ allow(Banzai::Filter::MarkdownFilter).to receive(:engine).and_return('Redcarpet')
visit_issue(project, issue)
end
-
it 'renders' do
expect(page).to have_selector('ul.task-list', count: 2)
expect(page).to have_selector('li.task-list-item', count: 7)
@@ -171,6 +182,30 @@ feature 'Task Lists' do
expect(page).to have_content('marked the task Task 1.1 as complete')
end
end
+
+ describe 'nested tasks', :js do
+ context 'with Redcarpet' do
+ let(:issue) { create(:issue, description: nested_tasks_markdown_redcarpet, author: user, project: project) }
+
+ before do
+ allow_any_instance_of(Banzai::Filter::MarkdownFilter).to receive(:engine).and_return('Redcarpet')
+ visit_issue(project, issue)
+ end
+
+ it_behaves_like 'shared nested tasks'
+ end
+
+ context 'with CommonMark' do
+ let(:issue) { create(:issue, description: nested_tasks_markdown, author: user, project: project) }
+
+ before do
+ allow_any_instance_of(Banzai::Filter::MarkdownFilter).to receive(:engine).and_return('CommonMark')
+ visit_issue(project, issue)
+ end
+
+ it_behaves_like 'shared nested tasks'
+ end
+ end
end
describe 'for Notes' do
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index b5bd5c505f2..b51ca5d130b 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -140,7 +140,7 @@ describe 'Signup' do
enforce_terms
end
- it 'asks the user to accept terms before going to the dashboard' do
+ it 'requires the user to check the checkbox' do
visit root_path
fill_in 'new_user_name', with: new_user.name
@@ -148,11 +148,24 @@ describe 'Signup' do
fill_in 'new_user_email', with: new_user.email
fill_in 'new_user_email_confirmation', with: new_user.email
fill_in 'new_user_password', with: new_user.password
- click_button "Register"
- expect_to_be_on_terms_page
+ click_button 'Register'
+
+ expect(current_path).to eq new_user_session_path
+ expect(page).to have_content(/you must accept our terms of service/i)
+ end
+
+ it 'asks the user to accept terms before going to the dashboard' do
+ visit root_path
+
+ fill_in 'new_user_name', with: new_user.name
+ fill_in 'new_user_username', with: new_user.username
+ fill_in 'new_user_email', with: new_user.email
+ fill_in 'new_user_email_confirmation', with: new_user.email
+ fill_in 'new_user_password', with: new_user.password
+ check :terms_opt_in
- click_button 'Accept terms'
+ click_button "Register"
expect(current_path).to eq dashboard_projects_path
end
diff --git a/spec/features/users/terms_spec.rb b/spec/features/users/terms_spec.rb
index 1efa5cd5490..5b2e7605c4d 100644
--- a/spec/features/users/terms_spec.rb
+++ b/spec/features/users/terms_spec.rb
@@ -3,12 +3,10 @@ require 'spec_helper'
describe 'Users > Terms' do
include TermsHelper
- let(:user) { create(:user) }
let!(:term) { create(:term, terms: 'By accepting, you promise to be nice!') }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- sign_in(user)
end
it 'shows the terms' do
@@ -17,86 +15,119 @@ describe 'Users > Terms' do
expect(page).to have_content('By accepting, you promise to be nice!')
end
- context 'declining the terms' do
- it 'returns the user to the app' do
- visit terms_path
+ it 'does not show buttons to accept, decline or sign out', :aggregate_failures do
+ visit terms_path
+
+ expect(page).not_to have_css('.footer-block')
+ expect(page).not_to have_content('Accept terms')
+ expect(page).not_to have_content('Decline and sign out')
+ expect(page).not_to have_content('Continue')
+ end
- click_button 'Decline and sign out'
+ context 'when signed in' do
+ let(:user) { create(:user) }
- expect(page).not_to have_content(term.terms)
- expect(user.reload.terms_accepted?).to be(false)
+ before do
+ sign_in(user)
end
- end
- context 'accepting the terms' do
- it 'returns the user to the app' do
- visit terms_path
+ context 'declining the terms' do
+ it 'returns the user to the app' do
+ visit terms_path
- click_button 'Accept terms'
+ click_button 'Decline and sign out'
- expect(page).not_to have_content(term.terms)
- expect(user.reload.terms_accepted?).to be(true)
+ expect(page).not_to have_content(term.terms)
+ expect(user.reload.terms_accepted?).to be(false)
+ end
end
- end
- context 'terms were enforced while session is active', :js do
- let(:project) { create(:project) }
+ context 'accepting the terms' do
+ it 'returns the user to the app' do
+ visit terms_path
- before do
- project.add_developer(user)
+ click_button 'Accept terms'
+
+ expect(page).not_to have_content(term.terms)
+ expect(user.reload.terms_accepted?).to be(true)
+ end
end
- it 'redirects to terms and back to where the user was going' do
- visit project_path(project)
+ context 'when the user has already accepted the terms' do
+ before do
+ accept_terms(user)
+ end
+
+ it 'allows the user to continue to the app' do
+ visit terms_path
+
+ expect(page).to have_content "You have already accepted the Terms of Service as #{user.to_reference}"
- enforce_terms
+ click_link 'Continue'
- within('.nav-sidebar') do
- click_link 'Issues'
+ expect(current_path).to eq(root_path)
end
+ end
+
+ context 'terms were enforced while session is active', :js do
+ let(:project) { create(:project) }
- expect_to_be_on_terms_page
+ before do
+ project.add_developer(user)
+ end
- click_button('Accept terms')
+ it 'redirects to terms and back to where the user was going' do
+ visit project_path(project)
- expect(current_path).to eq(project_issues_path(project))
- end
+ enforce_terms
- # Disabled until https://gitlab.com/gitlab-org/gitlab-ce/issues/37162 is solved properly
- xit 'redirects back to the page the user was trying to save' do
- visit new_project_issue_path(project)
+ within('.nav-sidebar') do
+ click_link 'Issues'
+ end
- fill_in :issue_title, with: 'Hello world, a new issue'
- fill_in :issue_description, with: "We don't want to lose what the user typed"
+ expect_to_be_on_terms_page
- enforce_terms
+ click_button('Accept terms')
- click_button 'Submit issue'
+ expect(current_path).to eq(project_issues_path(project))
+ end
- expect(current_path).to eq(terms_path)
+ # Disabled until https://gitlab.com/gitlab-org/gitlab-ce/issues/37162 is solved properly
+ xit 'redirects back to the page the user was trying to save' do
+ visit new_project_issue_path(project)
- click_button('Accept terms')
+ fill_in :issue_title, with: 'Hello world, a new issue'
+ fill_in :issue_description, with: "We don't want to lose what the user typed"
- expect(current_path).to eq(new_project_issue_path(project))
- expect(find_field('issue_title').value).to eq('Hello world, a new issue')
- expect(find_field('issue_description').value).to eq("We don't want to lose what the user typed")
- end
- end
+ enforce_terms
- context 'when the terms are enforced' do
- before do
- enforce_terms
+ click_button 'Submit issue'
+
+ expect(current_path).to eq(terms_path)
+
+ click_button('Accept terms')
+
+ expect(current_path).to eq(new_project_issue_path(project))
+ expect(find_field('issue_title').value).to eq('Hello world, a new issue')
+ expect(find_field('issue_description').value).to eq("We don't want to lose what the user typed")
+ end
end
- context 'signing out', :js do
- it 'allows the user to sign out without a response' do
- visit terms_path
+ context 'when the terms are enforced' do
+ before do
+ enforce_terms
+ end
+
+ context 'signing out', :js do
+ it 'allows the user to sign out without a response' do
+ visit terms_path
- find('.header-user-dropdown-toggle').click
- click_link('Sign out')
+ find('.header-user-dropdown-toggle').click
+ click_link('Sign out')
- expect(page).to have_content('Sign in')
- expect(page).to have_content('Register')
+ expect(page).to have_content('Sign in')
+ expect(page).to have_content('Register')
+ end
end
end
end
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
index 9f285e28535..63e15b365a4 100644
--- a/spec/finders/group_members_finder_spec.rb
+++ b/spec/finders/group_members_finder_spec.rb
@@ -29,4 +29,16 @@ describe GroupMembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member3, member4])
end
+
+ it 'returns members for descendant groups if requested', :nested_groups do
+ member1 = group.add_master(user2)
+ member2 = group.add_master(user1)
+ nested_group.add_master(user2)
+ member3 = nested_group.add_master(user3)
+ member4 = nested_group.add_master(user4)
+
+ result = described_class.new(group).execute(include_descendants: true)
+
+ expect(result.to_a).to match_array([member1, member2, member3, member4])
+ end
end
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index 7bb1f45322e..2fc5299b0f4 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -19,4 +19,16 @@ describe MembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member2, member3])
end
+
+ it 'includes nested group members if asked', :nested_groups do
+ project = create(:project, namespace: group)
+ nested_group.request_access(user1)
+ member1 = group.add_master(user2)
+ member2 = nested_group.add_master(user3)
+ member3 = project.add_master(user4)
+
+ result = described_class.new(project, user2).execute(include_descendants: true)
+
+ expect(result.to_a).to match_array([member1, member2, member3])
+ end
end
diff --git a/spec/fixtures/api/schemas/entities/merge_request_basic.json b/spec/fixtures/api/schemas/entities/merge_request_basic.json
index 46031961cca..cf257ac00de 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_basic.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_basic.json
@@ -13,7 +13,22 @@
"assignee_id": { "type": ["integer", "null"] },
"subscribed": { "type": ["boolean", "null"] },
"participants": { "type": "array" },
- "allow_maintainer_to_push": { "type": "boolean"}
+ "allow_collaboration": { "type": "boolean"},
+ "allow_maintainer_to_push": { "type": "boolean"},
+ "assignee": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "user.json" }
+ ]
+ },
+ "milestone": {
+ "type": [ "object", "null" ]
+ },
+ "labels": {
+ "type": [ "array", "null" ]
+ },
+ "task_status": { "type": "string" },
+ "task_status_short": { "type": "string" }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index 7be8c9e3e67..ee5588fa6c6 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -31,7 +31,7 @@
"source_project_id": { "type": "integer" },
"target_branch": { "type": "string" },
"target_project_id": { "type": "integer" },
- "allow_maintainer_to_push": { "type": "boolean"},
+ "allow_collaboration": { "type": "boolean"},
"metrics": {
"oneOf": [
{ "type": "null" },
diff --git a/spec/fixtures/api/schemas/list.json b/spec/fixtures/api/schemas/list.json
index 05922df6b81..b76ec115293 100644
--- a/spec/fixtures/api/schemas/list.json
+++ b/spec/fixtures/api/schemas/list.json
@@ -37,5 +37,5 @@
"title": { "type": "string" },
"position": { "type": ["integer", "null"] }
},
- "additionalProperties": false
+ "additionalProperties": true
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/commit/with_stats.json b/spec/fixtures/api/schemas/public_api/v4/commit/with_stats.json
new file mode 100644
index 00000000000..3b5dd547e69
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/commit/with_stats.json
@@ -0,0 +1,14 @@
+{
+ "type": "object",
+ "allOf": [
+ { "$ref": "basic.json" },
+ {
+ "required" : [
+ "stats"
+ ],
+ "properties": {
+ "stats": { "$ref": "../commit_stats.json" }
+ }
+ }
+ ]
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/commits_with_stats.json b/spec/fixtures/api/schemas/public_api/v4/commits_with_stats.json
new file mode 100644
index 00000000000..23511123ce4
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/commits_with_stats.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "commit/with_stats.json" }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
index f97461ce9cc..f7adc4e0b91 100644
--- a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
+++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
@@ -82,6 +82,7 @@
"human_time_estimate": { "type": ["string", "null"] },
"human_total_time_spent": { "type": ["string", "null"] }
},
+ "allow_collaboration": { "type": ["boolean", "null"] },
"allow_maintainer_to_push": { "type": ["boolean", "null"] }
},
"required": [
diff --git a/spec/fixtures/api/schemas/public_api/v4/milestones.json b/spec/fixtures/api/schemas/public_api/v4/milestones.json
index c3c42b6ee60..448e97d6c85 100644
--- a/spec/fixtures/api/schemas/public_api/v4/milestones.json
+++ b/spec/fixtures/api/schemas/public_api/v4/milestones.json
@@ -13,7 +13,8 @@
"created_at": { "type": "date" },
"updated_at": { "type": "date" },
"start_date": { "type": "date" },
- "due_date": { "type": "date" }
+ "due_date": { "type": "date" },
+ "web_url": { "type": "string" }
},
"required": [
"id", "iid", "title", "description", "state",
diff --git a/spec/fixtures/api/schemas/public_api/v4/snippets.json b/spec/fixtures/api/schemas/public_api/v4/snippets.json
index e37e9704649..d13d703e063 100644
--- a/spec/fixtures/api/schemas/public_api/v4/snippets.json
+++ b/spec/fixtures/api/schemas/public_api/v4/snippets.json
@@ -8,6 +8,7 @@
"title": { "type": "string" },
"file_name": { "type": ["string", "null"] },
"description": { "type": ["string", "null"] },
+ "visibility": { "type": "string" },
"web_url": { "type": "string" },
"created_at": { "type": "date" },
"updated_at": { "type": "date" },
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index da32a46675f..e5d01c3bd03 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -43,8 +43,14 @@ This text says this, ~~and this text doesn't~~.
### Superscript
-This is my 1^(st) time using superscript in Markdown. Now this is my
-2^(nd).
+This is my 1<sup>(st)</sup> time using superscript in Markdown. Now this is my
+2<sup>(nd)</sup>.
+
+Redcarpet supports this superscript syntax ( x^2 ).
+
+### Subscript
+
+This (C<sub>6</sub>H<sub>12</sub>O<sub>6</sub>) is an example of subscripts in Markdown.
### Next step
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
new file mode 100644
index 00000000000..b892f6b44ed
--- /dev/null
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe GitlabSchema do
+ it 'uses batch loading' do
+ expect(field_instrumenters).to include(BatchLoader::GraphQL)
+ end
+
+ it 'enables the preload instrumenter' do
+ expect(field_instrumenters).to include(BatchLoader::GraphQL)
+ end
+
+ it 'enables the authorization instrumenter' do
+ expect(field_instrumenters).to include(instance_of(::Gitlab::Graphql::Authorize::Instrumentation))
+ end
+
+ it 'enables using presenters' do
+ expect(field_instrumenters).to include(instance_of(::Gitlab::Graphql::Present::Instrumentation))
+ end
+
+ it 'has the base mutation' do
+ pending('Adding an empty mutation breaks the documentation explorer')
+
+ expect(described_class.mutation).to eq(::Types::MutationType.to_graphql)
+ end
+
+ it 'has the base query' do
+ expect(described_class.query).to eq(::Types::QueryType.to_graphql)
+ end
+
+ def field_instrumenters
+ described_class.instrumenters[:field]
+ end
+end
diff --git a/spec/graphql/resolvers/merge_request_resolver_spec.rb b/spec/graphql/resolvers/merge_request_resolver_spec.rb
new file mode 100644
index 00000000000..73993b3a039
--- /dev/null
+++ b/spec/graphql/resolvers/merge_request_resolver_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Resolvers::MergeRequestResolver do
+ include GraphqlHelpers
+
+ set(:project) { create(:project, :repository) }
+ set(:merge_request_1) { create(:merge_request, :simple, source_project: project, target_project: project) }
+ set(:merge_request_2) { create(:merge_request, :rebased, source_project: project, target_project: project) }
+
+ set(:other_project) { create(:project, :repository) }
+ set(:other_merge_request) { create(:merge_request, source_project: other_project, target_project: other_project) }
+
+ let(:iid_1) { merge_request_1.iid }
+ let(:iid_2) { merge_request_2.iid }
+
+ let(:other_iid) { other_merge_request.iid }
+
+ describe '#resolve' do
+ it 'batch-resolves merge requests by target project full path and IID' do
+ result = batch(max_queries: 2) do
+ [resolve_mr(project, iid_1), resolve_mr(project, iid_2)]
+ end
+
+ expect(result).to contain_exactly(merge_request_1, merge_request_2)
+ end
+
+ it 'can batch-resolve merge requests from different projects' do
+ result = batch(max_queries: 3) do
+ [resolve_mr(project, iid_1), resolve_mr(project, iid_2), resolve_mr(other_project, other_iid)]
+ end
+
+ expect(result).to contain_exactly(merge_request_1, merge_request_2, other_merge_request)
+ end
+
+ it 'resolves an unknown iid to nil' do
+ result = batch { resolve_mr(project, -1) }
+
+ expect(result).to be_nil
+ end
+ end
+
+ def resolve_mr(project, iid)
+ resolve(described_class, obj: project, args: { iid: iid })
+ end
+end
diff --git a/spec/graphql/resolvers/project_resolver_spec.rb b/spec/graphql/resolvers/project_resolver_spec.rb
new file mode 100644
index 00000000000..d4990c6492c
--- /dev/null
+++ b/spec/graphql/resolvers/project_resolver_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Resolvers::ProjectResolver do
+ include GraphqlHelpers
+
+ set(:project1) { create(:project) }
+ set(:project2) { create(:project) }
+
+ set(:other_project) { create(:project) }
+
+ describe '#resolve' do
+ it 'batch-resolves projects by full path' do
+ paths = [project1.full_path, project2.full_path]
+
+ result = batch(max_queries: 1) do
+ paths.map { |path| resolve_project(path) }
+ end
+
+ expect(result).to contain_exactly(project1, project2)
+ end
+
+ it 'resolves an unknown full_path to nil' do
+ result = batch { resolve_project('unknown/project') }
+
+ expect(result).to be_nil
+ end
+ end
+
+ def resolve_project(full_path)
+ resolve(described_class, args: { full_path: full_path })
+ end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
new file mode 100644
index 00000000000..b4eeca2e3f1
--- /dev/null
+++ b/spec/graphql/types/project_type_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe GitlabSchema.types['Project'] do
+ it { expect(described_class.graphql_name).to eq('Project') }
+
+ describe 'nested merge request' do
+ it { expect(described_class).to have_graphql_field(:merge_request) }
+
+ it 'authorizes the merge request' do
+ expect(described_class.fields['mergeRequest'])
+ .to require_graphql_authorizations(:read_merge_request)
+ end
+ end
+end
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
new file mode 100644
index 00000000000..e1df6f9811d
--- /dev/null
+++ b/spec/graphql/types/query_type_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe GitlabSchema.types['Query'] do
+ it 'is called Query' do
+ expect(described_class.graphql_name).to eq('Query')
+ end
+
+ it { is_expected.to have_graphql_fields(:project, :echo) }
+
+ describe 'project field' do
+ subject { described_class.fields['project'] }
+
+ it 'finds projects by full path' do
+ is_expected.to have_graphql_arguments(:full_path)
+ is_expected.to have_graphql_type(Types::ProjectType)
+ is_expected.to have_graphql_resolver(Resolvers::ProjectResolver)
+ end
+
+ it 'authorizes with read_project' do
+ is_expected.to require_graphql_authorizations(:read_project)
+ end
+ end
+end
diff --git a/spec/graphql/types/time_type_spec.rb b/spec/graphql/types/time_type_spec.rb
new file mode 100644
index 00000000000..4196d9d27d4
--- /dev/null
+++ b/spec/graphql/types/time_type_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe GitlabSchema.types['Time'] do
+ let(:iso) { "2018-06-04T15:23:50+02:00" }
+ let(:time) { Time.parse(iso) }
+
+ it { expect(described_class.graphql_name).to eq('Time') }
+
+ it 'coerces Time object into ISO 8601' do
+ expect(described_class.coerce_isolated_result(time)).to eq(iso)
+ end
+
+ it 'coerces an ISO-time into Time object' do
+ expect(described_class.coerce_isolated_input(iso)).to eq(time)
+ end
+end
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index 2390c1f3e5d..139387e0b24 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -47,9 +47,7 @@ describe EmailsHelper do
describe '#header_logo' do
context 'there is a brand item with a logo' do
it 'returns the brand header logo' do
- appearance = create :appearance, header_logo: fixture_file_upload(
- Rails.root.join('spec/fixtures/dk.png')
- )
+ appearance = create :appearance, header_logo: fixture_file_upload('spec/fixtures/dk.png')
expect(header_logo).to eq(
%{<img style="height: 50px" src="/uploads/-/system/appearance/header_logo/#{appearance.id}/dk.png" alt="Dk" />}
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index b48c252acd3..6c94bd4e504 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -4,9 +4,9 @@ describe GroupsHelper do
include ApplicationHelper
describe 'group_icon' do
- avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
-
it 'returns an url for the avatar' do
+ avatar_file_path = File.join('spec', 'fixtures', 'banana_sample.gif')
+
group = create(:group)
group.avatar = fixture_file_upload(avatar_file_path)
group.save!
@@ -17,9 +17,9 @@ describe GroupsHelper do
end
describe 'group_icon_url' do
- avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
-
it 'returns an url for the avatar' do
+ avatar_file_path = File.join('spec', 'fixtures', 'banana_sample.gif')
+
group = create(:group)
group.avatar = fixture_file_upload(avatar_file_path)
group.save!
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index c0dc9293397..1a720aae55c 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -298,7 +298,7 @@ describe MarkupHelper do
it 'preserves code color scheme' do
object = create_object("```ruby\ndef test\n 'hello world'\nend\n```")
- expected = "\n<pre class=\"code highlight js-syntax-highlight ruby\">" \
+ expected = "<pre class=\"code highlight js-syntax-highlight ruby\">" \
"<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>\n" \
"</code></pre>"
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index b77114a8152..cf98eed27f1 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -40,23 +40,6 @@ describe PageLayoutHelper do
end
end
- describe 'favicon' do
- it 'defaults to favicon.ico' do
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
- expect(helper.favicon).to eq 'favicon.ico'
- end
-
- it 'has blue favicon for development' do
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
- expect(helper.favicon).to eq 'favicon-blue.ico'
- end
-
- it 'has yellow favicon for canary' do
- stub_env('CANARY', 'true')
- expect(helper.favicon).to eq 'favicon-yellow.ico'
- end
- end
-
describe 'page_image' do
it 'defaults to the GitLab logo' do
expect(helper.page_image).to match_asset_path 'assets/gitlab_logo.png'
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index c9d2ec8a4ae..363ebc88afd 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -33,7 +33,7 @@ describe PreferencesHelper do
it "returns user's theme's css_class" do
stub_user(theme_id: 3)
- expect(helper.user_application_theme).to eq 'ui_light'
+ expect(helper.user_application_theme).to eq 'ui-light'
end
it 'returns the default when id is invalid' do
@@ -41,7 +41,7 @@ describe PreferencesHelper do
allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(1)
- expect(helper.user_application_theme).to eq 'ui_indigo'
+ expect(helper.user_application_theme).to eq 'ui-indigo'
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index f8877b6d1aa..5cf9e9e8f12 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -5,9 +5,9 @@ describe ProjectsHelper do
describe "#project_status_css_class" do
it "returns appropriate class" do
- expect(project_status_css_class("started")).to eq("active")
- expect(project_status_css_class("failed")).to eq("danger")
- expect(project_status_css_class("finished")).to eq("success")
+ expect(project_status_css_class("started")).to eq("table-active")
+ expect(project_status_css_class("failed")).to eq("table-danger")
+ expect(project_status_css_class("finished")).to eq("table-success")
end
end
@@ -90,6 +90,10 @@ describe ProjectsHelper do
expect(helper.project_list_cache_key(project)).to include(project.cache_key)
end
+ it "includes the last activity date" do
+ expect(helper.project_list_cache_key(project)).to include(project.last_activity_date)
+ end
+
it "includes the controller name" do
expect(helper.controller).to receive(:controller_name).and_return("testcontroller")
@@ -276,7 +280,11 @@ describe ProjectsHelper do
describe '#sanitizerepo_repo_path' do
let(:project) { create(:project, :repository) }
- let(:storage_path) { Gitlab.config.repositories.storages.default.legacy_disk_path }
+ let(:storage_path) do
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ Gitlab.config.repositories.storages.default.legacy_disk_path
+ end
+ end
before do
allow(Settings.shared).to receive(:[]).with('path').and_return('/base/repo/export/path')
@@ -435,4 +443,46 @@ describe ProjectsHelper do
expect(helper.send(:git_user_name)).to eq('John \"A\" Doe53')
end
end
+
+ describe 'show_xcode_link' do
+ let!(:project) { create(:project) }
+ let(:mac_ua) { 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36' }
+ let(:ios_ua) { 'Mozilla/5.0 (iPad; CPU OS 5_1_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B206 Safari/7534.48.3' }
+
+ context 'when the repository is xcode compatible' do
+ before do
+ allow(project.repository).to receive(:xcode_project?).and_return(true)
+ end
+
+ it 'returns false if the visitor is not using macos' do
+ allow(helper).to receive(:browser).and_return(Browser.new(ios_ua))
+
+ expect(helper.show_xcode_link?(project)).to eq(false)
+ end
+
+ it 'returns true if the visitor is using macos' do
+ allow(helper).to receive(:browser).and_return(Browser.new(mac_ua))
+
+ expect(helper.show_xcode_link?(project)).to eq(true)
+ end
+ end
+
+ context 'when the repository is not xcode compatible' do
+ before do
+ allow(project.repository).to receive(:xcode_project?).and_return(false)
+ end
+
+ it 'returns false if the visitor is not using macos' do
+ allow(helper).to receive(:browser).and_return(Browser.new(ios_ua))
+
+ expect(helper.show_xcode_link?(project)).to eq(false)
+ end
+
+ it 'returns false if the visitor is using macos' do
+ allow(helper).to receive(:browser).and_return(Browser.new(mac_ua))
+
+ expect(helper.show_xcode_link?(project)).to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb
index 4627a1e1872..c580b78c908 100644
--- a/spec/helpers/storage_helper_spec.rb
+++ b/spec/helpers/storage_helper_spec.rb
@@ -1,21 +1,25 @@
-require 'spec_helper'
+require "spec_helper"
describe StorageHelper do
- describe '#storage_counter' do
- it 'formats bytes to one decimal place' do
- expect(helper.storage_counter(1.23.megabytes)).to eq '1.2 MB'
+ describe "#storage_counter" do
+ it "formats bytes to one decimal place" do
+ expect(helper.storage_counter(1.23.megabytes)).to eq("1.2 MB")
end
- it 'does not add decimals for sizes < 1 MB' do
- expect(helper.storage_counter(23.5.kilobytes)).to eq '24 KB'
+ it "does not add decimals for sizes < 1 MB" do
+ expect(helper.storage_counter(23.5.kilobytes)).to eq("24 KB")
end
- it 'does not add decimals for zeroes' do
- expect(helper.storage_counter(2.megabytes)).to eq '2 MB'
+ it "does not add decimals for zeroes" do
+ expect(helper.storage_counter(2.megabytes)).to eq("2 MB")
end
- it 'uses commas as thousands separator' do
- expect(helper.storage_counter(100_000_000_000_000_000)).to eq '90,949.5 TB'
+ it "uses commas as thousands separator" do
+ if Gitlab.rails5?
+ expect(helper.storage_counter(100_000_000_000_000_000_000_000)).to eq("86,736.2 EB")
+ else
+ expect(helper.storage_counter(100_000_000_000_000_000)).to eq("90,949.5 TB")
+ end
end
end
end
diff --git a/spec/initializers/artifacts_direct_upload_support_spec.rb b/spec/initializers/artifacts_direct_upload_support_spec.rb
deleted file mode 100644
index bfb71da3388..00000000000
--- a/spec/initializers/artifacts_direct_upload_support_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-require 'spec_helper'
-
-describe 'Artifacts direct upload support' do
- subject do
- load Rails.root.join('config/initializers/artifacts_direct_upload_support.rb')
- end
-
- let(:connection) do
- { provider: provider }
- end
-
- before do
- stub_artifacts_setting(
- object_store: {
- enabled: enabled,
- direct_upload: direct_upload,
- connection: connection
- })
- end
-
- context 'when object storage is enabled' do
- let(:enabled) { true }
-
- context 'when direct upload is enabled' do
- let(:direct_upload) { true }
-
- context 'when provider is Google' do
- let(:provider) { 'Google' }
-
- it 'succeeds' do
- expect { subject }.not_to raise_error
- end
- end
-
- context 'when connection is empty' do
- let(:connection) { nil }
-
- it 'raises an error' do
- expect { subject }.to raise_error /object storage provider when 'direct_upload' of artifacts is used/
- end
- end
-
- context 'when other provider is used' do
- let(:provider) { 'AWS' }
-
- it 'raises an error' do
- expect { subject }.to raise_error /object storage provider when 'direct_upload' of artifacts is used/
- end
- end
- end
-
- context 'when direct upload is disabled' do
- let(:direct_upload) { false }
- let(:provider) { 'AWS' }
-
- it 'succeeds' do
- expect { subject }.not_to raise_error
- end
- end
- end
-
- context 'when object storage is disabled' do
- let(:enabled) { false }
- let(:direct_upload) { false }
- let(:provider) { 'AWS' }
-
- it 'succeeds' do
- expect { subject }.not_to raise_error
- end
- end
-end
diff --git a/spec/initializers/direct_upload_support_spec.rb b/spec/initializers/direct_upload_support_spec.rb
new file mode 100644
index 00000000000..e51d404e030
--- /dev/null
+++ b/spec/initializers/direct_upload_support_spec.rb
@@ -0,0 +1,90 @@
+require 'spec_helper'
+
+describe 'Direct upload support' do
+ subject do
+ load Rails.root.join('config/initializers/direct_upload_support.rb')
+ end
+
+ where(:config_name) do
+ %w(lfs artifacts uploads)
+ end
+
+ with_them do
+ let(:connection) do
+ { provider: provider }
+ end
+
+ let(:object_store) do
+ {
+ enabled: enabled,
+ direct_upload: direct_upload,
+ connection: connection
+ }
+ end
+
+ before do
+ allow(Gitlab.config).to receive_messages(to_settings(config_name => {
+ object_store: object_store
+ }))
+ end
+
+ context 'when object storage is enabled' do
+ let(:enabled) { true }
+
+ context 'when direct upload is enabled' do
+ let(:direct_upload) { true }
+
+ context 'when provider is AWS' do
+ let(:provider) { 'AWS' }
+
+ it 'succeeds' do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context 'when provider is Google' do
+ let(:provider) { 'Google' }
+
+ it 'succeeds' do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context 'when connection is empty' do
+ let(:connection) { nil }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error /are supported as a object storage provider when 'direct_upload' is used/
+ end
+ end
+
+ context 'when other provider is used' do
+ let(:provider) { 'Rackspace' }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error /are supported as a object storage provider when 'direct_upload' is used/
+ end
+ end
+ end
+
+ context 'when direct upload is disabled' do
+ let(:direct_upload) { false }
+ let(:provider) { 'AWS' }
+
+ it 'succeeds' do
+ expect { subject }.not_to raise_error
+ end
+ end
+ end
+
+ context 'when object storage is disabled' do
+ let(:enabled) { false }
+ let(:direct_upload) { false }
+ let(:provider) { 'Rackspace' }
+
+ it 'succeeds' do
+ expect { subject }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/initializers/fog_google_https_private_urls_spec.rb b/spec/initializers/fog_google_https_private_urls_spec.rb
index de3c157ab7b..08346b71fee 100644
--- a/spec/initializers/fog_google_https_private_urls_spec.rb
+++ b/spec/initializers/fog_google_https_private_urls_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-describe 'Fog::Storage::GoogleXML::File' do
+describe 'Fog::Storage::GoogleXML::File', :fog_requests do
let(:storage) do
Fog.mock!
- Fog::Storage.new({
- google_storage_access_key_id: "asdf",
- google_storage_secret_access_key: "asdf",
- provider: "Google"
- })
+ Fog::Storage.new(
+ google_storage_access_key_id: "asdf",
+ google_storage_secret_access_key: "asdf",
+ provider: "Google"
+ )
end
let(:file) do
diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js
index f920c4ca945..8b79624d9f4 100644
--- a/spec/javascripts/blob/viewer/index_spec.js
+++ b/spec/javascripts/blob/viewer/index_spec.js
@@ -32,7 +32,7 @@ describe('Blob viewer', () => {
afterEach(() => {
mock.restore();
- location.hash = '';
+ window.location.hash = '';
});
it('loads source file after switching views', (done) => {
@@ -49,7 +49,7 @@ describe('Blob viewer', () => {
});
it('loads source file when line number is in hash', (done) => {
- location.hash = '#L1';
+ window.location.hash = '#L1';
new BlobViewer();
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index 9b4db774b63..ad263791cd4 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -5,10 +5,10 @@
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import '~/boards/models/assignee';
import eventHub from '~/boards/eventhub';
import '~/vue_shared/models/label';
+import '~/vue_shared/models/assignee';
import '~/boards/models/list';
import '~/boards/stores/boards_store';
import boardCard from '~/boards/components/board_card.vue';
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 46fa10e1789..3f5ed4f3d07 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -7,9 +7,9 @@ import axios from '~/lib/utils/axios_utils';
import Cookies from 'js-cookie';
import '~/vue_shared/models/label';
+import '~/vue_shared/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
-import '~/boards/models/assignee';
import '~/boards/services/board_service';
import '~/boards/stores/boards_store';
import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data';
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index abeef272c68..05acf903933 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -5,9 +5,9 @@
import Vue from 'vue';
import '~/vue_shared/models/label';
+import '~/vue_shared/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
-import '~/boards/models/assignee';
import '~/boards/stores/boards_store';
import '~/boards/components/issue_card_inner';
import { listObj } from './mock_data';
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index d90f9a41231..db68096e3bd 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -3,9 +3,9 @@
import Vue from 'vue';
import '~/vue_shared/models/label';
+import '~/vue_shared/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
-import '~/boards/models/assignee';
import '~/boards/services/board_service';
import '~/boards/stores/boards_store';
import { mockBoardService } from './mock_data';
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index d5d1139de15..ac8bbb8f2a8 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -6,9 +6,9 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import _ from 'underscore';
import '~/vue_shared/models/label';
+import '~/vue_shared/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
-import '~/boards/models/assignee';
import '~/boards/services/board_service';
import '~/boards/stores/boards_store';
import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data';
diff --git a/spec/javascripts/boards/modal_store_spec.js b/spec/javascripts/boards/modal_store_spec.js
index 797693a21aa..a234c81fadf 100644
--- a/spec/javascripts/boards/modal_store_spec.js
+++ b/spec/javascripts/boards/modal_store_spec.js
@@ -1,9 +1,9 @@
/* global ListIssue */
import '~/vue_shared/models/label';
+import '~/vue_shared/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
-import '~/boards/models/assignee';
import Store from '~/boards/stores/modal_store';
describe('Modal store', () => {
diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js b/spec/javascripts/bootstrap_linked_tabs_spec.js
index 93dc60d59fe..6f679369289 100644
--- a/spec/javascripts/bootstrap_linked_tabs_spec.js
+++ b/spec/javascripts/bootstrap_linked_tabs_spec.js
@@ -36,7 +36,7 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs';
describe('on click', () => {
it('should change the url according to the clicked tab', () => {
- const historySpy = spyOn(history, 'replaceState').and.callFake(() => {});
+ const historySpy = spyOn(window.history, 'replaceState').and.callFake(() => {});
const linkedTabs = new LinkedTabs({
action: 'show',
diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js
index 6854b016852..9e43552f740 100644
--- a/spec/javascripts/clusters/stores/clusters_store_spec.js
+++ b/spec/javascripts/clusters/stores/clusters_store_spec.js
@@ -110,7 +110,7 @@ describe('Clusters Store', () => {
expect(
store.state.applications.jupyter.hostname,
- ).toEqual(`jupyter.${store.state.applications.ingress.externalIp}.xip.io`);
+ ).toEqual(`jupyter.${store.state.applications.ingress.externalIp}.nip.io`);
});
});
});
diff --git a/spec/javascripts/commits_spec.js b/spec/javascripts/commits_spec.js
index 60d100e8544..28b89157bd3 100644
--- a/spec/javascripts/commits_spec.js
+++ b/spec/javascripts/commits_spec.js
@@ -56,7 +56,7 @@ describe('Commits List', () => {
beforeEach(() => {
commitsList.searchField.val('');
- spyOn(history, 'replaceState').and.stub();
+ spyOn(window.history, 'replaceState').and.stub();
mock = new MockAdapter(axios);
mock.onGet('/h5bp/html5-boilerplate/commits/master').reply(200, {
diff --git a/spec/javascripts/create_merge_request_dropdown_spec.js b/spec/javascripts/create_merge_request_dropdown_spec.js
new file mode 100644
index 00000000000..b229765a8c5
--- /dev/null
+++ b/spec/javascripts/create_merge_request_dropdown_spec.js
@@ -0,0 +1,67 @@
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import CreateMergeRequestDropdown from '~/create_merge_request_dropdown';
+import { TEST_HOST } from 'spec/test_constants';
+
+describe('CreateMergeRequestDropdown', () => {
+ let axiosMock;
+ let dropdown;
+
+ beforeEach(() => {
+ axiosMock = new MockAdapter(axios);
+
+ setFixtures(`
+ <div id="dummy-wrapper-element">
+ <div class="available"></div>
+ <div class="unavailable">
+ <div class="fa"></div>
+ <div class="text"></div>
+ </div>
+ <div class="js-ref"></div>
+ <div class="js-create-merge-request"></div>
+ <div class="js-create-target"></div>
+ <div class="js-dropdown-toggle"></div>
+ </div>
+ `);
+
+ const dummyElement = document.getElementById('dummy-wrapper-element');
+ dropdown = new CreateMergeRequestDropdown(dummyElement);
+ dropdown.refsPath = `${TEST_HOST}/dummy/refs?search=`;
+ });
+
+ afterEach(() => {
+ axiosMock.restore();
+ });
+
+ describe('getRef', () => {
+ it('escapes branch names correctly', done => {
+ const endpoint = `${dropdown.refsPath}contains%23hash`;
+ spyOn(axios, 'get').and.callThrough();
+ axiosMock.onGet(endpoint).replyOnce({});
+
+ dropdown
+ .getRef('contains#hash')
+ .then(() => {
+ expect(axios.get).toHaveBeenCalledWith(endpoint);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('updateCreatePaths', () => {
+ it('escapes branch names correctly', () => {
+ dropdown.createBranchPath = `${TEST_HOST}/branches?branch_name=some-branch&issue=42`;
+ dropdown.createMrPath = `${TEST_HOST}/create_merge_request?branch_name=some-branch&ref=master`;
+
+ dropdown.updateCreatePaths('branch', 'contains#hash');
+
+ expect(dropdown.createBranchPath).toBe(
+ `${TEST_HOST}/branches?branch_name=contains%23hash&issue=42`,
+ );
+ expect(dropdown.createMrPath).toBe(
+ `${TEST_HOST}/create_merge_request?branch_name=contains%23hash&ref=master`,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/datetime_utility_spec.js b/spec/javascripts/datetime_utility_spec.js
index a8d09202154..e224ed46d18 100644
--- a/spec/javascripts/datetime_utility_spec.js
+++ b/spec/javascripts/datetime_utility_spec.js
@@ -149,23 +149,22 @@ describe('getSundays', () => {
});
});
-describe('getTimeframeWindow', () => {
- it('returns array of dates representing a timeframe based on provided length and date', () => {
- const date = new Date(2018, 0, 1);
+describe('getTimeframeWindowFrom', () => {
+ it('returns array of date objects upto provided length start with provided startDate', () => {
+ const startDate = new Date(2018, 0, 1);
const mockTimeframe = [
- new Date(2017, 9, 1),
- new Date(2017, 10, 1),
- new Date(2017, 11, 1),
new Date(2018, 0, 1),
new Date(2018, 1, 1),
- new Date(2018, 2, 31),
+ new Date(2018, 2, 1),
+ new Date(2018, 3, 1),
+ new Date(2018, 4, 31),
];
- const timeframe = datetimeUtility.getTimeframeWindow(6, date);
-
- expect(timeframe.length).toBe(6);
+ const timeframe = datetimeUtility.getTimeframeWindowFrom(startDate, 5);
+ expect(timeframe.length).toBe(5);
timeframe.forEach((timeframeItem, index) => {
- expect(timeframeItem.getFullYear() === mockTimeframe[index].getFullYear()).toBeTruthy();
- expect(timeframeItem.getMonth() === mockTimeframe[index].getMonth()).toBeTruthy();
+ console.log(timeframeItem);
+ expect(timeframeItem.getFullYear() === mockTimeframe[index].getFullYear()).toBe(true);
+ expect(timeframeItem.getMonth() === mockTimeframe[index].getMonth()).toBe(true);
expect(timeframeItem.getDate() === mockTimeframe[index].getDate()).toBeTruthy();
});
});
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index 615168645b4..6968fbc7ce7 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -220,7 +220,7 @@ describe('Environment', () => {
);
component = mountComponent(EnvironmentsComponent, mockData);
- spyOn(history, 'pushState').and.stub();
+ spyOn(window.history, 'pushState').and.stub();
});
describe('updateContent', () => {
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index f5ce4df0bfe..51d4213c38f 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -177,7 +177,7 @@ describe('Environments Folder View', () => {
});
component = mountComponent(Component, mockData);
- spyOn(history, 'pushState').and.stub();
+ spyOn(window.history, 'pushState').and.stub();
});
describe('updateContent', () => {
diff --git a/spec/javascripts/fixtures/images/green_box.png b/spec/javascripts/fixtures/images/green_box.png
new file mode 100644
index 00000000000..cd1ff9f9ade
--- /dev/null
+++ b/spec/javascripts/fixtures/images/green_box.png
Binary files differ
diff --git a/spec/javascripts/fixtures/images/red_box.png b/spec/javascripts/fixtures/images/red_box.png
new file mode 100644
index 00000000000..73b2927da0f
--- /dev/null
+++ b/spec/javascripts/fixtures/images/red_box.png
Binary files differ
diff --git a/spec/javascripts/gl_field_errors_spec.js b/spec/javascripts/gl_field_errors_spec.js
index 4e93fd91751..108e0064c47 100644
--- a/spec/javascripts/gl_field_errors_spec.js
+++ b/spec/javascripts/gl_field_errors_spec.js
@@ -8,7 +8,9 @@ describe('GL Style Field Errors', function() {
beforeEach(function() {
loadFixtures('static/gl_field_errors.html.raw');
- const $form = this.$form = $('form.gl-show-field-errors');
+ const $form = $('form.gl-show-field-errors');
+
+ this.$form = $form;
this.fieldErrors = new GlFieldErrors($form);
});
diff --git a/spec/javascripts/helpers/user_mock_data_helper.js b/spec/javascripts/helpers/user_mock_data_helper.js
index 323fee3767e..f6c3ce5aecc 100644
--- a/spec/javascripts/helpers/user_mock_data_helper.js
+++ b/spec/javascripts/helpers/user_mock_data_helper.js
@@ -1,7 +1,7 @@
export default {
createNumberRandomUsers(numberUsers) {
const users = [];
- for (let i = 0; i < numberUsers; i = i += 1) {
+ for (let i = 0; i < numberUsers; i += 1) {
users.push(
{
avatar: 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
diff --git a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
index cc7e0a3f26d..bf96170f703 100644
--- a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
@@ -19,6 +19,7 @@ describe('Multi-file editor commit sidebar list item', () => {
vm = createComponentWithStore(Component, store, {
file: f,
actionComponent: 'stage-button',
+ activeFileKey: `staged-${f.key}`,
}).$mount();
});
@@ -89,4 +90,20 @@ describe('Multi-file editor commit sidebar list item', () => {
});
});
});
+
+ describe('is active', () => {
+ it('does not add active class when dont keys match', () => {
+ expect(vm.$el.querySelector('.is-active')).toBe(null);
+ });
+
+ it('adds active class when keys match', done => {
+ vm.keyPrefix = 'staged';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.is-active')).not.toBe(null);
+
+ done();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/ide/components/commit_sidebar/list_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_spec.js
index 54625ef90f8..b786be55019 100644
--- a/spec/javascripts/ide/components/commit_sidebar/list_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/list_spec.js
@@ -16,7 +16,10 @@ describe('Multi-file editor commit sidebar list', () => {
iconName: 'staged',
action: 'stageAllChanges',
actionBtnText: 'stage all',
+ actionBtnIcon: 'history',
itemActionComponent: 'stage-button',
+ activeFileKey: 'staged-testing',
+ keyPrefix: 'staged',
});
vm.$store.state.rightPanelCollapsed = false;
@@ -40,7 +43,7 @@ describe('Multi-file editor commit sidebar list', () => {
});
it('renders list', () => {
- expect(vm.$el.querySelectorAll('li').length).toBe(1);
+ expect(vm.$el.querySelectorAll('.multi-file-commit-list > li').length).toBe(1);
});
});
diff --git a/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js b/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js
index 6bf8710bda7..a5b906da8a1 100644
--- a/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js
@@ -39,7 +39,7 @@ describe('IDE stage file button', () => {
});
it('calls store with discard button', () => {
- vm.$el.querySelectorAll('.btn')[1].click();
+ vm.$el.querySelector('.dropdown-menu button').click();
expect(vm.discardFileChanges).toHaveBeenCalledWith(f.path);
});
diff --git a/spec/javascripts/ide/components/jobs/detail/description_spec.js b/spec/javascripts/ide/components/jobs/detail/description_spec.js
new file mode 100644
index 00000000000..9b715a41499
--- /dev/null
+++ b/spec/javascripts/ide/components/jobs/detail/description_spec.js
@@ -0,0 +1,28 @@
+import Vue from 'vue';
+import Description from '~/ide/components/jobs/detail/description.vue';
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+import { jobs } from '../../../mock_data';
+
+describe('IDE job description', () => {
+ const Component = Vue.extend(Description);
+ let vm;
+
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ job: jobs[0],
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders job details', () => {
+ expect(vm.$el.textContent).toContain('#1');
+ expect(vm.$el.textContent).toContain('test');
+ });
+
+ it('renders CI icon', () => {
+ expect(vm.$el.querySelector('.ci-status-icon .ic-status_passed_borderless')).not.toBe(null);
+ });
+});
diff --git a/spec/javascripts/ide/components/jobs/detail/scroll_button_spec.js b/spec/javascripts/ide/components/jobs/detail/scroll_button_spec.js
new file mode 100644
index 00000000000..fff382a107f
--- /dev/null
+++ b/spec/javascripts/ide/components/jobs/detail/scroll_button_spec.js
@@ -0,0 +1,59 @@
+import Vue from 'vue';
+import ScrollButton from '~/ide/components/jobs/detail/scroll_button.vue';
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+
+describe('IDE job log scroll button', () => {
+ const Component = Vue.extend(ScrollButton);
+ let vm;
+
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ direction: 'up',
+ disabled: false,
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('iconName', () => {
+ ['up', 'down'].forEach(direction => {
+ it(`returns icon name for ${direction}`, () => {
+ vm.direction = direction;
+
+ expect(vm.iconName).toBe(`scroll_${direction}`);
+ });
+ });
+ });
+
+ describe('tooltipTitle', () => {
+ it('returns title for up', () => {
+ expect(vm.tooltipTitle).toBe('Scroll to top');
+ });
+
+ it('returns title for down', () => {
+ vm.direction = 'down';
+
+ expect(vm.tooltipTitle).toBe('Scroll to bottom');
+ });
+ });
+
+ it('emits click event on click', () => {
+ spyOn(vm, '$emit');
+
+ vm.$el.querySelector('.btn-scroll').click();
+
+ expect(vm.$emit).toHaveBeenCalledWith('click');
+ });
+
+ it('disables button when disabled is true', done => {
+ vm.disabled = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.btn-scroll').hasAttribute('disabled')).toBe(true);
+
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/ide/components/jobs/detail_spec.js b/spec/javascripts/ide/components/jobs/detail_spec.js
new file mode 100644
index 00000000000..8f8d4b9709e
--- /dev/null
+++ b/spec/javascripts/ide/components/jobs/detail_spec.js
@@ -0,0 +1,185 @@
+import Vue from 'vue';
+import JobDetail from '~/ide/components/jobs/detail.vue';
+import { createStore } from '~/ide/stores';
+import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
+import { jobs } from '../../mock_data';
+
+describe('IDE jobs detail view', () => {
+ const Component = Vue.extend(JobDetail);
+ let vm;
+
+ beforeEach(() => {
+ const store = createStore();
+
+ store.state.pipelines.detailJob = {
+ ...jobs[0],
+ isLoading: true,
+ output: 'testing',
+ rawPath: `${gl.TEST_HOST}/raw`,
+ };
+
+ vm = createComponentWithStore(Component, store);
+
+ spyOn(vm, 'fetchJobTrace').and.returnValue(Promise.resolve());
+
+ vm = vm.$mount();
+
+ spyOn(vm.$refs.buildTrace, 'scrollTo');
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('calls fetchJobTrace on mount', () => {
+ expect(vm.fetchJobTrace).toHaveBeenCalled();
+ });
+
+ it('scrolls to bottom on mount', done => {
+ setTimeout(() => {
+ expect(vm.$refs.buildTrace.scrollTo).toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('renders job output', () => {
+ expect(vm.$el.querySelector('.bash').textContent).toContain('testing');
+ });
+
+ it('renders empty message output', done => {
+ vm.$store.state.pipelines.detailJob.output = '';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.bash').textContent).toContain('No messages were logged');
+
+ done();
+ });
+ });
+
+ it('renders loading icon', () => {
+ expect(vm.$el.querySelector('.build-loader-animation')).not.toBe(null);
+ expect(vm.$el.querySelector('.build-loader-animation').style.display).toBe('');
+ });
+
+ it('hides output when loading', () => {
+ expect(vm.$el.querySelector('.bash')).not.toBe(null);
+ expect(vm.$el.querySelector('.bash').style.display).toBe('none');
+ });
+
+ it('hide loading icon when isLoading is false', done => {
+ vm.$store.state.pipelines.detailJob.isLoading = false;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.build-loader-animation').style.display).toBe('none');
+
+ done();
+ });
+ });
+
+ it('resets detailJob when clicking header button', () => {
+ spyOn(vm, 'setDetailJob');
+
+ vm.$el.querySelector('.btn').click();
+
+ expect(vm.setDetailJob).toHaveBeenCalledWith(null);
+ });
+
+ it('renders raw path link', () => {
+ expect(vm.$el.querySelector('.controllers-buttons').getAttribute('href')).toBe(
+ `${gl.TEST_HOST}/raw`,
+ );
+ });
+
+ describe('scroll buttons', () => {
+ it('triggers scrollDown when clicking down button', done => {
+ spyOn(vm, 'scrollDown');
+
+ vm.$el.querySelectorAll('.btn-scroll')[1].click();
+
+ vm.$nextTick(() => {
+ expect(vm.scrollDown).toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('triggers scrollUp when clicking up button', done => {
+ spyOn(vm, 'scrollUp');
+
+ vm.scrollPos = 1;
+
+ vm
+ .$nextTick()
+ .then(() => vm.$el.querySelector('.btn-scroll').click())
+ .then(() => vm.$nextTick())
+ .then(() => {
+ expect(vm.scrollUp).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('scrollDown', () => {
+ it('scrolls build trace to bottom', () => {
+ spyOnProperty(vm.$refs.buildTrace, 'scrollHeight').and.returnValue(1000);
+
+ vm.scrollDown();
+
+ expect(vm.$refs.buildTrace.scrollTo).toHaveBeenCalledWith(0, 1000);
+ });
+ });
+
+ describe('scrollUp', () => {
+ it('scrolls build trace to top', () => {
+ vm.scrollUp();
+
+ expect(vm.$refs.buildTrace.scrollTo).toHaveBeenCalledWith(0, 0);
+ });
+ });
+
+ describe('scrollBuildLog', () => {
+ beforeEach(() => {
+ spyOnProperty(vm.$refs.buildTrace, 'offsetHeight').and.returnValue(100);
+ spyOnProperty(vm.$refs.buildTrace, 'scrollHeight').and.returnValue(200);
+ });
+
+ it('sets scrollPos to bottom when at the bottom', done => {
+ spyOnProperty(vm.$refs.buildTrace, 'scrollTop').and.returnValue(100);
+
+ vm.scrollBuildLog();
+
+ setTimeout(() => {
+ expect(vm.scrollPos).toBe(1);
+
+ done();
+ });
+ });
+
+ it('sets scrollPos to top when at the top', done => {
+ spyOnProperty(vm.$refs.buildTrace, 'scrollTop').and.returnValue(0);
+ vm.scrollPos = 1;
+
+ vm.scrollBuildLog();
+
+ setTimeout(() => {
+ expect(vm.scrollPos).toBe(0);
+
+ done();
+ });
+ });
+
+ it('resets scrollPos when not at top or bottom', done => {
+ spyOnProperty(vm.$refs.buildTrace, 'scrollTop').and.returnValue(10);
+
+ vm.scrollBuildLog();
+
+ setTimeout(() => {
+ expect(vm.scrollPos).toBe('');
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/ide/components/jobs/item_spec.js b/spec/javascripts/ide/components/jobs/item_spec.js
index 7c1dd4e475c..79e07f00e7b 100644
--- a/spec/javascripts/ide/components/jobs/item_spec.js
+++ b/spec/javascripts/ide/components/jobs/item_spec.js
@@ -26,4 +26,14 @@ describe('IDE jobs item', () => {
it('renders CI icon', () => {
expect(vm.$el.querySelector('.ic-status_passed_borderless')).not.toBe(null);
});
+
+ it('does not render view logs button if not started', done => {
+ vm.job.started = false;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.btn')).toBe(null);
+
+ done();
+ });
+ });
});
diff --git a/spec/javascripts/ide/components/merge_requests/dropdown_spec.js b/spec/javascripts/ide/components/merge_requests/dropdown_spec.js
new file mode 100644
index 00000000000..74884c9a362
--- /dev/null
+++ b/spec/javascripts/ide/components/merge_requests/dropdown_spec.js
@@ -0,0 +1,47 @@
+import Vue from 'vue';
+import { createStore } from '~/ide/stores';
+import Dropdown from '~/ide/components/merge_requests/dropdown.vue';
+import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
+import { mergeRequests } from '../../mock_data';
+
+describe('IDE merge requests dropdown', () => {
+ const Component = Vue.extend(Dropdown);
+ let vm;
+
+ beforeEach(() => {
+ const store = createStore();
+
+ vm = createComponentWithStore(Component, store, { show: false }).$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('does not render tabs when show is false', () => {
+ expect(vm.$el.querySelector('.nav-links')).toBe(null);
+ });
+
+ describe('when show is true', () => {
+ beforeEach(done => {
+ vm.show = true;
+ vm.$store.state.mergeRequests.assigned.mergeRequests.push(mergeRequests[0]);
+
+ vm.$nextTick(done);
+ });
+
+ it('renders tabs', () => {
+ expect(vm.$el.querySelector('.nav-links')).not.toBe(null);
+ });
+
+ it('renders count for assigned & created data', () => {
+ expect(vm.$el.querySelector('.nav-links a').textContent).toContain('Created by me');
+ expect(vm.$el.querySelector('.nav-links a .badge').textContent).toContain('0');
+
+ expect(vm.$el.querySelectorAll('.nav-links a')[1].textContent).toContain('Assigned to me');
+ expect(
+ vm.$el.querySelectorAll('.nav-links a')[1].querySelector('.badge').textContent,
+ ).toContain('1');
+ });
+ });
+});
diff --git a/spec/javascripts/ide/components/merge_requests/item_spec.js b/spec/javascripts/ide/components/merge_requests/item_spec.js
new file mode 100644
index 00000000000..51c4cddef2f
--- /dev/null
+++ b/spec/javascripts/ide/components/merge_requests/item_spec.js
@@ -0,0 +1,61 @@
+import Vue from 'vue';
+import Item from '~/ide/components/merge_requests/item.vue';
+import mountCompontent from '../../../helpers/vue_mount_component_helper';
+
+describe('IDE merge request item', () => {
+ const Component = Vue.extend(Item);
+ let vm;
+
+ beforeEach(() => {
+ vm = mountCompontent(Component, {
+ item: {
+ iid: 1,
+ projectPathWithNamespace: 'gitlab-org/gitlab-ce',
+ title: 'Merge request title',
+ },
+ currentId: '1',
+ currentProjectId: 'gitlab-org/gitlab-ce',
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders merge requests data', () => {
+ expect(vm.$el.textContent).toContain('Merge request title');
+ expect(vm.$el.textContent).toContain('gitlab-org/gitlab-ce!1');
+ });
+
+ it('renders icon if ID matches currentId', () => {
+ expect(vm.$el.querySelector('.ic-mobile-issue-close')).not.toBe(null);
+ });
+
+ it('does not render icon if ID does not match currentId', done => {
+ vm.currentId = '2';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ic-mobile-issue-close')).toBe(null);
+
+ done();
+ });
+ });
+
+ it('does not render icon if project ID does not match', done => {
+ vm.currentProjectId = 'test/test';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ic-mobile-issue-close')).toBe(null);
+
+ done();
+ });
+ });
+
+ it('emits click event on click', () => {
+ spyOn(vm, '$emit');
+
+ vm.$el.click();
+
+ expect(vm.$emit).toHaveBeenCalledWith('click', vm.item);
+ });
+});
diff --git a/spec/javascripts/ide/components/merge_requests/list_spec.js b/spec/javascripts/ide/components/merge_requests/list_spec.js
new file mode 100644
index 00000000000..f4b393778dc
--- /dev/null
+++ b/spec/javascripts/ide/components/merge_requests/list_spec.js
@@ -0,0 +1,126 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import List from '~/ide/components/merge_requests/list.vue';
+import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
+import { mergeRequests } from '../../mock_data';
+import { resetStore } from '../../helpers';
+
+describe('IDE merge requests list', () => {
+ const Component = Vue.extend(List);
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponentWithStore(Component, store, {
+ type: 'created',
+ emptyText: 'empty text',
+ });
+
+ spyOn(vm, 'fetchMergeRequests');
+
+ vm.$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+
+ resetStore(vm.$store);
+ });
+
+ it('calls fetch on mounted', () => {
+ expect(vm.fetchMergeRequests).toHaveBeenCalledWith({
+ type: 'created',
+ search: '',
+ });
+ });
+
+ it('renders loading icon', done => {
+ vm.$store.state.mergeRequests.created.isLoading = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
+
+ done();
+ });
+ });
+
+ it('renders empty text when no merge requests exist', () => {
+ expect(vm.$el.textContent).toContain('empty text');
+ });
+
+ it('renders no search results text when search is not empty', done => {
+ vm.search = 'testing';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.textContent).toContain('No merge requests found');
+
+ done();
+ });
+ });
+
+ describe('with merge requests', () => {
+ beforeEach(done => {
+ vm.$store.state.mergeRequests.created.mergeRequests.push({
+ ...mergeRequests[0],
+ projectPathWithNamespace: 'gitlab-org/gitlab-ce',
+ });
+
+ vm.$nextTick(done);
+ });
+
+ it('renders list', () => {
+ expect(vm.$el.querySelectorAll('li').length).toBe(1);
+ expect(vm.$el.querySelector('li').textContent).toContain(mergeRequests[0].title);
+ });
+
+ it('calls openMergeRequest when clicking merge request', done => {
+ spyOn(vm, 'openMergeRequest');
+ vm.$el.querySelector('li button').click();
+
+ vm.$nextTick(() => {
+ expect(vm.openMergeRequest).toHaveBeenCalledWith({
+ projectPath: 'gitlab-org/gitlab-ce',
+ id: 1,
+ });
+
+ done();
+ });
+ });
+ });
+
+ describe('focusSearch', () => {
+ it('focuses search input when loading is false', done => {
+ spyOn(vm.$refs.searchInput, 'focus');
+
+ vm.$store.state.mergeRequests.created.isLoading = false;
+ vm.focusSearch();
+
+ vm.$nextTick(() => {
+ expect(vm.$refs.searchInput.focus).toHaveBeenCalled();
+
+ done();
+ });
+ });
+ });
+
+ describe('searchMergeRequests', () => {
+ beforeEach(() => {
+ spyOn(vm, 'loadMergeRequests');
+
+ jasmine.clock().install();
+ });
+
+ afterEach(() => {
+ jasmine.clock().uninstall();
+ });
+
+ it('calls loadMergeRequests on input in search field', () => {
+ const event = new Event('input');
+
+ vm.$el.querySelector('input').dispatchEvent(event);
+
+ jasmine.clock().tick(300);
+
+ expect(vm.loadMergeRequests).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/ide/components/pipelines/list_spec.js b/spec/javascripts/ide/components/pipelines/list_spec.js
index 2bb5aa08c3b..68487733cb9 100644
--- a/spec/javascripts/ide/components/pipelines/list_spec.js
+++ b/spec/javascripts/ide/components/pipelines/list_spec.js
@@ -45,12 +45,15 @@ describe('IDE pipelines list', () => {
setTimeout(done);
});
- afterEach(() => {
- vm.$store.dispatch('pipelines/stopPipelinePolling');
- vm.$store.dispatch('pipelines/clearEtagPoll');
-
+ afterEach(done => {
vm.$destroy();
mock.restore();
+
+ vm.$store
+ .dispatch('pipelines/stopPipelinePolling')
+ .then(() => vm.$store.dispatch('pipelines/clearEtagPoll'))
+ .then(done)
+ .catch(done.fail);
});
it('renders pipeline data', () => {
diff --git a/spec/javascripts/ide/components/repo_commit_section_spec.js b/spec/javascripts/ide/components/repo_commit_section_spec.js
index 5e3e00a180b..6bf309fb4bf 100644
--- a/spec/javascripts/ide/components/repo_commit_section_spec.js
+++ b/spec/javascripts/ide/components/repo_commit_section_spec.js
@@ -56,7 +56,7 @@ describe('RepoCommitSection', () => {
vm.$store.state.entries[f.path] = f;
});
- return vm.$mount();
+ return vm;
}
beforeEach(done => {
@@ -64,6 +64,10 @@ describe('RepoCommitSection', () => {
vm = createComponent();
+ spyOn(vm, 'openPendingTab').and.callThrough();
+
+ vm.$mount();
+
spyOn(service, 'getTreeData').and.returnValue(
Promise.resolve({
headers: {
@@ -98,6 +102,7 @@ describe('RepoCommitSection', () => {
store.state.noChangesStateSvgPath = 'nochangessvg';
store.state.committedStateSvgPath = 'svg';
+ vm.$destroy();
vm = createComponentWithStore(Component, store).$mount();
expect(vm.$el.querySelector('.js-empty-state').textContent.trim()).toContain('No changes');
@@ -106,7 +111,7 @@ describe('RepoCommitSection', () => {
});
it('renders a commit section', () => {
- const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')];
+ const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list > li')];
const allFiles = vm.$store.state.changedFiles.concat(vm.$store.state.stagedFiles);
expect(changedFileElements.length).toEqual(4);
@@ -135,22 +140,26 @@ describe('RepoCommitSection', () => {
vm.$el.querySelector('.multi-file-discard-btn .btn').click();
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.ide-commit-list-container').querySelectorAll('li').length).toBe(
- 1,
- );
+ expect(
+ vm.$el
+ .querySelector('.ide-commit-list-container')
+ .querySelectorAll('.multi-file-commit-list > li').length,
+ ).toBe(1);
done();
});
});
it('discards a single file', done => {
- vm.$el.querySelectorAll('.multi-file-discard-btn .btn')[1].click();
+ vm.$el.querySelector('.multi-file-discard-btn .dropdown-menu button').click();
Vue.nextTick(() => {
expect(vm.$el.querySelector('.ide-commit-list-container').textContent).not.toContain('file1');
- expect(vm.$el.querySelector('.ide-commit-list-container').querySelectorAll('li').length).toBe(
- 1,
- );
+ expect(
+ vm.$el
+ .querySelector('.ide-commit-list-container')
+ .querySelectorAll('.multi-file-commit-list > li').length,
+ ).toBe(1);
done();
});
@@ -176,5 +185,12 @@ describe('RepoCommitSection', () => {
expect(store.state.openFiles.length).toBe(1);
expect(store.state.openFiles[0].pending).toBe(true);
});
+
+ it('calls openPendingTab', () => {
+ expect(vm.openPendingTab).toHaveBeenCalledWith({
+ file: vm.lastOpenedFile,
+ keyPrefix: 'unstaged',
+ });
+ });
});
});
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
index d3f80e6f9c0..2256deb7dac 100644
--- a/spec/javascripts/ide/components/repo_editor_spec.js
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -3,7 +3,6 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
import repoEditor from '~/ide/components/repo_editor.vue';
-import monacoLoader from '~/ide/monaco_loader';
import Editor from '~/ide/lib/editor';
import { activityBarViews } from '~/ide/constants';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
@@ -25,13 +24,10 @@ describe('RepoEditor', () => {
f.tempFile = true;
vm.$store.state.openFiles.push(f);
Vue.set(vm.$store.state.entries, f.path, f);
- vm.monaco = true;
vm.$mount();
- monacoLoader(['vs/editor/editor.main'], () => {
- setTimeout(done, 0);
- });
+ Vue.nextTick(() => setTimeout(done));
});
afterEach(() => {
@@ -319,6 +315,17 @@ describe('RepoEditor', () => {
done();
});
});
+
+ it('calls updateDimensions when rightPane is updated', done => {
+ vm.$store.state.rightPane = 'testing';
+
+ vm.$nextTick(() => {
+ expect(vm.editor.updateDimensions).toHaveBeenCalled();
+ expect(vm.editor.updateDiffView).toHaveBeenCalled();
+
+ done();
+ });
+ });
});
describe('show tabs', () => {
diff --git a/spec/javascripts/ide/lib/common/model_manager_spec.js b/spec/javascripts/ide/lib/common/model_manager_spec.js
index c00d590c580..38ffa317e8e 100644
--- a/spec/javascripts/ide/lib/common/model_manager_spec.js
+++ b/spec/javascripts/ide/lib/common/model_manager_spec.js
@@ -1,18 +1,12 @@
-/* global monaco */
import eventHub from '~/ide/eventhub';
-import monacoLoader from '~/ide/monaco_loader';
import ModelManager from '~/ide/lib/common/model_manager';
import { file } from '../../helpers';
describe('Multi-file editor library model manager', () => {
let instance;
- beforeEach(done => {
- monacoLoader(['vs/editor/editor.main'], () => {
- instance = new ModelManager(monaco);
-
- done();
- });
+ beforeEach(() => {
+ instance = new ModelManager();
});
afterEach(() => {
diff --git a/spec/javascripts/ide/lib/common/model_spec.js b/spec/javascripts/ide/lib/common/model_spec.js
index c278bf92b08..f096e06f43c 100644
--- a/spec/javascripts/ide/lib/common/model_spec.js
+++ b/spec/javascripts/ide/lib/common/model_spec.js
@@ -1,23 +1,17 @@
-/* global monaco */
import eventHub from '~/ide/eventhub';
-import monacoLoader from '~/ide/monaco_loader';
import Model from '~/ide/lib/common/model';
import { file } from '../../helpers';
describe('Multi-file editor library model', () => {
let model;
- beforeEach(done => {
+ beforeEach(() => {
spyOn(eventHub, '$on').and.callThrough();
- monacoLoader(['vs/editor/editor.main'], () => {
- const f = file('path');
- f.mrChange = { diff: 'ABC' };
- f.baseRaw = 'test';
- model = new Model(monaco, f);
-
- done();
- });
+ const f = file('path');
+ f.mrChange = { diff: 'ABC' };
+ f.baseRaw = 'test';
+ model = new Model(f);
});
afterEach(() => {
@@ -38,7 +32,7 @@ describe('Multi-file editor library model', () => {
const f = file('path');
model.dispose();
- model = new Model(monaco, f, {
+ model = new Model(f, {
...f,
content: '123 testing',
});
diff --git a/spec/javascripts/ide/lib/decorations/controller_spec.js b/spec/javascripts/ide/lib/decorations/controller_spec.js
index e1c4ca570b6..a112361e0d1 100644
--- a/spec/javascripts/ide/lib/decorations/controller_spec.js
+++ b/spec/javascripts/ide/lib/decorations/controller_spec.js
@@ -1,6 +1,4 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import editor from '~/ide/lib/editor';
+import Editor from '~/ide/lib/editor';
import DecorationsController from '~/ide/lib/decorations/controller';
import Model from '~/ide/lib/common/model';
import { file } from '../../helpers';
@@ -10,16 +8,12 @@ describe('Multi-file editor library decorations controller', () => {
let controller;
let model;
- beforeEach(done => {
- monacoLoader(['vs/editor/editor.main'], () => {
- editorInstance = editor.create(monaco);
- editorInstance.createInstance(document.createElement('div'));
+ beforeEach(() => {
+ editorInstance = Editor.create();
+ editorInstance.createInstance(document.createElement('div'));
- controller = new DecorationsController(editorInstance);
- model = new Model(monaco, file('path'));
-
- done();
- });
+ controller = new DecorationsController(editorInstance);
+ model = new Model(file('path'));
});
afterEach(() => {
diff --git a/spec/javascripts/ide/lib/diff/controller_spec.js b/spec/javascripts/ide/lib/diff/controller_spec.js
index fd8ab3b4f1d..96abd1dcd9e 100644
--- a/spec/javascripts/ide/lib/diff/controller_spec.js
+++ b/spec/javascripts/ide/lib/diff/controller_spec.js
@@ -1,6 +1,5 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import editor from '~/ide/lib/editor';
+import { Range } from 'monaco-editor';
+import Editor from '~/ide/lib/editor';
import ModelManager from '~/ide/lib/common/model_manager';
import DecorationsController from '~/ide/lib/decorations/controller';
import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/ide/lib/diff/controller';
@@ -14,20 +13,16 @@ describe('Multi-file editor library dirty diff controller', () => {
let decorationsController;
let model;
- beforeEach(done => {
- monacoLoader(['vs/editor/editor.main'], () => {
- editorInstance = editor.create(monaco);
- editorInstance.createInstance(document.createElement('div'));
+ beforeEach(() => {
+ editorInstance = Editor.create();
+ editorInstance.createInstance(document.createElement('div'));
- modelManager = new ModelManager(monaco);
- decorationsController = new DecorationsController(editorInstance);
+ modelManager = new ModelManager();
+ decorationsController = new DecorationsController(editorInstance);
- model = modelManager.addModel(file('path'));
+ model = modelManager.addModel(file('path'));
- controller = new DirtyDiffController(modelManager, decorationsController);
-
- done();
- });
+ controller = new DirtyDiffController(modelManager, decorationsController);
});
afterEach(() => {
@@ -170,7 +165,7 @@ describe('Multi-file editor library dirty diff controller', () => {
[],
[
{
- range: new monaco.Range(1, 1, 1, 1),
+ range: new Range(1, 1, 1, 1),
options: {
isWholeLine: true,
linesDecorationsClassName: 'dirty-diff dirty-diff-modified',
diff --git a/spec/javascripts/ide/lib/editor_spec.js b/spec/javascripts/ide/lib/editor_spec.js
index b88a12264ca..c2cb964ea87 100644
--- a/spec/javascripts/ide/lib/editor_spec.js
+++ b/spec/javascripts/ide/lib/editor_spec.js
@@ -1,6 +1,5 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import editor from '~/ide/lib/editor';
+import { editor as monacoEditor } from 'monaco-editor';
+import Editor from '~/ide/lib/editor';
import { file } from '../helpers';
describe('Multi-file editor library', () => {
@@ -8,18 +7,14 @@ describe('Multi-file editor library', () => {
let el;
let holder;
- beforeEach(done => {
+ beforeEach(() => {
el = document.createElement('div');
holder = document.createElement('div');
el.appendChild(holder);
document.body.appendChild(el);
- monacoLoader(['vs/editor/editor.main'], () => {
- instance = editor.create(monaco);
-
- done();
- });
+ instance = Editor.create();
});
afterEach(() => {
@@ -29,20 +24,20 @@ describe('Multi-file editor library', () => {
});
it('creates instance of editor', () => {
- expect(editor.editorInstance).not.toBeNull();
+ expect(Editor.editorInstance).not.toBeNull();
});
it('creates instance returns cached instance', () => {
- expect(editor.create(monaco)).toEqual(instance);
+ expect(Editor.create()).toEqual(instance);
});
describe('createInstance', () => {
it('creates editor instance', () => {
- spyOn(instance.monaco.editor, 'create').and.callThrough();
+ spyOn(monacoEditor, 'create').and.callThrough();
instance.createInstance(holder);
- expect(instance.monaco.editor.create).toHaveBeenCalled();
+ expect(monacoEditor.create).toHaveBeenCalled();
});
it('creates dirty diff controller', () => {
@@ -60,11 +55,11 @@ describe('Multi-file editor library', () => {
describe('createDiffInstance', () => {
it('creates editor instance', () => {
- spyOn(instance.monaco.editor, 'createDiffEditor').and.callThrough();
+ spyOn(monacoEditor, 'createDiffEditor').and.callThrough();
instance.createDiffInstance(holder);
- expect(instance.monaco.editor.createDiffEditor).toHaveBeenCalledWith(holder, {
+ expect(monacoEditor.createDiffEditor).toHaveBeenCalledWith(holder, {
model: null,
contextmenu: true,
minimap: {
@@ -268,4 +263,23 @@ describe('Multi-file editor library', () => {
expect(instance.isDiffEditorType).toBe(false);
});
});
+
+ it('sets quickSuggestions to false when language is markdown', () => {
+ instance.createInstance(holder);
+
+ spyOn(instance.instance, 'updateOptions').and.callThrough();
+
+ const model = instance.createModel({
+ ...file(),
+ key: 'index.md',
+ path: 'index.md',
+ });
+
+ instance.attachModel(model);
+
+ expect(instance.instance.updateOptions).toHaveBeenCalledWith({
+ readOnly: false,
+ quickSuggestions: false,
+ });
+ });
});
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index dcf857f7e04..dd87a43f370 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -75,6 +75,7 @@ export const jobs = [
},
stage: 'test',
duration: 1,
+ started: new Date(),
},
{
id: 2,
@@ -86,6 +87,7 @@ export const jobs = [
},
stage: 'test',
duration: 1,
+ started: new Date(),
},
{
id: 3,
@@ -97,6 +99,7 @@ export const jobs = [
},
stage: 'test',
duration: 1,
+ started: new Date(),
},
{
id: 4,
@@ -108,6 +111,7 @@ export const jobs = [
},
stage: 'build',
duration: 1,
+ started: new Date(),
},
];
diff --git a/spec/javascripts/ide/monaco_loader_spec.js b/spec/javascripts/ide/monaco_loader_spec.js
deleted file mode 100644
index 7ab315aa8c8..00000000000
--- a/spec/javascripts/ide/monaco_loader_spec.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import monacoContext from 'monaco-editor/dev/vs/loader';
-import monacoLoader from '~/ide/monaco_loader';
-
-describe('MonacoLoader', () => {
- it('calls require.config and exports require', () => {
- expect(monacoContext.require.getConfig()).toEqual(
- jasmine.objectContaining({
- paths: {
- vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase
- },
- }),
- );
- expect(monacoLoader).toBe(monacoContext.require);
- });
-});
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
index 7bebc2288e3..5746683917e 100644
--- a/spec/javascripts/ide/stores/actions/file_spec.js
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -166,12 +166,12 @@ describe('IDE store file actions', () => {
});
it('resets location.hash for line highlighting', done => {
- location.hash = 'test';
+ window.location.hash = 'test';
store
.dispatch('setFileActive', localFile.path)
.then(() => {
- expect(location.hash).not.toBe('test');
+ expect(window.location.hash).not.toBe('test');
done();
})
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
index a2869ff378b..133ad627f34 100644
--- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -108,77 +108,6 @@ describe('IDE commit module actions', () => {
});
});
- describe('checkCommitStatus', () => {
- beforeEach(() => {
- store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
- store.state.projects.abcproject = {
- branches: {
- master: {
- workingReference: '1',
- },
- },
- };
- });
-
- it('calls service', done => {
- spyOn(service, 'getBranchData').and.returnValue(
- Promise.resolve({
- data: {
- commit: { id: '123' },
- },
- }),
- );
-
- store
- .dispatch('commit/checkCommitStatus')
- .then(() => {
- expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master');
-
- done();
- })
- .catch(done.fail);
- });
-
- it('returns true if current ref does not equal returned ID', done => {
- spyOn(service, 'getBranchData').and.returnValue(
- Promise.resolve({
- data: {
- commit: { id: '123' },
- },
- }),
- );
-
- store
- .dispatch('commit/checkCommitStatus')
- .then(val => {
- expect(val).toBeTruthy();
-
- done();
- })
- .catch(done.fail);
- });
-
- it('returns false if current ref equals returned ID', done => {
- spyOn(service, 'getBranchData').and.returnValue(
- Promise.resolve({
- data: {
- commit: { id: '1' },
- },
- }),
- );
-
- store
- .dispatch('commit/checkCommitStatus')
- .then(val => {
- expect(val).toBeFalsy();
-
- done();
- })
- .catch(done.fail);
- });
- });
-
describe('updateFilesAfterCommit', () => {
const data = {
id: '123',
@@ -314,6 +243,7 @@ describe('IDE commit module actions', () => {
...file('changed'),
type: 'blob',
active: true,
+ lastCommitSha: '123456789',
};
store.state.stagedFiles.push(f);
store.state.changedFiles = [
@@ -366,6 +296,7 @@ describe('IDE commit module actions', () => {
file_path: jasmine.anything(),
content: jasmine.anything(),
encoding: jasmine.anything(),
+ last_commit_id: undefined,
},
],
start_branch: 'master',
@@ -376,6 +307,32 @@ describe('IDE commit module actions', () => {
.catch(done.fail);
});
+ it('sends lastCommit ID when not creating new branch', done => {
+ store.state.commit.commitAction = '1';
+
+ store
+ .dispatch('commit/commitChanges')
+ .then(() => {
+ expect(service.commit).toHaveBeenCalledWith('abcproject', {
+ branch: jasmine.anything(),
+ commit_message: 'testing 123',
+ actions: [
+ {
+ action: 'update',
+ file_path: jasmine.anything(),
+ content: jasmine.anything(),
+ encoding: jasmine.anything(),
+ last_commit_id: '123456789',
+ },
+ ],
+ start_branch: undefined,
+ });
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
it('sets last Commit Msg', done => {
store
.dispatch('commit/commitChanges')
diff --git a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
index b571cfb963a..fa4c18931e5 100644
--- a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
@@ -8,7 +8,9 @@ import actions, {
receiveMergeRequestsSuccess,
fetchMergeRequests,
resetMergeRequests,
+ openMergeRequest,
} from '~/ide/stores/modules/merge_requests/actions';
+import router from '~/ide/ide_router';
import { mergeRequests } from '../../../mock_data';
import testAction from '../../../../helpers/vuex_action_helper';
@@ -29,9 +31,9 @@ describe('IDE merge requests actions', () => {
it('should should commit request', done => {
testAction(
requestMergeRequests,
- null,
+ 'created',
mockedState,
- [{ type: types.REQUEST_MERGE_REQUESTS }],
+ [{ type: types.REQUEST_MERGE_REQUESTS, payload: 'created' }],
[],
done,
);
@@ -48,16 +50,16 @@ describe('IDE merge requests actions', () => {
it('should should commit error', done => {
testAction(
receiveMergeRequestsError,
- null,
+ 'created',
mockedState,
- [{ type: types.RECEIVE_MERGE_REQUESTS_ERROR }],
+ [{ type: types.RECEIVE_MERGE_REQUESTS_ERROR, payload: 'created' }],
[],
done,
);
});
it('creates flash message', () => {
- receiveMergeRequestsError({ commit() {} });
+ receiveMergeRequestsError({ commit() {} }, 'created');
expect(flashSpy).toHaveBeenCalled();
});
@@ -67,9 +69,14 @@ describe('IDE merge requests actions', () => {
it('should commit received data', done => {
testAction(
receiveMergeRequestsSuccess,
- 'data',
+ { type: 'created', data: 'data' },
mockedState,
- [{ type: types.RECEIVE_MERGE_REQUESTS_SUCCESS, payload: 'data' }],
+ [
+ {
+ type: types.RECEIVE_MERGE_REQUESTS_SUCCESS,
+ payload: { type: 'created', data: 'data' },
+ },
+ ],
[],
done,
);
@@ -86,14 +93,14 @@ describe('IDE merge requests actions', () => {
mock.onGet(/\/api\/v4\/merge_requests(.*)$/).replyOnce(200, mergeRequests);
});
- it('calls API with params from state', () => {
+ it('calls API with params', () => {
const apiSpy = spyOn(axios, 'get').and.callThrough();
- fetchMergeRequests({ dispatch() {}, state: mockedState });
+ fetchMergeRequests({ dispatch() {}, state: mockedState }, { type: 'created' });
expect(apiSpy).toHaveBeenCalledWith(jasmine.anything(), {
params: {
- scope: 'assigned-to-me',
+ scope: 'created-by-me',
state: 'opened',
search: '',
},
@@ -103,11 +110,14 @@ describe('IDE merge requests actions', () => {
it('calls API with search', () => {
const apiSpy = spyOn(axios, 'get').and.callThrough();
- fetchMergeRequests({ dispatch() {}, state: mockedState }, 'testing search');
+ fetchMergeRequests(
+ { dispatch() {}, state: mockedState },
+ { type: 'created', search: 'testing search' },
+ );
expect(apiSpy).toHaveBeenCalledWith(jasmine.anything(), {
params: {
- scope: 'assigned-to-me',
+ scope: 'created-by-me',
state: 'opened',
search: 'testing search',
},
@@ -117,7 +127,7 @@ describe('IDE merge requests actions', () => {
it('dispatches request', done => {
testAction(
fetchMergeRequests,
- null,
+ { type: 'created' },
mockedState,
[],
[
@@ -132,13 +142,16 @@ describe('IDE merge requests actions', () => {
it('dispatches success with received data', done => {
testAction(
fetchMergeRequests,
- null,
+ { type: 'created' },
mockedState,
[],
[
{ type: 'requestMergeRequests' },
{ type: 'resetMergeRequests' },
- { type: 'receiveMergeRequestsSuccess', payload: mergeRequests },
+ {
+ type: 'receiveMergeRequestsSuccess',
+ payload: { type: 'created', data: mergeRequests },
+ },
],
done,
);
@@ -153,7 +166,7 @@ describe('IDE merge requests actions', () => {
it('dispatches error', done => {
testAction(
fetchMergeRequests,
- null,
+ { type: 'created' },
mockedState,
[],
[
@@ -171,12 +184,59 @@ describe('IDE merge requests actions', () => {
it('commits reset', done => {
testAction(
resetMergeRequests,
- null,
+ 'created',
mockedState,
- [{ type: types.RESET_MERGE_REQUESTS }],
+ [{ type: types.RESET_MERGE_REQUESTS, payload: 'created' }],
[],
done,
);
});
});
+
+ describe('openMergeRequest', () => {
+ beforeEach(() => {
+ spyOn(router, 'push');
+ });
+
+ it('commits reset mutations and actions', done => {
+ const commit = jasmine.createSpy();
+ const dispatch = jasmine.createSpy().and.returnValue(Promise.resolve());
+ openMergeRequest({ commit, dispatch }, { projectPath: 'gitlab-org/gitlab-ce', id: '1' });
+
+ setTimeout(() => {
+ expect(commit.calls.argsFor(0)).toEqual(['CLEAR_PROJECTS', null, { root: true }]);
+ expect(commit.calls.argsFor(1)).toEqual(['SET_CURRENT_MERGE_REQUEST', '1', { root: true }]);
+ expect(commit.calls.argsFor(2)).toEqual(['RESET_OPEN_FILES', null, { root: true }]);
+
+ expect(dispatch.calls.argsFor(0)).toEqual(['setCurrentBranchId', '', { root: true }]);
+ expect(dispatch.calls.argsFor(1)).toEqual([
+ 'pipelines/stopPipelinePolling',
+ null,
+ { root: true },
+ ]);
+ expect(dispatch.calls.argsFor(2)).toEqual(['setRightPane', null, { root: true }]);
+ expect(dispatch.calls.argsFor(3)).toEqual([
+ 'pipelines/resetLatestPipeline',
+ null,
+ { root: true },
+ ]);
+ expect(dispatch.calls.argsFor(4)).toEqual([
+ 'pipelines/clearEtagPoll',
+ null,
+ { root: true },
+ ]);
+
+ done();
+ });
+ });
+
+ it('pushes new route', () => {
+ openMergeRequest(
+ { commit() {}, dispatch: () => Promise.resolve() },
+ { projectPath: 'gitlab-org/gitlab-ce', id: '1' },
+ );
+
+ expect(router.push).toHaveBeenCalledWith('/project/gitlab-org/gitlab-ce/merge_requests/1');
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/modules/merge_requests/mutations_spec.js b/spec/javascripts/ide/stores/modules/merge_requests/mutations_spec.js
index 664d3914564..ea03131d90d 100644
--- a/spec/javascripts/ide/stores/modules/merge_requests/mutations_spec.js
+++ b/spec/javascripts/ide/stores/modules/merge_requests/mutations_spec.js
@@ -12,26 +12,29 @@ describe('IDE merge requests mutations', () => {
describe(types.REQUEST_MERGE_REQUESTS, () => {
it('sets loading to true', () => {
- mutations[types.REQUEST_MERGE_REQUESTS](mockedState);
+ mutations[types.REQUEST_MERGE_REQUESTS](mockedState, 'created');
- expect(mockedState.isLoading).toBe(true);
+ expect(mockedState.created.isLoading).toBe(true);
});
});
describe(types.RECEIVE_MERGE_REQUESTS_ERROR, () => {
it('sets loading to false', () => {
- mutations[types.RECEIVE_MERGE_REQUESTS_ERROR](mockedState);
+ mutations[types.RECEIVE_MERGE_REQUESTS_ERROR](mockedState, 'created');
- expect(mockedState.isLoading).toBe(false);
+ expect(mockedState.created.isLoading).toBe(false);
});
});
describe(types.RECEIVE_MERGE_REQUESTS_SUCCESS, () => {
it('sets merge requests', () => {
gon.gitlab_url = gl.TEST_HOST;
- mutations[types.RECEIVE_MERGE_REQUESTS_SUCCESS](mockedState, mergeRequests);
+ mutations[types.RECEIVE_MERGE_REQUESTS_SUCCESS](mockedState, {
+ type: 'created',
+ data: mergeRequests,
+ });
- expect(mockedState.mergeRequests).toEqual([
+ expect(mockedState.created.mergeRequests).toEqual([
{
id: 1,
iid: 1,
@@ -47,9 +50,9 @@ describe('IDE merge requests mutations', () => {
it('clears merge request array', () => {
mockedState.mergeRequests = ['test'];
- mutations[types.RESET_MERGE_REQUESTS](mockedState);
+ mutations[types.RESET_MERGE_REQUESTS](mockedState, 'created');
- expect(mockedState.mergeRequests).toEqual([]);
+ expect(mockedState.created.mergeRequests).toEqual([]);
});
});
});
diff --git a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
index f26eaf9c81f..f47e69d6e5b 100644
--- a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
@@ -13,9 +13,16 @@ import actions, {
receiveJobsSuccess,
fetchJobs,
toggleStageCollapsed,
+ setDetailJob,
+ requestJobTrace,
+ receiveJobTraceError,
+ receiveJobTraceSuccess,
+ fetchJobTrace,
+ resetLatestPipeline,
} from '~/ide/stores/modules/pipelines/actions';
import state from '~/ide/stores/modules/pipelines/state';
import * as types from '~/ide/stores/modules/pipelines/mutation_types';
+import { rightSidebarViews } from '~/ide/constants';
import testAction from '../../../../helpers/vuex_action_helper';
import { pipelines, jobs } from '../../../mock_data';
@@ -281,4 +288,149 @@ describe('IDE pipelines actions', () => {
);
});
});
+
+ describe('setDetailJob', () => {
+ it('commits job', done => {
+ testAction(
+ setDetailJob,
+ 'job',
+ mockedState,
+ [{ type: types.SET_DETAIL_JOB, payload: 'job' }],
+ [{ type: 'setRightPane' }],
+ done,
+ );
+ });
+
+ it('dispatches setRightPane as pipeline when job is null', done => {
+ testAction(
+ setDetailJob,
+ null,
+ mockedState,
+ [{ type: types.SET_DETAIL_JOB }],
+ [{ type: 'setRightPane', payload: rightSidebarViews.pipelines }],
+ done,
+ );
+ });
+
+ it('dispatches setRightPane as job', done => {
+ testAction(
+ setDetailJob,
+ 'job',
+ mockedState,
+ [{ type: types.SET_DETAIL_JOB }],
+ [{ type: 'setRightPane', payload: rightSidebarViews.jobsDetail }],
+ done,
+ );
+ });
+ });
+
+ describe('requestJobTrace', () => {
+ it('commits request', done => {
+ testAction(requestJobTrace, null, mockedState, [{ type: types.REQUEST_JOB_TRACE }], [], done);
+ });
+ });
+
+ describe('receiveJobTraceError', () => {
+ it('commits error', done => {
+ testAction(
+ receiveJobTraceError,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_JOB_TRACE_ERROR }],
+ [],
+ done,
+ );
+ });
+
+ it('creates flash message', () => {
+ const flashSpy = spyOnDependency(actions, 'flash');
+
+ receiveJobTraceError({ commit() {} });
+
+ expect(flashSpy).toHaveBeenCalled();
+ });
+ });
+
+ describe('receiveJobTraceSuccess', () => {
+ it('commits data', done => {
+ testAction(
+ receiveJobTraceSuccess,
+ 'data',
+ mockedState,
+ [{ type: types.RECEIVE_JOB_TRACE_SUCCESS, payload: 'data' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchJobTrace', () => {
+ beforeEach(() => {
+ mockedState.detailJob = {
+ path: `${gl.TEST_HOST}/project/builds`,
+ };
+ });
+
+ describe('success', () => {
+ beforeEach(() => {
+ spyOn(axios, 'get').and.callThrough();
+ mock.onGet(`${gl.TEST_HOST}/project/builds/trace`).replyOnce(200, { html: 'html' });
+ });
+
+ it('dispatches request', done => {
+ testAction(
+ fetchJobTrace,
+ null,
+ mockedState,
+ [],
+ [
+ { type: 'requestJobTrace' },
+ { type: 'receiveJobTraceSuccess', payload: { html: 'html' } },
+ ],
+ done,
+ );
+ });
+
+ it('sends get request to correct URL', () => {
+ fetchJobTrace({ state: mockedState, dispatch() {} });
+
+ expect(axios.get).toHaveBeenCalledWith(`${gl.TEST_HOST}/project/builds/trace`, {
+ params: { format: 'json' },
+ });
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(`${gl.TEST_HOST}/project/builds/trace`).replyOnce(500);
+ });
+
+ it('dispatches error', done => {
+ testAction(
+ fetchJobTrace,
+ null,
+ mockedState,
+ [],
+ [{ type: 'requestJobTrace' }, { type: 'receiveJobTraceError' }],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('resetLatestPipeline', () => {
+ it('commits reset mutations', done => {
+ testAction(
+ resetLatestPipeline,
+ null,
+ mockedState,
+ [
+ { type: types.RECEIVE_LASTEST_PIPELINE_SUCCESS, payload: null },
+ { type: types.SET_DETAIL_JOB, payload: null },
+ ],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js
index 6285c01d483..eb7346bd5fc 100644
--- a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js
+++ b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js
@@ -147,6 +147,10 @@ describe('IDE pipelines mutations', () => {
name: job.name,
status: job.status,
path: job.build_path,
+ rawPath: `${job.build_path}/raw`,
+ started: job.started,
+ isLoading: false,
+ output: '',
})),
);
});
@@ -171,4 +175,49 @@ describe('IDE pipelines mutations', () => {
expect(mockedState.stages[0].isCollapsed).toBe(false);
});
});
+
+ describe(types.SET_DETAIL_JOB, () => {
+ it('sets detail job', () => {
+ mutations[types.SET_DETAIL_JOB](mockedState, jobs[0]);
+
+ expect(mockedState.detailJob).toEqual(jobs[0]);
+ });
+ });
+
+ describe(types.REQUEST_JOB_TRACE, () => {
+ beforeEach(() => {
+ mockedState.detailJob = { ...jobs[0] };
+ });
+
+ it('sets loading on detail job', () => {
+ mutations[types.REQUEST_JOB_TRACE](mockedState);
+
+ expect(mockedState.detailJob.isLoading).toBe(true);
+ });
+ });
+
+ describe(types.RECEIVE_JOB_TRACE_ERROR, () => {
+ beforeEach(() => {
+ mockedState.detailJob = { ...jobs[0], isLoading: true };
+ });
+
+ it('sets loading to false on detail job', () => {
+ mutations[types.RECEIVE_JOB_TRACE_ERROR](mockedState);
+
+ expect(mockedState.detailJob.isLoading).toBe(false);
+ });
+ });
+
+ describe(types.RECEIVE_JOB_TRACE_SUCCESS, () => {
+ beforeEach(() => {
+ mockedState.detailJob = { ...jobs[0], isLoading: true };
+ });
+
+ it('sets output on detail job', () => {
+ mutations[types.RECEIVE_JOB_TRACE_SUCCESS](mockedState, { html: 'html' });
+
+ expect(mockedState.detailJob.output).toBe('html');
+ expect(mockedState.detailJob.isLoading).toBe(false);
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/mutations/file_spec.js b/spec/javascripts/ide/stores/mutations/file_spec.js
index e83961fcedc..52f83be8e8c 100644
--- a/spec/javascripts/ide/stores/mutations/file_spec.js
+++ b/spec/javascripts/ide/stores/mutations/file_spec.js
@@ -152,6 +152,53 @@ describe('IDE store file mutations', () => {
expect(localFile.mrChange.diff).toBe('ABC');
});
+
+ it('has diffMode replaced by default', () => {
+ mutations.SET_FILE_MERGE_REQUEST_CHANGE(localState, {
+ file: localFile,
+ mrChange: {
+ diff: 'ABC',
+ },
+ });
+
+ expect(localFile.mrChange.diffMode).toBe('replaced');
+ });
+
+ it('has diffMode new', () => {
+ mutations.SET_FILE_MERGE_REQUEST_CHANGE(localState, {
+ file: localFile,
+ mrChange: {
+ diff: 'ABC',
+ new_file: true,
+ },
+ });
+
+ expect(localFile.mrChange.diffMode).toBe('new');
+ });
+
+ it('has diffMode deleted', () => {
+ mutations.SET_FILE_MERGE_REQUEST_CHANGE(localState, {
+ file: localFile,
+ mrChange: {
+ diff: 'ABC',
+ deleted_file: true,
+ },
+ });
+
+ expect(localFile.mrChange.diffMode).toBe('deleted');
+ });
+
+ it('has diffMode renamed', () => {
+ mutations.SET_FILE_MERGE_REQUEST_CHANGE(localState, {
+ file: localFile,
+ mrChange: {
+ diff: 'ABC',
+ renamed_file: true,
+ },
+ });
+
+ expect(localFile.mrChange.diffMode).toBe('renamed');
+ });
});
describe('DISCARD_FILE_CHANGES', () => {
diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js
index f38ac6dd82f..a7bd443af51 100644
--- a/spec/javascripts/ide/stores/utils_spec.js
+++ b/spec/javascripts/ide/stores/utils_spec.js
@@ -1,4 +1,5 @@
import * as utils from '~/ide/stores/utils';
+import { file } from '../helpers';
describe('Multi-file store utils', () => {
describe('setPageTitle', () => {
@@ -63,4 +64,59 @@ describe('Multi-file store utils', () => {
expect(foundEntry).toBeUndefined();
});
});
+
+ describe('createCommitPayload', () => {
+ it('returns API payload', () => {
+ const state = {
+ commitMessage: 'commit message',
+ };
+ const rootState = {
+ stagedFiles: [
+ {
+ ...file('staged'),
+ path: 'staged',
+ content: 'updated file content',
+ lastCommitSha: '123456789',
+ },
+ {
+ ...file('newFile'),
+ path: 'added',
+ tempFile: true,
+ content: 'new file content',
+ base64: true,
+ lastCommitSha: '123456789',
+ },
+ ],
+ currentBranchId: 'master',
+ };
+ const payload = utils.createCommitPayload({
+ branch: 'master',
+ newBranch: false,
+ state,
+ rootState,
+ });
+
+ expect(payload).toEqual({
+ branch: 'master',
+ commit_message: 'commit message',
+ actions: [
+ {
+ action: 'update',
+ file_path: 'staged',
+ content: 'updated file content',
+ encoding: 'text',
+ last_commit_id: '123456789',
+ },
+ {
+ action: 'create',
+ file_path: 'added',
+ content: 'new file content',
+ encoding: 'base64',
+ last_commit_id: '123456789',
+ },
+ ],
+ start_branch: undefined,
+ });
+ });
+ });
});
diff --git a/spec/javascripts/importer_status_spec.js b/spec/javascripts/importer_status_spec.js
index 0575d02886d..63cdb3d5114 100644
--- a/spec/javascripts/importer_status_spec.js
+++ b/spec/javascripts/importer_status_spec.js
@@ -45,7 +45,25 @@ describe('Importer Status', () => {
currentTarget: document.querySelector('.js-add-to-import'),
})
.then(() => {
- expect(document.querySelector('tr').classList.contains('active')).toEqual(true);
+ expect(document.querySelector('tr').classList.contains('table-active')).toEqual(true);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('shows error message after failed POST request', (done) => {
+ appendSetFixtures('<div class="flash-container"></div>');
+
+ mock.onPost(importUrl).reply(422, {
+ errors: 'You forgot your lunch',
+ });
+
+ instance.addToImport({
+ currentTarget: document.querySelector('.js-add-to-import'),
+ })
+ .then(() => {
+ const flashMessage = document.querySelector('.flash-text');
+ expect(flashMessage.textContent.trim()).toEqual('An error occurred while importing project: You forgot your lunch');
done();
})
.catch(done.fail);
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index bf1f0c822fe..eb5e0bddb74 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -145,7 +145,7 @@ describe('Issuable output', () => {
resolve({
data: {
confidential: false,
- web_url: location.pathname,
+ web_url: window.location.pathname,
},
});
}));
@@ -177,7 +177,7 @@ describe('Issuable output', () => {
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
resolve({
data: {
- web_url: location.pathname,
+ web_url: window.location.pathname,
confidential: vm.isConfidential,
},
});
diff --git a/spec/javascripts/jobs/header_spec.js b/spec/javascripts/jobs/header_spec.js
index 4f861c39d3f..cef30a007db 100644
--- a/spec/javascripts/jobs/header_spec.js
+++ b/spec/javascripts/jobs/header_spec.js
@@ -13,6 +13,9 @@ describe('Job details header', () => {
const threeWeeksAgo = new Date();
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
+ const twoDaysAgo = new Date();
+ twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
+
props = {
job: {
status: {
@@ -31,7 +34,7 @@ describe('Job details header', () => {
email: 'foo@bar.com',
avatar_url: 'link',
},
- started: '2018-01-08T09:48:27.319Z',
+ started: twoDaysAgo.toISOString(),
new_issue_path: 'path',
},
isLoading: false,
@@ -69,7 +72,7 @@ describe('Job details header', () => {
.querySelector('.header-main-content')
.textContent.replace(/\s+/g, ' ')
.trim(),
- ).toEqual('failed Job #123 triggered 3 weeks ago by Foo');
+ ).toEqual('failed Job #123 triggered 2 days ago by Foo');
});
it('should render new issue link', () => {
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index 25ca8eb6c0b..dd025255bd1 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -20,7 +20,7 @@ export default {
group: 'success',
has_details: true,
details_path: '/root/ci-mock/-/jobs/4757',
- favicon: '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: {
icon: 'retry',
title: 'Retry',
@@ -78,7 +78,7 @@ export default {
group: 'success',
has_details: true,
details_path: '/root/ci-mock/pipelines/140',
- favicon: '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
},
duration: 6,
finished_at: '2017-06-01T17:32:00.042Z',
diff --git a/spec/javascripts/labels_select_spec.js b/spec/javascripts/labels_select_spec.js
index a2b89c0aef5..386e00bfd0c 100644
--- a/spec/javascripts/labels_select_spec.js
+++ b/spec/javascripts/labels_select_spec.js
@@ -40,5 +40,9 @@ describe('LabelsSelect', () => {
it('generated label item template has correct label styles', () => {
expect($labelEl.find('span.label').attr('style')).toBe(`background-color: ${label.color}; color: ${label.text_color};`);
});
+
+ it('generated label item has a badge class', () => {
+ expect($labelEl.find('span').hasClass('badge')).toEqual(true);
+ });
});
});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 27f06573432..a9ec7f42a9d 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -2,6 +2,7 @@
import axios from '~/lib/utils/axios_utils';
import * as commonUtils from '~/lib/utils/common_utils';
import MockAdapter from 'axios-mock-adapter';
+import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data';
describe('common_utils', () => {
describe('parseUrl', () => {
@@ -39,13 +40,13 @@ describe('common_utils', () => {
});
it('should decode params', () => {
- history.pushState('', '', '?label_name%5B%5D=test');
+ window.history.pushState('', '', '?label_name%5B%5D=test');
expect(
commonUtils.getUrlParamsArray()[0],
).toBe('label_name[]=test');
- history.pushState('', '', '?');
+ window.history.pushState('', '', '?');
});
});
@@ -395,6 +396,7 @@ describe('common_utils', () => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
favicon.setAttribute('href', 'default/favicon');
+ favicon.setAttribute('data-default-href', 'default/favicon');
document.body.appendChild(favicon);
});
@@ -413,7 +415,7 @@ describe('common_utils', () => {
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
- favicon.setAttribute('href', 'default/favicon');
+ favicon.setAttribute('data-original-href', 'default/favicon');
document.body.appendChild(favicon);
});
@@ -421,12 +423,43 @@ describe('common_utils', () => {
document.body.removeChild(document.getElementById('favicon'));
});
- it('should reset page favicon to tanuki', () => {
+ it('should reset page favicon to the default icon', () => {
+ const favicon = document.getElementById('favicon');
+ favicon.setAttribute('href', 'new/favicon');
commonUtils.resetFavicon();
expect(document.getElementById('favicon').getAttribute('href')).toEqual('default/favicon');
});
});
+ describe('createOverlayIcon', () => {
+ it('should return the favicon with the overlay', (done) => {
+ commonUtils.createOverlayIcon(faviconDataUrl, overlayDataUrl).then((url) => {
+ expect(url).toEqual(faviconWithOverlayDataUrl);
+ done();
+ });
+ });
+ });
+
+ describe('setFaviconOverlay', () => {
+ beforeEach(() => {
+ const favicon = document.createElement('link');
+ favicon.setAttribute('id', 'favicon');
+ favicon.setAttribute('data-original-href', faviconDataUrl);
+ document.body.appendChild(favicon);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(document.getElementById('favicon'));
+ });
+
+ it('should set page favicon to provided favicon overlay', (done) => {
+ commonUtils.setFaviconOverlay(overlayDataUrl).then(() => {
+ expect(document.getElementById('favicon').getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
+ done();
+ });
+ });
+ });
+
describe('setCiStatusFavicon', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
let mock;
@@ -434,6 +467,8 @@ describe('common_utils', () => {
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
+ favicon.setAttribute('href', 'null');
+ favicon.setAttribute('data-original-href', faviconDataUrl);
document.body.appendChild(favicon);
mock = new MockAdapter(axios);
});
@@ -449,7 +484,7 @@ describe('common_utils', () => {
commonUtils.setCiStatusFavicon(BUILD_URL)
.then(() => {
const favicon = document.getElementById('favicon');
- expect(favicon.getAttribute('href')).toEqual('null');
+ expect(favicon.getAttribute('href')).toEqual(faviconDataUrl);
done();
})
// Error is already caught in catch() block of setCiStatusFavicon,
@@ -458,16 +493,14 @@ describe('common_utils', () => {
});
it('should set page favicon to CI status favicon based on provided status', (done) => {
- const FAVICON_PATH = '//icon_status_success';
-
mock.onGet(BUILD_URL).reply(200, {
- favicon: FAVICON_PATH,
+ favicon: overlayDataUrl,
});
commonUtils.setCiStatusFavicon(BUILD_URL)
.then(() => {
const favicon = document.getElementById('favicon');
- expect(favicon.getAttribute('href')).toEqual(FAVICON_PATH);
+ expect(favicon.getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
done();
})
.catch(done.fail);
diff --git a/spec/javascripts/lib/utils/mock_data.js b/spec/javascripts/lib/utils/mock_data.js
new file mode 100644
index 00000000000..fd0d62b751f
--- /dev/null
+++ b/spec/javascripts/lib/utils/mock_data.js
@@ -0,0 +1,5 @@
+export const faviconDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACcFBMVEX////iQyniQyniQyniQyniQyniQyniQyniQynhRiriQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniRCniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQyniQynhQiniQiniQiniQinhQinpUSjqUSjqTyjqTyjqTyjlSCniRCniQynjRCjqTyjsZSjrWyj8oib9kSb8pyb9pib8oyb8fyb3ZSb4Zib8fCb8oyb8oyb8oyb8pCb8cSbiQyn7bCb8cib8oyb8oSb8bSbtVSjpTij8nyb8oyb8oyb8lCb2Yyf3ZCf8mCb8oyb8oyb8oyb8iib8bSbiRCn8gyb8oyb8eCbpTinrUSj8oyb8oyb8oyb8pSb8bib4Zif0YCf8byb8oyb8oyb8oyb7oib8oyb8nCbjRSn9bib8ayb8nib8oyb8oyb8oyb8kSbpTyjpTyj8jib8oyb8oyb8oyb8fib0Xyf2ZSb8gCb8oyb6pSb8oyb8dib+cCbgQCnjRSn8cCb8oib8oyb8oyb8oybqUCjnSyn8bCb8oyb8oyb8oyb8myb2YyfyXyf8oyb8oyb8hibhQSn+bib8iSb8oyb8qCb+fSbmSSnqTyj8oib9pCb1YifxXyf7pSb8oCb8pCb+mCb0fCf8pSb7hSXvcSjiQyniQinqTyj9kCb9bib9byb+cCbqUSjiRCnsVCj+cSb8pib8bCb8bSbgQCn7bCb8bibjRSn8oyb8ayb8oib8aib8pCbjRCn8pybhQinhQSn8pSb7ayb7aSb6aib8eib///8IbM+7AAAAr3RSTlMBA3NtX2vT698HGQcRLwWLiXnv++3V+eEd/R8HE2V/Y5HjyefdFw99YWfJ+/3nwQP78/HvX1VTQ/kdA2HzbQXj9fX79/3DGf379/33T/v99/f7ba33+/f1+9/18/v59V339flzF/H9+fX3/fMhBwOh9/v5/fmvBV/z+fP3Awnp9/f38+UFgff7+/37+4c77/f7/flFz/f59dFr7/v98Wnr+/f3I5/197EDBU1ZAwUD8/kLUwAAAAFiS0dEAIgFHUgAAAAHdElNRQfhBQoLHiBV6/1lAAACHUlEQVQ4y41TZXsTQRCe4FAIUigN7m7FXY+iLRQKBG2x4g7BjhZ3Le7uMoEkFJprwyQk0CC/iZnNhUZaHt4vt6/szO7cHcD/wFKjZrJWq3YMq1M3eVc9rFzXR2yQkuA3RGxkjZLGiEk9miA2tURJs1RsnhhokYYtzaU13WZDbBVnW1sjo43J2vI6tZ0lLtFeAh1M0lECneI7dGYtrUtk3RUVIKaEJR25qw27yT0s3W0qEHuPlB4RradivXo7GX36xnbo51SQ+fWHARmCgYMGDxkaxbD3SssYPmIkwKgPLrfA87EETTg/fVaSa/SYsQDjSsd7DcGEsr+BieVKmaRNBsjUtClTfUI900y/5Mt05c8oJQKYSURZ2UqYFa0w283M588JEM2BuRwI5EqT8nmmXzZf4l8XsGNfCIv4QcHFklhiBpaqAsuC4tghj+ySyOdjeJYrP7RCCuR/E5tWAqxaLcmCNSyujdxjHZdbn8UHoA0bN/GoNm8hjQJb/ZzYpo6w3TB27JRduxxqrA7YzbWCezixN8RD2Oc2/Ptlfx7o5uT1A4XMiwzj4HfEikNe7+Ew0ZGjeuW70eEYaeHjxomTiKd++E4XnKGz8d+HDufOB3Ky3RcwdNF1qZiKLyf/B44r2tWf15wV143cwI2qfi8dbtKtX6Hbd+6G74EDqkTm/QcPH/0ufFyNLXjy9NnzF9Xb8BJevYY38C+8fZcg/AF3QTYemVkCwwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wNS0xMFQxMTozMDozMiswMjowMMzup8UAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDUtMTBUMTE6MzA6MzIrMDI6MDC9sx95AAAAAElFTkSuQmCC';
+
+export const overlayDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA85JREFUWAntVllIVGEUPv/9b46O41KplYN7PeRkti8TjQlhCUGh3MmeQugpIsGKAi2soIcIooiohxYKK2daqDAlIpIiWwxtQaJcaHE0d5tMrbn37z9XRqfR0TvVW56Hudf//uec72zfEWBCJjIwkYGJDPzvGSD/KgExN3Oi2Q+2DJgSDYQEMwItVGH1iZGmJw/Si1y+/PwVAMYYib22MYc/8hVQFgKDEfYoId0KYzagAQebsos/ewMZoeB9wdffcTYpQSaCTWHKoqSQaDk7zkIt0+aCUR8BelEHrf3dUNv9AcqbnsHtT5UKB/hTASh0SLYjnjb/CIDRJi0XiFAaJOpCD8zLpdb4NB66b1OfelthX815dtdRRfiti2aAXLvVLiMQ6olGyztGDkSo4JGGXk8/QFdGpYzpHG2GBQTDhtgVhPEaVbbVpvI6GJz22rv4TcAfrYI1x7Rj5MWWAppomKFVVb2302SFzUkZHAbkG+0b1+Gh77yNYjrmqnWTrLBLRxdvBWv8qlFujH/kYjJYyvLkj71t78zAUvzMAMnHhpN4zf9UREJhd8omyssxu1IgazQDwDnHUcNuH6vhPIE1fmuBzHt74Hn7W89jWGtcAjoaIDOFrdcMYJBkgOCoaRF0Lj0oglddDbCj6tRvKjphEpgjkzEQs2YAKsNxMzjn3nKurhzK+Ly7xe28ua8TwgMMcHJZnvvT0BPtEEKM4tDJ+C8GvIIk4ylINIXVZ0EUKJxYuh3mhCeokbudl6TtVc88dfBdLwbyaWB6zQCYQJpBYSrDGQxBQ/ZWRM2B+VNmQnVnHWx7elyNuL2/R336co7KyJR8CL9oLgEuFlREevWUkEl6uGwpVEG4FBm0OEf9N10NMgPlvWYAuNVwsWDKvcUNYsHUWTCZ13ysyFEXe6TO6aC8CUr9IiK+A05TQrc8yjwmxARHeeMAPlfQJw+AQRwu0YhL/GDXi9NwufG+S8dYkuYMqIb4SsWthotlNMOUCOM6r+G9cqXxPmd1dqrBav/o1zJy2l5/NUjJA/VORwYuFnOUaTQcPs9wMqwV++Xv8oADxKAcZ8nLPr8AoGW+xR6HSqYk3GodAz2QNj0V+Gr26dT9ASNH5239Pf0gktVNWZca8ZvfAFBprWS6hSu1pqt++Y0PD+WIwDAhIWQGtzvSHDbcodfFUFB9hg1Gjs5LXqIdFL+acFBl+FddqYwdxsWC3I70OvgfUaA65zhq2O2c8VxYcyIGFTVlXegYtvCXANCQZJMobjVcLMjtSK/IcEgyOOe8Ve5w7ryKDefp2P3+C/5ohv8HZmVLAAAAAElFTkSuQmCC';
+
+export const faviconWithOverlayDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAGt0lEQVRYR8WWf3DT5R3H35/vN0lDiCztSuiPAEnTFhSOUamIzFGokwMH55g0E845yjbP6+4qIoiHY6JjnHLeRI6h9jgQpQcD3ImH2u00eHBwjGthKLI1TSm26S8oKYVS0vT7/X52z7ckTUhqC/yx55/kks/zeb+ez6/nIWYm/B8XJQB4SGq6lL+CJA47vvRtvWs2D0nNl/Kf0qBZxx6u23arv0QAAIHivK8BynB4ffa7BgDQXJx/ngGnw+uThgTwP5ZnMocoJAxVxZDVZ+0L5n5WF75TkMafjLdJxpSg2E+gqW1X7zk3rbpaifhLiEBgTv4mEFbpBoTyu01DoDj/dQAv9rvjtdnp/k3Yx9rgAMV5QYCsAAwAgg6vL/1OTy/2BYrzzwLIBWACuNHhrXPG+otGoKaw0JA58kqGJtOFfgPS8yWT4sz88nzj7UIIfz+wd0mRdEZPLMnp2V/8R0+JrhLbBYFHJvwWzBUxYgqYNzhG+zfEhm24MIE5ectBtP0W+y0Or29FcoDifHFSRxwAcMrh9c0YrmisXaA4r0V0U8xvopgDDq9PpCQ+Ag0/zbEbNUNbMiG9fTwkDTsKHpJa2t1Zmiw1AqLg+tMZ+R6WVVtnZ2qP6Ib+FIjh05G3lsDrB4xjUIbRDeM+WZLJYZ4B1rKMzKPm/fdyzs9qg6WT225IMnPcuYjxbPZhn57qaA00zc4/QYT7b1b/wAZmDYSLjsN1WcmiM+6jXz7JTCs1aNPASBjrtrCGOXVBLK9ph72772bc0REZcsQlkEVoOxblhaFBH0Bxi6GBWFNC8gpV0XqYSe/hI85R9o1zxr/QaZbdbmuW9oRzljRrzBRkW9JhMaTgYugKzl35DlXNJ/Fp43FImoZnz7T0ln7bLihM0g85N627vkWPgLrbvYyCvAP1+rRIWETA5QsyQlcJYOCbMRasWpALtljwSsFyeJxFYsoNWqdN1y/ildM78Y/WGjxx8TL+ol3oluy8VupKe7cfoNLdCJkdqEUPOmBJ5ksJoae91mBps5lQ6pkIm20MPiz6A3KsmcNukDe/3Ye3zh3A77Q2XqcGjslLz88i/nB8pkpSoL8nAFSTBpUN4qSxS5KB5jOGUOniCebmzFQcevSN2xKP+Fp7ajt21f8TOxU/5i45JZFS6XwcTB9HxZgUnGTRNgk31x5jet+aGU7jWw+UweOcPeyTxxoqrGL25+UwdjehSvnmOVIqcz4C8y8GAABcQwjnYI5NheikhQWT+EZmDh2ev/l7cz4U2cGmYyg78TYqVH87Kbtd1wFY4hsVQAt14zu2RiDaTUZMf/BHWD35STx37wDv94k1dLeh7MRmvDZ1GR5Inxg17dX6MPnjZfh5X6tGSqXrV2B8ACIx98UNGOlV4CxCuA6zqIeq9FQ8c68bhx7ZiIK06CQdVF+Il3y1Hq03gnDfk4Uj8zbH2T51dCPOtlW39Q+iPTl2VSMfwKPiKw8aTuhgpl1Zdqxzj8PphRWwm21xZjv9VcgYkYb52dP132PFbSYr/la0DpNtrrg9a2oqsKfB2zlwG+4nSe1z7QDjaQBi2Eh6J4QRwimYt43LwOsuB2oX7YLVMCLqTAya3xx/EwZJxtYHy3WhyMkHExebXz3zAbbXfdo7AFBRaMAz1Ypa6XoaoPejKRGteZm6D3SlWVdOcOHo/Lfj2u9aXw+WHNmA00G/DiFEO0Jd+meyk0fIf/+vLfik6Xhj4qN0v7i5HCY1bBQPk+ij9GSzNbzYNdH03kMrscARfzvHQgiBocSFTVHVCrW+u+WrpK9iCIgS1rRK93oG/1GkRJVIup8KMNs1Sw/1rUtALD36ZzRca8XeJDmPtRc18vDn5SCJViYHENY3IZTK3JkE7RAYtpdkp3bAaJeOzN+CsSMTX+wqa7ih9sbVSLI2WV3znihAJYXZPThA7M6KQoM2MniyhUxTioxTpKLMadjx8Jqh5k3S//8d9GOh92XWmP/aXLKvfHgA0ZTklL0jj9m6UR6L5+9bjFWTPLcFIWbCY1+8pHb0drWybJ4aWLQrODyAWJndzoyylNyGg0hL+bV7Ll4rKIWB5CFBxMlLj21SL4W6QjDQjwOL9n4tNt0+AADPfo+UqgXPHJLSJrkso7F6ylLMy56OFMmYACIKblvtQext8Iqp0swyLYiI3zEAbs6Ml3cXv/p3Y+ryq5KcnSKb1Jmj75P7X0Rm/UV0tvO86r/WIhORwszvkmHEehH2WMo7ikDUQUWhoaIG+NNc96Os8eMEmklE2Qy2ANTO0OrA+CwFOFBfsq8pWZ7+B25aDBxvPp+QAAAAAElFTkSuQmCC';
diff --git a/spec/javascripts/lib/utils/url_utility_spec.js b/spec/javascripts/lib/utils/url_utility_spec.js
new file mode 100644
index 00000000000..c7f4092911c
--- /dev/null
+++ b/spec/javascripts/lib/utils/url_utility_spec.js
@@ -0,0 +1,29 @@
+import { webIDEUrl } from '~/lib/utils/url_utility';
+
+describe('URL utility', () => {
+ describe('webIDEUrl', () => {
+ afterEach(() => {
+ gon.relative_url_root = '';
+ });
+
+ describe('without relative_url_root', () => {
+ it('returns IDE path with route', () => {
+ expect(webIDEUrl('/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
+ '/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1',
+ );
+ });
+ });
+
+ describe('with relative_url_root', () => {
+ beforeEach(() => {
+ gon.relative_url_root = '/gitlab';
+ });
+
+ it('returns IDE path with route', () => {
+ expect(webIDEUrl('/gitlab/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
+ '/gitlab/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1',
+ );
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/graph_spec.js b/spec/javascripts/monitoring/graph_spec.js
index 220228e5c08..a46a387a534 100644
--- a/spec/javascripts/monitoring/graph_spec.js
+++ b/spec/javascripts/monitoring/graph_spec.js
@@ -18,9 +18,7 @@ const createComponent = propsData => {
}).$mount();
};
-const convertedMetrics = convertDatesMultipleSeries(
- singleRowMetricsMultipleSeries,
-);
+const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
describe('Graph', () => {
beforeEach(() => {
@@ -36,7 +34,7 @@ describe('Graph', () => {
projectPath,
});
- expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(
+ expect(component.$el.querySelector('.prometheus-graph-title').innerText.trim()).toBe(
component.graphData.title,
);
});
@@ -52,9 +50,7 @@ describe('Graph', () => {
});
const transformedHeight = `${component.graphHeight - 100}`;
- expect(component.axisTransform.indexOf(transformedHeight)).not.toEqual(
- -1,
- );
+ expect(component.axisTransform.indexOf(transformedHeight)).not.toEqual(-1);
});
it('outerViewBox gets a width and height property based on the DOM size of the element', () => {
diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js
index 224debbeff6..a7d1e4331eb 100644
--- a/spec/javascripts/notes/components/comment_form_spec.js
+++ b/spec/javascripts/notes/components/comment_form_spec.js
@@ -84,7 +84,7 @@ describe('issue_comment_form component', () => {
it('should render textarea with placeholder', () => {
expect(
vm.$el.querySelector('.js-main-target-form textarea').getAttribute('placeholder'),
- ).toEqual('Write a comment or drag your files here...');
+ ).toEqual('Write a comment or drag your files here…');
});
it('should make textarea disabled while requesting', (done) => {
diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index 1dfe890e05e..c9e549d2096 100644
--- a/spec/javascripts/notes/components/note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -20,7 +20,7 @@ describe('issue_note_actions component', () => {
beforeEach(() => {
props = {
- accessLevel: 'Master',
+ accessLevel: 'Maintainer',
authorId: 26,
canDelete: true,
canEdit: true,
@@ -67,7 +67,7 @@ describe('issue_note_actions component', () => {
beforeEach(() => {
store.dispatch('setUserData', {});
props = {
- accessLevel: 'Master',
+ accessLevel: 'Maintainer',
authorId: 26,
canDelete: false,
canEdit: false,
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index 0e792eee5e9..d494c63ff11 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -106,7 +106,7 @@ describe('note_app', () => {
expect(vm.$el.querySelector('.js-main-target-form').tagName).toEqual('FORM');
expect(
vm.$el.querySelector('.js-main-target-form textarea').getAttribute('placeholder'),
- ).toEqual('Write a comment or drag your files here...');
+ ).toEqual('Write a comment or drag your files here…');
});
it('should render form comment button as disabled', () => {
@@ -129,7 +129,7 @@ describe('note_app', () => {
expect(vm.$el.querySelector('.js-main-target-form').tagName).toEqual('FORM');
expect(
vm.$el.querySelector('.js-main-target-form textarea').getAttribute('placeholder'),
- ).toEqual('Write a comment or drag your files here...');
+ ).toEqual('Write a comment or drag your files here…');
});
});
diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js
index f841a408d09..413d4f69434 100644
--- a/spec/javascripts/notes/components/note_form_spec.js
+++ b/spec/javascripts/notes/components/note_form_spec.js
@@ -49,7 +49,7 @@ describe('issue_note_form component', () => {
it('should render text area with placeholder', () => {
expect(
vm.$el.querySelector('textarea').getAttribute('placeholder'),
- ).toEqual('Write a comment or drag your files here...');
+ ).toEqual('Write a comment or drag your files here…');
});
it('should link to markdown docs', () => {
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index bfe3a65feee..fa7adc32193 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -340,6 +340,79 @@ export const loggedOutnoteableData = {
'/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue',
};
+export const collapseNotesMock = [
+ {
+ expanded: true,
+ id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
+ individual_note: true,
+ notes: [
+ {
+ id: 1390,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: 'test',
+ path: '/root',
+ },
+ created_at: '2018-02-26T18:07:41.071Z',
+ updated_at: '2018-02-26T18:07:41.071Z',
+ system: true,
+ system_note_icon_name: 'pencil',
+ noteable_id: 98,
+ noteable_type: 'Issue',
+ type: null,
+ human_access: 'Owner',
+ note: 'changed the description',
+ note_html: '<p dir="auto">changed the description</p>',
+ current_user: { can_edit: false },
+ discussion_id: 'b97fb7bda470a65b3e009377a9032edec0a4dd05',
+ emoji_awardable: false,
+ path: '/h5bp/html5-boilerplate/notes/1057',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fh5bp%2Fhtml5-boilerplate%2Fissues%2F10%23note_1057&user_id=1',
+ },
+ ],
+ },
+ {
+ expanded: true,
+ id: 'ffde43f25984ad7f2b4275135e0e2846875336c0',
+ individual_note: true,
+ notes: [
+ {
+ id: 1391,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Root',
+ username: 'root',
+ state: 'active',
+ avatar_url: 'test',
+ path: '/root',
+ },
+ created_at: '2018-02-26T18:13:24.071Z',
+ updated_at: '2018-02-26T18:13:24.071Z',
+ system: true,
+ system_note_icon_name: 'pencil',
+ noteable_id: 99,
+ noteable_type: 'Issue',
+ type: null,
+ human_access: 'Owner',
+ note: 'changed the description',
+ note_html: '<p dir="auto">changed the description</p>',
+ current_user: { can_edit: false },
+ discussion_id: '3eb958b4d81dec207ec3537a2f3bd8b9f271bb34',
+ emoji_awardable: false,
+ path: '/h5bp/html5-boilerplate/notes/1057',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fh5bp%2Fhtml5-boilerplate%2Fissues%2F10%23note_1057&user_id=1',
+ },
+ ],
+ },
+];
+
export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
GET: {
'/gitlab-org/gitlab-ce/issues/26/discussions.json': [
@@ -575,3 +648,508 @@ export function discussionNoteInterceptor(request, next) {
}),
);
}
+
+export const notesWithDescriptionChanges = [
+ {
+ id: '39b271c2033e9ed43d8edb393702f65f7a830459',
+ reply_id: '39b271c2033e9ed43d8edb393702f65f7a830459',
+ expanded: true,
+ notes: [
+ {
+ id: 901,
+ type: null,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-05-29T12:05:36.117Z',
+ updated_at: '2018-05-29T12:05:36.117Z',
+ system: false,
+ noteable_id: 182,
+ noteable_type: 'Issue',
+ resolvable: false,
+ noteable_iid: 12,
+ note:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ note_html:
+ '<p dir="auto">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>',
+ current_user: { can_edit: true, can_award_emoji: true },
+ resolved: false,
+ resolved_by: null,
+ discussion_id: '39b271c2033e9ed43d8edb393702f65f7a830459',
+ emoji_awardable: true,
+ award_emoji: [],
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-shell%2Fissues%2F12%23note_901&user_id=1',
+ human_access: 'Owner',
+ toggle_award_path: '/gitlab-org/gitlab-shell/notes/901/toggle_award_emoji',
+ path: '/gitlab-org/gitlab-shell/notes/901',
+ },
+ ],
+ individual_note: true,
+ resolvable: false,
+ resolved: false,
+ diff_discussion: false,
+ },
+ {
+ id: '4852335d7dc40b9ceb8fde1a2bb9c1b67e4c7795',
+ reply_id: '4852335d7dc40b9ceb8fde1a2bb9c1b67e4c7795',
+ expanded: true,
+ notes: [
+ {
+ id: 902,
+ type: null,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-05-29T12:05:58.694Z',
+ updated_at: '2018-05-29T12:05:58.694Z',
+ system: false,
+ noteable_id: 182,
+ noteable_type: 'Issue',
+ resolvable: false,
+ noteable_iid: 12,
+ note:
+ 'Varius vel pharetra vel turpis nunc eget lorem. Ipsum dolor sit amet consectetur adipiscing.',
+ note_html:
+ '<p dir="auto">Varius vel pharetra vel turpis nunc eget lorem. Ipsum dolor sit amet consectetur adipiscing.</p>',
+ current_user: { can_edit: true, can_award_emoji: true },
+ resolved: false,
+ resolved_by: null,
+ discussion_id: '4852335d7dc40b9ceb8fde1a2bb9c1b67e4c7795',
+ emoji_awardable: true,
+ award_emoji: [],
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-shell%2Fissues%2F12%23note_902&user_id=1',
+ human_access: 'Owner',
+ toggle_award_path: '/gitlab-org/gitlab-shell/notes/902/toggle_award_emoji',
+ path: '/gitlab-org/gitlab-shell/notes/902',
+ },
+ ],
+ individual_note: true,
+ resolvable: false,
+ resolved: false,
+ diff_discussion: false,
+ },
+ {
+ id: '7f1feda384083eb31763366e6392399fde6f3f31',
+ reply_id: '7f1feda384083eb31763366e6392399fde6f3f31',
+ expanded: true,
+ notes: [
+ {
+ id: 903,
+ type: null,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-05-29T12:06:05.772Z',
+ updated_at: '2018-05-29T12:06:05.772Z',
+ system: true,
+ noteable_id: 182,
+ noteable_type: 'Issue',
+ resolvable: false,
+ noteable_iid: 12,
+ note: 'changed the description',
+ note_html: '<p dir="auto">changed the description</p>',
+ current_user: { can_edit: false, can_award_emoji: true },
+ resolved: false,
+ resolved_by: null,
+ system_note_icon_name: 'pencil-square',
+ discussion_id: '7f1feda384083eb31763366e6392399fde6f3f31',
+ emoji_awardable: false,
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-shell%2Fissues%2F12%23note_903&user_id=1',
+ human_access: 'Owner',
+ path: '/gitlab-org/gitlab-shell/notes/903',
+ },
+ ],
+ individual_note: true,
+ resolvable: false,
+ resolved: false,
+ diff_discussion: false,
+ },
+ {
+ id: '091865fe3ae20f0045234a3d103e3b15e73405b5',
+ reply_id: '091865fe3ae20f0045234a3d103e3b15e73405b5',
+ expanded: true,
+ notes: [
+ {
+ id: 904,
+ type: null,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-05-29T12:06:16.112Z',
+ updated_at: '2018-05-29T12:06:16.112Z',
+ system: false,
+ noteable_id: 182,
+ noteable_type: 'Issue',
+ resolvable: false,
+ noteable_iid: 12,
+ note: 'Ullamcorper eget nulla facilisi etiam',
+ note_html: '<p dir="auto">Ullamcorper eget nulla facilisi etiam</p>',
+ current_user: { can_edit: true, can_award_emoji: true },
+ resolved: false,
+ resolved_by: null,
+ discussion_id: '091865fe3ae20f0045234a3d103e3b15e73405b5',
+ emoji_awardable: true,
+ award_emoji: [],
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-shell%2Fissues%2F12%23note_904&user_id=1',
+ human_access: 'Owner',
+ toggle_award_path: '/gitlab-org/gitlab-shell/notes/904/toggle_award_emoji',
+ path: '/gitlab-org/gitlab-shell/notes/904',
+ },
+ ],
+ individual_note: true,
+ resolvable: false,
+ resolved: false,
+ diff_discussion: false,
+ },
+ {
+ id: 'a21cf2e804acc3c60d07e37d75e395f5a9a4d044',
+ reply_id: 'a21cf2e804acc3c60d07e37d75e395f5a9a4d044',
+ expanded: true,
+ notes: [
+ {
+ id: 905,
+ type: null,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-05-29T12:06:28.851Z',
+ updated_at: '2018-05-29T12:06:28.851Z',
+ system: true,
+ noteable_id: 182,
+ noteable_type: 'Issue',
+ resolvable: false,
+ noteable_iid: 12,
+ note: 'changed the description',
+ note_html: '<p dir="auto">changed the description</p>',
+ current_user: { can_edit: false, can_award_emoji: true },
+ resolved: false,
+ resolved_by: null,
+ system_note_icon_name: 'pencil-square',
+ discussion_id: 'a21cf2e804acc3c60d07e37d75e395f5a9a4d044',
+ emoji_awardable: false,
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-shell%2Fissues%2F12%23note_905&user_id=1',
+ human_access: 'Owner',
+ path: '/gitlab-org/gitlab-shell/notes/905',
+ },
+ ],
+ individual_note: true,
+ resolvable: false,
+ resolved: false,
+ diff_discussion: false,
+ },
+ {
+ id: '70411b08cdfc01f24187a06d77daa33464cb2620',
+ reply_id: '70411b08cdfc01f24187a06d77daa33464cb2620',
+ expanded: true,
+ notes: [
+ {
+ id: 906,
+ type: null,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-05-29T12:20:02.925Z',
+ updated_at: '2018-05-29T12:20:02.925Z',
+ system: true,
+ noteable_id: 182,
+ noteable_type: 'Issue',
+ resolvable: false,
+ noteable_iid: 12,
+ note: 'changed the description',
+ note_html: '<p dir="auto">changed the description</p>',
+ current_user: { can_edit: false, can_award_emoji: true },
+ resolved: false,
+ resolved_by: null,
+ system_note_icon_name: 'pencil-square',
+ discussion_id: '70411b08cdfc01f24187a06d77daa33464cb2620',
+ emoji_awardable: false,
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-shell%2Fissues%2F12%23note_906&user_id=1',
+ human_access: 'Owner',
+ path: '/gitlab-org/gitlab-shell/notes/906',
+ },
+ ],
+ individual_note: true,
+ resolvable: false,
+ resolved: false,
+ diff_discussion: false,
+ },
+];
+
+export const collapsedSystemNotes = [
+ {
+ id: '39b271c2033e9ed43d8edb393702f65f7a830459',
+ reply_id: '39b271c2033e9ed43d8edb393702f65f7a830459',
+ expanded: true,
+ notes: [
+ {
+ id: 901,
+ type: null,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-05-29T12:05:36.117Z',
+ updated_at: '2018-05-29T12:05:36.117Z',
+ system: false,
+ noteable_id: 182,
+ noteable_type: 'Issue',
+ resolvable: false,
+ noteable_iid: 12,
+ note:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ note_html:
+ '<p dir="auto">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>',
+ current_user: { can_edit: true, can_award_emoji: true },
+ resolved: false,
+ resolved_by: null,
+ discussion_id: '39b271c2033e9ed43d8edb393702f65f7a830459',
+ emoji_awardable: true,
+ award_emoji: [],
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-shell%2Fissues%2F12%23note_901&user_id=1',
+ human_access: 'Owner',
+ toggle_award_path: '/gitlab-org/gitlab-shell/notes/901/toggle_award_emoji',
+ path: '/gitlab-org/gitlab-shell/notes/901',
+ },
+ ],
+ individual_note: true,
+ resolvable: false,
+ resolved: false,
+ diff_discussion: false,
+ },
+ {
+ id: '4852335d7dc40b9ceb8fde1a2bb9c1b67e4c7795',
+ reply_id: '4852335d7dc40b9ceb8fde1a2bb9c1b67e4c7795',
+ expanded: true,
+ notes: [
+ {
+ id: 902,
+ type: null,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-05-29T12:05:58.694Z',
+ updated_at: '2018-05-29T12:05:58.694Z',
+ system: false,
+ noteable_id: 182,
+ noteable_type: 'Issue',
+ resolvable: false,
+ noteable_iid: 12,
+ note:
+ 'Varius vel pharetra vel turpis nunc eget lorem. Ipsum dolor sit amet consectetur adipiscing.',
+ note_html:
+ '<p dir="auto">Varius vel pharetra vel turpis nunc eget lorem. Ipsum dolor sit amet consectetur adipiscing.</p>',
+ current_user: { can_edit: true, can_award_emoji: true },
+ resolved: false,
+ resolved_by: null,
+ discussion_id: '4852335d7dc40b9ceb8fde1a2bb9c1b67e4c7795',
+ emoji_awardable: true,
+ award_emoji: [],
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-shell%2Fissues%2F12%23note_902&user_id=1',
+ human_access: 'Owner',
+ toggle_award_path: '/gitlab-org/gitlab-shell/notes/902/toggle_award_emoji',
+ path: '/gitlab-org/gitlab-shell/notes/902',
+ },
+ ],
+ individual_note: true,
+ resolvable: false,
+ resolved: false,
+ diff_discussion: false,
+ },
+ {
+ id: '091865fe3ae20f0045234a3d103e3b15e73405b5',
+ reply_id: '091865fe3ae20f0045234a3d103e3b15e73405b5',
+ expanded: true,
+ notes: [
+ {
+ id: 904,
+ type: null,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-05-29T12:06:16.112Z',
+ updated_at: '2018-05-29T12:06:16.112Z',
+ system: false,
+ noteable_id: 182,
+ noteable_type: 'Issue',
+ resolvable: false,
+ noteable_iid: 12,
+ note: 'Ullamcorper eget nulla facilisi etiam',
+ note_html: '<p dir="auto">Ullamcorper eget nulla facilisi etiam</p>',
+ current_user: { can_edit: true, can_award_emoji: true },
+ resolved: false,
+ resolved_by: null,
+ discussion_id: '091865fe3ae20f0045234a3d103e3b15e73405b5',
+ emoji_awardable: true,
+ award_emoji: [],
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-shell%2Fissues%2F12%23note_904&user_id=1',
+ human_access: 'Owner',
+ toggle_award_path: '/gitlab-org/gitlab-shell/notes/904/toggle_award_emoji',
+ path: '/gitlab-org/gitlab-shell/notes/904',
+ },
+ ],
+ individual_note: true,
+ resolvable: false,
+ resolved: false,
+ diff_discussion: false,
+ },
+ {
+ id: 'a21cf2e804acc3c60d07e37d75e395f5a9a4d044',
+ reply_id: 'a21cf2e804acc3c60d07e37d75e395f5a9a4d044',
+ expanded: true,
+ notes: [
+ {
+ id: 905,
+ type: null,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-05-29T12:06:28.851Z',
+ updated_at: '2018-05-29T12:06:28.851Z',
+ system: true,
+ noteable_id: 182,
+ noteable_type: 'Issue',
+ resolvable: false,
+ noteable_iid: 12,
+ note: 'changed the description',
+ note_html: '\n <p dir="auto">changed the description 2 times within 1 minute </p>',
+ current_user: { can_edit: false, can_award_emoji: true },
+ resolved: false,
+ resolved_by: null,
+ system_note_icon_name: 'pencil-square',
+ discussion_id: 'a21cf2e804acc3c60d07e37d75e395f5a9a4d044',
+ emoji_awardable: false,
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-shell%2Fissues%2F12%23note_905&user_id=1',
+ human_access: 'Owner',
+ path: '/gitlab-org/gitlab-shell/notes/905',
+ times_updated: 2,
+ },
+ ],
+ individual_note: true,
+ resolvable: false,
+ resolved: false,
+ diff_discussion: false,
+ },
+ {
+ id: '70411b08cdfc01f24187a06d77daa33464cb2620',
+ reply_id: '70411b08cdfc01f24187a06d77daa33464cb2620',
+ expanded: true,
+ notes: [
+ {
+ id: 906,
+ type: null,
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-05-29T12:20:02.925Z',
+ updated_at: '2018-05-29T12:20:02.925Z',
+ system: true,
+ noteable_id: 182,
+ noteable_type: 'Issue',
+ resolvable: false,
+ noteable_iid: 12,
+ note: 'changed the description',
+ note_html: '<p dir="auto">changed the description</p>',
+ current_user: { can_edit: false, can_award_emoji: true },
+ resolved: false,
+ resolved_by: null,
+ system_note_icon_name: 'pencil-square',
+ discussion_id: '70411b08cdfc01f24187a06d77daa33464cb2620',
+ emoji_awardable: false,
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-shell%2Fissues%2F12%23note_906&user_id=1',
+ human_access: 'Owner',
+ path: '/gitlab-org/gitlab-shell/notes/906',
+ },
+ ],
+ individual_note: true,
+ resolvable: false,
+ resolved: false,
+ diff_discussion: false,
+ },
+];
diff --git a/spec/javascripts/notes/stores/collapse_utils_spec.js b/spec/javascripts/notes/stores/collapse_utils_spec.js
new file mode 100644
index 00000000000..06a6aab932a
--- /dev/null
+++ b/spec/javascripts/notes/stores/collapse_utils_spec.js
@@ -0,0 +1,46 @@
+import {
+ isDescriptionSystemNote,
+ changeDescriptionNote,
+ getTimeDifferenceMinutes,
+ collapseSystemNotes,
+} from '~/notes/stores/collapse_utils';
+import {
+ notesWithDescriptionChanges,
+ collapsedSystemNotes,
+} from '../mock_data';
+
+describe('Collapse utils', () => {
+ const mockSystemNote = {
+ note: 'changed the description',
+ note_html: '<p dir="auto">changed the description</p>',
+ system: true,
+ created_at: '2018-05-14T21:28:00.000Z',
+ };
+
+ it('checks if a system note is of a description type', () => {
+ expect(isDescriptionSystemNote(mockSystemNote)).toEqual(true);
+ });
+
+ it('returns false when a system note is not a description type', () => {
+ expect(isDescriptionSystemNote(Object.assign({}, mockSystemNote, { note: 'foo' }))).toEqual(false);
+ });
+
+ it('changes the description to contain the number of changed times', () => {
+ const changedNote = changeDescriptionNote(mockSystemNote, 3, 5);
+
+ expect(changedNote.times_updated).toEqual(3);
+ expect(changedNote.note_html.trim()).toContain('<p dir="auto">changed the description 3 times within 5 minutes </p>');
+ });
+
+ it('gets the time difference between two notes', () => {
+ const anotherSystemNote = {
+ created_at: '2018-05-14T21:33:00.000Z',
+ };
+
+ expect(getTimeDifferenceMinutes(mockSystemNote, anotherSystemNote)).toEqual(5);
+ });
+
+ it('collapses all description system notes made within 10 minutes or less from each other', () => {
+ expect(collapseSystemNotes(notesWithDescriptionChanges)).toEqual(collapsedSystemNotes);
+ });
+});
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index 8b2a8d2cd7a..e5550580bf8 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -1,8 +1,9 @@
import * as getters from '~/notes/stores/getters';
-import { notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data';
+import { notesDataMock, userDataMock, noteableDataMock, individualNote, collapseNotesMock } from '../mock_data';
describe('Getters Notes Store', () => {
let state;
+
beforeEach(() => {
state = {
notes: [individualNote],
@@ -20,6 +21,22 @@ describe('Getters Notes Store', () => {
});
});
+ describe('Collapsed notes', () => {
+ const stateCollapsedNotes = {
+ notes: collapseNotesMock,
+ targetNoteHash: 'hash',
+ lastFetchedAt: 'timestamp',
+
+ notesData: notesDataMock,
+ userData: userDataMock,
+ noteableData: noteableDataMock,
+ };
+
+ it('should return a single system note when a description was updated multiple times', () => {
+ expect(getters.notes(stateCollapsedNotes).length).toEqual(1);
+ });
+ });
+
describe('targetNoteHash', () => {
it('should return `targetNoteHash`', () => {
expect(getters.targetNoteHash(state)).toEqual('hash');
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 648fb3e9bd3..acbf23e2007 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -974,7 +974,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
).toBeFalsy();
expect(
$tempNoteHeader
- .find('.d-none.d-sm-block')
+ .find('.d-none.d-sm-inline-block')
.text()
.trim(),
).toEqual(currentUserFullname);
@@ -1020,7 +1020,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
const $tempNoteHeader = $tempNote.find('.note-header');
expect(
$tempNoteHeader
- .find('.d-none.d-sm-block')
+ .find('.d-none.d-sm-inline-block')
.text()
.trim(),
).toEqual('Foo &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;');
diff --git a/spec/javascripts/pipelines/graph/mock_data.js b/spec/javascripts/pipelines/graph/mock_data.js
index 70eba98e939..9e25a4b3fed 100644
--- a/spec/javascripts/pipelines/graph/mock_data.js
+++ b/spec/javascripts/pipelines/graph/mock_data.js
@@ -20,7 +20,7 @@ export default {
has_details: true,
details_path: '/root/ci-mock/pipelines/123',
favicon:
- '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
},
duration: 9,
finished_at: '2017-04-19T14:30:27.542Z',
@@ -40,7 +40,7 @@ export default {
has_details: true,
details_path: '/root/ci-mock/builds/4153',
favicon:
- '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: {
icon: 'retry',
title: 'Retry',
@@ -65,7 +65,7 @@ export default {
has_details: true,
details_path: '/root/ci-mock/builds/4153',
favicon:
- '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: {
icon: 'retry',
title: 'Retry',
@@ -85,7 +85,7 @@ export default {
has_details: true,
details_path: '/root/ci-mock/pipelines/123#test',
favicon:
- '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
},
path: '/root/ci-mock/pipelines/123#test',
dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=test',
@@ -105,7 +105,7 @@ export default {
has_details: true,
details_path: '/root/ci-mock/builds/4166',
favicon:
- '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: {
icon: 'retry',
title: 'Retry',
@@ -130,7 +130,7 @@ export default {
has_details: true,
details_path: '/root/ci-mock/builds/4166',
favicon:
- '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: {
icon: 'retry',
title: 'Retry',
@@ -152,7 +152,7 @@ export default {
has_details: true,
details_path: '/root/ci-mock/builds/4159',
favicon:
- '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: {
icon: 'retry',
title: 'Retry',
@@ -177,7 +177,7 @@ export default {
has_details: true,
details_path: '/root/ci-mock/builds/4159',
favicon:
- '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: {
icon: 'retry',
title: 'Retry',
@@ -197,7 +197,7 @@ export default {
has_details: true,
details_path: '/root/ci-mock/pipelines/123#deploy',
favicon:
- '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
},
path: '/root/ci-mock/pipelines/123#deploy',
dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=deploy',
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index ff17602da2b..50141bd99b4 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -427,7 +427,7 @@ describe('Pipelines', () => {
describe('methods', () => {
beforeEach(() => {
- spyOn(history, 'pushState').and.stub();
+ spyOn(window.history, 'pushState').and.stub();
});
describe('updateContent', () => {
diff --git a/spec/javascripts/profile/account/components/update_username_spec.js b/spec/javascripts/profile/account/components/update_username_spec.js
index bac306edf5a..5311499fb73 100644
--- a/spec/javascripts/profile/account/components/update_username_spec.js
+++ b/spec/javascripts/profile/account/components/update_username_spec.js
@@ -90,7 +90,8 @@ describe('UpdateUsername component', () => {
it('confirmation modal should escape usernames properly', done => {
const { modalBody } = findElements();
- vm.username = vm.newUsername = '<i>Italic</i>';
+ vm.username = '<i>Italic</i>';
+ vm.newUsername = vm.username;
Vue.nextTick()
.then(() => {
diff --git a/spec/javascripts/settings_panels_spec.js b/spec/javascripts/settings_panels_spec.js
index 4fba36bd4de..c1a69bd7018 100644
--- a/spec/javascripts/settings_panels_spec.js
+++ b/spec/javascripts/settings_panels_spec.js
@@ -9,11 +9,11 @@ describe('Settings Panels', () => {
describe('initSettingsPane', () => {
afterEach(() => {
- location.hash = '';
+ window.location.hash = '';
});
it('should expand linked hash fragment panel', () => {
- location.hash = '#autodevops-settings';
+ window.location.hash = '#autodevops-settings';
const pipelineSettingsPanel = document.querySelector('#autodevops-settings');
// Our test environment automatically expands everything so we need to clear that out first
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index d73608ed0ed..b10d8be6781 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -66,7 +66,7 @@ describe('ShortcutsIssuable', function () {
});
describe('with a multi-line selection', () => {
it('quotes the selected lines as a group', () => {
- stubSelection('<p>Selected line one.</p>\n\n<p>Selected line two.</p>\n\n<p>Selected line three.</p>');
+ stubSelection('<p>Selected line one.</p>\n<p>Selected line two.</p>\n<p>Selected line three.</p>');
this.shortcut.replyWithSelectedText(true);
expect($(this.selector).val()).toBe('> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n');
});
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 2411d33a496..994011b262a 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -39,7 +39,8 @@ jasmine.getJSONFixtures().fixturesPath = FIXTURES_PATH;
beforeAll(() => jasmine.addMatchers(customMatchers));
// globalize common libraries
-window.$ = window.jQuery = $;
+window.$ = $;
+window.jQuery = window.$;
// stub expected globals
window.gl = window.gl || {};
diff --git a/spec/javascripts/test_constants.js b/spec/javascripts/test_constants.js
index df59195e9f6..a820dd2d09c 100644
--- a/spec/javascripts/test_constants.js
+++ b/spec/javascripts/test_constants.js
@@ -2,3 +2,6 @@ export const FIXTURES_PATH = '/base/spec/javascripts/fixtures';
export const TEST_HOST = 'http://test.host';
export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/one_white_pixel.png`;
+
+export const GREEN_BOX_IMAGE_URL = `${FIXTURES_PATH}/images/green_box.png`;
+export const RED_BOX_IMAGE_URL = `${FIXTURES_PATH}/images/red_box.png`;
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js
index 6784b498c29..10143402acf 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js
@@ -1,12 +1,12 @@
import Vue from 'vue';
-import authorTimeComponent from '~/vue_merge_request_widget/components/mr_widget_author_time.vue';
+import MrWidgetAuthorTime from '~/vue_merge_request_widget/components/mr_widget_author_time.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
-describe('MRWidgetAuthorTime', () => {
+describe('MrWidgetAuthorTime', () => {
let vm;
beforeEach(() => {
- const Component = Vue.extend(authorTimeComponent);
+ const Component = Vue.extend(MrWidgetAuthorTime);
vm = mountComponent(Component, {
actionText: 'Merged by',
@@ -34,7 +34,7 @@ describe('MRWidgetAuthorTime', () => {
});
it('renders provided time', () => {
- expect(vm.$el.querySelector('time').getAttribute('title')).toEqual('2017-03-23T23:02:00.807Z');
+ expect(vm.$el.querySelector('time').getAttribute('data-original-title')).toEqual('2017-03-23T23:02:00.807Z');
expect(vm.$el.querySelector('time').textContent.trim()).toEqual('12 hours ago');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
index 9b9c9656979..3d36e46d863 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
@@ -12,6 +12,7 @@ describe('MRWidgetHeader', () => {
afterEach(() => {
vm.$destroy();
+ gon.relative_url_root = '';
});
describe('computed', () => {
@@ -145,7 +146,16 @@ describe('MRWidgetHeader', () => {
const button = vm.$el.querySelector('.js-web-ide');
expect(button.textContent.trim()).toEqual('Web IDE');
- expect(button.getAttribute('href')).toEqual('undefined/-/ide/projectabc');
+ expect(button.getAttribute('href')).toEqual('/-/ide/projectabc');
+ });
+
+ it('renders web ide button with relative URL', () => {
+ gon.relative_url_root = '/gitlab';
+
+ const button = vm.$el.querySelector('.js-web-ide');
+
+ expect(button.textContent.trim()).toEqual('Web IDE');
+ expect(button.getAttribute('href')).toEqual('/-/ide/projectabc');
});
it('renders download dropdown with links', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
index a0a74648328..8de99fd3c96 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
@@ -6,6 +6,7 @@ import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('MRWidgetFailedToMerge', () => {
const dummyIntervalId = 1337;
let Component;
+ let mr;
let vm;
beforeEach(() => {
@@ -13,10 +14,11 @@ describe('MRWidgetFailedToMerge', () => {
spyOn(eventHub, '$emit');
spyOn(window, 'setInterval').and.returnValue(dummyIntervalId);
spyOn(window, 'clearInterval').and.stub();
+ mr = {
+ mergeError: 'Merge error happened',
+ };
vm = mountComponent(Component, {
- mr: {
- mergeError: 'Merge error happened.',
- },
+ mr,
});
});
@@ -44,6 +46,19 @@ describe('MRWidgetFailedToMerge', () => {
expect(vm.timerText).toEqual('Refreshing in a second to show the updated status...');
});
});
+
+ describe('mergeError', () => {
+ it('removes forced line breaks', done => {
+ mr.mergeError = 'contains<br />line breaks<br />';
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.mergeError).toBe('contains line breaks');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
describe('created', () => {
@@ -103,7 +118,7 @@ describe('MRWidgetFailedToMerge', () => {
it('renders given error', () => {
expect(vm.$el.querySelector('.has-error-message').textContent.trim()).toEqual(
- 'Merge error happened..',
+ 'Merge error happened.',
);
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
index adeea03481f..efa5c878678 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
@@ -39,7 +39,8 @@ describe('MRWidgetMerged', () => {
readableClosedAt: '',
},
updatedAt: 'mergedUpdatedAt',
- shortMergeCommitSha: 'asdf1234',
+ shortMergeCommitSha: '958c0475',
+ mergeCommitSha: '958c047516e182dfc52317f721f696e8a1ee85ed',
mergeCommitPath: 'http://localhost:3000/root/nautilus/commit/f7ce827c314c9340b075657fd61c789fb01cf74d',
sourceBranch: 'bar',
targetBranch,
@@ -153,7 +154,7 @@ describe('MRWidgetMerged', () => {
it('shows button to copy commit SHA to clipboard', () => {
expect(selectors.copyMergeShaButton).toExist();
- expect(selectors.copyMergeShaButton.getAttribute('data-clipboard-text')).toBe(vm.mr.shortMergeCommitSha);
+ expect(selectors.copyMergeShaButton.getAttribute('data-clipboard-text')).toBe(vm.mr.mergeCommitSha);
});
it('shows merge commit SHA link', () => {
@@ -186,7 +187,7 @@ describe('MRWidgetMerged', () => {
it('should use mergedEvent mergedAt as tooltip title', () => {
expect(
- vm.$el.querySelector('time').getAttribute('title'),
+ vm.$el.querySelector('time').getAttribute('data-original-title'),
).toBe('Jan 24, 2018 1:02pm GMT+0000');
});
});
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 30918428da2..6342ea00436 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -5,6 +5,7 @@ import notify from '~/lib/utils/notify';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from './mock_data';
+import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from '../lib/utils/mock_data';
const returnPromise = data => new Promise((resolve) => {
resolve({
@@ -273,6 +274,7 @@ describe('mrWidgetOptions', () => {
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
+ favicon.setAttribute('data-original-href', faviconDataUrl);
document.body.appendChild(favicon);
faviconElement = document.getElementById('favicon');
@@ -282,10 +284,13 @@ describe('mrWidgetOptions', () => {
document.body.removeChild(document.getElementById('favicon'));
});
- it('should call setFavicon method', () => {
- vm.setFaviconHelper();
-
- expect(faviconElement.getAttribute('href')).toEqual(vm.mr.ciStatusFaviconPath);
+ it('should call setFavicon method', (done) => {
+ vm.mr.ciStatusFaviconPath = overlayDataUrl;
+ vm.setFaviconHelper().then(() => {
+ expect(faviconElement.getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
+ done();
+ })
+ .catch(done.fail);
});
it('should not call setFavicon when there is no ciStatusFaviconPath', () => {
diff --git a/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js b/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js
index 383f0cd29ea..e2c34508b0d 100644
--- a/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js
+++ b/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js
@@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import contentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { GREEN_BOX_IMAGE_URL } from 'spec/test_constants';
describe('ContentViewer', () => {
let vm;
@@ -41,12 +42,12 @@ describe('ContentViewer', () => {
it('renders image preview', done => {
createComponent({
- path: 'test.jpg',
+ path: GREEN_BOX_IMAGE_URL,
fileSize: 1024,
});
setTimeout(() => {
- expect(vm.$el.querySelector('.image_file img').getAttribute('src')).toBe('test.jpg');
+ expect(vm.$el.querySelector('.image_file img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
done();
});
@@ -59,9 +60,8 @@ describe('ContentViewer', () => {
});
setTimeout(() => {
- expect(vm.$el.querySelector('.file-info').textContent.trim()).toContain(
- 'test.abc (1.00 KiB)',
- );
+ expect(vm.$el.querySelector('.file-info').textContent.trim()).toContain('test.abc');
+ expect(vm.$el.querySelector('.file-info').textContent.trim()).toContain('(1.00 KiB)');
expect(vm.$el.querySelector('.btn.btn-default').textContent.trim()).toContain('Download');
done();
diff --git a/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js b/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js
new file mode 100644
index 00000000000..71d9145bf22
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js
@@ -0,0 +1,70 @@
+import Vue from 'vue';
+import diffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
+
+describe('DiffViewer', () => {
+ let vm;
+
+ function createComponent(props) {
+ const DiffViewer = Vue.extend(diffViewer);
+ vm = mountComponent(DiffViewer, props);
+ }
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders image diff', done => {
+ window.gon = {
+ relative_url_root: '',
+ };
+
+ createComponent({
+ diffMode: 'replaced',
+ newPath: GREEN_BOX_IMAGE_URL,
+ newSha: 'ABC',
+ oldPath: RED_BOX_IMAGE_URL,
+ oldSha: 'DEF',
+ projectPath: '',
+ });
+
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.deleted .image_file img').getAttribute('src')).toBe(
+ `//raw/DEF/${RED_BOX_IMAGE_URL}`,
+ );
+
+ expect(vm.$el.querySelector('.added .image_file img').getAttribute('src')).toBe(
+ `//raw/ABC/${GREEN_BOX_IMAGE_URL}`,
+ );
+
+ done();
+ });
+ });
+
+ it('renders fallback download diff display', done => {
+ createComponent({
+ diffMode: 'replaced',
+ newPath: 'test.abc',
+ newSha: 'ABC',
+ oldPath: 'testold.abc',
+ oldSha: 'DEF',
+ });
+
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.deleted .file-info').textContent.trim()).toContain(
+ 'testold.abc',
+ );
+ expect(vm.$el.querySelector('.deleted .btn.btn-default').textContent.trim()).toContain(
+ 'Download',
+ );
+
+ expect(vm.$el.querySelector('.added .file-info').textContent.trim()).toContain('test.abc');
+ expect(vm.$el.querySelector('.added .btn.btn-default').textContent.trim()).toContain(
+ 'Download',
+ );
+
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js b/spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
new file mode 100644
index 00000000000..b878286ae3f
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
@@ -0,0 +1,185 @@
+import Vue from 'vue';
+import imageDiffViewer from '~/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
+
+describe('ImageDiffViewer', () => {
+ let vm;
+
+ function createComponent(props) {
+ const ImageDiffViewer = Vue.extend(imageDiffViewer);
+ vm = mountComponent(ImageDiffViewer, props);
+ }
+
+ const triggerEvent = (eventName, el = vm.$el, clientX = 0) => {
+ const event = document.createEvent('MouseEvents');
+ event.initMouseEvent(
+ eventName,
+ true,
+ true,
+ window,
+ 1,
+ clientX,
+ 0,
+ clientX,
+ 0,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null,
+ );
+
+ el.dispatchEvent(event);
+ };
+
+ const dragSlider = (sliderElement, dragPixel = 20) => {
+ triggerEvent('mousedown', sliderElement);
+ triggerEvent('mousemove', document.body, dragPixel);
+ triggerEvent('mouseup', document.body);
+ };
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders image diff for replaced', done => {
+ createComponent({
+ diffMode: 'replaced',
+ newPath: GREEN_BOX_IMAGE_URL,
+ oldPath: RED_BOX_IMAGE_URL,
+ });
+
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.added .image_file img').getAttribute('src')).toBe(
+ GREEN_BOX_IMAGE_URL,
+ );
+ expect(vm.$el.querySelector('.deleted .image_file img').getAttribute('src')).toBe(
+ RED_BOX_IMAGE_URL,
+ );
+
+ expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe('2-up');
+ expect(vm.$el.querySelector('.view-modes-menu li:nth-child(2)').textContent.trim()).toBe(
+ 'Swipe',
+ );
+ expect(vm.$el.querySelector('.view-modes-menu li:nth-child(3)').textContent.trim()).toBe(
+ 'Onion skin',
+ );
+
+ done();
+ });
+ });
+
+ it('renders image diff for new', done => {
+ createComponent({
+ diffMode: 'new',
+ newPath: GREEN_BOX_IMAGE_URL,
+ oldPath: '',
+ });
+
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.added .image_file img').getAttribute('src')).toBe(
+ GREEN_BOX_IMAGE_URL,
+ );
+
+ done();
+ });
+ });
+
+ it('renders image diff for deleted', done => {
+ createComponent({
+ diffMode: 'deleted',
+ newPath: '',
+ oldPath: RED_BOX_IMAGE_URL,
+ });
+
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.deleted .image_file img').getAttribute('src')).toBe(
+ RED_BOX_IMAGE_URL,
+ );
+
+ done();
+ });
+ });
+
+ describe('swipeMode', () => {
+ beforeEach(done => {
+ createComponent({
+ diffMode: 'replaced',
+ newPath: GREEN_BOX_IMAGE_URL,
+ oldPath: RED_BOX_IMAGE_URL,
+ });
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('switches to Swipe Mode', done => {
+ vm.$el.querySelector('.view-modes-menu li:nth-child(2)').click();
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe('Swipe');
+ done();
+ });
+ });
+
+ it('drag handler is working', done => {
+ vm.$el.querySelector('.view-modes-menu li:nth-child(2)').click();
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.swipe-bar').style.left).toBe('1px');
+ expect(vm.$el.querySelector('.top-handle')).not.toBeNull();
+
+ dragSlider(vm.$el.querySelector('.swipe-bar'), 40);
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.swipe-bar').style.left).toBe('-20px');
+ done();
+ });
+ });
+ });
+ });
+
+ describe('onionSkin', () => {
+ beforeEach(done => {
+ createComponent({
+ diffMode: 'replaced',
+ newPath: GREEN_BOX_IMAGE_URL,
+ oldPath: RED_BOX_IMAGE_URL,
+ });
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('switches to Onion Skin Mode', done => {
+ vm.$el.querySelector('.view-modes-menu li:nth-child(3)').click();
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe(
+ 'Onion skin',
+ );
+ done();
+ });
+ });
+
+ it('has working drag handler', done => {
+ vm.$el.querySelector('.view-modes-menu li:nth-child(3)').click();
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.dragger').style.left).toBe('100px');
+
+ dragSlider(vm.$el.querySelector('.dragger'));
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.dragger').style.left).toBe('20px');
+ expect(vm.$el.querySelector('.added.frame').style.opacity).toBe('0.2');
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/gl_modal_spec.js b/spec/javascripts/vue_shared/components/gl_modal_spec.js
index 85cb1b90fc6..e4737714312 100644
--- a/spec/javascripts/vue_shared/components/gl_modal_spec.js
+++ b/spec/javascripts/vue_shared/components/gl_modal_spec.js
@@ -190,4 +190,45 @@ describe('GlModal', () => {
});
});
});
+
+ describe('handling sizes', () => {
+ it('should render modal-sm', () => {
+ vm = mountComponent(modalComponent, {
+ modalSize: 'sm',
+ });
+
+ expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-sm')).toEqual(true);
+ });
+
+ it('should render modal-lg', () => {
+ vm = mountComponent(modalComponent, {
+ modalSize: 'lg',
+ });
+
+ expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-lg')).toEqual(true);
+ });
+
+ it('should render modal-xl', () => {
+ vm = mountComponent(modalComponent, {
+ modalSize: 'xl',
+ });
+
+ expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-xl')).toEqual(true);
+ });
+
+ it('should not add modal size classes when md size is passed', () => {
+ vm = mountComponent(modalComponent, {
+ modalSize: 'md',
+ });
+
+ expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-md')).toEqual(false);
+ });
+
+ it('should not add modal size classes by default', () => {
+ vm = mountComponent(modalComponent, {});
+
+ expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-sm')).toEqual(false);
+ expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-lg')).toEqual(false);
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js b/spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js
new file mode 100644
index 00000000000..2388660b0c2
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js
@@ -0,0 +1,13 @@
+import * as domUtils from '~/vue_shared/components/lib/utils/dom_utils';
+
+describe('domUtils', () => {
+ describe('pixeliseValue', () => {
+ it('should add px to a given Number', () => {
+ expect(domUtils.pixeliseValue(12)).toEqual('12px');
+ });
+
+ it('should not add px to 0', () => {
+ expect(domUtils.pixeliseValue(0)).toEqual('');
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
index 4397b00acfa..370a296bd8f 100644
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
@@ -82,7 +82,7 @@ describe('DropdownValueComponent', () => {
});
it('renders label element with tooltip and styles based on label details', () => {
- const labelEl = vm.$el.querySelector('a span.label.color-label');
+ const labelEl = vm.$el.querySelector('a span.badge.color-label');
expect(labelEl).not.toBeNull();
expect(labelEl.dataset.placement).toBe('bottom');
expect(labelEl.dataset.container).toBe('body');
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index c63f15e5880..c36b607a34e 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -51,7 +51,7 @@ describe('Pagination component', () => {
expect(
component.$el.querySelector('.js-previous-button').classList.contains('disabled'),
- ).toEqual(true);
+ ).toEqual(true);
component.$el.querySelector('.js-previous-button a').click();
diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb
index 99872211a4e..63f2298357f 100644
--- a/spec/lib/backup/files_spec.rb
+++ b/spec/lib/backup/files_spec.rb
@@ -46,7 +46,9 @@ describe Backup::Files do
end
it 'calls tar command with unlink' do
- expect(subject).to receive(:run_pipeline!).with([%w(gzip -cd), %w(tar --unlink-first --recursive-unlink -C /var/gitlab-registry -xf -)], any_args)
+ expect(subject).to receive(:tar).and_return('blabla-tar')
+
+ expect(subject).to receive(:run_pipeline!).with([%w(gzip -cd), %w(blabla-tar --unlink-first --recursive-unlink -C /var/gitlab-registry -xf -)], any_args)
subject.restore
end
end
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index 23c04a1a101..ca319679e80 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -274,16 +274,13 @@ describe Backup::Manager do
}
)
- # the Fog mock only knows about directories we create explicitly
Fog.mock!
+
+ # the Fog mock only knows about directories we create explicitly
connection = ::Fog::Storage.new(Gitlab.config.backup.upload.connection.symbolize_keys)
connection.directories.create(key: Gitlab.config.backup.upload.remote_directory)
end
- after do
- Fog.unmock!
- end
-
context 'target path' do
it 'uses the tar filename by default' do
expect_any_instance_of(Fog::Collection).to receive(:create)
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index f583b2021a2..92a27e308d2 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -34,7 +34,9 @@ describe Backup::Repository do
let(:timestamp) { Time.utc(2017, 3, 22) }
let(:temp_dirs) do
Gitlab.config.repositories.storages.map do |name, storage|
- File.join(storage.legacy_disk_path, '..', 'repositories.old.' + timestamp.to_i.to_s)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ File.join(storage.legacy_disk_path, '..', 'repositories.old.' + timestamp.to_i.to_s)
+ end
end
end
diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
index 8224dc5a6b9..b645e49bd43 100644
--- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
+++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
@@ -11,4 +11,8 @@ describe Banzai::Filter::BlockquoteFenceFilter do
expect(output).to eq(expected)
end
+
+ it 'allows trailing whitespace on blockquote fence lines' do
+ expect(filter(">>> \ntest\n>>> ")).to eq("> test")
+ end
end
diff --git a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
index c19de7b784a..41f957c4e00 100644
--- a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::ImageLazyLoadFilter, lib: true do
+describe Banzai::Filter::ImageLazyLoadFilter do
include FilterSpecHelper
def image(path)
diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb
index 00c407d1b69..ab14d77d552 100644
--- a/spec/lib/banzai/filter/markdown_filter_spec.rb
+++ b/spec/lib/banzai/filter/markdown_filter_spec.rb
@@ -7,13 +7,13 @@ describe Banzai::Filter::MarkdownFilter do
it 'adds language to lang attribute when specified' do
result = filter("```html\nsome code\n```")
- expect(result).to start_with("\n<pre><code lang=\"html\">")
+ expect(result).to start_with("<pre><code lang=\"html\">")
end
it 'does not add language to lang attribute when not specified' do
result = filter("```\nsome code\n```")
- expect(result).to start_with("\n<pre><code>")
+ expect(result).to start_with("<pre><code>")
end
end
end
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index f8fa9b2d13d..91d4a60ba95 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -3,7 +3,8 @@ require 'spec_helper'
describe Banzai::Filter::MilestoneReferenceFilter do
include FilterSpecHelper
- let(:group) { create(:group, :public) }
+ let(:parent_group) { create(:group, :public) }
+ let(:group) { create(:group, :public, parent: parent_group) }
let(:project) { create(:project, :public, group: group) }
it 'requires project context' do
@@ -340,6 +341,13 @@ describe Banzai::Filter::MilestoneReferenceFilter do
expect(doc.css('a')).to be_empty
end
+
+ it 'supports parent group references', :nested_groups do
+ milestone.update!(group: parent_group)
+
+ doc = reference_filter("See #{reference}")
+ expect(doc.css('a').first.text).to eq(milestone.name)
+ end
end
context 'group context' do
diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
index 136646bd4ee..454ad1589b9 100644
--- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
@@ -99,7 +99,7 @@ describe Gitlab::Auth::UserAuthFinders do
context 'when the request format is empty' do
it 'the method call does not modify the original value' do
- env['action_dispatch.request.formats'] = nil
+ env.delete('action_dispatch.request.formats')
find_user_from_feed_token
diff --git a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
new file mode 100644
index 00000000000..877c061d11b
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 20180529152628 do
+ include TraceHelpers
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:builds) { table(:ci_builds) }
+ let(:job_artifacts) { table(:ci_job_artifacts) }
+
+ before do
+ namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
+ projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123)
+ @build = builds.create!(id: 1, project_id: 123, status: 'success', type: 'Ci::Build')
+ end
+
+ context 'when trace file exsits at the right place' do
+ before do
+ create_legacy_trace(@build, 'trace in file')
+ end
+
+ it 'correctly archive legacy traces' do
+ expect(job_artifacts.count).to eq(0)
+ expect(File.exist?(legacy_trace_path(@build))).to be_truthy
+
+ described_class.new.perform(1, 1)
+
+ expect(job_artifacts.count).to eq(1)
+ expect(File.exist?(legacy_trace_path(@build))).to be_falsy
+ expect(File.read(archived_trace_path(job_artifacts.first))).to eq('trace in file')
+ end
+ end
+
+ context 'when trace file does not exsits at the right place' do
+ it 'does not raise errors nor create job artifact' do
+ expect { described_class.new.perform(1, 1) }.not_to raise_error
+
+ expect(job_artifacts.count).to eq(0)
+ end
+ end
+
+ context 'when trace data exsits in database' do
+ before do
+ create_legacy_trace_in_db(@build, 'trace in db')
+ end
+
+ it 'correctly archive legacy traces' do
+ expect(job_artifacts.count).to eq(0)
+ expect(@build.read_attribute(:trace)).not_to be_empty
+
+ described_class.new.perform(1, 1)
+
+ @build.reload
+ expect(job_artifacts.count).to eq(1)
+ expect(@build.read_attribute(:trace)).to be_nil
+ expect(File.read(archived_trace_path(job_artifacts.first))).to eq('trace in db')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
index 0d2074eed22..0dee683350f 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
@@ -114,7 +114,7 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :sidekiq, :migra
it 'does not drop the temporary tracking table after processing the batch, if there are still untracked rows' do
subject.perform(1, untracked_files_for_uploads.last.id - 1)
- expect(ActiveRecord::Base.connection.table_exists?(:untracked_files_for_uploads)).to be_truthy
+ expect(ActiveRecord::Base.connection.data_source_exists?(:untracked_files_for_uploads)).to be_truthy
end
it 'drops the temporary tracking table after processing the batch, if there are no untracked rows left' do
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index 5c8a19a53bc..468f6ff6d24 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -20,6 +20,13 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do
Rainbow.enabled = @rainbow
end
+ around do |example|
+ # TODO migrate BareRepositoryImport https://gitlab.com/gitlab-org/gitaly/issues/953
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ example.run
+ end
+ end
+
shared_examples 'importing a repository' do
describe '.execute' do
it 'creates a project for a repository in storage' do
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index 1504826c7a5..afd8f5da39f 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -62,8 +62,10 @@ describe ::Gitlab::BareRepositoryImport::Repository do
before do
gitlab_shell.create_repository(repository_storage, hashed_path)
- repository = Rugged::Repository.new(repo_path)
- repository.config['gitlab.fullpath'] = 'to/repo'
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ repository = Rugged::Repository.new(repo_path)
+ repository.config['gitlab.fullpath'] = 'to/repo'
+ end
end
after do
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
index 48e9902027c..1cb8143a9e9 100644
--- a/spec/lib/gitlab/checks/change_access_spec.rb
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -52,7 +52,7 @@ describe Gitlab::Checks::ChangeAccess do
context 'with protected tag' do
let!(:protected_tag) { create(:protected_tag, project: project, name: 'v*') }
- context 'as master' do
+ context 'as maintainer' do
before do
project.add_master(user)
end
@@ -138,7 +138,7 @@ describe Gitlab::Checks::ChangeAccess do
context 'if the user is not allowed to delete protected branches' do
it 'raises an error' do
- expect { subject.exec }.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.')
+ expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to delete protected branches from this project. Only a project maintainer or owner can delete a protected branch.')
end
end
diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb
index a65012d2314..0e0788ce974 100644
--- a/spec/lib/gitlab/checks/force_push_spec.rb
+++ b/spec/lib/gitlab/checks/force_push_spec.rb
@@ -1,21 +1,17 @@
require 'spec_helper'
describe Gitlab::Checks::ForcePush do
- let(:project) { create(:project, :repository) }
- let(:repository) { project.repository.raw }
+ set(:project) { create(:project, :repository) }
- context "exit code checking", :skip_gitaly_mock do
- it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do
- allow(repository).to receive(:popen).and_return(['normal output', 0])
+ describe '.force_push?' do
+ it 'returns false if the repo is empty' do
+ allow(project).to receive(:empty_repo?).and_return(true)
- expect { described_class.force_push?(project, 'oldrev', 'newrev') }.not_to raise_error
+ expect(described_class.force_push?(project, 'HEAD', 'HEAD~')).to be(false)
end
- it "raises a GitError error if the `popen` call to git returns a non-zero exit code" do
- allow(repository).to receive(:popen).and_return(['error', 1])
-
- expect { described_class.force_push?(project, 'oldrev', 'newrev') }
- .to raise_error(Gitlab::Git::Repository::GitError)
+ it 'checks if old rev is an anchestor' do
+ expect(described_class.force_push?(project, 'HEAD', 'HEAD~')).to be(true)
end
end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index bc5a5e43103..2e204da307d 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -49,7 +49,7 @@ describe Gitlab::Ci::Config do
describe '.new' do
it 'raises error' do
expect { config }.to raise_error(
- Gitlab::Ci::Config::Loader::FormatError,
+ ::Gitlab::Ci::Config::Loader::FormatError,
/Invalid configuration format/
)
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index c5a4d9b4778..284aed91e29 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Populate do
- set(:project) { create(:project) }
+ set(:project) { create(:project, :repository) }
set(:user) { create(:user) }
let(:pipeline) do
@@ -174,7 +174,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
end
let(:pipeline) do
- build(:ci_pipeline, ref: 'master', config: config)
+ build(:ci_pipeline, ref: 'master', project: project, config: config)
end
it_behaves_like 'a correct pipeline'
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
index c53294d091c..a8dc5356413 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
- set(:project) { create(:project) }
+ set(:project) { create(:project, :repository) }
set(:user) { create(:user) }
let(:command) do
diff --git a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb
index 477c7477df0..40dfd893465 100644
--- a/spec/lib/gitlab/ci/pipeline/preloader_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/preloader_spec.rb
@@ -3,18 +3,47 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Preloader do
- describe '.preload' do
- it 'preloads the author of every pipeline commit' do
- commit = double(:commit)
- pipeline = double(:pipeline, commit: commit)
+ let(:stage) { double(:stage) }
+ let(:commit) { double(:commit) }
- expect(commit)
- .to receive(:lazy_author)
+ let(:pipeline) do
+ double(:pipeline, commit: commit, stages: [stage])
+ end
+
+ describe '.preload!' do
+ context 'when preloading multiple commits' do
+ let(:project) { create(:project, :repository) }
+
+ it 'preloads all commits once' do
+ expect(Commit).to receive(:decorate).once.and_call_original
+
+ pipelines = [build_pipeline(ref: 'HEAD'),
+ build_pipeline(ref: 'HEAD~1')]
+
+ described_class.preload!(pipelines)
+ end
+
+ def build_pipeline(ref:)
+ build_stubbed(:ci_pipeline, project: project, sha: project.commit(ref).id)
+ end
+ end
+
+ it 'preloads commit authors and number of warnings' do
+ expect(commit).to receive(:lazy_author)
+ expect(pipeline).to receive(:number_of_warnings)
+ expect(stage).to receive(:number_of_warnings)
+
+ described_class.preload!([pipeline])
+ end
+
+ it 'returns original collection' do
+ allow(commit).to receive(:lazy_author)
+ allow(pipeline).to receive(:number_of_warnings)
+ allow(stage).to receive(:number_of_warnings)
- expect(pipeline)
- .to receive(:number_of_warnings)
+ pipelines = [pipeline, pipeline]
- described_class.preload([pipeline])
+ expect(described_class.preload!(pipelines)).to eq pipelines
end
end
end
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index e9d755c2021..d6510649dba 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Ci::Trace, :clean_gitlab_redis_cache do
+describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do
let(:build) { create(:ci_build) }
let(:trace) { described_class.new(build) }
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index ecb16daec96..fa5327c26f0 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
module Gitlab
module Ci
- describe YamlProcessor, :lib do
+ describe YamlProcessor do
subject { described_class.new(config) }
describe 'our current .gitlab-ci.yml' do
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index 19028495f52..55490f37ac7 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -5,6 +5,13 @@ describe Gitlab::CurrentSettings do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
+ shared_context 'with settings in cache' do
+ before do
+ create(:application_setting)
+ described_class.current_application_settings # warm the cache
+ end
+ end
+
describe '#current_application_settings', :use_clean_rails_memory_store_caching do
it 'allows keys to be called directly' do
db_settings = create(:application_setting,
@@ -31,16 +38,29 @@ describe Gitlab::CurrentSettings do
end
context 'with DB unavailable' do
- before do
- # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues
- # during the initialization phase of the test suite, so instead let's mock the internals of it
- allow(ActiveRecord::Base.connection).to receive(:active?).and_return(false)
+ context 'and settings in cache' do
+ include_context 'with settings in cache'
+
+ it 'fetches the settings from cache without issuing any query' do
+ expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
+ end
end
- it 'returns an in-memory ApplicationSetting object' do
- expect(ApplicationSetting).not_to receive(:current)
+ context 'and no settings in cache' do
+ before do
+ # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues
+ # during the initialization phase of the test suite, so instead let's mock the internals of it
+ allow(ActiveRecord::Base.connection).to receive(:active?).and_return(false)
+ expect(ApplicationSetting).not_to receive(:current)
+ end
- expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
+ it 'returns an in-memory ApplicationSetting object' do
+ expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
+ end
+
+ it 'does not issue any query' do
+ expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
+ end
end
end
@@ -52,73 +72,86 @@ describe Gitlab::CurrentSettings do
ar_wrapped_defaults.slice(*::ApplicationSetting.defaults.keys)
end
- before do
- # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(true)` causes issues
- # during the initialization phase of the test suite, so instead let's mock the internals of it
- allow(ActiveRecord::Base.connection).to receive(:active?).and_return(true)
- allow(ActiveRecord::Base.connection).to receive(:cached_table_exists?).with('application_settings').and_return(true)
- end
+ context 'and settings in cache' do
+ include_context 'with settings in cache'
- it 'creates default ApplicationSettings if none are present' do
- settings = described_class.current_application_settings
-
- expect(settings).to be_a(ApplicationSetting)
- expect(settings).to be_persisted
- expect(settings).to have_attributes(settings_from_defaults)
+ it 'fetches the settings from cache' do
+ # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(true)` causes issues
+ # during the initialization phase of the test suite, so instead let's mock the internals of it
+ expect(ActiveRecord::Base.connection).not_to receive(:active?)
+ expect(ActiveRecord::Base.connection).not_to receive(:cached_table_exists?)
+ expect(ActiveRecord::Migrator).not_to receive(:needs_migration?)
+ expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
+ end
end
- context 'with migrations pending' do
+ context 'and no settings in cache' do
before do
- expect(ActiveRecord::Migrator).to receive(:needs_migration?).and_return(true)
+ allow(ActiveRecord::Base.connection).to receive(:active?).and_return(true)
+ allow(ActiveRecord::Base.connection).to receive(:cached_table_exists?).with('application_settings').and_return(true)
end
- it 'returns an in-memory ApplicationSetting object' do
+ it 'creates default ApplicationSettings if none are present' do
settings = described_class.current_application_settings
- expect(settings).to be_a(Gitlab::FakeApplicationSettings)
- expect(settings.sign_in_enabled?).to eq(settings.sign_in_enabled)
- expect(settings.sign_up_enabled?).to eq(settings.sign_up_enabled)
+ expect(settings).to be_a(ApplicationSetting)
+ expect(settings).to be_persisted
+ expect(settings).to have_attributes(settings_from_defaults)
end
- it 'uses the existing database settings and falls back to defaults' do
- db_settings = create(:application_setting,
- home_page_url: 'http://mydomain.com',
- signup_enabled: false)
- settings = described_class.current_application_settings
- app_defaults = ApplicationSetting.last
-
- expect(settings).to be_a(Gitlab::FakeApplicationSettings)
- expect(settings.home_page_url).to eq(db_settings.home_page_url)
- expect(settings.signup_enabled?).to be_falsey
- expect(settings.signup_enabled).to be_falsey
-
- # Check that unspecified values use the defaults
- settings.reject! { |key, _| [:home_page_url, :signup_enabled].include? key }
- settings.each { |key, _| expect(settings[key]).to eq(app_defaults[key]) }
+ context 'with migrations pending' do
+ before do
+ expect(ActiveRecord::Migrator).to receive(:needs_migration?).and_return(true)
+ end
+
+ it 'returns an in-memory ApplicationSetting object' do
+ settings = described_class.current_application_settings
+
+ expect(settings).to be_a(Gitlab::FakeApplicationSettings)
+ expect(settings.sign_in_enabled?).to eq(settings.sign_in_enabled)
+ expect(settings.sign_up_enabled?).to eq(settings.sign_up_enabled)
+ end
+
+ it 'uses the existing database settings and falls back to defaults' do
+ db_settings = create(:application_setting,
+ home_page_url: 'http://mydomain.com',
+ signup_enabled: false)
+ settings = described_class.current_application_settings
+ app_defaults = ApplicationSetting.last
+
+ expect(settings).to be_a(Gitlab::FakeApplicationSettings)
+ expect(settings.home_page_url).to eq(db_settings.home_page_url)
+ expect(settings.signup_enabled?).to be_falsey
+ expect(settings.signup_enabled).to be_falsey
+
+ # Check that unspecified values use the defaults
+ settings.reject! { |key, _| [:home_page_url, :signup_enabled].include? key }
+ settings.each { |key, _| expect(settings[key]).to eq(app_defaults[key]) }
+ end
end
- end
- context 'when ApplicationSettings.current is present' do
- it 'returns the existing application settings' do
- expect(ApplicationSetting).to receive(:current).and_return(:current_settings)
+ context 'when ApplicationSettings.current is present' do
+ it 'returns the existing application settings' do
+ expect(ApplicationSetting).to receive(:current).and_return(:current_settings)
- expect(described_class.current_application_settings).to eq(:current_settings)
+ expect(described_class.current_application_settings).to eq(:current_settings)
+ end
end
- end
- context 'when the application_settings table does not exists' do
- it 'returns an in-memory ApplicationSetting object' do
- expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::StatementInvalid)
+ context 'when the application_settings table does not exists' do
+ it 'returns an in-memory ApplicationSetting object' do
+ expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::StatementInvalid)
- expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
+ expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
+ end
end
- end
- context 'when the application_settings table is not fully migrated' do
- it 'returns an in-memory ApplicationSetting object' do
- expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::UnknownAttributeError)
+ context 'when the application_settings table is not fully migrated' do
+ it 'returns an in-memory ApplicationSetting object' do
+ expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::UnknownAttributeError)
- expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
+ expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
+ end
end
end
end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 8ac36ae8bab..8bb246aa4bd 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -314,8 +314,13 @@ describe Gitlab::Database do
describe '.cached_table_exists?' do
it 'only retrieves data once per table' do
- expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:projects).once.and_call_original
- expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:bogus_table_name).once.and_call_original
+ if Gitlab.rails5?
+ expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:projects).once.and_call_original
+ expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:bogus_table_name).once.and_call_original
+ else
+ expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:projects).once.and_call_original
+ expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:bogus_table_name).once.and_call_original
+ end
2.times do
expect(described_class.cached_table_exists?(:projects)).to be_truthy
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 0588fe935c3..5dfbb8e71f8 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -79,7 +79,9 @@ describe Gitlab::Diff::File do
let(:diffs) { commit.diffs }
before do
- info_dir_path = File.join(project.repository.path_to_repo, 'info')
+ info_dir_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ File.join(project.repository.path_to_repo, 'info')
+ end
FileUtils.mkdir(info_dir_path) unless File.exist?(info_dir_path)
File.write(File.join(info_dir_path, 'attributes'), "*.md -diff\n")
@@ -470,56 +472,69 @@ describe Gitlab::Diff::File do
end
describe '#diff_hunk' do
- let(:raw_diff) do
- <<EOS
-@@ -6,12 +6,18 @@ module Popen
-
- def popen(cmd, path=nil)
- unless cmd.is_a?(Array)
-- raise "System commands must be given as an array of strings"
-+ raise RuntimeError, "System commands must be given as an array of strings"
- end
-
- path ||= Dir.pwd
-- vars = { "PWD" => path }
-- options = { chdir: path }
-+
-+ vars = {
-+ "PWD" => path
-+ }
-+
-+ options = {
-+ chdir: path
-+ }
-
- unless File.directory?(path)
- FileUtils.mkdir_p(path)
-@@ -19,6 +25,7 @@ module Popen
-
- @cmd_output = ""
- @cmd_status = 0
-+
- Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
- @cmd_output << stdout.read
- @cmd_output << stderr.read
-EOS
- end
-
- it 'returns raw diff up to given line index' do
- allow(diff_file).to receive(:raw_diff) { raw_diff }
- diff_line = instance_double(Gitlab::Diff::Line, index: 5)
-
- diff_hunk = <<EOS
-@@ -6,12 +6,18 @@ module Popen
-
- def popen(cmd, path=nil)
- unless cmd.is_a?(Array)
-- raise "System commands must be given as an array of strings"
-+ raise RuntimeError, "System commands must be given as an array of strings"
- end
-EOS
-
- expect(diff_file.diff_hunk(diff_line)).to eq(diff_hunk)
+ context 'when first line is a match' do
+ let(:raw_diff) do
+ <<~EOS
+ --- a/files/ruby/popen.rb
+ +++ b/files/ruby/popen.rb
+ @@ -6,12 +6,18 @@ module Popen
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ - raise "System commands must be given as an array of strings"
+ + raise RuntimeError, "System commands must be given as an array of strings"
+ end
+ EOS
+ end
+
+ it 'returns raw diff up to given line index' do
+ allow(diff_file).to receive(:raw_diff) { raw_diff }
+ diff_line = instance_double(Gitlab::Diff::Line, index: 4)
+
+ diff_hunk = <<~EOS
+ @@ -6,12 +6,18 @@ module Popen
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ - raise "System commands must be given as an array of strings"
+ + raise RuntimeError, "System commands must be given as an array of strings"
+ EOS
+
+ expect(diff_file.diff_hunk(diff_line)).to eq(diff_hunk.strip)
+ end
+ end
+
+ context 'when first line is not a match' do
+ let(:raw_diff) do
+ <<~EOS
+ @@ -1,4 +1,4 @@
+ -Copyright (c) 2011-2017 GitLab B.V.
+ +Copyright (c) 2011-2019 GitLab B.V.
+
+ With regard to the GitLab Software:
+
+ @@ -9,17 +9,21 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ EOS
+ end
+
+ it 'returns raw diff up to given line index' do
+ allow(diff_file).to receive(:raw_diff) { raw_diff }
+ diff_line = instance_double(Gitlab::Diff::Line, index: 5)
+
+ diff_hunk = <<~EOS
+ -Copyright (c) 2011-2017 GitLab B.V.
+ +Copyright (c) 2011-2019 GitLab B.V.
+
+ With regard to the GitLab Software:
+
+ @@ -9,17 +9,21 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ EOS
+
+ expect(diff_file.diff_hunk(diff_line)).to eq(diff_hunk.strip)
+ end
end
end
end
diff --git a/spec/lib/gitlab/favicon_spec.rb b/spec/lib/gitlab/favicon_spec.rb
new file mode 100644
index 00000000000..f36111a4946
--- /dev/null
+++ b/spec/lib/gitlab/favicon_spec.rb
@@ -0,0 +1,52 @@
+require 'rails_helper'
+
+RSpec.describe Gitlab::Favicon, :request_store do
+ describe '.main' do
+ it 'defaults to favicon.png' do
+ allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
+ expect(described_class.main).to match_asset_path '/assets/favicon.png'
+ end
+
+ it 'has blue favicon for development' do
+ allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
+ expect(described_class.main).to match_asset_path '/assets/favicon-blue.png'
+ end
+
+ it 'has yellow favicon for canary' do
+ stub_env('CANARY', 'true')
+ expect(described_class.main).to match_asset_path 'favicon-yellow.png'
+ end
+
+ it 'uses the custom favicon if a favicon appearance is present' do
+ create :appearance, favicon: fixture_file_upload('spec/fixtures/dk.png')
+ expect(described_class.main).to match %r{/uploads/-/system/appearance/favicon/\d+/dk.png}
+ end
+ end
+
+ describe '.status_overlay' do
+ subject { described_class.status_overlay('favicon_status_created') }
+
+ it 'returns the overlay for the status' do
+ expect(subject).to match_asset_path '/assets/ci_favicons/favicon_status_created.png'
+ end
+ end
+
+ describe '.available_status_names' do
+ subject { described_class.available_status_names }
+
+ it 'returns the available status names' do
+ expect(subject).to eq %w(
+ favicon_status_canceled
+ favicon_status_created
+ favicon_status_failed
+ favicon_status_manual
+ favicon_status_not_found
+ favicon_status_pending
+ favicon_status_running
+ favicon_status_skipped
+ favicon_status_success
+ favicon_status_warning
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb
index 793228701cf..ba790b717ae 100644
--- a/spec/lib/gitlab/git/blame_spec.rb
+++ b/spec/lib/gitlab/git/blame_spec.rb
@@ -7,7 +7,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
Gitlab::Git::Blame.new(repository, SeedRepo::Commit::ID, "CONTRIBUTING.md")
end
- shared_examples 'blaming a file' do
+ describe 'blaming a file' do
context "each count" do
it do
data = []
@@ -68,12 +68,4 @@ describe Gitlab::Git::Blame, seed_helper: true do
end
end
end
-
- context 'when Gitaly blame feature is enabled' do
- it_behaves_like 'blaming a file'
- end
-
- context 'when Gitaly blame feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'blaming a file'
- end
end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 94eaf86ef80..6015086f002 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -149,7 +149,9 @@ describe Gitlab::Git::Blob, seed_helper: true do
it 'limits the size of a large file' do
blob_size = Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE + 1
buffer = Array.new(blob_size, 0)
- rugged_blob = Rugged::Blob.from_buffer(repository.rugged, buffer.join(''))
+ rugged_blob = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ Rugged::Blob.from_buffer(repository.rugged, buffer.join(''))
+ end
blob = Gitlab::Git::Blob.raw(repository, rugged_blob)
expect(blob.size).to eq(blob_size)
@@ -164,7 +166,9 @@ describe Gitlab::Git::Blob, seed_helper: true do
context 'when sha references a tree' do
it 'returns nil' do
- tree = repository.rugged.rev_parse('master^{tree}')
+ tree = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ repository.rugged.rev_parse('master^{tree}')
+ end
blob = Gitlab::Git::Blob.raw(repository, tree.oid)
@@ -278,7 +282,11 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
describe '.batch_lfs_pointers' do
- let(:tree_object) { repository.rugged.rev_parse('master^{tree}') }
+ let(:tree_object) do
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ repository.rugged.rev_parse('master^{tree}')
+ end
+ end
let(:non_lfs_blob) do
Gitlab::Git::Blob.find(
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index a19155ed5b0..ec1a684cfbc 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -69,7 +69,9 @@ describe Gitlab::Git::Branch, seed_helper: true do
Gitlab::Git.committer_hash(email: user.email, name: user.name)
end
let(:params) do
- parents = [repository.rugged.head.target]
+ parents = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ [repository.rugged.head.target]
+ end
tree = parents.first.tree
{
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 89be8a1b7f2..ae69a362dda 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -4,12 +4,15 @@ describe Gitlab::Git::Commit, seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
let(:commit) { described_class.find(repository, SeedRepo::Commit::ID) }
let(:rugged_commit) do
- repository.rugged.lookup(SeedRepo::Commit::ID)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ repository.rugged.lookup(SeedRepo::Commit::ID)
+ end
end
-
describe "Commit info" do
before do
- repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
+ repo = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
+ end
@committer = {
email: 'mike@smith.com',
@@ -58,7 +61,9 @@ describe Gitlab::Git::Commit, seed_helper: true do
after do
# Erase the new commit so other tests get the original repo
- repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
+ repo = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
+ end
repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
end
end
@@ -115,7 +120,9 @@ describe Gitlab::Git::Commit, seed_helper: true do
describe '.find' do
it "should return first head commit if without params" do
expect(described_class.last(repository).id).to eq(
- repository.rugged.head.target.oid
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ repository.rugged.head.target.oid
+ end
)
end
@@ -414,6 +421,16 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
end
+ describe '#batch_by_oid' do
+ context 'when oids is empty' do
+ it 'makes no Gitaly request' do
+ expect(Gitlab::GitalyClient).not_to receive(:call)
+
+ described_class.batch_by_oid(repository, [])
+ end
+ end
+ end
+
shared_examples 'extracting commit signature' do
context 'when the commit is signed' do
let(:commit_id) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
diff --git a/spec/lib/gitlab/git/committer_with_hooks_spec.rb b/spec/lib/gitlab/git/committer_with_hooks_spec.rb
index 267056b96e6..2100690f873 100644
--- a/spec/lib/gitlab/git/committer_with_hooks_spec.rb
+++ b/spec/lib/gitlab/git/committer_with_hooks_spec.rb
@@ -1,154 +1,156 @@
require 'spec_helper'
describe Gitlab::Git::CommitterWithHooks, seed_helper: true do
- shared_examples 'calling wiki hooks' do
- let(:project) { create(:project) }
- let(:user) { project.owner }
- let(:project_wiki) { ProjectWiki.new(project, user) }
- let(:wiki) { project_wiki.wiki }
- let(:options) do
- {
- id: user.id,
- username: user.username,
- name: user.name,
- email: user.email,
- message: 'commit message'
- }
- end
-
- subject { described_class.new(wiki, options) }
+ # TODO https://gitlab.com/gitlab-org/gitaly/issues/1234
+ skip 'needs to be moved to gitaly-ruby test suite' do
+ shared_examples 'calling wiki hooks' do
+ let(:project) { create(:project) }
+ let(:user) { project.owner }
+ let(:project_wiki) { ProjectWiki.new(project, user) }
+ let(:wiki) { project_wiki.wiki }
+ let(:options) do
+ {
+ id: user.id,
+ username: user.username,
+ name: user.name,
+ email: user.email,
+ message: 'commit message'
+ }
+ end
- before do
- project_wiki.create_page('home', 'test content')
- end
+ subject { described_class.new(wiki, options) }
- shared_examples 'failing pre-receive hook' do
before do
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([false, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('update')
- expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive')
+ project_wiki.create_page('home', 'test content')
end
- it 'raises exception' do
- expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
- end
+ shared_examples 'failing pre-receive hook' do
+ before do
+ expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([false, ''])
+ expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('update')
+ expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive')
+ end
- it 'does not create a new commit inside the repository' do
- current_rev = find_current_rev
+ it 'raises exception' do
+ expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
+ end
- expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
+ it 'does not create a new commit inside the repository' do
+ current_rev = find_current_rev
- expect(current_rev).to eq find_current_rev
- end
- end
+ expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
- shared_examples 'failing update hook' do
- before do
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([false, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive')
+ expect(current_rev).to eq find_current_rev
+ end
end
- it 'raises exception' do
- expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
- end
+ shared_examples 'failing update hook' do
+ before do
+ expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, ''])
+ expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([false, ''])
+ expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive')
+ end
- it 'does not create a new commit inside the repository' do
- current_rev = find_current_rev
+ it 'raises exception' do
+ expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
+ end
- expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
+ it 'does not create a new commit inside the repository' do
+ current_rev = find_current_rev
- expect(current_rev).to eq find_current_rev
- end
- end
+ expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
- shared_examples 'failing post-receive hook' do
- before do
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([true, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('post-receive').and_return([false, ''])
+ expect(current_rev).to eq find_current_rev
+ end
end
- it 'does not raise exception' do
- expect { subject.commit }.not_to raise_error
- end
+ shared_examples 'failing post-receive hook' do
+ before do
+ expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, ''])
+ expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([true, ''])
+ expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('post-receive').and_return([false, ''])
+ end
+
+ it 'does not raise exception' do
+ expect { subject.commit }.not_to raise_error
+ end
- it 'creates the commit' do
- current_rev = find_current_rev
+ it 'creates the commit' do
+ current_rev = find_current_rev
- subject.commit
+ subject.commit
- expect(current_rev).not_to eq find_current_rev
+ expect(current_rev).not_to eq find_current_rev
+ end
end
- end
- shared_examples 'when hooks call succceeds' do
- let(:hook) { double(:hook) }
+ shared_examples 'when hooks call succceeds' do
+ let(:hook) { double(:hook) }
- it 'calls the three hooks' do
- expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
- expect(hook).to receive(:trigger).exactly(3).times.and_return([true, nil])
+ it 'calls the three hooks' do
+ expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
+ expect(hook).to receive(:trigger).exactly(3).times.and_return([true, nil])
- subject.commit
- end
+ subject.commit
+ end
- it 'creates the commit' do
- current_rev = find_current_rev
+ it 'creates the commit' do
+ current_rev = find_current_rev
- subject.commit
+ subject.commit
- expect(current_rev).not_to eq find_current_rev
+ expect(current_rev).not_to eq find_current_rev
+ end
end
- end
- context 'when creating a page' do
- before do
- project_wiki.create_page('index', 'test content')
+ context 'when creating a page' do
+ before do
+ project_wiki.create_page('index', 'test content')
+ end
+
+ it_behaves_like 'failing pre-receive hook'
+ it_behaves_like 'failing update hook'
+ it_behaves_like 'failing post-receive hook'
+ it_behaves_like 'when hooks call succceeds'
end
- it_behaves_like 'failing pre-receive hook'
- it_behaves_like 'failing update hook'
- it_behaves_like 'failing post-receive hook'
- it_behaves_like 'when hooks call succceeds'
- end
+ context 'when updating a page' do
+ before do
+ project_wiki.update_page(find_page('home'), content: 'some other content', format: :markdown)
+ end
- context 'when updating a page' do
- before do
- project_wiki.update_page(find_page('home'), content: 'some other content', format: :markdown)
+ it_behaves_like 'failing pre-receive hook'
+ it_behaves_like 'failing update hook'
+ it_behaves_like 'failing post-receive hook'
+ it_behaves_like 'when hooks call succceeds'
end
- it_behaves_like 'failing pre-receive hook'
- it_behaves_like 'failing update hook'
- it_behaves_like 'failing post-receive hook'
- it_behaves_like 'when hooks call succceeds'
- end
+ context 'when deleting a page' do
+ before do
+ project_wiki.delete_page(find_page('home'))
+ end
- context 'when deleting a page' do
- before do
- project_wiki.delete_page(find_page('home'))
+ it_behaves_like 'failing pre-receive hook'
+ it_behaves_like 'failing update hook'
+ it_behaves_like 'failing post-receive hook'
+ it_behaves_like 'when hooks call succceeds'
end
- it_behaves_like 'failing pre-receive hook'
- it_behaves_like 'failing update hook'
- it_behaves_like 'failing post-receive hook'
- it_behaves_like 'when hooks call succceeds'
- end
+ def find_current_rev
+ wiki.gollum_wiki.repo.commits.first&.sha
+ end
- def find_current_rev
- wiki.gollum_wiki.repo.commits.first&.sha
+ def find_page(name)
+ wiki.page(title: name)
+ end
end
- def find_page(name)
- wiki.page(title: name)
+ context 'when Gitaly is enabled' do
+ it_behaves_like 'calling wiki hooks'
end
- end
-
- # TODO: Uncomment once Gitaly updates the ruby vendor code
- # context 'when Gitaly is enabled' do
- # it_behaves_like 'calling wiki hooks'
- # end
- context 'when Gitaly is disabled', :skip_gitaly_mock do
- it_behaves_like 'calling wiki hooks'
+ context 'when Gitaly is disabled', :disable_gitaly do
+ it_behaves_like 'calling wiki hooks'
+ end
end
end
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index 4a7b06003fc..3bb0b5be15b 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -27,8 +27,10 @@ EOT
too_large: false
}
- @rugged_diff = repository.rugged.diff("5937ac0a7beb003549fc5fd26fc247adbce4a52e^", "5937ac0a7beb003549fc5fd26fc247adbce4a52e", paths:
- [".gitmodules"]).patches.first
+ @rugged_diff = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ repository.rugged.diff("5937ac0a7beb003549fc5fd26fc247adbce4a52e^", "5937ac0a7beb003549fc5fd26fc247adbce4a52e", paths:
+ [".gitmodules"]).patches.first
+ end
end
describe '.new' do
diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb
index 8b715d717c1..f5d8503c30c 100644
--- a/spec/lib/gitlab/git/gitlab_projects_spec.rb
+++ b/spec/lib/gitlab/git/gitlab_projects_spec.rb
@@ -5,6 +5,13 @@ describe Gitlab::Git::GitlabProjects do
TestEnv.clean_test_path
end
+ around do |example|
+ # TODO move this spec to gitaly-ruby. GitlabProjects is not used in gitlab-ce
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ example.run
+ end
+ end
+
let(:project) { create(:project, :repository) }
if $VERBOSE
@@ -190,36 +197,30 @@ describe Gitlab::Git::GitlabProjects do
end
end
- context 'when Gitaly import_repository feature is enabled' do
- it_behaves_like 'importing repository'
- end
+ describe 'logging' do
+ it 'imports a repo' do
+ message = "Importing project from <#{import_url}> to <#{tmp_repo_path}>."
+ expect(logger).to receive(:info).with(message)
- context 'when Gitaly import_repository feature is disabled', :disable_gitaly do
- describe 'logging' do
- it 'imports a repo' do
- message = "Importing project from <#{import_url}> to <#{tmp_repo_path}>."
- expect(logger).to receive(:info).with(message)
-
- subject
- end
+ subject
end
+ end
- context 'timeout' do
- it 'does not import a repo' do
- stub_spawn_timeout(cmd, timeout, nil)
+ context 'timeout' do
+ it 'does not import a repo' do
+ stub_spawn_timeout(cmd, timeout, nil)
- message = "Importing project from <#{import_url}> to <#{tmp_repo_path}> failed."
- expect(logger).to receive(:error).with(message)
+ message = "Importing project from <#{import_url}> to <#{tmp_repo_path}> failed."
+ expect(logger).to receive(:error).with(message)
- is_expected.to be_falsy
+ is_expected.to be_falsy
- expect(gl_projects.output).to eq("Timed out\n")
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
- end
+ expect(gl_projects.output).to eq("Timed out\n")
+ expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
end
-
- it_behaves_like 'importing repository'
end
+
+ it_behaves_like 'importing repository'
end
describe '#fork_repository' do
@@ -232,9 +233,6 @@ describe Gitlab::Git::GitlabProjects do
before do
FileUtils.mkdir_p(dest_repos_path)
-
- # Undo spec_helper stub that deletes hooks
- allow_any_instance_of(described_class).to receive(:fork_repository).and_call_original
end
after do
@@ -258,51 +256,45 @@ describe Gitlab::Git::GitlabProjects do
end
end
- context 'when Gitaly fork_repository feature is enabled' do
- it_behaves_like 'forking a repository'
- end
-
- context 'when Gitaly fork_repository feature is disabled', :disable_gitaly do
- it_behaves_like 'forking a repository'
+ it_behaves_like 'forking a repository'
- # We seem to be stuck to having only one working Gitaly storage in tests, changing
- # that is not very straight-forward so I'm leaving this test here for now till
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/41393 is fixed.
- context 'different storages' do
- let(:dest_repos) { 'alternative' }
- let(:dest_repos_path) { File.join(File.dirname(tmp_repos_path), dest_repos) }
+ # We seem to be stuck to having only one working Gitaly storage in tests, changing
+ # that is not very straight-forward so I'm leaving this test here for now till
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/41393 is fixed.
+ context 'different storages' do
+ let(:dest_repos) { 'alternative' }
+ let(:dest_repos_path) { File.join(File.dirname(tmp_repos_path), dest_repos) }
- before do
- stub_storage_settings(dest_repos => { 'path' => dest_repos_path })
- end
+ before do
+ stub_storage_settings(dest_repos => { 'path' => dest_repos_path })
+ end
- it 'forks the repo' do
- is_expected.to be_truthy
+ it 'forks the repo' do
+ is_expected.to be_truthy
- expect(File.exist?(dest_repo)).to be_truthy
- expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy
- expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy
- end
+ expect(File.exist?(dest_repo)).to be_truthy
+ expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy
+ expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy
end
+ end
- describe 'log messages' do
- describe 'successful fork' do
- it do
- message = "Forking repository from <#{tmp_repo_path}> to <#{dest_repo}>."
- expect(logger).to receive(:info).with(message)
+ describe 'log messages' do
+ describe 'successful fork' do
+ it do
+ message = "Forking repository from <#{tmp_repo_path}> to <#{dest_repo}>."
+ expect(logger).to receive(:info).with(message)
- subject
- end
+ subject
end
+ end
- describe 'failed fork due existing destination' do
- it do
- FileUtils.mkdir_p(dest_repo)
- message = "fork-repository failed: destination repository <#{dest_repo}> already exists."
- expect(logger).to receive(:error).with(message)
+ describe 'failed fork due existing destination' do
+ it do
+ FileUtils.mkdir_p(dest_repo)
+ message = "fork-repository failed: destination repository <#{dest_repo}> already exists."
+ expect(logger).to receive(:error).with(message)
- subject
- end
+ subject
end
end
end
diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb
index 2fe1f5603ce..a45c8510b15 100644
--- a/spec/lib/gitlab/git/hook_spec.rb
+++ b/spec/lib/gitlab/git/hook_spec.rb
@@ -8,25 +8,39 @@ describe Gitlab::Git::Hook do
allow_any_instance_of(described_class).to receive(:trigger).and_call_original
end
+ around do |example|
+ # TODO move hook tests to gitaly-ruby. Hook will disappear from gitlab-ce
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ example.run
+ end
+ end
+
describe "#trigger" do
- let(:project) { create(:project, :repository) }
+ set(:project) { create(:project, :repository) }
let(:repository) { project.repository.raw_repository }
let(:repo_path) { repository.path }
+ let(:hooks_dir) { File.join(repo_path, 'hooks') }
let(:user) { create(:user) }
let(:gl_id) { Gitlab::GlId.gl_id(user) }
let(:gl_username) { user.username }
def create_hook(name)
- FileUtils.mkdir_p(File.join(repo_path, 'hooks'))
- File.open(File.join(repo_path, 'hooks', name), 'w', 0755) do |f|
- f.write('exit 0')
+ FileUtils.mkdir_p(hooks_dir)
+ hook_path = File.join(hooks_dir, name)
+ File.open(hook_path, 'w', 0755) do |f|
+ f.write(<<~HOOK)
+ #!/bin/sh
+ exit 0
+ HOOK
end
end
def create_failing_hook(name)
- FileUtils.mkdir_p(File.join(repo_path, 'hooks'))
- File.open(File.join(repo_path, 'hooks', name), 'w', 0755) do |f|
- f.write(<<-HOOK)
+ FileUtils.mkdir_p(hooks_dir)
+ hook_path = File.join(hooks_dir, name)
+ File.open(hook_path, 'w', 0755) do |f|
+ f.write(<<~HOOK)
+ #!/bin/sh
echo 'regular message from the hook'
echo 'error message from the hook' 1>&2
echo 'error message from the hook line 2' 1>&2
@@ -38,7 +52,7 @@ describe Gitlab::Git::Hook do
['pre-receive', 'post-receive', 'update'].each do |hook_name|
context "when triggering a #{hook_name} hook" do
context "when the hook is successful" do
- let(:hook_path) { File.join(repo_path, 'hooks', hook_name) }
+ let(:hook_path) { File.join(hooks_dir, hook_name) }
let(:gl_repository) { Gitlab::GlRepository.gl_repository(project, false) }
let(:env) do
{
@@ -76,7 +90,7 @@ describe Gitlab::Git::Hook do
status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref)
expect(status).to be false
- expect(errors).to eq("error message from the hook<br>error message from the hook line 2<br>")
+ expect(errors).to eq("error message from the hook\nerror message from the hook line 2\n")
end
end
end
diff --git a/spec/lib/gitlab/git/hooks_service_spec.rb b/spec/lib/gitlab/git/hooks_service_spec.rb
index 3ed3feb4c74..9337aa39e13 100644
--- a/spec/lib/gitlab/git/hooks_service_spec.rb
+++ b/spec/lib/gitlab/git/hooks_service_spec.rb
@@ -26,24 +26,24 @@ describe Gitlab::Git::HooksService, seed_helper: true do
context 'when pre-receive hook failed' do
it 'does not call post-receive hook' do
- expect(service).to receive(:run_hook).with('pre-receive').and_return([false, ''])
+ expect(service).to receive(:run_hook).with('pre-receive').and_return([false, 'hello world'])
expect(service).not_to receive(:run_hook).with('post-receive')
expect do
service.execute(user, repository, blankrev, newrev, ref)
- end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
+ end.to raise_error(Gitlab::Git::PreReceiveError, 'hello world')
end
end
context 'when update hook failed' do
it 'does not call post-receive hook' do
expect(service).to receive(:run_hook).with('pre-receive').and_return([true, nil])
- expect(service).to receive(:run_hook).with('update').and_return([false, ''])
+ expect(service).to receive(:run_hook).with('update').and_return([false, 'hello world'])
expect(service).not_to receive(:run_hook).with('post-receive')
expect do
service.execute(user, repository, blankrev, newrev, ref)
- end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
+ end.to raise_error(Gitlab::Git::PreReceiveError, 'hello world')
end
end
end
diff --git a/spec/lib/gitlab/git/index_spec.rb b/spec/lib/gitlab/git/index_spec.rb
index 73fbc6a6afa..16e6bd35449 100644
--- a/spec/lib/gitlab/git/index_spec.rb
+++ b/spec/lib/gitlab/git/index_spec.rb
@@ -8,6 +8,13 @@ describe Gitlab::Git::Index, seed_helper: true do
index.read_tree(repository.lookup('master').tree)
end
+ around do |example|
+ # TODO move these specs to gitaly-ruby. The Index class will disappear from gitlab-ce
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ example.run
+ end
+ end
+
describe '#create' do
let(:options) do
{
diff --git a/spec/lib/gitlab/git/pre_receive_error_spec.rb b/spec/lib/gitlab/git/pre_receive_error_spec.rb
new file mode 100644
index 00000000000..1b8be62dec6
--- /dev/null
+++ b/spec/lib/gitlab/git/pre_receive_error_spec.rb
@@ -0,0 +1,9 @@
+require 'spec_helper'
+
+describe Gitlab::Git::PreReceiveError do
+ it 'makes its message HTML-friendly' do
+ ex = described_class.new("hello\nworld\n")
+
+ expect(ex.message).to eq('hello<br>world<br>')
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 7a9621d9c78..595482f76d5 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -77,17 +77,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#root_ref' do
- context 'with gitaly disabled' do
- before do
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
- end
-
- it 'calls #discover_default_branch' do
- expect(repository).to receive(:discover_default_branch)
- repository.root_ref
- end
- end
-
it 'returns UTF-8' do
expect(repository.root_ref).to be_utf8
end
@@ -153,45 +142,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- describe "#discover_default_branch" do
- let(:master) { 'master' }
- let(:feature) { 'feature' }
- let(:feature2) { 'feature2' }
-
- around do |example|
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- it "returns 'master' when master exists" do
- expect(repository).to receive(:branch_names).at_least(:once).and_return([feature, master])
- expect(repository.discover_default_branch).to eq('master')
- end
-
- it "returns non-master when master exists but default branch is set to something else" do
- File.write(File.join(repository_path, 'HEAD'), 'ref: refs/heads/feature')
- expect(repository).to receive(:branch_names).at_least(:once).and_return([feature, master])
- expect(repository.discover_default_branch).to eq('feature')
- File.write(File.join(repository_path, 'HEAD'), 'ref: refs/heads/master')
- end
-
- it "returns a non-master branch when only one exists" do
- expect(repository).to receive(:branch_names).at_least(:once).and_return([feature])
- expect(repository.discover_default_branch).to eq('feature')
- end
-
- it "returns a non-master branch when more than one exists and master does not" do
- expect(repository).to receive(:branch_names).at_least(:once).and_return([feature, feature2])
- expect(repository.discover_default_branch).to eq('feature')
- end
-
- it "returns nil when no branch exists" do
- expect(repository).to receive(:branch_names).at_least(:once).and_return([])
- expect(repository.discover_default_branch).to be_nil
- end
- end
-
describe '#branch_names' do
subject { repository.branch_names }
@@ -255,7 +205,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
let(:expected_path) { File.join(storage_path, cache_key, expected_filename) }
let(:expected_prefix) { "gitlab-git-test-#{ref}-#{SeedRepo::LastCommit::ID}" }
- subject(:metadata) { repository.archive_metadata(ref, storage_path, format, append_sha: append_sha) }
+ subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha) }
it 'sets CommitId to the commit SHA' do
expect(metadata['CommitId']).to eq(SeedRepo::LastCommit::ID)
@@ -373,6 +323,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
context '#submodules' do
around do |example|
+ # TODO #submodules will be removed, has been migrated to gitaly
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
example.run
end
@@ -474,7 +425,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#has_local_branches?' do
- shared_examples 'check for local branches' do
+ context 'check for local branches' do
it { expect(repository.has_local_branches?).to eq(true) }
context 'mutable' do
@@ -508,14 +459,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
end
-
- context 'with gitaly' do
- it_behaves_like 'check for local branches'
- end
-
- context 'without gitaly', :skip_gitaly_mock do
- it_behaves_like 'check for local branches'
- end
end
describe "#delete_branch" do
@@ -1055,6 +998,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe "#rugged_commits_between" do
around do |example|
+ # TODO #rugged_commits_between will be removed, has been migrated to gitaly
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
example.run
end
@@ -1170,7 +1114,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#count_commits' do
- shared_examples 'extended commit counting' do
+ describe 'extended commit counting' do
context 'with after timestamp' do
it 'returns the number of commits after timestamp' do
options = { ref: 'master', after: Time.iso8601('2013-03-03T20:15:01+00:00') }
@@ -1255,14 +1199,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
end
-
- context 'when Gitaly count_commits feature is enabled' do
- it_behaves_like 'extended commit counting'
- end
-
- context 'when Gitaly count_commits feature is disabled', :disable_gitaly do
- it_behaves_like 'extended commit counting'
- end
end
describe '#autocrlf' do
@@ -1392,24 +1328,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- # With Gitaly enabled, Gitaly just doesn't return deleted branches.
- context 'with deleted branch with Gitaly disabled' do
- before do
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
- end
-
- it 'returns no results' do
- ref = double()
- allow(ref).to receive(:name) { 'bad-branch' }
- allow(ref).to receive(:target) { raise Rugged::ReferenceError }
- branches = double()
- allow(branches).to receive(:each) { [ref].each }
- allow(repository_rugged).to receive(:branches) { branches }
-
- expect(subject).to be_empty
- end
- end
-
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :branches
end
@@ -1703,6 +1621,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
let(:refs) { ['deadbeef', SeedRepo::RubyBlob::ID, '909e6157199'] }
around do |example|
+ # TODO #batch_existence isn't used anywhere, can we remove it?
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
example.run
end
@@ -1761,70 +1680,52 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#languages' do
- shared_examples 'languages' do
- it 'returns exactly the expected results' do
- languages = repository.languages('4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6')
- expected_languages = [
- { value: 66.63, label: "Ruby", color: "#701516", highlight: "#701516" },
- { value: 22.96, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" },
- { value: 7.9, label: "HTML", color: "#e34c26", highlight: "#e34c26" },
- { value: 2.51, label: "CoffeeScript", color: "#244776", highlight: "#244776" }
- ]
-
- expect(languages.size).to eq(expected_languages.size)
+ it 'returns exactly the expected results' do
+ languages = repository.languages('4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6')
+ expected_languages = [
+ { value: 66.63, label: "Ruby", color: "#701516", highlight: "#701516" },
+ { value: 22.96, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" },
+ { value: 7.9, label: "HTML", color: "#e34c26", highlight: "#e34c26" },
+ { value: 2.51, label: "CoffeeScript", color: "#244776", highlight: "#244776" }
+ ]
- expected_languages.size.times do |i|
- a = expected_languages[i]
- b = languages[i]
+ expect(languages.size).to eq(expected_languages.size)
- expect(a.keys.sort).to eq(b.keys.sort)
- expect(a[:value]).to be_within(0.1).of(b[:value])
-
- non_float_keys = a.keys - [:value]
- expect(a.values_at(*non_float_keys)).to eq(b.values_at(*non_float_keys))
- end
- end
+ expected_languages.size.times do |i|
+ a = expected_languages[i]
+ b = languages[i]
- it "uses the repository's HEAD when no ref is passed" do
- lang = repository.languages.first
+ expect(a.keys.sort).to eq(b.keys.sort)
+ expect(a[:value]).to be_within(0.1).of(b[:value])
- expect(lang[:label]).to eq('Ruby')
+ non_float_keys = a.keys - [:value]
+ expect(a.values_at(*non_float_keys)).to eq(b.values_at(*non_float_keys))
end
end
- it_behaves_like 'languages'
+ it "uses the repository's HEAD when no ref is passed" do
+ lang = repository.languages.first
- context 'with rugged', :skip_gitaly_mock do
- it_behaves_like 'languages'
+ expect(lang[:label]).to eq('Ruby')
end
end
describe '#license_short_name' do
- shared_examples 'acquiring the Licensee license key' do
- subject { repository.license_short_name }
+ subject { repository.license_short_name }
- context 'when no license file can be found' do
- let(:project) { create(:project, :repository) }
- let(:repository) { project.repository.raw_repository }
-
- before do
- project.repository.delete_file(project.owner, 'LICENSE', message: 'remove license', branch_name: 'master')
- end
+ context 'when no license file can be found' do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository.raw_repository }
- it { is_expected.to be_nil }
+ before do
+ project.repository.delete_file(project.owner, 'LICENSE', message: 'remove license', branch_name: 'master')
end
- context 'when an mit license is found' do
- it { is_expected.to eq('mit') }
- end
+ it { is_expected.to be_nil }
end
- context 'when gitaly is enabled' do
- it_behaves_like 'acquiring the Licensee license key'
- end
-
- context 'when gitaly is disabled', :disable_gitaly do
- it_behaves_like 'acquiring the Licensee license key'
+ context 'when an mit license is found' do
+ it { is_expected.to eq('mit') }
end
end
@@ -2002,6 +1903,18 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(config).to include("fullpath = #{repository_path}")
end
end
+
+ context 'repository does not exist' do
+ it 'raises NoRepository and does not call Gitaly WriteConfig' do
+ repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '')
+
+ expect(repository.gitaly_repository_client).not_to receive(:write_config)
+
+ expect do
+ repository.write_config(full_path: 'foo/bar.git')
+ end.to raise_error(Gitlab::Git::Repository::NoRepository)
+ end
+ end
end
context "when gitaly_write_config is enabled" do
diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb
index 32ec1e029c8..b752c3e8341 100644
--- a/spec/lib/gitlab/git/rev_list_spec.rb
+++ b/spec/lib/gitlab/git/rev_list_spec.rb
@@ -9,9 +9,11 @@ describe Gitlab::Git::RevList do
end
def stub_popen_rev_list(*additional_args, with_lazy_block: true, output:)
+ repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository.path }
+
params = [
args_for_popen(additional_args),
- repository.path,
+ repo_path,
{},
hash_including(lazy_block: with_lazy_block ? anything : nil)
]
@@ -91,14 +93,4 @@ describe Gitlab::Git::RevList do
expect { |b| rev_list.all_objects(&b) }.to yield_with_args(%w[sha1 sha2])
end
end
-
- context "#missed_ref" do
- let(:rev_list) { described_class.new(repository, oldrev: 'oldrev', newrev: 'newrev') }
-
- it 'calls out to `popen`' do
- stub_popen_rev_list('--max-count=1', 'oldrev', '^newrev', with_lazy_block: false, output: "sha1\nsha2")
-
- expect(rev_list.missed_ref).to eq(%w[sha1 sha2])
- end
- end
end
diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb
index 722d697c28e..b63658e1b3b 100644
--- a/spec/lib/gitlab/git/wiki_spec.rb
+++ b/spec/lib/gitlab/git/wiki_spec.rb
@@ -6,9 +6,7 @@ describe Gitlab::Git::Wiki do
let(:project_wiki) { ProjectWiki.new(project, user) }
subject { project_wiki.wiki }
- # Remove skip_gitaly_mock flag when gitaly_find_page when
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/42039 is solved
- describe '#page', :skip_gitaly_mock do
+ describe '#page' do
before do
create_page('page1', 'content')
create_page('foo/page1', 'content foo/page1')
@@ -25,6 +23,22 @@ describe Gitlab::Git::Wiki do
end
end
+ describe '#delete_page' do
+ after do
+ destroy_page('page1')
+ end
+
+ it 'only removes the page with the same path' do
+ create_page('page1', 'content')
+ create_page('*', 'content')
+
+ subject.delete_page('*', commit_details('whatever'))
+
+ expect(subject.pages.count).to eq 1
+ expect(subject.pages.first.title).to eq 'page1'
+ end
+ end
+
def create_page(name, content)
subject.write_page(name, :markdown, content, commit_details(name))
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index dfffea7797f..ff32025253a 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -552,7 +552,7 @@ describe Gitlab::GitAccess do
it 'returns not found' do
project.add_guest(user)
repo = project.repository
- FileUtils.rm_rf(repo.path)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access { FileUtils.rm_rf(repo.path) }
# Sanity check for rm_rf
expect(repo.exists?).to eq(false)
@@ -750,20 +750,22 @@ describe Gitlab::GitAccess do
def merge_into_protected_branch
@protected_branch_merge_commit ||= begin
- stub_git_hooks
- project.repository.add_branch(user, unprotected_branch, 'feature')
- target_branch = project.repository.lookup('feature')
- source_branch = project.repository.create_file(
- user,
- 'filename',
- 'This is the file content',
- message: 'This is a good commit message',
- branch_name: unprotected_branch)
- rugged = project.repository.rugged
- author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
-
- merge_index = rugged.merge_commits(target_branch, source_branch)
- Rugged::Commit.create(rugged, author: author, committer: author, message: "commit message", parents: [target_branch, source_branch], tree: merge_index.write_tree(rugged))
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ stub_git_hooks
+ project.repository.add_branch(user, unprotected_branch, 'feature')
+ target_branch = project.repository.lookup('feature')
+ source_branch = project.repository.create_file(
+ user,
+ 'filename',
+ 'This is the file content',
+ message: 'This is a good commit message',
+ branch_name: unprotected_branch)
+ rugged = project.repository.rugged
+ author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
+
+ merge_index = rugged.merge_commits(target_branch, source_branch)
+ Rugged::Commit.create(rugged, author: author, committer: author, message: "commit message", parents: [target_branch, source_branch], tree: merge_index.write_tree(rugged))
+ end
end
end
@@ -932,6 +934,22 @@ describe Gitlab::GitAccess do
expect(project.repository).to receive(:clean_stale_repository_files).and_call_original
expect { push_access_check }.not_to raise_error
end
+
+ it 'avoids N+1 queries', :request_store do
+ # Run this once to establish a baseline. Cached queries should get
+ # cached, so that when we introduce another change we shouldn't see
+ # additional queries.
+ access.check('git-receive-pack', changes)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ access.check('git-receive-pack', changes)
+ end
+
+ changes = ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature']
+
+ # There is still an N+1 query with protected branches
+ expect { access.check('git-receive-pack', changes) }.not_to exceed_query_limit(control_count).with_threshold(1)
+ end
end
end
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 730ede99fc9..9c6c9fe13bf 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -52,7 +52,9 @@ describe Gitlab::GitAccessWiki do
context 'when the wiki repository does not exist' do
it 'returns not found' do
wiki_repo = project.wiki.repository
- FileUtils.rm_rf(wiki_repo.path)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ FileUtils.rm_rf(wiki_repo.path)
+ end
# Sanity check for rm_rf
expect(wiki_repo.exists?).to eq(false)
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 9fbdd73ee0e..9709f1f5646 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -48,7 +48,7 @@ describe Gitlab::GitalyClient::OperationService do
.and_return(response)
expect { subject }.to raise_error(
- Gitlab::Git::HooksService::PreReceiveError, "something failed")
+ Gitlab::Git::PreReceiveError, "something failed")
end
end
end
@@ -85,7 +85,7 @@ describe Gitlab::GitalyClient::OperationService do
.and_return(response)
expect { subject }.to raise_error(
- Gitlab::Git::HooksService::PreReceiveError, "something failed")
+ Gitlab::Git::PreReceiveError, "something failed")
end
end
end
diff --git a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
index d34ca0b76b8..81fe97c1e49 100644
--- a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
@@ -180,12 +180,12 @@ describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cach
allow(importer.user_finder)
.to receive(:user_id_for)
- .ordered.with(issue.assignees[0])
+ .with(issue.assignees[0])
.and_return(4)
allow(importer.user_finder)
.to receive(:user_id_for)
- .ordered.with(issue.assignees[1])
+ .with(issue.assignees[1])
.and_return(5)
expect(Gitlab::Database)
diff --git a/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb
new file mode 100644
index 00000000000..4857f2afbe2
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::Importer::LfsObjectImporter do
+ let(:project) { create(:project) }
+ let(:download_link) { "http://www.gitlab.com/lfs_objects/oid" }
+
+ let(:github_lfs_object) do
+ Gitlab::GithubImport::Representation::LfsObject.new(
+ oid: 'oid', download_link: download_link
+ )
+ end
+
+ let(:importer) { described_class.new(github_lfs_object, project, nil) }
+
+ describe '#execute' do
+ it 'calls the LfsDownloadService with the lfs object attributes' do
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadService)
+ .to receive(:execute).with('oid', download_link)
+
+ importer.execute
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
new file mode 100644
index 00000000000..5f5c6b803c0
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::Importer::LfsObjectsImporter do
+ let(:project) { double(:project, id: 4, import_source: 'foo/bar') }
+ let(:client) { double(:client) }
+ let(:download_link) { "http://www.gitlab.com/lfs_objects/oid" }
+
+ let(:github_lfs_object) { ['oid', download_link] }
+
+ describe '#parallel?' do
+ it 'returns true when running in parallel mode' do
+ importer = described_class.new(project, client)
+ expect(importer).to be_parallel
+ end
+
+ it 'returns false when running in sequential mode' do
+ importer = described_class.new(project, client, parallel: false)
+ expect(importer).not_to be_parallel
+ end
+ end
+
+ describe '#execute' do
+ context 'when running in parallel mode' do
+ it 'imports lfs objects in parallel' do
+ importer = described_class.new(project, client)
+
+ expect(importer).to receive(:parallel_import)
+
+ importer.execute
+ end
+ end
+
+ context 'when running in sequential mode' do
+ it 'imports lfs objects in sequence' do
+ importer = described_class.new(project, client, parallel: false)
+
+ expect(importer).to receive(:sequential_import)
+
+ importer.execute
+ end
+ end
+ end
+
+ describe '#sequential_import' do
+ it 'imports each lfs object in sequence' do
+ importer = described_class.new(project, client, parallel: false)
+ lfs_object_importer = double(:lfs_object_importer)
+
+ allow(importer)
+ .to receive(:each_object_to_import)
+ .and_yield(['oid', download_link])
+
+ expect(Gitlab::GithubImport::Importer::LfsObjectImporter)
+ .to receive(:new)
+ .with(
+ an_instance_of(Gitlab::GithubImport::Representation::LfsObject),
+ project,
+ client
+ )
+ .and_return(lfs_object_importer)
+
+ expect(lfs_object_importer).to receive(:execute)
+
+ importer.sequential_import
+ end
+ end
+
+ describe '#parallel_import' do
+ it 'imports each lfs object in parallel' do
+ importer = described_class.new(project, client)
+
+ allow(importer)
+ .to receive(:each_object_to_import)
+ .and_yield(github_lfs_object)
+
+ expect(Gitlab::GithubImport::ImportLfsObjectWorker)
+ .to receive(:perform_async)
+ .with(project.id, an_instance_of(Hash), an_instance_of(String))
+
+ waiter = importer.parallel_import
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(1)
+ end
+ end
+
+ describe '#collection_options' do
+ it 'returns an empty Hash' do
+ importer = described_class.new(project, client)
+
+ expect(importer.collection_options).to eq({})
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
index 35f3fdf8304..3422a1e82fc 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
@@ -40,13 +40,19 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
describe '#execute' do
it 'imports the pull request' do
+ mr = double(:merge_request, id: 10)
+
expect(importer)
.to receive(:create_merge_request)
- .and_return(10)
+ .and_return([mr, false])
+
+ expect(importer)
+ .to receive(:insert_git_data)
+ .with(mr, false)
expect_any_instance_of(Gitlab::GithubImport::IssuableFinder)
.to receive(:cache_database_id)
- .with(10)
+ .with(mr.id)
importer.execute
end
@@ -99,18 +105,11 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
importer.create_merge_request
end
- it 'returns the ID of the created merge request' do
- id = importer.create_merge_request
-
- expect(id).to be_a_kind_of(Numeric)
- end
-
- it 'creates the merge request diffs' do
- importer.create_merge_request
-
- mr = project.merge_requests.take
+ it 'returns the created merge request' do
+ mr, exists = importer.create_merge_request
- expect(mr.merge_request_diffs.exists?).to eq(true)
+ expect(mr).to be_instance_of(MergeRequest)
+ expect(exists).to eq(false)
end
end
@@ -217,5 +216,77 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
expect { importer.create_merge_request }.not_to raise_error
end
end
+
+ context 'when the merge request already exists' do
+ before do
+ allow(importer.user_finder)
+ .to receive(:author_id_for)
+ .with(pull_request)
+ .and_return([user.id, true])
+
+ allow(importer.user_finder)
+ .to receive(:assignee_id_for)
+ .with(pull_request)
+ .and_return(user.id)
+ end
+
+ it 'returns the existing merge request' do
+ mr1, exists1 = importer.create_merge_request
+ mr2, exists2 = importer.create_merge_request
+
+ expect(mr2).to eq(mr1)
+ expect(exists1).to eq(false)
+ expect(exists2).to eq(true)
+ end
+ end
+ end
+
+ describe '#insert_git_data' do
+ before do
+ allow(importer.milestone_finder)
+ .to receive(:id_for)
+ .with(pull_request)
+ .and_return(milestone.id)
+
+ allow(importer.user_finder)
+ .to receive(:author_id_for)
+ .with(pull_request)
+ .and_return([user.id, true])
+
+ allow(importer.user_finder)
+ .to receive(:assignee_id_for)
+ .with(pull_request)
+ .and_return(user.id)
+ end
+
+ it 'creates the merge request diffs' do
+ mr, exists = importer.create_merge_request
+
+ importer.insert_git_data(mr, exists)
+
+ expect(mr.merge_request_diffs.exists?).to eq(true)
+ end
+
+ it 'creates the merge request diff commits' do
+ mr, exists = importer.create_merge_request
+
+ importer.insert_git_data(mr, exists)
+
+ diff = mr.merge_request_diffs.take
+
+ expect(diff.merge_request_diff_commits.exists?).to eq(true)
+ end
+
+ context 'when the merge request exists' do
+ it 'creates the merge request diffs if they do not yet exist' do
+ mr, _ = importer.create_merge_request
+
+ mr.merge_request_diffs.delete_all
+
+ importer.insert_git_data(mr, true)
+
+ expect(mr.merge_request_diffs.exists?).to eq(true)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
index cc9e4b67e72..d8f01dcb76b 100644
--- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
@@ -14,7 +14,8 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
disk_path: 'foo',
repository: repository,
create_wiki: true,
- import_state: import_state
+ import_state: import_state,
+ lfs_enabled?: true
)
end
diff --git a/spec/lib/gitlab/github_import/sequential_importer_spec.rb b/spec/lib/gitlab/github_import/sequential_importer_spec.rb
index 6089b0b751f..05d3243f806 100644
--- a/spec/lib/gitlab/github_import/sequential_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/sequential_importer_spec.rb
@@ -30,7 +30,6 @@ describe Gitlab::GithubImport::SequentialImporter do
expect(instance).to receive(:execute)
end
- expect(repository).to receive(:after_import)
expect(importer.execute).to eq(true)
end
end
diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb
index ab9a166db00..47f37cae98f 100644
--- a/spec/lib/gitlab/gpg_spec.rb
+++ b/spec/lib/gitlab/gpg_spec.rb
@@ -74,6 +74,19 @@ describe Gitlab::Gpg do
email: 'nannie.bernhard@example.com'
}])
end
+
+ it 'rejects non UTF-8 names and addresses' do
+ public_key = double(:key)
+ fingerprints = double(:fingerprints)
+ email = "\xEEch@test.com".force_encoding('ASCII-8BIT')
+ uid = double(:uid, name: 'Test User', email: email)
+ raw_key = double(:raw_key, uids: [uid])
+ allow(Gitlab::Gpg::CurrentKeyChain).to receive(:fingerprints_from_key).with(public_key).and_return(fingerprints)
+ allow(GPGME::Key).to receive(:find).with(:public, anything).and_return([raw_key])
+
+ user_infos = described_class.user_infos_from_key(public_key)
+ expect(user_infos).to eq([])
+ end
end
describe '.current_home_dir' do
diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
new file mode 100644
index 00000000000..813ae43b4d3
--- /dev/null
+++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe Gitlab::HashedStorage::Migrator do
+ describe '#bulk_schedule' do
+ it 'schedules job to StorageMigratorWorker' do
+ Sidekiq::Testing.fake! do
+ expect { subject.bulk_schedule(1, 5) }.to change(StorageMigratorWorker.jobs, :size).by(1)
+ end
+ end
+ end
+
+ describe '#bulk_migrate' do
+ let(:projects) { create_list(:project, 2, :legacy_storage) }
+ let(:ids) { projects.map(&:id) }
+
+ it 'enqueue jobs to ProjectMigrateHashedStorageWorker' do
+ Sidekiq::Testing.fake! do
+ expect { subject.bulk_migrate(ids.min, ids.max) }.to change(ProjectMigrateHashedStorageWorker.jobs, :size).by(2)
+ end
+ end
+
+ it 'sets projects as read only' do
+ allow(ProjectMigrateHashedStorageWorker).to receive(:perform_async).twice
+ subject.bulk_migrate(ids.min, ids.max)
+
+ projects.each do |project|
+ expect(project.reload.repository_read_only?).to be_truthy
+ end
+ end
+
+ it 'rescues and log exceptions' do
+ allow_any_instance_of(Project).to receive(:migrate_to_hashed_storage!).and_raise(StandardError)
+ expect { subject.bulk_migrate(ids.min, ids.max) }.not_to raise_error
+ end
+
+ it 'delegates each project in specified range to #migrate' do
+ projects.each do |project|
+ expect(subject).to receive(:migrate).with(project)
+ end
+
+ subject.bulk_migrate(ids.min, ids.max)
+ end
+ end
+
+ describe '#migrate' do
+ let(:project) { create(:project, :legacy_storage, :empty_repo) }
+
+ it 'enqueues job to ProjectMigrateHashedStorageWorker' do
+ Sidekiq::Testing.fake! do
+ expect { subject.migrate(project) }.to change(ProjectMigrateHashedStorageWorker.jobs, :size).by(1)
+ end
+ end
+
+ it 'rescues and log exceptions' do
+ allow(project).to receive(:migrate_to_hashed_storage!).and_raise(StandardError)
+
+ expect { subject.migrate(project) }.not_to raise_error
+ end
+
+ it 'sets project as read only' do
+ allow(ProjectMigrateHashedStorageWorker).to receive(:perform_async)
+ subject.migrate(project)
+
+ expect(project.reload.repository_read_only?).to be_truthy
+ end
+
+ it 'migrate project' do
+ Sidekiq::Testing.inline! do
+ subject.migrate(project)
+ end
+
+ expect(project.reload.hashed_storage?(:attachments)).to be_truthy
+ end
+ end
+end
diff --git a/spec/lib/gitlab/i18n/metadata_entry_spec.rb b/spec/lib/gitlab/i18n/metadata_entry_spec.rb
index ab71d6454a9..a399517cc04 100644
--- a/spec/lib/gitlab/i18n/metadata_entry_spec.rb
+++ b/spec/lib/gitlab/i18n/metadata_entry_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::I18n::MetadataEntry do
- describe '#expected_plurals' do
+ describe '#expected_forms' do
it 'returns the number of plurals' do
data = {
msgid: "",
@@ -22,7 +22,7 @@ describe Gitlab::I18n::MetadataEntry do
}
entry = described_class.new(data)
- expect(entry.expected_plurals).to eq(2)
+ expect(entry.expected_forms).to eq(2)
end
it 'returns 0 for the POT-metadata' do
@@ -45,7 +45,7 @@ describe Gitlab::I18n::MetadataEntry do
}
entry = described_class.new(data)
- expect(entry.expected_plurals).to eq(0)
+ expect(entry.expected_forms).to eq(0)
end
end
end
diff --git a/spec/lib/gitlab/i18n/po_linter_spec.rb b/spec/lib/gitlab/i18n/po_linter_spec.rb
index 3a962ba7f22..3dbc23d2aaf 100644
--- a/spec/lib/gitlab/i18n/po_linter_spec.rb
+++ b/spec/lib/gitlab/i18n/po_linter_spec.rb
@@ -1,10 +1,31 @@
require 'spec_helper'
require 'simple_po_parser'
+# Disabling this cop to allow for multi-language examples in comments
+# rubocop:disable Style/AsciiComments
describe Gitlab::I18n::PoLinter do
let(:linter) { described_class.new(po_path) }
let(:po_path) { 'spec/fixtures/valid.po' }
+ def fake_translation(msgid:, translation:, plural_id: nil, plurals: [])
+ data = { msgid: msgid, msgid_plural: plural_id }
+
+ if plural_id
+ [translation, *plurals].each_with_index do |plural, index|
+ allow(FastGettext::Translation).to receive(:n_).with(msgid, plural_id, index).and_return(plural)
+ data.merge!("msgstr[#{index}]" => plural)
+ end
+ else
+ allow(FastGettext::Translation).to receive(:_).with(msgid).and_return(translation)
+ data[:msgstr] = translation
+ end
+
+ Gitlab::I18n::TranslationEntry.new(
+ data,
+ plurals.size + 1
+ )
+ end
+
describe '#errors' do
it 'only calls validation once' do
expect(linter).to receive(:validate_po).once.and_call_original
@@ -155,9 +176,8 @@ describe Gitlab::I18n::PoLinter do
describe '#validate_entries' do
it 'keeps track of errors for entries' do
- fake_invalid_entry = Gitlab::I18n::TranslationEntry.new(
- { msgid: "Hello %{world}", msgstr: "Bonjour %{monde}" }, 2
- )
+ fake_invalid_entry = fake_translation(msgid: "Hello %{world}",
+ translation: "Bonjour %{monde}")
allow(linter).to receive(:translation_entries) { [fake_invalid_entry] }
expect(linter).to receive(:validate_entry)
@@ -177,6 +197,7 @@ describe Gitlab::I18n::PoLinter do
expect(linter).to receive(:validate_newlines).with([], fake_entry)
expect(linter).to receive(:validate_number_of_plurals).with([], fake_entry)
expect(linter).to receive(:validate_unescaped_chars).with([], fake_entry)
+ expect(linter).to receive(:validate_translation).with([], fake_entry)
linter.validate_entry(fake_entry)
end
@@ -185,7 +206,7 @@ describe Gitlab::I18n::PoLinter do
describe '#validate_number_of_plurals' do
it 'validates when there are an incorrect number of translations' do
fake_metadata = double
- allow(fake_metadata).to receive(:expected_plurals).and_return(2)
+ allow(fake_metadata).to receive(:expected_forms).and_return(2)
allow(linter).to receive(:metadata_entry).and_return(fake_metadata)
fake_entry = Gitlab::I18n::TranslationEntry.new(
@@ -201,13 +222,16 @@ describe Gitlab::I18n::PoLinter do
end
describe '#validate_variables' do
- it 'validates both signular and plural in a pluralized string when the entry has a singular' do
- pluralized_entry = Gitlab::I18n::TranslationEntry.new(
- { msgid: 'Hello %{world}',
- msgid_plural: 'Hello all %{world}',
- 'msgstr[0]' => 'Bonjour %{world}',
- 'msgstr[1]' => 'Bonjour tous %{world}' },
- 2
+ before do
+ allow(linter).to receive(:validate_variables_in_message).and_call_original
+ end
+
+ it 'validates both singular and plural in a pluralized string when the entry has a singular' do
+ pluralized_entry = fake_translation(
+ msgid: 'Hello %{world}',
+ translation: 'Bonjour %{world}',
+ plural_id: 'Hello all %{world}',
+ plurals: ['Bonjour tous %{world}']
)
expect(linter).to receive(:validate_variables_in_message)
@@ -221,11 +245,10 @@ describe Gitlab::I18n::PoLinter do
end
it 'only validates plural when there is no separate singular' do
- pluralized_entry = Gitlab::I18n::TranslationEntry.new(
- { msgid: 'Hello %{world}',
- msgid_plural: 'Hello all %{world}',
- 'msgstr[0]' => 'Bonjour %{world}' },
- 1
+ pluralized_entry = fake_translation(
+ msgid: 'Hello %{world}',
+ translation: 'Bonjour %{world}',
+ plural_id: 'Hello all %{world}'
)
expect(linter).to receive(:validate_variables_in_message)
@@ -235,37 +258,65 @@ describe Gitlab::I18n::PoLinter do
end
it 'validates the message variables' do
- entry = Gitlab::I18n::TranslationEntry.new(
- { msgid: 'Hello', msgstr: 'Bonjour' },
- 2
- )
+ entry = fake_translation(msgid: 'Hello', translation: 'Bonjour')
expect(linter).to receive(:validate_variables_in_message)
.with([], 'Hello', 'Bonjour')
linter.validate_variables([], entry)
end
+
+ it 'validates variable usage in message ids' do
+ entry = fake_translation(
+ msgid: 'Hello %{world}',
+ translation: 'Bonjour %{world}',
+ plural_id: 'Hello all %{world}',
+ plurals: ['Bonjour tous %{world}']
+ )
+
+ expect(linter).to receive(:validate_variables_in_message)
+ .with([], 'Hello %{world}', 'Hello %{world}')
+ .and_call_original
+ expect(linter).to receive(:validate_variables_in_message)
+ .with([], 'Hello all %{world}', 'Hello all %{world}')
+ .and_call_original
+
+ linter.validate_variables([], entry)
+ end
end
describe '#validate_variables_in_message' do
it 'detects when a variables are used incorrectly' do
errors = []
- expected_errors = ['<hello %{world} %d> is missing: [%{hello}]',
- '<hello %{world} %d> is using unknown variables: [%{world}]',
- 'is combining multiple unnamed variables']
+ expected_errors = ['<%d hello %{world} %s> is missing: [%{hello}]',
+ '<%d hello %{world} %s> is using unknown variables: [%{world}]',
+ 'is combining multiple unnamed variables',
+ 'is combining named variables with unnamed variables']
- linter.validate_variables_in_message(errors, '%{hello} world %d', 'hello %{world} %d')
+ linter.validate_variables_in_message(errors, '%d %{hello} world %s', '%d hello %{world} %s')
expect(errors).to include(*expected_errors)
end
+
+ it 'does not allow combining 1 `%d` unnamed variable with named variables' do
+ errors = []
+
+ linter.validate_variables_in_message(errors,
+ '%{type} detected %d vulnerability',
+ '%{type} detecteerde %d kwetsbaarheid')
+
+ expect(errors).not_to be_empty
+ end
end
describe '#validate_translation' do
+ let(:entry) { fake_translation(msgid: 'Hello %{world}', translation: 'Bonjour %{world}') }
+
it 'succeeds with valid variables' do
errors = []
- linter.validate_translation(errors, 'Hello %{world}', ['%{world}'])
+ linter.validate_translation(errors, entry)
expect(errors).to be_empty
end
@@ -275,43 +326,80 @@ describe Gitlab::I18n::PoLinter do
expect(FastGettext::Translation).to receive(:_) { raise 'broken' }
- linter.validate_translation(errors, 'Hello', [])
+ linter.validate_translation(errors, entry)
- expect(errors).to include('Failure translating to en with []: broken')
+ expect(errors).to include('Failure translating to en: broken')
end
it 'adds an error message when translating fails when translating with context' do
+ entry = fake_translation(msgid: 'Tests|Hello', translation: 'broken')
errors = []
expect(FastGettext::Translation).to receive(:s_) { raise 'broken' }
- linter.validate_translation(errors, 'Tests|Hello', [])
+ linter.validate_translation(errors, entry)
- expect(errors).to include('Failure translating to en with []: broken')
+ expect(errors).to include('Failure translating to en: broken')
end
it "adds an error when trying to translate with incorrect variables when using unnamed variables" do
+ entry = fake_translation(msgid: 'Hello %s', translation: 'Hello %d')
errors = []
- linter.validate_translation(errors, 'Hello %d', ['%s'])
+ linter.validate_translation(errors, entry)
- expect(errors.first).to start_with("Failure translating to en with")
+ expect(errors.first).to start_with("Failure translating to en")
end
it "adds an error when trying to translate with named variables when unnamed variables are expected" do
+ entry = fake_translation(msgid: 'Hello %s', translation: 'Hello %{thing}')
errors = []
- linter.validate_translation(errors, 'Hello %d', ['%{world}'])
+ linter.validate_translation(errors, entry)
- expect(errors.first).to start_with("Failure translating to en with")
+ expect(errors.first).to start_with("Failure translating to en")
end
- it 'adds an error when translated with incorrect variables using named variables' do
- errors = []
+ it 'tests translation for all given forms' do
+ # Fake a language that has 3 forms to translate
+ fake_metadata = double
+ allow(fake_metadata).to receive(:forms_to_test).and_return(3)
+ allow(linter).to receive(:metadata_entry).and_return(fake_metadata)
+ entry = fake_translation(
+ msgid: '%d exception',
+ translation: '%d uitzondering',
+ plural_id: '%d exceptions',
+ plurals: ['%d uitzonderingen', '%d uitzonderingetjes']
+ )
+
+ # Make each count use a different index
+ allow(linter).to receive(:index_for_pluralization).with(0).and_return(0)
+ allow(linter).to receive(:index_for_pluralization).with(1).and_return(1)
+ allow(linter).to receive(:index_for_pluralization).with(2).and_return(2)
+
+ expect(FastGettext::Translation).to receive(:n_).with('%d exception', '%d exceptions', 0).and_call_original
+ expect(FastGettext::Translation).to receive(:n_).with('%d exception', '%d exceptions', 1).and_call_original
+ expect(FastGettext::Translation).to receive(:n_).with('%d exception', '%d exceptions', 2).and_call_original
+
+ linter.validate_translation([], entry)
+ end
+ end
+
+ describe '#numbers_covering_all_plurals' do
+ it 'can correctly find all required numbers to translate to Polish' do
+ # Polish used as an example with 3 different forms:
+ # 0, all plurals except the ones ending in 2,3,4: Kotów
+ # 1: Kot
+ # 2-3-4: Koty
+ # So translating with [0, 1, 2] will give us all different posibilities
+ fake_metadata = double
+ allow(fake_metadata).to receive(:forms_to_test).and_return(4)
+ allow(linter).to receive(:metadata_entry).and_return(fake_metadata)
+ allow(linter).to receive(:locale).and_return('pl_PL')
- linter.validate_translation(errors, 'Hello %{thing}', ['%d'])
+ numbers = linter.numbers_covering_all_plurals
- expect(errors.first).to start_with("Failure translating to en with")
+ expect(numbers).to contain_exactly(0, 1, 2)
end
end
@@ -336,3 +424,4 @@ describe Gitlab::I18n::PoLinter do
end
end
end
+# rubocop:enable Style/AsciiComments
diff --git a/spec/lib/gitlab/i18n/translation_entry_spec.rb b/spec/lib/gitlab/i18n/translation_entry_spec.rb
index f68bc8feff9..b301e6ea443 100644
--- a/spec/lib/gitlab/i18n/translation_entry_spec.rb
+++ b/spec/lib/gitlab/i18n/translation_entry_spec.rb
@@ -109,7 +109,7 @@ describe Gitlab::I18n::TranslationEntry do
data = { msgid: %w(hello world) }
entry = described_class.new(data, 2)
- expect(entry.msgid_contains_newlines?).to be_truthy
+ expect(entry.msgid_has_multiple_lines?).to be_truthy
end
end
@@ -118,7 +118,7 @@ describe Gitlab::I18n::TranslationEntry do
data = { msgid_plural: %w(hello world) }
entry = described_class.new(data, 2)
- expect(entry.plural_id_contains_newlines?).to be_truthy
+ expect(entry.plural_id_has_multiple_lines?).to be_truthy
end
end
@@ -127,7 +127,7 @@ describe Gitlab::I18n::TranslationEntry do
data = { msgstr: %w(hello world) }
entry = described_class.new(data, 2)
- expect(entry.translations_contain_newlines?).to be_truthy
+ expect(entry.translations_have_multiple_lines?).to be_truthy
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index a129855dbd8..2ea66479c1b 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -257,6 +257,7 @@ project:
- import_data
- commit_statuses
- pipelines
+- stages
- builds
- runner_projects
- runners
diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
index f40d4bc2d08..2223f163177 100644
--- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::AvatarSaver do
let(:shared) { project.import_export_shared }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
- let(:project_with_avatar) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let(:project_with_avatar) { create(:project, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
let(:project) { create(:project) }
before do
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index 17e06a6a83f..71fd5a51c3b 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -41,8 +41,10 @@ describe 'forked project import' do
after do
FileUtils.rm_rf(export_path)
- FileUtils.rm_rf(project_with_repo.repository.path_to_repo)
- FileUtils.rm_rf(project.repository.path_to_repo)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ FileUtils.rm_rf(project_with_repo.repository.path_to_repo)
+ FileUtils.rm_rf(project.repository.path_to_repo)
+ end
end
it 'can access the MR' do
diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
index b793636c4d6..68eaa70e6b6 100644
--- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
+++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
@@ -19,7 +19,9 @@ describe Gitlab::ImportExport::MergeRequestParser do
end
after do
- FileUtils.rm_rf(project.repository.path_to_repo)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ FileUtils.rm_rf(project.repository.path_to_repo)
+ end
end
it 'has a source branch' do
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index dc806d036ff..013b8895f67 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -23,8 +23,10 @@ describe Gitlab::ImportExport::RepoRestorer do
after do
FileUtils.rm_rf(export_path)
- FileUtils.rm_rf(project_with_repo.repository.path_to_repo)
- FileUtils.rm_rf(project.repository.path_to_repo)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ FileUtils.rm_rf(project_with_repo.repository.path_to_repo)
+ FileUtils.rm_rf(project.repository.path_to_repo)
+ end
end
it 'restores the repo successfully' do
@@ -34,7 +36,9 @@ describe Gitlab::ImportExport::RepoRestorer do
it 'has the webhooks' do
restorer.restore
- expect(Gitlab::Git::Hook.new('post-receive', project.repository.raw_repository)).to exist
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ expect(Gitlab::Git::Hook.new('post-receive', project.repository.raw_repository)).to exist
+ end
end
end
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 2aebdc57f7c..0a1e3eb83d3 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -540,6 +540,7 @@ ProjectAutoDevops:
- id
- enabled
- domain
+- deploy_strategy
- project_id
- created_at
- updated_at
diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
index 1304d8fabfc..095687fa89d 100644
--- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::UploadsSaver do
describe 'bundle a project Git repo' do
let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" }
- let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
+ let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
let(:shared) { project.import_export_shared }
before do
diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb
index f2fa315e3ec..10341486512 100644
--- a/spec/lib/gitlab/import_sources_spec.rb
+++ b/spec/lib/gitlab/import_sources_spec.rb
@@ -91,4 +91,23 @@ describe Gitlab::ImportSources do
end
end
end
+
+ describe 'imports_repository? checker' do
+ let(:allowed_importers) { %w[github gitlab_project] }
+
+ it 'fails if any importer other than the allowed ones implements this method' do
+ current_importers = described_class.values.select { |kind| described_class.importer(kind).try(:imports_repository?) }
+ not_allowed_importers = current_importers - allowed_importers
+
+ expect(not_allowed_importers).to be_empty, failure_message(not_allowed_importers)
+ end
+
+ def failure_message(importers_class_names)
+ <<-MSG
+ It looks like the #{importers_class_names.join(', ')} importers implements its own way to import the repository.
+ That means that the lfs object download must be handled for each of them. You can use 'LfsImportService' and
+ 'LfsDownloadService' to implement it. After that, add the importer name to the list of allowed importers in this spec.
+ MSG
+ end
+ end
end
diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
index eb1b13704ea..972b17d5b12 100644
--- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb
@@ -44,16 +44,44 @@ describe Gitlab::LegacyGithubImport::ProjectCreator do
end
context 'when GitHub project is public' do
- before do
- allow_any_instance_of(ApplicationSetting).to receive(:default_project_visibility).and_return(Gitlab::VisibilityLevel::INTERNAL)
- end
-
- it 'sets project visibility to the default project visibility' do
+ it 'sets project visibility to public' do
repo.private = false
project = service.execute
- expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
+
+ context 'when visibility level is restricted' do
+ context 'when GitHub project is private' do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE])
+ allow_any_instance_of(ApplicationSetting).to receive(:default_project_visibility).and_return(Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it 'sets project visibility to the default project visibility' do
+ repo.private = true
+
+ project = service.execute
+
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ end
+ end
+
+ context 'when GitHub project is public' do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ allow_any_instance_of(ApplicationSetting).to receive(:default_project_visibility).and_return(Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it 'sets project visibility to the default project visibility' do
+ repo.private = false
+
+ project = service.execute
+
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ end
end
end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index a40330d853f..e90e0aba0a4 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -90,11 +90,13 @@ describe Gitlab::PathRegex do
let(:routes_not_starting_in_wildcard) { routes_without_format.select { |p| p !~ %r{^/[:*]} } }
let(:top_level_words) do
- words = routes_not_starting_in_wildcard.map do |route|
- route.split('/')[1]
- end.compact
-
- (words + ee_top_level_words + files_in_public + Array(API::API.prefix.to_s)).uniq
+ routes_not_starting_in_wildcard
+ .map { |route| route.split('/')[1] }
+ .concat(ee_top_level_words)
+ .concat(files_in_public)
+ .concat(Array(API::API.prefix.to_s))
+ .compact
+ .uniq
end
let(:ee_top_level_words) do
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 548eb28fe4d..4059188fba1 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -135,6 +135,51 @@ describe Gitlab::Profiler do
end
end
+ describe '.clean_backtrace' do
+ it 'uses the Rails backtrace cleaner' do
+ backtrace = []
+
+ expect(Rails.backtrace_cleaner).to receive(:clean).with(backtrace)
+
+ described_class.clean_backtrace(backtrace)
+ end
+
+ it 'removes lines from IGNORE_BACKTRACES' do
+ backtrace = [
+ "lib/gitlab/gitaly_client.rb:294:in `block (2 levels) in migrate'",
+ "lib/gitlab/gitaly_client.rb:331:in `allow_n_plus_1_calls'",
+ "lib/gitlab/gitaly_client.rb:280:in `block in migrate'",
+ "lib/gitlab/metrics/influx_db.rb:103:in `measure'",
+ "lib/gitlab/gitaly_client.rb:278:in `migrate'",
+ "lib/gitlab/git/repository.rb:1451:in `gitaly_migrate'",
+ "lib/gitlab/git/commit.rb:66:in `find'",
+ "app/models/repository.rb:1047:in `find_commit'",
+ "lib/gitlab/metrics/instrumentation.rb:159:in `block in find_commit'",
+ "lib/gitlab/metrics/method_call.rb:36:in `measure'",
+ "lib/gitlab/metrics/instrumentation.rb:159:in `find_commit'",
+ "app/models/repository.rb:113:in `commit'",
+ "lib/gitlab/i18n.rb:50:in `with_locale'",
+ "lib/gitlab/middleware/multipart.rb:95:in `call'",
+ "lib/gitlab/request_profiler/middleware.rb:14:in `call'",
+ "ee/lib/gitlab/database/load_balancing/rack_middleware.rb:37:in `call'",
+ "ee/lib/gitlab/jira/middleware.rb:15:in `call'"
+ ]
+
+ expect(described_class.clean_backtrace(backtrace))
+ .to eq([
+ "lib/gitlab/gitaly_client.rb:294:in `block (2 levels) in migrate'",
+ "lib/gitlab/gitaly_client.rb:331:in `allow_n_plus_1_calls'",
+ "lib/gitlab/gitaly_client.rb:280:in `block in migrate'",
+ "lib/gitlab/gitaly_client.rb:278:in `migrate'",
+ "lib/gitlab/git/repository.rb:1451:in `gitaly_migrate'",
+ "lib/gitlab/git/commit.rb:66:in `find'",
+ "app/models/repository.rb:1047:in `find_commit'",
+ "app/models/repository.rb:113:in `commit'",
+ "ee/lib/gitlab/jira/middleware.rb:15:in `call'"
+ ])
+ end
+ end
+
describe '.with_custom_logger' do
context 'when the logger is set' do
it 'uses the replacement logger for the duration of the block' do
diff --git a/spec/lib/gitlab/quick_actions/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb
index f7c288f2393..0166f6c2ee0 100644
--- a/spec/lib/gitlab/quick_actions/extractor_spec.rb
+++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb
@@ -182,6 +182,14 @@ describe Gitlab::QuickActions::Extractor do
expect(msg).to eq "hello\nworld"
end
+ it 'extracts command case insensitive' do
+ msg = %(hello\n/PoWer @user.name %9.10 ~"bar baz.2"\nworld)
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to eq [['power', '@user.name %9.10 ~"bar baz.2"']]
+ expect(msg).to eq "hello\nworld"
+ end
+
it 'does not extract noop commands' do
msg = %(hello\nworld\n/reopen\n/noop_command)
msg, commands = extractor.extract_commands(msg)
@@ -206,6 +214,14 @@ describe Gitlab::QuickActions::Extractor do
expect(msg).to eq "hello\nworld\nthis is great? SHRUG"
end
+ it 'extracts and performs substitution commands case insensitive' do
+ msg = %(hello\nworld\n/reOpen\n/sHRuG this is great?)
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to eq [['reopen'], ['shrug', 'this is great?']]
+ expect(msg).to eq "hello\nworld\nthis is great? SHRUG"
+ end
+
it 'extracts and performs substitution commands with comments' do
msg = %(hello\nworld\n/reopen\n/substitution wow this is a thing.)
msg, commands = extractor.extract_commands(msg)
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index bf6ee4b0b59..c435f988cdd 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -405,7 +405,11 @@ describe Gitlab::Shell do
describe '#create_repository' do
shared_examples '#create_repository' do
let(:repository_storage) { 'default' }
- let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path }
+ let(:repository_storage_path) do
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ Gitlab.config.repositories.storages[repository_storage].legacy_disk_path
+ end
+ end
let(:repo_name) { 'project/path' }
let(:created_path) { File.join(repository_storage_path, repo_name + '.git') }
@@ -495,13 +499,15 @@ describe Gitlab::Shell do
end
it 'returns true when the command succeeds' do
- expect(gitlab_projects).to receive(:fork_repository).with('nfs-file05', 'fork/path.git') { true }
+ expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:fork_repository)
+ .with(repository.raw_repository) { :gitaly_response_object }
is_expected.to be_truthy
end
it 'return false when the command fails' do
- expect(gitlab_projects).to receive(:fork_repository).with('nfs-file05', 'fork/path.git') { false }
+ expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:fork_repository)
+ .with(repository.raw_repository) { raise GRPC::BadStatus, 'bla' }
is_expected.to be_falsy
end
@@ -643,7 +649,7 @@ describe Gitlab::Shell do
subject do
gitlab_shell.fetch_remote(repository.raw_repository, remote_name,
- forced: true, no_tags: true, ssh_auth: ssh_auth)
+ forced: true, no_tags: true, ssh_auth: ssh_auth)
end
it 'passes the correct params to the gitaly service' do
@@ -658,21 +664,43 @@ describe Gitlab::Shell do
describe '#import_repository' do
let(:import_url) { 'https://gitlab.com/gitlab-org/gitlab-ce.git' }
- it 'returns true when the command succeeds' do
- expect(gitlab_projects).to receive(:import_project).with(import_url, timeout) { true }
+ context 'with gitaly' do
+ it 'returns true when the command succeeds' do
+ expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository).with(import_url)
- result = gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url)
+ result = gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url)
- expect(result).to be_truthy
+ expect(result).to be_truthy
+ end
+
+ it 'raises an exception when the command fails' do
+ expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository)
+ .with(import_url) { raise GRPC::BadStatus, 'bla' }
+ expect_any_instance_of(Gitlab::Shell::GitalyGitlabProjects).to receive(:output) { 'error'}
+
+ expect do
+ gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url)
+ end.to raise_error(Gitlab::Shell::Error, "error")
+ end
end
- it 'raises an exception when the command fails' do
- allow(gitlab_projects).to receive(:output) { 'error' }
- expect(gitlab_projects).to receive(:import_project) { false }
+ context 'without gitaly', :disable_gitaly do
+ it 'returns true when the command succeeds' do
+ expect(gitlab_projects).to receive(:import_project).with(import_url, timeout) { true }
- expect do
- gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url)
- end.to raise_error(Gitlab::Shell::Error, "error")
+ result = gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url)
+
+ expect(result).to be_truthy
+ end
+
+ it 'raises an exception when the command fails' do
+ allow(gitlab_projects).to receive(:output) { 'error' }
+ expect(gitlab_projects).to receive(:import_project) { false }
+
+ expect do
+ gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url)
+ end.to raise_error(Gitlab::Shell::Error, "error")
+ end
end
end
end
diff --git a/spec/lib/gitlab/sql/cte_spec.rb b/spec/lib/gitlab/sql/cte_spec.rb
new file mode 100644
index 00000000000..d6763c7b2e1
--- /dev/null
+++ b/spec/lib/gitlab/sql/cte_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Gitlab::SQL::CTE, :postgresql do
+ describe '#to_arel' do
+ it 'generates an Arel relation for the CTE body' do
+ relation = User.where(id: 1)
+ cte = described_class.new(:cte_name, relation)
+ sql = cte.to_arel.to_sql
+ name = ActiveRecord::Base.connection.quote_table_name(:cte_name)
+
+ sql1 = ActiveRecord::Base.connection.unprepared_statement do
+ relation.except(:order).to_sql
+ end
+
+ expect(sql).to eq("#{name} AS (#{sql1})")
+ end
+ end
+
+ describe '#alias_to' do
+ it 'returns an alias for the CTE' do
+ cte = described_class.new(:cte_name, nil)
+ table = Arel::Table.new(:kittens)
+
+ source_name = ActiveRecord::Base.connection.quote_table_name(:cte_name)
+ alias_name = ActiveRecord::Base.connection.quote_table_name(:kittens)
+
+ expect(cte.alias_to(table).to_sql).to eq("#{source_name} AS #{alias_name}")
+ end
+ end
+
+ describe '#apply_to' do
+ it 'applies a CTE to an ActiveRecord::Relation' do
+ user = create(:user)
+ cte = described_class.new(:cte_name, User.where(id: user.id))
+
+ relation = cte.apply_to(User.all)
+
+ expect(relation.to_sql).to match(/WITH .+cte_name/)
+ expect(relation.to_a).to eq(User.where(id: user.id).to_a)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sql/glob_spec.rb b/spec/lib/gitlab/sql/glob_spec.rb
index f0bb4294d62..1cf8935bfe3 100644
--- a/spec/lib/gitlab/sql/glob_spec.rb
+++ b/spec/lib/gitlab/sql/glob_spec.rb
@@ -35,8 +35,9 @@ describe Gitlab::SQL::Glob do
value = query("SELECT #{quote(string)} LIKE #{pattern}")
.rows.flatten.first
+ check = Gitlab.rails5? ? true : 't'
case value
- when 't', 1
+ when check, 1
true
else
false
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
index ecacea6bb35..a8213988f70 100644
--- a/spec/lib/gitlab/themes_spec.rb
+++ b/spec/lib/gitlab/themes_spec.rb
@@ -5,9 +5,9 @@ describe Gitlab::Themes, lib: true do
it 'returns a space-separated list of class names' do
css = described_class.body_classes
- expect(css).to include('ui_indigo')
- expect(css).to include(' ui_dark ')
- expect(css).to include(' ui_blue')
+ expect(css).to include('ui-indigo')
+ expect(css).to include('ui-dark')
+ expect(css).to include('ui-blue')
end
end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 81dbbb962dd..6f5f9938eca 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -58,20 +58,6 @@ describe Gitlab::UrlBlocker do
end
end
- it 'returns true for a non-alphanumeric username' do
- stub_resolv
-
- aggregate_failures do
- expect(described_class).to be_blocked_url('ssh://-oProxyCommand=whoami@example.com/a')
-
- # The leading character here is a Unicode "soft hyphen"
- expect(described_class).to be_blocked_url('ssh://­oProxyCommand=whoami@example.com/a')
-
- # Unicode alphanumerics are allowed
- expect(described_class).not_to be_blocked_url('ssh://ÄŸitlab@example.com/a')
- end
- end
-
it 'returns true for invalid URL' do
expect(described_class.blocked_url?('http://:8080')).to be true
end
@@ -120,6 +106,38 @@ describe Gitlab::UrlBlocker do
allow(Addrinfo).to receive(:getaddrinfo).and_call_original
end
end
+
+ context 'when enforce_user is' do
+ before do
+ stub_resolv
+ end
+
+ context 'false (default)' do
+ it 'does not block urls with a non-alphanumeric username' do
+ expect(described_class).not_to be_blocked_url('ssh://-oProxyCommand=whoami@example.com/a')
+
+ # The leading character here is a Unicode "soft hyphen"
+ expect(described_class).not_to be_blocked_url('ssh://­oProxyCommand=whoami@example.com/a')
+
+ # Unicode alphanumerics are allowed
+ expect(described_class).not_to be_blocked_url('ssh://ÄŸitlab@example.com/a')
+ end
+ end
+
+ context 'true' do
+ it 'blocks urls with a non-alphanumeric username' do
+ aggregate_failures do
+ expect(described_class).to be_blocked_url('ssh://-oProxyCommand=whoami@example.com/a', enforce_user: true)
+
+ # The leading character here is a Unicode "soft hyphen"
+ expect(described_class).to be_blocked_url('ssh://­oProxyCommand=whoami@example.com/a', enforce_user: true)
+
+ # Unicode alphanumerics are allowed
+ expect(described_class).not_to be_blocked_url('ssh://ÄŸitlab@example.com/a', enforce_user: true)
+ end
+ end
+ end
+ end
end
# Resolv does not support resolving UTF-8 domain names
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index b81749cf428..9f495a5d50b 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -22,6 +22,31 @@ describe Gitlab::UrlBuilder do
end
end
+ context 'when passing a Milestone' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :public, namespace: group) }
+
+ context 'belonging to a project' do
+ it 'returns a proper URL' do
+ milestone = create(:milestone, project: project)
+
+ url = described_class.build(milestone)
+
+ expect(url).to eq "#{Settings.gitlab['url']}/#{milestone.project.full_path}/milestones/#{milestone.iid}"
+ end
+ end
+
+ context 'belonging to a group' do
+ it 'returns a proper URL' do
+ milestone = create(:milestone, group: group)
+
+ url = described_class.build(milestone)
+
+ expect(url).to eq "#{Settings.gitlab['url']}/groups/#{milestone.group.full_path}/-/milestones/#{milestone.iid}"
+ end
+ end
+ end
+
context 'when passing a MergeRequest' do
it 'returns a proper URL' do
merge_request = build_stubbed(:merge_request, iid: 42)
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index a716e6f5434..22d921716aa 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -32,6 +32,7 @@ describe Gitlab::UsageData do
mattermost_enabled
edition
version
+ installation_type
uuid
hostname
signup
@@ -156,6 +157,7 @@ describe Gitlab::UsageData do
it "gathers license data" do
expect(subject[:uuid]).to eq(Gitlab::CurrentSettings.uuid)
expect(subject[:version]).to eq(Gitlab::VERSION)
+ expect(subject[:installation_type]).to eq(Gitlab::INSTALLATION_TYPE)
expect(subject[:active_user_count]).to eq(User.active.count)
expect(subject[:recorded_at]).to be_a(Time)
end
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index 97b6069f64d..0469d984a40 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -142,7 +142,7 @@ describe Gitlab::UserAccess do
target_project: canonical_project,
source_project: project,
source_branch: 'awesome-feature',
- allow_maintainer_to_push: true
+ allow_collaboration: true
)
end
diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb
index 7c97cee982a..fc08ebcfc6d 100644
--- a/spec/lib/gitlab/utils/override_spec.rb
+++ b/spec/lib/gitlab/utils/override_spec.rb
@@ -1,7 +1,13 @@
-require 'spec_helper'
+require 'fast_spec_helper'
describe Gitlab::Utils::Override do
- let(:base) { Struct.new(:good) }
+ let(:base) do
+ Struct.new(:good) do
+ def self.good
+ 0
+ end
+ end
+ end
let(:derived) { Class.new(base).tap { |m| m.extend described_class } }
let(:extension) { Module.new.tap { |m| m.extend described_class } }
@@ -9,6 +15,14 @@ describe Gitlab::Utils::Override do
let(:prepending_class) { base.tap { |m| m.prepend extension } }
let(:including_class) { base.tap { |m| m.include extension } }
+ let(:prepending_class_methods) do
+ base.tap { |m| m.singleton_class.prepend extension }
+ end
+
+ let(:extending_class_methods) do
+ base.tap { |m| m.extend extension }
+ end
+
let(:klass) { subject }
def good(mod)
@@ -36,7 +50,7 @@ describe Gitlab::Utils::Override do
shared_examples 'checking as intended' do
it 'checks ok for overriding method' do
good(subject)
- result = klass.new(0).good
+ result = instance.good
expect(result).to eq(1)
described_class.verify!
@@ -45,7 +59,25 @@ describe Gitlab::Utils::Override do
it 'raises NotImplementedError when it is not overriding anything' do
expect do
bad(subject)
- klass.new(0).bad
+ instance.bad
+ described_class.verify!
+ end.to raise_error(NotImplementedError)
+ end
+ end
+
+ shared_examples 'checking as intended, nothing was overridden' do
+ it 'raises NotImplementedError because it is not overriding it' do
+ expect do
+ good(subject)
+ instance.good
+ described_class.verify!
+ end.to raise_error(NotImplementedError)
+ end
+
+ it 'raises NotImplementedError when it is not overriding anything' do
+ expect do
+ bad(subject)
+ instance.bad
described_class.verify!
end.to raise_error(NotImplementedError)
end
@@ -54,7 +86,7 @@ describe Gitlab::Utils::Override do
shared_examples 'nothing happened' do
it 'does not complain when it is overriding something' do
good(subject)
- result = klass.new(0).good
+ result = instance.good
expect(result).to eq(1)
described_class.verify!
@@ -62,7 +94,7 @@ describe Gitlab::Utils::Override do
it 'does not complain when it is not overriding anything' do
bad(subject)
- result = klass.new(0).bad
+ result = instance.bad
expect(result).to eq(true)
described_class.verify!
@@ -75,83 +107,97 @@ describe Gitlab::Utils::Override do
end
describe '#override' do
- context 'when STATIC_VERIFICATION is set' do
- before do
- stub_env('STATIC_VERIFICATION', 'true')
- end
+ context 'when instance is klass.new(0)' do
+ let(:instance) { klass.new(0) }
- context 'when subject is a class' do
- subject { derived }
+ context 'when STATIC_VERIFICATION is set' do
+ before do
+ stub_env('STATIC_VERIFICATION', 'true')
+ end
- it_behaves_like 'checking as intended'
- end
+ context 'when subject is a class' do
+ subject { derived }
+
+ it_behaves_like 'checking as intended'
+ end
+
+ context 'when subject is a module, and class is prepending it' do
+ subject { extension }
+ let(:klass) { prepending_class }
+
+ it_behaves_like 'checking as intended'
+ end
- context 'when subject is a module, and class is prepending it' do
- subject { extension }
- let(:klass) { prepending_class }
+ context 'when subject is a module, and class is including it' do
+ subject { extension }
+ let(:klass) { including_class }
- it_behaves_like 'checking as intended'
+ it_behaves_like 'checking as intended, nothing was overridden'
+ end
end
- context 'when subject is a module, and class is including it' do
- subject { extension }
- let(:klass) { including_class }
+ context 'when STATIC_VERIFICATION is not set' do
+ before do
+ stub_env('STATIC_VERIFICATION', nil)
+ end
- it 'raises NotImplementedError because it is not overriding it' do
- expect do
- good(subject)
- klass.new(0).good
- described_class.verify!
- end.to raise_error(NotImplementedError)
+ context 'when subject is a class' do
+ subject { derived }
+
+ it_behaves_like 'nothing happened'
end
- it 'raises NotImplementedError when it is not overriding anything' do
- expect do
- bad(subject)
- klass.new(0).bad
- described_class.verify!
- end.to raise_error(NotImplementedError)
+ context 'when subject is a module, and class is prepending it' do
+ subject { extension }
+ let(:klass) { prepending_class }
+
+ it_behaves_like 'nothing happened'
end
- end
- end
- end
- context 'when STATIC_VERIFICATION is not set' do
- before do
- stub_env('STATIC_VERIFICATION', nil)
- end
+ context 'when subject is a module, and class is including it' do
+ subject { extension }
+ let(:klass) { including_class }
- context 'when subject is a class' do
- subject { derived }
+ it 'does not complain when it is overriding something' do
+ good(subject)
+ result = instance.good
- it_behaves_like 'nothing happened'
- end
+ expect(result).to eq(0)
+ described_class.verify!
+ end
- context 'when subject is a module, and class is prepending it' do
- subject { extension }
- let(:klass) { prepending_class }
+ it 'does not complain when it is not overriding anything' do
+ bad(subject)
+ result = instance.bad
- it_behaves_like 'nothing happened'
+ expect(result).to eq(true)
+ described_class.verify!
+ end
+ end
+ end
end
- context 'when subject is a module, and class is including it' do
- subject { extension }
- let(:klass) { including_class }
+ context 'when instance is klass' do
+ let(:instance) { klass }
- it 'does not complain when it is overriding something' do
- good(subject)
- result = klass.new(0).good
+ context 'when STATIC_VERIFICATION is set' do
+ before do
+ stub_env('STATIC_VERIFICATION', 'true')
+ end
- expect(result).to eq(0)
- described_class.verify!
- end
+ context 'when subject is a module, and class is prepending it' do
+ subject { extension }
+ let(:klass) { prepending_class_methods }
- it 'does not complain when it is not overriding anything' do
- bad(subject)
- result = klass.new(0).bad
+ it_behaves_like 'checking as intended'
+ end
- expect(result).to eq(true)
- described_class.verify!
+ context 'when subject is a module, and class is extending it' do
+ subject { extension }
+ let(:klass) { extending_class_methods }
+
+ it_behaves_like 'checking as intended, nothing was overridden'
+ end
end
end
end
diff --git a/spec/lib/gitlab/verify/job_artifacts_spec.rb b/spec/lib/gitlab/verify/job_artifacts_spec.rb
index ec490bdfde2..6e916a56564 100644
--- a/spec/lib/gitlab/verify/job_artifacts_spec.rb
+++ b/spec/lib/gitlab/verify/job_artifacts_spec.rb
@@ -21,15 +21,38 @@ describe Gitlab::Verify::JobArtifacts do
FileUtils.rm_f(artifact.file.path)
expect(failures.keys).to contain_exactly(artifact)
- expect(failure).to be_a(Errno::ENOENT)
- expect(failure.to_s).to include(artifact.file.path)
+ expect(failure).to include('No such file or directory')
+ expect(failure).to include(artifact.file.path)
end
it 'fails artifacts with a mismatched checksum' do
File.truncate(artifact.file.path, 0)
expect(failures.keys).to contain_exactly(artifact)
- expect(failure.to_s).to include('Checksum mismatch')
+ expect(failure).to include('Checksum mismatch')
+ end
+
+ context 'with remote files' do
+ let(:file) { double(:file) }
+
+ before do
+ stub_artifacts_object_storage
+ artifact.update!(file_store: ObjectStorage::Store::REMOTE)
+ expect(CarrierWave::Storage::Fog::File).to receive(:new).and_return(file)
+ end
+
+ it 'passes artifacts in object storage that exist' do
+ expect(file).to receive(:exists?).and_return(true)
+
+ expect(failures).to eq({})
+ end
+
+ it 'fails artifacts in object storage that do not exist' do
+ expect(file).to receive(:exists?).and_return(false)
+
+ expect(failures.keys).to contain_exactly(artifact)
+ expect(failure).to include('Remote object does not exist')
+ end
end
end
end
diff --git a/spec/lib/gitlab/verify/lfs_objects_spec.rb b/spec/lib/gitlab/verify/lfs_objects_spec.rb
index 0f890e2c7ce..2feaedd6f14 100644
--- a/spec/lib/gitlab/verify/lfs_objects_spec.rb
+++ b/spec/lib/gitlab/verify/lfs_objects_spec.rb
@@ -21,30 +21,37 @@ describe Gitlab::Verify::LfsObjects do
FileUtils.rm_f(lfs_object.file.path)
expect(failures.keys).to contain_exactly(lfs_object)
- expect(failure).to be_a(Errno::ENOENT)
- expect(failure.to_s).to include(lfs_object.file.path)
+ expect(failure).to include('No such file or directory')
+ expect(failure).to include(lfs_object.file.path)
end
it 'fails LFS objects with a mismatched oid' do
File.truncate(lfs_object.file.path, 0)
expect(failures.keys).to contain_exactly(lfs_object)
- expect(failure.to_s).to include('Checksum mismatch')
+ expect(failure).to include('Checksum mismatch')
end
context 'with remote files' do
+ let(:file) { double(:file) }
+
before do
stub_lfs_object_storage
+ lfs_object.update!(file_store: ObjectStorage::Store::REMOTE)
+ expect(CarrierWave::Storage::Fog::File).to receive(:new).and_return(file)
end
- it 'skips LFS objects in object storage' do
- local_failure = create(:lfs_object)
- create(:lfs_object, :object_storage)
+ it 'passes LFS objects in object storage that exist' do
+ expect(file).to receive(:exists?).and_return(true)
+
+ expect(failures).to eq({})
+ end
- failures = {}
- described_class.new(batch_size: 10).run_batches { |_, failed| failures.merge!(failed) }
+ it 'fails LFS objects in object storage that do not exist' do
+ expect(file).to receive(:exists?).and_return(false)
- expect(failures.keys).to contain_exactly(local_failure)
+ expect(failures.keys).to contain_exactly(lfs_object)
+ expect(failure).to include('Remote object does not exist')
end
end
end
diff --git a/spec/lib/gitlab/verify/uploads_spec.rb b/spec/lib/gitlab/verify/uploads_spec.rb
index 85768308edc..296866d3319 100644
--- a/spec/lib/gitlab/verify/uploads_spec.rb
+++ b/spec/lib/gitlab/verify/uploads_spec.rb
@@ -23,37 +23,44 @@ describe Gitlab::Verify::Uploads do
FileUtils.rm_f(upload.absolute_path)
expect(failures.keys).to contain_exactly(upload)
- expect(failure).to be_a(Errno::ENOENT)
- expect(failure.to_s).to include(upload.absolute_path)
+ expect(failure).to include('No such file or directory')
+ expect(failure).to include(upload.absolute_path)
end
it 'fails uploads with a mismatched checksum' do
upload.update!(checksum: 'something incorrect')
expect(failures.keys).to contain_exactly(upload)
- expect(failure.to_s).to include('Checksum mismatch')
+ expect(failure).to include('Checksum mismatch')
end
it 'fails uploads with a missing precalculated checksum' do
upload.update!(checksum: '')
expect(failures.keys).to contain_exactly(upload)
- expect(failure.to_s).to include('Checksum missing')
+ expect(failure).to include('Checksum missing')
end
context 'with remote files' do
+ let(:file) { double(:file) }
+
before do
stub_uploads_object_storage(AvatarUploader)
+ upload.update!(store: ObjectStorage::Store::REMOTE)
+ expect(CarrierWave::Storage::Fog::File).to receive(:new).and_return(file)
end
- it 'skips uploads in object storage' do
- local_failure = create(:upload)
- create(:upload, :object_storage)
+ it 'passes uploads in object storage that exist' do
+ expect(file).to receive(:exists?).and_return(true)
+
+ expect(failures).to eq({})
+ end
- failures = {}
- described_class.new(batch_size: 10).run_batches { |_, failed| failures.merge!(failed) }
+ it 'fails uploads in object storage that do not exist' do
+ expect(file).to receive(:exists?).and_return(false)
- expect(failures.keys).to contain_exactly(local_failure)
+ expect(failures.keys).to contain_exactly(upload)
+ expect(failure).to include('Remote object does not exist')
end
end
end
diff --git a/spec/lib/microsoft_teams/notifier_spec.rb b/spec/lib/microsoft_teams/notifier_spec.rb
index 3035693812f..c9756544bd6 100644
--- a/spec/lib/microsoft_teams/notifier_spec.rb
+++ b/spec/lib/microsoft_teams/notifier_spec.rb
@@ -8,7 +8,7 @@ describe MicrosoftTeams::Notifier do
let(:options) do
{
title: 'JohnDoe4/project2',
- pretext: '[[JohnDoe4/project2](http://localhost/namespace2/gitlabhq)] Issue [#1 Awesome issue](http://localhost/namespace2/gitlabhq/issues/1) opened by user6',
+ summary: '[[JohnDoe4/project2](http://localhost/namespace2/gitlabhq)] Issue [#1 Awesome issue](http://localhost/namespace2/gitlabhq/issues/1) opened by user6',
activity: {
title: 'Issue opened by user6',
subtitle: 'in [JohnDoe4/project2](http://localhost/namespace2/gitlabhq)',
diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb
new file mode 100644
index 00000000000..e0569218d78
--- /dev/null
+++ b/spec/lib/object_storage/direct_upload_spec.rb
@@ -0,0 +1,168 @@
+require 'spec_helper'
+
+describe ObjectStorage::DirectUpload do
+ let(:credentials) do
+ {
+ provider: 'AWS',
+ aws_access_key_id: 'AWS_ACCESS_KEY_ID',
+ aws_secret_access_key: 'AWS_SECRET_ACCESS_KEY'
+ }
+ end
+
+ let(:storage_url) { 'https://uploads.s3.amazonaws.com/' }
+
+ let(:bucket_name) { 'uploads' }
+ let(:object_name) { 'tmp/uploads/my-file' }
+ let(:maximum_size) { 1.gigabyte }
+
+ let(:direct_upload) { described_class.new(credentials, bucket_name, object_name, has_length: has_length, maximum_size: maximum_size) }
+
+ before do
+ Fog.unmock!
+ end
+
+ describe '#has_length' do
+ context 'is known' do
+ let(:has_length) { true }
+ let(:maximum_size) { nil }
+
+ it "maximum size is not required" do
+ expect { direct_upload }.not_to raise_error
+ end
+ end
+
+ context 'is unknown' do
+ let(:has_length) { false }
+
+ context 'and maximum size is specified' do
+ let(:maximum_size) { 1.gigabyte }
+
+ it "does not raise an error" do
+ expect { direct_upload }.not_to raise_error
+ end
+ end
+
+ context 'and maximum size is not specified' do
+ let(:maximum_size) { nil }
+
+ it "raises an error" do
+ expect { direct_upload }.to raise_error /maximum_size has to be specified if length is unknown/
+ end
+ end
+ end
+ end
+
+ describe '#to_hash' do
+ subject { direct_upload.to_hash }
+
+ shared_examples 'a valid upload' do
+ it "returns valid structure" do
+ expect(subject).to have_key(:Timeout)
+ expect(subject[:GetURL]).to start_with(storage_url)
+ expect(subject[:StoreURL]).to start_with(storage_url)
+ expect(subject[:DeleteURL]).to start_with(storage_url)
+ end
+ end
+
+ shared_examples 'a valid upload with multipart data' do
+ before do
+ stub_object_storage_multipart_init(storage_url, "myUpload")
+ end
+
+ it_behaves_like 'a valid upload'
+
+ it "returns valid structure" do
+ expect(subject).to have_key(:MultipartUpload)
+ expect(subject[:MultipartUpload]).to have_key(:PartSize)
+ expect(subject[:MultipartUpload][:PartURLs]).to all(start_with(storage_url))
+ expect(subject[:MultipartUpload][:PartURLs]).to all(include('uploadId=myUpload'))
+ expect(subject[:MultipartUpload][:CompleteURL]).to start_with(storage_url)
+ expect(subject[:MultipartUpload][:CompleteURL]).to include('uploadId=myUpload')
+ expect(subject[:MultipartUpload][:AbortURL]).to start_with(storage_url)
+ expect(subject[:MultipartUpload][:AbortURL]).to include('uploadId=myUpload')
+ end
+ end
+
+ shared_examples 'a valid upload without multipart data' do
+ it_behaves_like 'a valid upload'
+
+ it "returns valid structure" do
+ expect(subject).not_to have_key(:MultipartUpload)
+ end
+ end
+
+ context 'when AWS is used' do
+ context 'when length is known' do
+ let(:has_length) { true }
+
+ it_behaves_like 'a valid upload without multipart data'
+ end
+
+ context 'when length is unknown' do
+ let(:has_length) { false }
+
+ it_behaves_like 'a valid upload with multipart data' do
+ context 'when maximum upload size is 10MB' do
+ let(:maximum_size) { 10.megabyte }
+
+ it 'returns only 2 parts' do
+ expect(subject[:MultipartUpload][:PartURLs].length).to eq(2)
+ end
+
+ it 'part size is mimimum, 5MB' do
+ expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte)
+ end
+ end
+
+ context 'when maximum upload size is 12MB' do
+ let(:maximum_size) { 12.megabyte }
+
+ it 'returns only 3 parts' do
+ expect(subject[:MultipartUpload][:PartURLs].length).to eq(3)
+ end
+
+ it 'part size is rounded-up to 5MB' do
+ expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte)
+ end
+ end
+
+ context 'when maximum upload size is 49GB' do
+ let(:maximum_size) { 49.gigabyte }
+
+ it 'returns maximum, 100 parts' do
+ expect(subject[:MultipartUpload][:PartURLs].length).to eq(100)
+ end
+
+ it 'part size is rounded-up to 5MB' do
+ expect(subject[:MultipartUpload][:PartSize]).to eq(505.megabyte)
+ end
+ end
+ end
+ end
+ end
+
+ context 'when Google is used' do
+ let(:credentials) do
+ {
+ provider: 'Google',
+ google_storage_access_key_id: 'GOOGLE_ACCESS_KEY_ID',
+ google_storage_secret_access_key: 'GOOGLE_SECRET_ACCESS_KEY'
+ }
+ end
+
+ let(:storage_url) { 'https://storage.googleapis.com/uploads/' }
+
+ context 'when length is known' do
+ let(:has_length) { true }
+
+ it_behaves_like 'a valid upload without multipart data'
+ end
+
+ context 'when length is unknown' do
+ let(:has_length) { false }
+
+ it_behaves_like 'a valid upload without multipart data'
+ end
+ end
+ end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 69eafbe4bbe..775ca4ba0eb 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -68,7 +68,7 @@ describe Notify do
end
it 'contains the description' do
- is_expected.to have_html_escaped_body_text issue.description
+ is_expected.to have_body_text issue.description
end
it 'does not add a reason header' do
@@ -89,7 +89,7 @@ describe Notify do
end
it 'contains a link to note author' do
- is_expected.to have_html_escaped_body_text(issue.author_name)
+ is_expected.to have_body_text(issue.author_name)
is_expected.to have_body_text 'created an issue:'
end
end
@@ -115,8 +115,8 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
is_expected.to have_referable_subject(issue, reply: true)
- is_expected.to have_html_escaped_body_text(previous_assignee.name)
- is_expected.to have_html_escaped_body_text(assignee.name)
+ is_expected.to have_body_text(previous_assignee.name)
+ is_expected.to have_body_text(assignee.name)
is_expected.to have_body_text(project_issue_path(project, issue))
end
end
@@ -190,7 +190,7 @@ describe Notify do
aggregate_failures do
is_expected.to have_referable_subject(issue, reply: true)
is_expected.to have_body_text(status)
- is_expected.to have_html_escaped_body_text(current_user.name)
+ is_expected.to have_body_text(current_user.name)
is_expected.to have_body_text(project_issue_path project, issue)
end
end
@@ -243,7 +243,7 @@ describe Notify do
end
it 'contains the description' do
- is_expected.to have_html_escaped_body_text merge_request.description
+ is_expected.to have_body_text merge_request.description
end
context 'when sent with a reason' do
@@ -260,7 +260,7 @@ describe Notify do
end
it 'contains a link to note author' do
- is_expected.to have_html_escaped_body_text merge_request.author_name
+ is_expected.to have_body_text merge_request.author_name
is_expected.to have_body_text 'created a merge request:'
end
end
@@ -286,9 +286,9 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
is_expected.to have_referable_subject(merge_request, reply: true)
- is_expected.to have_html_escaped_body_text(previous_assignee.name)
+ is_expected.to have_body_text(previous_assignee.name)
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
- is_expected.to have_html_escaped_body_text(assignee.name)
+ is_expected.to have_body_text(assignee.name)
end
end
@@ -358,7 +358,7 @@ describe Notify do
aggregate_failures do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text(status)
- is_expected.to have_html_escaped_body_text(current_user.name)
+ is_expected.to have_body_text(current_user.name)
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
end
end
@@ -526,7 +526,7 @@ describe Notify do
it 'has the correct subject and body' do
is_expected.to have_referable_subject(project_snippet, reply: true)
- is_expected.to have_html_escaped_body_text project_snippet_note.note
+ is_expected.to have_body_text project_snippet_note.note
end
end
@@ -539,7 +539,7 @@ describe Notify do
it 'has the correct subject and body' do
is_expected.to have_subject("#{project.name} | Project was moved")
- is_expected.to have_html_escaped_body_text project.full_name
+ is_expected.to have_body_text project.full_name
is_expected.to have_body_text(project.ssh_url_to_repo)
end
end
@@ -566,7 +566,7 @@ describe Notify do
expect(to_emails).to eq([recipient.notification_email])
is_expected.to have_subject "Request to join the #{project.full_name} project"
- is_expected.to have_html_escaped_body_text project.full_name
+ is_expected.to have_body_text project.full_name
is_expected.to have_body_text project_project_members_url(project)
is_expected.to have_body_text project_member.human_access
end
@@ -586,7 +586,7 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{project.full_name} project was denied"
- is_expected.to have_html_escaped_body_text project.full_name
+ is_expected.to have_body_text project.full_name
is_expected.to have_body_text project.web_url
end
end
@@ -603,7 +603,7 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{project.full_name} project was granted"
- is_expected.to have_html_escaped_body_text project.full_name
+ is_expected.to have_body_text project.full_name
is_expected.to have_body_text project.web_url
is_expected.to have_body_text project_member.human_access
end
@@ -633,7 +633,7 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{project.full_name} project"
- is_expected.to have_html_escaped_body_text project.full_name
+ is_expected.to have_body_text project.full_name
is_expected.to have_body_text project.full_name
is_expected.to have_body_text project_member.human_access
is_expected.to have_body_text project_member.invite_token
@@ -657,10 +657,10 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation accepted'
- is_expected.to have_html_escaped_body_text project.full_name
+ is_expected.to have_body_text project.full_name
is_expected.to have_body_text project.web_url
is_expected.to have_body_text project_member.invite_email
- is_expected.to have_html_escaped_body_text invited_user.name
+ is_expected.to have_body_text invited_user.name
end
end
@@ -680,7 +680,7 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation declined'
- is_expected.to have_html_escaped_body_text project.full_name
+ is_expected.to have_body_text project.full_name
is_expected.to have_body_text project.web_url
is_expected.to have_body_text project_member.invite_email
end
@@ -932,7 +932,7 @@ describe Notify do
end
it 'contains the message from the note' do
- is_expected.to have_html_escaped_body_text note.note
+ is_expected.to have_body_text note.note
end
it 'contains an introduction' do
@@ -991,7 +991,7 @@ describe Notify do
expect(to_emails).to eq([recipient.notification_email])
is_expected.to have_subject "Request to join the #{group.name} group"
- is_expected.to have_html_escaped_body_text group.name
+ is_expected.to have_body_text group.name
is_expected.to have_body_text group_group_members_url(group)
is_expected.to have_body_text group_member.human_access
end
@@ -1010,7 +1010,7 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{group.name} group was denied"
- is_expected.to have_html_escaped_body_text group.name
+ is_expected.to have_body_text group.name
is_expected.to have_body_text group.web_url
end
end
@@ -1026,7 +1026,7 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{group.name} group was granted"
- is_expected.to have_html_escaped_body_text group.name
+ is_expected.to have_body_text group.name
is_expected.to have_body_text group.web_url
is_expected.to have_body_text group_member.human_access
end
@@ -1056,7 +1056,7 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{group.name} group"
- is_expected.to have_html_escaped_body_text group.name
+ is_expected.to have_body_text group.name
is_expected.to have_body_text group.web_url
is_expected.to have_body_text group_member.human_access
is_expected.to have_body_text group_member.invite_token
@@ -1080,10 +1080,10 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation accepted'
- is_expected.to have_html_escaped_body_text group.name
+ is_expected.to have_body_text group.name
is_expected.to have_body_text group.web_url
is_expected.to have_body_text group_member.invite_email
- is_expected.to have_html_escaped_body_text invited_user.name
+ is_expected.to have_body_text invited_user.name
end
end
@@ -1103,7 +1103,7 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation declined'
- is_expected.to have_html_escaped_body_text group.name
+ is_expected.to have_body_text group.name
is_expected.to have_body_text group.web_url
is_expected.to have_body_text group_member.invite_email
end
@@ -1396,7 +1396,7 @@ describe Notify do
it 'has the correct subject and body' do
is_expected.to have_referable_subject(personal_snippet, reply: true)
- is_expected.to have_html_escaped_body_text personal_snippet_note.note
+ is_expected.to have_body_text personal_snippet_note.note
end
end
end
diff --git a/spec/migrations/active_record/schema_spec.rb b/spec/migrations/active_record/schema_spec.rb
index e132529d8d8..9d35b3cd642 100644
--- a/spec/migrations/active_record/schema_spec.rb
+++ b/spec/migrations/active_record/schema_spec.rb
@@ -5,7 +5,11 @@ require 'spec_helper'
describe ActiveRecord::Schema do
let(:latest_migration_timestamp) do
- migrations = Dir[Rails.root.join('db', 'migrate', '*'), Rails.root.join('db', 'post_migrate', '*')]
+ migrations_paths = %w[db ee/db]
+ .product(%w[migrate post_migrate])
+ .map { |path| Rails.root.join(*path, '*') }
+
+ migrations = Dir[*migrations_paths]
migrations.map { |migration| File.basename(migration).split('_').first.to_i }.max
end
diff --git a/spec/migrations/change_default_value_for_dsa_key_restriction_spec.rb b/spec/migrations/change_default_value_for_dsa_key_restriction_spec.rb
new file mode 100644
index 00000000000..7e61ab9b52e
--- /dev/null
+++ b/spec/migrations/change_default_value_for_dsa_key_restriction_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20180531220618_change_default_value_for_dsa_key_restriction.rb')
+
+describe ChangeDefaultValueForDsaKeyRestriction, :migration do
+ let(:application_settings) { table(:application_settings) }
+
+ before do
+ application_settings.create!
+ end
+
+ it 'changes the default value for dsa_key_restriction' do
+ expect(application_settings.first.dsa_key_restriction).to eq(0)
+
+ migrate!
+
+ application_settings.reset_column_information
+ new_setting = application_settings.create!
+
+ expect(application_settings.count).to eq(2)
+ expect(new_setting.dsa_key_restriction).to eq(-1)
+ end
+
+ it 'changes the existing setting' do
+ setting = application_settings.last
+
+ expect(setting.dsa_key_restriction).to eq(0)
+
+ migrate!
+
+ expect(application_settings.count).to eq(1)
+ expect(setting.reload.dsa_key_restriction).to eq(-1)
+ end
+end
diff --git a/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb b/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb
new file mode 100644
index 00000000000..1ee6c440cf4
--- /dev/null
+++ b/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180603190921_migrate_object_storage_upload_sidekiq_queue.rb')
+
+describe MigrateObjectStorageUploadSidekiqQueue, :sidekiq, :redis do
+ include Gitlab::Database::MigrationHelpers
+
+ context 'when there are jobs in the queue' do
+ it 'correctly migrates queue when migrating up' do
+ Sidekiq::Testing.disable! do
+ stubbed_worker(queue: 'object_storage_upload').perform_async('Something', [1])
+ stubbed_worker(queue: 'object_storage:object_storage_background_move').perform_async('Something', [1])
+
+ described_class.new.up
+
+ expect(sidekiq_queue_length('object_storage_upload')).to eq 0
+ expect(sidekiq_queue_length('object_storage:object_storage_background_move')).to eq 2
+ end
+ end
+ end
+
+ context 'when there are no jobs in the queues' do
+ it 'does not raise error when migrating up' do
+ expect { described_class.new.up }.not_to raise_error
+ end
+ end
+
+ def stubbed_worker(queue:)
+ Class.new do
+ include Sidekiq::Worker
+ sidekiq_options queue: queue
+ end
+ end
+end
diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
index 4ee1d255fbd..ac34efa4f9d 100644
--- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
+++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
@@ -6,7 +6,11 @@ require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_
describe MigrateProcessCommitWorkerJobs do
let(:project) { create(:project, :legacy_storage, :repository) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let(:commit) { project.commit.raw.rugged_commit }
+ let(:commit) do
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.commit.raw.rugged_commit
+ end
+ end
describe 'Project' do
describe 'find_including_path' do
diff --git a/spec/migrations/remove_soft_removed_objects_spec.rb b/spec/migrations/remove_soft_removed_objects_spec.rb
index fb70c284f5e..d0bde98b80e 100644
--- a/spec/migrations/remove_soft_removed_objects_spec.rb
+++ b/spec/migrations/remove_soft_removed_objects_spec.rb
@@ -3,6 +3,18 @@ require Rails.root.join('db', 'post_migrate', '20171207150343_remove_soft_remove
describe RemoveSoftRemovedObjects, :migration do
describe '#up' do
+ let!(:groups) do
+ table(:namespaces).tap do |t|
+ t.inheritance_column = nil
+ end
+ end
+
+ let!(:routes) do
+ table(:routes).tap do |t|
+ t.inheritance_column = nil
+ end
+ end
+
it 'removes various soft removed objects' do
5.times do
create_with_deleted_at(:issue)
@@ -28,19 +40,20 @@ describe RemoveSoftRemovedObjects, :migration do
it 'removes routes of soft removed personal namespaces' do
namespace = create_with_deleted_at(:namespace)
- group = create(:group) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ group = groups.create!(name: 'group', path: 'group_path', type: 'Group')
+ routes.create!(source_id: group.id, source_type: 'Group', name: 'group', path: 'group_path')
- expect(Route.where(source: namespace).exists?).to eq(true)
- expect(Route.where(source: group).exists?).to eq(true)
+ expect(routes.where(source_id: namespace.id).exists?).to eq(true)
+ expect(routes.where(source_id: group.id).exists?).to eq(true)
run_migration
- expect(Route.where(source: namespace).exists?).to eq(false)
- expect(Route.where(source: group).exists?).to eq(true)
+ expect(routes.where(source_id: namespace.id).exists?).to eq(false)
+ expect(routes.where(source_id: group.id).exists?).to eq(true)
end
it 'schedules the removal of soft removed groups' do
- group = create_with_deleted_at(:group)
+ group = create_deleted_group
admin = create(:user, admin: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
expect_any_instance_of(GroupDestroyWorker)
@@ -51,7 +64,7 @@ describe RemoveSoftRemovedObjects, :migration do
end
it 'does not remove soft removed groups when no admin user could be found' do
- create_with_deleted_at(:group)
+ create_deleted_group
expect_any_instance_of(GroupDestroyWorker)
.not_to receive(:perform)
@@ -74,4 +87,13 @@ describe RemoveSoftRemovedObjects, :migration do
row
end
+
+ def create_deleted_group
+ group = groups.create!(name: 'group', path: 'group_path', type: 'Group')
+ routes.create!(source_id: group.id, source_type: 'Group', name: 'group', path: 'group_path')
+
+ groups.where(id: group.id).update_all(deleted_at: 1.year.ago)
+
+ group
+ end
end
diff --git a/spec/migrations/schedule_to_archive_legacy_traces_spec.rb b/spec/migrations/schedule_to_archive_legacy_traces_spec.rb
new file mode 100644
index 00000000000..d3eac3c45ea
--- /dev/null
+++ b/spec/migrations/schedule_to_archive_legacy_traces_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180529152628_schedule_to_archive_legacy_traces')
+
+describe ScheduleToArchiveLegacyTraces, :migration do
+ include TraceHelpers
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:builds) { table(:ci_builds) }
+ let(:job_artifacts) { table(:ci_job_artifacts) }
+
+ before do
+ namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
+ projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123)
+ @build_success = builds.create!(id: 1, project_id: 123, status: 'success', type: 'Ci::Build')
+ @build_failed = builds.create!(id: 2, project_id: 123, status: 'failed', type: 'Ci::Build')
+ @builds_canceled = builds.create!(id: 3, project_id: 123, status: 'canceled', type: 'Ci::Build')
+ @build_running = builds.create!(id: 4, project_id: 123, status: 'running', type: 'Ci::Build')
+
+ create_legacy_trace(@build_success, 'This job is done')
+ create_legacy_trace(@build_failed, 'This job is done')
+ create_legacy_trace(@builds_canceled, 'This job is done')
+ create_legacy_trace(@build_running, 'This job is not done yet')
+ end
+
+ it 'correctly archive legacy traces' do
+ expect(job_artifacts.count).to eq(0)
+ expect(File.exist?(legacy_trace_path(@build_success))).to be_truthy
+ expect(File.exist?(legacy_trace_path(@build_failed))).to be_truthy
+ expect(File.exist?(legacy_trace_path(@builds_canceled))).to be_truthy
+ expect(File.exist?(legacy_trace_path(@build_running))).to be_truthy
+
+ migrate!
+
+ expect(job_artifacts.count).to eq(3)
+ expect(File.exist?(legacy_trace_path(@build_success))).to be_falsy
+ expect(File.exist?(legacy_trace_path(@build_failed))).to be_falsy
+ expect(File.exist?(legacy_trace_path(@builds_canceled))).to be_falsy
+ expect(File.exist?(legacy_trace_path(@build_running))).to be_truthy
+ expect(File.exist?(archived_trace_path(job_artifacts.where(job_id: @build_success.id).first))).to be_truthy
+ expect(File.exist?(archived_trace_path(job_artifacts.where(job_id: @build_failed.id).first))).to be_truthy
+ expect(File.exist?(archived_trace_path(job_artifacts.where(job_id: @builds_canceled.id).first))).to be_truthy
+ expect(job_artifacts.where(job_id: @build_running.id)).not_to be_exist
+ end
+end
diff --git a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
index 560409f08de..5f5ba426d69 100644
--- a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
+++ b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb
@@ -49,10 +49,14 @@ describe TurnNestedGroupsIntoRegularGroupsForMysql do
end
it 'renames the repository of any projects' do
- expect(updated_project.repository.path)
+ repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ updated_project.repository.path
+ end
+
+ expect(repo_path)
.to end_with("#{parent_group.name}-#{child_group.name}/#{updated_project.path}.git")
- expect(File.directory?(updated_project.repository.path)).to eq(true)
+ expect(File.directory?(repo_path)).to eq(true)
end
it 'creates a redirect route for renamed projects' do
diff --git a/spec/models/application_setting/term_spec.rb b/spec/models/application_setting/term_spec.rb
index 1eddf3c56ff..aa49594f4d1 100644
--- a/spec/models/application_setting/term_spec.rb
+++ b/spec/models/application_setting/term_spec.rb
@@ -12,4 +12,41 @@ describe ApplicationSetting::Term do
expect(described_class.latest).to eq(terms)
end
end
+
+ describe '#accepted_by_user?' do
+ let(:user) { create(:user) }
+ let(:term) { create(:term) }
+
+ it 'is true when the user accepted the terms' do
+ accept_terms(term, user)
+
+ expect(term.accepted_by_user?(user)).to be(true)
+ end
+
+ it 'is false when the user declined the terms' do
+ decline_terms(term, user)
+
+ expect(term.accepted_by_user?(user)).to be(false)
+ end
+
+ it 'does not cause a query when the user accepted the current terms' do
+ accept_terms(term, user)
+
+ expect { term.accepted_by_user?(user) }.not_to exceed_query_limit(0)
+ end
+
+ it 'returns false if the currently accepted terms are different' do
+ accept_terms(create(:term), user)
+
+ expect(term.accepted_by_user?(user)).to be(false)
+ end
+
+ def accept_terms(term, user)
+ Users::RespondToTermsService.new(user, term).execute(accepted: true)
+ end
+
+ def decline_terms(term, user)
+ Users::RespondToTermsService.new(user, term).execute(accepted: false)
+ end
+ end
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 968267a6d24..3e6656e0f12 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -110,10 +110,9 @@ describe ApplicationSetting do
# Upgraded databases will have this sort of content
context 'repository_storages is a String, not an Array' do
before do
- setting.__send__(:raw_write_attribute, :repository_storages, 'default')
+ described_class.where(id: setting.id).update_all(repository_storages: 'default')
end
- it { expect(setting.repository_storages_before_type_cast).to eq('default') }
it { expect(setting.repository_storages).to eq(['default']) }
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 66c9708b4cf..51b9b518117 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -116,6 +116,26 @@ describe Ci::Build do
end
end
+ describe '.with_live_trace' do
+ subject { described_class.with_live_trace }
+
+ context 'when build has live trace' do
+ let!(:build) { create(:ci_build, :success, :trace_live) }
+
+ it 'selects the build' do
+ is_expected.to eq([build])
+ end
+ end
+
+ context 'when build does not have live trace' do
+ let!(:build) { create(:ci_build, :success, :trace_artifact) }
+
+ it 'does not select the build' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe '#actionize' do
context 'when build is a created' do
before do
@@ -1528,7 +1548,9 @@ describe Ci::Build do
let(:predefined_variables) do
[
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
+ { key: 'CI_PIPELINE_URL', value: project.web_url + "/pipelines/#{pipeline.id}", public: true },
{ key: 'CI_JOB_ID', value: build.id.to_s, public: true },
+ { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true },
{ key: 'CI_JOB_TOKEN', value: build.token, public: false },
{ key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
{ key: 'CI_BUILD_TOKEN', value: build.token, public: false },
@@ -2151,6 +2173,7 @@ describe Ci::Build do
it 'does not return prohibited variables' do
keys = %w[CI_JOB_ID
+ CI_JOB_URL
CI_JOB_TOKEN
CI_BUILD_ID
CI_BUILD_TOKEN
@@ -2506,4 +2529,76 @@ describe Ci::Build do
end
end
end
+
+ describe 'pages deployments' do
+ set(:build) { create(:ci_build, project: project, user: user) }
+
+ context 'when job is "pages"' do
+ before do
+ build.name = 'pages'
+ end
+
+ context 'when pages are enabled' do
+ before do
+ allow(Gitlab.config.pages).to receive_messages(enabled: true)
+ end
+
+ it 'is marked as pages generator' do
+ expect(build).to be_pages_generator
+ end
+
+ context 'job succeeds' do
+ it "calls pages worker" do
+ expect(PagesWorker).to receive(:perform_async).with(:deploy, build.id)
+
+ build.success!
+ end
+ end
+
+ context 'job fails' do
+ it "does not call pages worker" do
+ expect(PagesWorker).not_to receive(:perform_async)
+
+ build.drop!
+ end
+ end
+ end
+
+ context 'when pages are disabled' do
+ before do
+ allow(Gitlab.config.pages).to receive_messages(enabled: false)
+ end
+
+ it 'is not marked as pages generator' do
+ expect(build).not_to be_pages_generator
+ end
+
+ context 'job succeeds' do
+ it "does not call pages worker" do
+ expect(PagesWorker).not_to receive(:perform_async)
+
+ build.success!
+ end
+ end
+ end
+ end
+
+ context 'when job is not "pages"' do
+ before do
+ build.name = 'other-job'
+ end
+
+ it 'is not marked as pages generator' do
+ expect(build).not_to be_pages_generator
+ end
+
+ context 'job succeeds' do
+ it "does not call pages worker" do
+ expect(PagesWorker).not_to receive(:perform_async)
+
+ build.success
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index cbcf1e55979..b5a6d959ccb 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -54,14 +54,6 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
it { is_expected.to eq('Sample data in db') }
end
-
- context 'when data_store is others' do
- before do
- build_trace_chunk.send(:write_attribute, :data_store, -1)
- end
-
- it { expect { subject }.to raise_error('Unsupported data store') }
- end
end
describe '#set_data' do
diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb
index 51123e73fe6..838fa63cb1f 100644
--- a/spec/models/ci/group_spec.rb
+++ b/spec/models/ci/group_spec.rb
@@ -41,4 +41,55 @@ describe Ci::Group do
end
end
end
+
+ describe '.fabricate' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+ let(:stage) { create(:ci_stage_entity, pipeline: pipeline) }
+
+ before do
+ create_build(:ci_build, name: 'rspec 0 2')
+ create_build(:ci_build, name: 'rspec 0 1')
+ create_build(:ci_build, name: 'spinach 0 1')
+ create_build(:commit_status, name: 'aaaaa')
+ end
+
+ it 'returns an array of three groups' do
+ expect(stage.groups).to be_a Array
+ expect(stage.groups).to all(be_a described_class)
+ expect(stage.groups.size).to eq 3
+ end
+
+ it 'returns groups with correctly ordered statuses' do
+ expect(stage.groups.first.jobs.map(&:name))
+ .to eq ['aaaaa']
+ expect(stage.groups.second.jobs.map(&:name))
+ .to eq ['rspec 0 1', 'rspec 0 2']
+ expect(stage.groups.third.jobs.map(&:name))
+ .to eq ['spinach 0 1']
+ end
+
+ it 'returns groups with correct names' do
+ expect(stage.groups.map(&:name))
+ .to eq %w[aaaaa rspec spinach]
+ end
+
+ context 'when a name is nil on legacy pipelines' do
+ before do
+ pipeline.builds.first.update_attribute(:name, nil)
+ end
+
+ it 'returns an array of three groups' do
+ expect(stage.groups.map(&:name))
+ .to eq ['', 'aaaaa', 'rspec', 'spinach']
+ end
+ end
+
+ def create_build(type, status: 'success', **opts)
+ create(type, pipeline: pipeline,
+ stage: stage.name,
+ status: status,
+ stage_id: stage.id,
+ **opts)
+ end
+ end
end
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index a3e119cbc27..efddd4e7662 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -76,12 +76,12 @@ describe Ci::JobArtifact do
context 'updating the artifact file' do
it 'updates the artifact size' do
- artifact.update!(file: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png')))
+ artifact.update!(file: fixture_file_upload('spec/fixtures/dk.png'))
expect(artifact.size).to eq(1062)
end
it 'updates the project statistics' do
- expect { artifact.update!(file: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) }
+ expect { artifact.update!(file: fixture_file_upload('spec/fixtures/dk.png')) }
.to change { artifact.project.statistics.reload.build_artifacts_size }
.by(1062 - 106365)
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 24692ebb9a3..a41657b53b7 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -194,7 +194,7 @@ describe Ci::Pipeline, :mailer do
it 'does contains persisted variables' do
keys = subject.map { |variable| variable[:key] }
- expect(keys).to eq %w[CI_PIPELINE_ID]
+ expect(keys).to eq %w[CI_PIPELINE_ID CI_PIPELINE_URL]
end
end
end
@@ -537,6 +537,87 @@ describe Ci::Pipeline, :mailer do
end
end
end
+
+ describe '#stages' do
+ before do
+ create(:ci_stage_entity, project: project,
+ pipeline: pipeline,
+ name: 'build')
+ end
+
+ it 'returns persisted stages' do
+ expect(pipeline.stages).not_to be_empty
+ expect(pipeline.stages).to all(be_persisted)
+ end
+ end
+
+ describe '#ordered_stages' do
+ before do
+ create(:ci_stage_entity, project: project,
+ pipeline: pipeline,
+ position: 4,
+ name: 'deploy')
+
+ create(:ci_build, project: project,
+ pipeline: pipeline,
+ stage: 'test',
+ stage_idx: 3,
+ name: 'test')
+
+ create(:ci_build, project: project,
+ pipeline: pipeline,
+ stage: 'build',
+ stage_idx: 2,
+ name: 'build')
+
+ create(:ci_stage_entity, project: project,
+ pipeline: pipeline,
+ position: 1,
+ name: 'sanity')
+
+ create(:ci_stage_entity, project: project,
+ pipeline: pipeline,
+ position: 5,
+ name: 'cleanup')
+ end
+
+ subject { pipeline.ordered_stages }
+
+ context 'when using legacy stages' do
+ before do
+ stub_feature_flags(ci_pipeline_persisted_stages: false)
+ end
+
+ it 'returns legacy stages in valid order' do
+ expect(subject.map(&:name)).to eq %w[build test]
+ end
+ end
+
+ context 'when using persisted stages' do
+ before do
+ stub_feature_flags(ci_pipeline_persisted_stages: true)
+ end
+
+ context 'when pipelines is not complete' do
+ it 'still returns legacy stages' do
+ expect(subject).to all(be_a Ci::LegacyStage)
+ expect(subject.map(&:name)).to eq %w[build test]
+ end
+ end
+
+ context 'when pipeline is complete' do
+ before do
+ pipeline.succeed!
+ end
+
+ it 'returns stages in valid order' do
+ expect(subject).to all(be_a Ci::Stage)
+ expect(subject.map(&:name))
+ .to eq %w[sanity build test deploy cleanup]
+ end
+ end
+ end
+ end
end
describe 'state machine' do
@@ -1181,6 +1262,43 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#update_status' do
+ context 'when pipeline is empty' do
+ it 'updates does not change pipeline status' do
+ expect(pipeline.statuses.latest.status).to be_nil
+
+ expect { pipeline.update_status }
+ .to change { pipeline.reload.status }.to 'skipped'
+ end
+ end
+
+ context 'when updating status to pending' do
+ before do
+ allow(pipeline)
+ .to receive_message_chain(:statuses, :latest, :status)
+ .and_return(:running)
+ end
+
+ it 'updates pipeline status to running' do
+ expect { pipeline.update_status }
+ .to change { pipeline.reload.status }.to 'running'
+ end
+ end
+
+ context 'when statuses status was not recognized' do
+ before do
+ allow(pipeline)
+ .to receive(:latest_builds_status)
+ .and_return(:unknown)
+ end
+
+ it 'raises an exception' do
+ expect { pipeline.update_status }
+ .to raise_error(HasStatus::UnknownStatusError)
+ end
+ end
+ end
+
describe '#detailed_status' do
subject { pipeline.detailed_status(user) }
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 0f072aa1719..f6433234573 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -549,7 +549,7 @@ describe Ci::Runner do
end
describe '#update_cached_info' do
- let(:runner) { create(:ci_runner) }
+ let(:runner) { create(:ci_runner, :project) }
subject { runner.update_cached_info(architecture: '18-bit') }
@@ -570,17 +570,22 @@ describe Ci::Runner do
runner.contacted_at = 2.hours.ago
end
- it 'updates database' do
- expect_redis_update
+ context 'with invalid runner' do
+ before do
+ runner.projects = []
+ end
+
+ it 'still updates redis cache and database' do
+ expect(runner).to be_invalid
- expect { subject }.to change { runner.reload.read_attribute(:contacted_at) }
- .and change { runner.reload.read_attribute(:architecture) }
+ expect_redis_update
+ does_db_update
+ end
end
- it 'updates cache' do
+ it 'updates redis cache and database' do
expect_redis_update
-
- subject
+ does_db_update
end
end
@@ -590,6 +595,11 @@ describe Ci::Runner do
expect(redis).to receive(:set).with(redis_key, anything, any_args)
end
end
+
+ def does_db_update
+ expect { subject }.to change { runner.reload.read_attribute(:contacted_at) }
+ .and change { runner.reload.read_attribute(:architecture) }
+ end
end
describe '#destroy' do
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index a00db1d2bfc..22a4556c10c 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -65,7 +65,31 @@ describe Ci::Stage, :models do
end
end
- context 'when stage is skipped' do
+ context 'when stage has only created builds' do
+ let(:stage) { create(:ci_stage_entity, status: :created) }
+
+ before do
+ create(:ci_build, :created, stage_id: stage.id)
+ end
+
+ it 'updates status to skipped' do
+ expect(stage.reload.status).to eq 'created'
+ end
+ end
+
+ context 'when stage is skipped because of skipped builds' do
+ before do
+ create(:ci_build, :skipped, stage_id: stage.id)
+ end
+
+ it 'updates status to skipped' do
+ expect { stage.update_status }
+ .to change { stage.reload.status }
+ .to 'skipped'
+ end
+ end
+
+ context 'when stage is skipped because is empty' do
it 'updates status to skipped' do
expect { stage.update_status }
.to change { stage.reload.status }
@@ -86,9 +110,85 @@ describe Ci::Stage, :models do
expect(stage.reload).to be_failed
end
end
+
+ context 'when statuses status was not recognized' do
+ before do
+ allow(stage)
+ .to receive_message_chain(:statuses, :latest, :status)
+ .and_return(:unknown)
+ end
+
+ it 'raises an exception' do
+ expect { stage.update_status }
+ .to raise_error(HasStatus::UnknownStatusError)
+ end
+ end
+ end
+
+ describe '#detailed_status' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:user) { create(:user) }
+ let(:stage) { create(:ci_stage_entity, status: :created) }
+ subject { stage.detailed_status(user) }
+
+ where(:statuses, :label) do
+ %w[created] | :created
+ %w[success] | :passed
+ %w[pending] | :pending
+ %w[skipped] | :skipped
+ %w[canceled] | :canceled
+ %w[success failed] | :failed
+ %w[running pending] | :running
+ end
+
+ with_them do
+ before do
+ statuses.each do |status|
+ create(:commit_status, project: stage.project,
+ pipeline: stage.pipeline,
+ stage_id: stage.id,
+ status: status)
+
+ stage.update_status
+ end
+ end
+
+ it 'has a correct label' do
+ expect(subject.label).to eq label.to_s
+ end
+ end
+
+ context 'when stage has warnings' do
+ before do
+ create(:ci_build, project: stage.project,
+ pipeline: stage.pipeline,
+ stage_id: stage.id,
+ status: :failed,
+ allow_failure: true)
+
+ stage.update_status
+ end
+
+ it 'is passed with warnings' do
+ expect(subject.label).to eq 'passed with warnings'
+ end
+ end
end
- describe '#index' do
+ describe '#groups' do
+ before do
+ create(:ci_build, stage_id: stage.id, name: 'rspec 0 1')
+ create(:ci_build, stage_id: stage.id, name: 'rspec 0 2')
+ end
+
+ it 'groups stage builds by name' do
+ expect(stage.groups).to be_one
+ expect(stage.groups.first.name).to eq 'rspec'
+ end
+ end
+
+ describe '#position' do
context 'when stage has been imported and does not have position index set' do
before do
stage.update_column(:position, nil)
@@ -119,4 +219,42 @@ describe Ci::Stage, :models do
end
end
end
+
+ context 'when stage has warnings' do
+ before do
+ create(:ci_build, :failed, :allowed_to_fail, stage_id: stage.id)
+ end
+
+ describe '#has_warnings?' do
+ it 'returns true' do
+ expect(stage).to have_warnings
+ end
+ end
+
+ describe '#number_of_warnings' do
+ it 'returns a lazy stage warnings counter' do
+ lazy_queries = ActiveRecord::QueryRecorder.new do
+ stage.number_of_warnings
+ end
+
+ synced_queries = ActiveRecord::QueryRecorder.new do
+ stage.number_of_warnings.to_i
+ end
+
+ expect(lazy_queries.count).to eq 0
+ expect(synced_queries.count).to eq 1
+
+ expect(stage.number_of_warnings.inspect).to include 'BatchLoader'
+ expect(stage.number_of_warnings).to eq 1
+ end
+ end
+ end
+
+ context 'when stage does not have warnings' do
+ describe '#has_warnings?' do
+ it 'returns false' do
+ expect(stage).not_to have_warnings
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index b3797c1fb46..2d75422ee68 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -156,7 +156,7 @@ describe CacheMarkdownField do
end
it { expect(thing.foo_html).to eq(updated_html) }
- it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION) }
+ it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) }
end
describe '#cached_html_up_to_date?' do
@@ -234,7 +234,7 @@ describe CacheMarkdownField do
it 'returns default version when version is nil' do
thing.cached_markdown_version = nil
- is_expected.to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION)
+ is_expected.to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION)
end
end
@@ -261,7 +261,7 @@ describe CacheMarkdownField do
thing.cached_markdown_version = nil
thing.refresh_markdown_cache
- expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION)
+ expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION)
end
end
@@ -346,7 +346,7 @@ describe CacheMarkdownField do
expect(thing.foo_html).to eq(updated_html)
expect(thing.baz_html).to eq(updated_html)
- expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION)
+ expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION)
end
end
@@ -366,7 +366,7 @@ describe CacheMarkdownField do
expect(thing.foo_html).to eq(updated_html)
expect(thing.baz_html).to eq(updated_html)
- expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION)
+ expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION)
end
end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index f83b52e8975..9fe1186a8c9 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -67,6 +67,30 @@ describe Group do
end
end
+ describe '#notification_settings', :nested_groups do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:sub_group) { create(:group, parent_id: group.id) }
+
+ before do
+ group.add_developer(user)
+ sub_group.add_developer(user)
+ end
+
+ it 'also gets notification settings from parent groups' do
+ expect(sub_group.notification_settings.size).to eq(2)
+ expect(sub_group.notification_settings).to include(group.notification_settings.first)
+ end
+
+ context 'when sub group is deleted' do
+ it 'does not delete parent notification settings' do
+ expect do
+ sub_group.destroy
+ end.to change { NotificationSetting.count }.by(-1)
+ end
+ end
+ end
+
describe '#visibility_level_allowed_by_parent' do
let(:parent) { create(:group, :internal) }
let(:sub_group) { build(:group, parent_id: parent.id) }
@@ -240,7 +264,7 @@ describe Group do
it "is false if avatar is html page" do
group.update_attribute(:avatar, 'uploads/avatar.html')
- expect(group.avatar_type).to eq(["file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff"])
+ expect(group.avatar_type).to eq(["file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico"])
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 65cc9372cbe..3f028b3bd5c 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -16,7 +16,11 @@ describe MergeRequest do
describe '#squash_in_progress?' do
shared_examples 'checking whether a squash is in progress' do
- let(:repo_path) { subject.source_project.repository.path }
+ let(:repo_path) do
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ subject.source_project.repository.path
+ end
+ end
let(:squash_path) { File.join(repo_path, "gitlab-worktree", "squash-#{subject.id}") }
before do
@@ -2197,7 +2201,11 @@ describe MergeRequest do
describe '#rebase_in_progress?' do
shared_examples 'checking whether a rebase is in progress' do
- let(:repo_path) { subject.source_project.repository.path }
+ let(:repo_path) do
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ subject.source_project.repository.path
+ end
+ end
let(:rebase_path) { File.join(repo_path, "gitlab-worktree", "rebase-#{subject.id}") }
before do
@@ -2237,25 +2245,25 @@ describe MergeRequest do
end
end
- describe '#allow_maintainer_to_push' do
+ describe '#allow_collaboration' do
let(:merge_request) do
- build(:merge_request, source_branch: 'fixes', allow_maintainer_to_push: true)
+ build(:merge_request, source_branch: 'fixes', allow_collaboration: true)
end
it 'is false when pushing by a maintainer is not possible' do
- expect(merge_request).to receive(:maintainer_push_possible?) { false }
+ expect(merge_request).to receive(:collaborative_push_possible?) { false }
- expect(merge_request.allow_maintainer_to_push).to be_falsy
+ expect(merge_request.allow_collaboration).to be_falsy
end
it 'is true when pushing by a maintainer is possible' do
- expect(merge_request).to receive(:maintainer_push_possible?) { true }
+ expect(merge_request).to receive(:collaborative_push_possible?) { true }
- expect(merge_request.allow_maintainer_to_push).to be_truthy
+ expect(merge_request.allow_collaboration).to be_truthy
end
end
- describe '#maintainer_push_possible?' do
+ describe '#collaborative_push_possible?' do
let(:merge_request) do
build(:merge_request, source_branch: 'fixes')
end
@@ -2267,14 +2275,14 @@ describe MergeRequest do
it 'does not allow maintainer to push if the source project is the same as the target' do
merge_request.target_project = merge_request.source_project = create(:project, :public)
- expect(merge_request.maintainer_push_possible?).to be_falsy
+ expect(merge_request.collaborative_push_possible?).to be_falsy
end
it 'allows maintainer to push when both source and target are public' do
merge_request.target_project = build(:project, :public)
merge_request.source_project = build(:project, :public)
- expect(merge_request.maintainer_push_possible?).to be_truthy
+ expect(merge_request.collaborative_push_possible?).to be_truthy
end
it 'is not available for protected branches' do
@@ -2285,11 +2293,11 @@ describe MergeRequest do
.with(merge_request.source_project, 'fixes')
.and_return(true)
- expect(merge_request.maintainer_push_possible?).to be_falsy
+ expect(merge_request.collaborative_push_possible?).to be_falsy
end
end
- describe '#can_allow_maintainer_to_push?' do
+ describe '#can_allow_collaboration?' do
let(:target_project) { create(:project, :public) }
let(:source_project) { fork_project(target_project) }
let(:merge_request) do
@@ -2301,17 +2309,17 @@ describe MergeRequest do
let(:user) { create(:user) }
before do
- allow(merge_request).to receive(:maintainer_push_possible?) { true }
+ allow(merge_request).to receive(:collaborative_push_possible?) { true }
end
it 'is false if the user does not have push access to the source project' do
- expect(merge_request.can_allow_maintainer_to_push?(user)).to be_falsy
+ expect(merge_request.can_allow_collaboration?(user)).to be_falsy
end
it 'is true when the user has push access to the source project' do
source_project.add_developer(user)
- expect(merge_request.can_allow_maintainer_to_push?(user)).to be_truthy
+ expect(merge_request.can_allow_collaboration?(user)).to be_truthy
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 6f702d8d95e..18b01c3e6b7 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -301,12 +301,18 @@ describe Namespace do
end
def project_rugged(project)
- project.repository.rugged
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.rugged
+ end
end
end
describe '#rm_dir', 'callback' do
- let(:repository_storage_path) { Gitlab.config.repositories.storages.default.legacy_disk_path }
+ let(:repository_storage_path) do
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ Gitlab.config.repositories.storages.default.legacy_disk_path
+ end
+ end
let(:path_in_dir) { File.join(repository_storage_path, namespace.full_path) }
let(:deleted_path) { namespace.full_path.gsub(namespace.path, "#{namespace.full_path}+#{namespace.id}+deleted") }
let(:deleted_path_in_dir) { File.join(repository_storage_path, deleted_path) }
diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb
index eda0e1da835..13fe47799ed 100644
--- a/spec/models/notification_recipient_spec.rb
+++ b/spec/models/notification_recipient_spec.rb
@@ -13,4 +13,48 @@ describe NotificationRecipient do
expect(recipient.has_access?).to be_falsy
end
+
+ context '#notification_setting' do
+ context 'for child groups', :nested_groups do
+ let!(:moved_group) { create(:group) }
+ let(:group) { create(:group) }
+ let(:sub_group_1) { create(:group, parent: group) }
+ let(:sub_group_2) { create(:group, parent: sub_group_1) }
+ let(:project) { create(:project, namespace: moved_group) }
+
+ before do
+ sub_group_2.add_owner(user)
+ moved_group.add_owner(user)
+ Groups::TransferService.new(moved_group, user).execute(sub_group_2)
+
+ moved_group.reload
+ end
+
+ context 'when notification setting is global' do
+ before do
+ user.notification_settings_for(group).global!
+ user.notification_settings_for(sub_group_1).mention!
+ user.notification_settings_for(sub_group_2).global!
+ user.notification_settings_for(moved_group).global!
+ end
+
+ it 'considers notification setting from the first parent without global setting' do
+ expect(subject.notification_setting.source).to eq(sub_group_1)
+ end
+ end
+
+ context 'when notification setting is not global' do
+ before do
+ user.notification_settings_for(group).global!
+ user.notification_settings_for(sub_group_1).mention!
+ user.notification_settings_for(sub_group_2).watch!
+ user.notification_settings_for(moved_group).disabled!
+ end
+
+ it 'considers notification setting from lowest group member in hierarchy' do
+ expect(subject.notification_setting.source).to eq(moved_group)
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index 7545c0797e9..749b2094787 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -5,6 +5,8 @@ describe ProjectAutoDevops do
it { is_expected.to belong_to(:project) }
+ it { is_expected.to define_enum_for(:deploy_strategy) }
+
it { is_expected.to respond_to(:created_at) }
it { is_expected.to respond_to(:updated_at) }
@@ -67,8 +69,127 @@ describe ProjectAutoDevops do
end
end
+ context 'when deploy_strategy is manual' do
+ let(:domain) { 'example.com' }
+
+ before do
+ auto_devops.deploy_strategy = 'manual'
+ end
+
+ it do
+ expect(auto_devops.predefined_variables.map { |var| var[:key] })
+ .to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED")
+ end
+ end
+
+ context 'when deploy_strategy is continuous' do
+ let(:domain) { 'example.com' }
+
+ before do
+ auto_devops.deploy_strategy = 'continuous'
+ end
+
+ it do
+ expect(auto_devops.predefined_variables.map { |var| var[:key] })
+ .not_to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED")
+ end
+ end
+
def domain_variable
{ key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }
end
end
+
+ describe '#set_gitlab_deploy_token' do
+ let(:auto_devops) { build(:project_auto_devops, project: project) }
+
+ context 'when the project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it 'should not create a gitlab deploy token' do
+ expect do
+ auto_devops.save
+ end.not_to change { DeployToken.count }
+ end
+ end
+
+ context 'when the project is internal' do
+ let(:project) { create(:project, :repository, :internal) }
+
+ it 'should create a gitlab deploy token' do
+ expect do
+ auto_devops.save
+ end.to change { DeployToken.count }.by(1)
+ end
+ end
+
+ context 'when the project is private' do
+ let(:project) { create(:project, :repository, :private) }
+
+ it 'should create a gitlab deploy token' do
+ expect do
+ auto_devops.save
+ end.to change { DeployToken.count }.by(1)
+ end
+ end
+
+ context 'when autodevops is enabled at project level' do
+ let(:project) { create(:project, :repository, :internal) }
+ let(:auto_devops) { build(:project_auto_devops, project: project) }
+
+ it 'should create a deploy token' do
+ expect do
+ auto_devops.save
+ end.to change { DeployToken.count }.by(1)
+ end
+ end
+
+ context 'when autodevops is enabled at instancel level' do
+ let(:project) { create(:project, :repository, :internal) }
+ let(:auto_devops) { build(:project_auto_devops, :disabled, project: project) }
+
+ it 'should create a deploy token' do
+ allow(Gitlab::CurrentSettings).to receive(:auto_devops_enabled?).and_return(true)
+
+ expect do
+ auto_devops.save
+ end.to change { DeployToken.count }.by(1)
+ end
+ end
+
+ context 'when autodevops is disabled' do
+ let(:project) { create(:project, :repository, :internal) }
+ let(:auto_devops) { build(:project_auto_devops, :disabled, project: project) }
+
+ it 'should not create a deploy token' do
+ expect do
+ auto_devops.save
+ end.not_to change { DeployToken.count }
+ end
+ end
+
+ context 'when the project already has an active gitlab-deploy-token' do
+ let(:project) { create(:project, :repository, :internal) }
+ let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) }
+ let(:auto_devops) { build(:project_auto_devops, project: project) }
+
+ it 'should not create a deploy token' do
+ expect do
+ auto_devops.save
+ end.not_to change { DeployToken.count }
+ end
+ end
+
+ context 'when the project already has a revoked gitlab-deploy-token' do
+ let(:project) { create(:project, :repository, :internal) }
+ let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, :expired, projects: [project]) }
+ let(:auto_devops) { build(:project_auto_devops, project: project) }
+
+ it 'should not create a deploy token' do
+ expect do
+ auto_devops.save
+ end.not_to change { DeployToken.count }
+ end
+ end
+ end
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 54ef0be67ff..6c637533c6b 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe JiraService do
include Gitlab::Routing
+ include AssetsHelpers
describe '#options' do
let(:service) do
@@ -164,6 +165,8 @@ describe JiraService do
it "creates Remote Link reference in JIRA for comment" do
@jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
+ favicon_path = "http://localhost/assets/#{find_asset('favicon.png').digest_path}"
+
# Creates comment
expect(WebMock).to have_requested(:post, @comment_url)
# Creates Remote Link in JIRA issue fields
@@ -173,7 +176,7 @@ describe JiraService do
object: {
url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}",
title: "GitLab: Solved by commit #{merge_request.diff_head_sha}.",
- icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" },
+ icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: true }
}
)
@@ -464,4 +467,18 @@ describe JiraService do
end
end
end
+
+ describe 'favicon urls', :request_store do
+ it 'includes the standard favicon' do
+ props = described_class.new.send(:build_remote_link_props, url: 'http://example.com', title: 'title')
+ expect(props[:object][:icon][:url16x16]).to match %r{^http://localhost/assets/favicon(?:-\h+).png$}
+ end
+
+ it 'includes returns the custom favicon' do
+ create :appearance, favicon: fixture_file_upload('spec/fixtures/dk.png')
+
+ props = described_class.new.send(:build_remote_link_props, url: 'http://example.com', title: 'title')
+ expect(props[:object][:icon][:url16x16]).to match %r{^http://localhost/uploads/-/system/appearance/favicon/\d+/dk.png$}
+ end
+ end
end
diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb
index 8d9ee96227f..3351c6280b4 100644
--- a/spec/models/project_services/microsoft_teams_service_spec.rb
+++ b/spec/models/project_services/microsoft_teams_service_spec.rb
@@ -225,10 +225,15 @@ describe MicrosoftTeamsService do
it 'calls Microsoft Teams API for pipeline events' do
data = Gitlab::DataBuilder::Pipeline.build(pipeline)
+ data[:markdown] = true
chat_service.execute(data)
- expect(WebMock).to have_requested(:post, webhook_url).once
+ message = ChatMessage::PipelineMessage.new(data)
+
+ expect(WebMock).to have_requested(:post, webhook_url)
+ .with(body: hash_including({ summary: message.summary }))
+ .once
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 9a76452a808..a2f8fac2f38 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -238,20 +238,27 @@ describe Project do
expect(project2.import_data).to be_nil
end
- it "does not allow blocked import_url localhost" do
+ it "does not allow import_url pointing to localhost" do
project2 = build(:project, import_url: 'http://localhost:9000/t.git')
expect(project2).to be_invalid
expect(project2.errors[:import_url].first).to include('Requests to localhost are not allowed')
end
- it "does not allow blocked import_url port" do
+ it "does not allow import_url with invalid ports" do
project2 = build(:project, import_url: 'http://github.com:25/t.git')
expect(project2).to be_invalid
expect(project2.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
end
+ it "does not allow import_url with invalid user" do
+ project2 = build(:project, import_url: 'http://$user:password@github.com/t.git')
+
+ expect(project2).to be_invalid
+ expect(project2.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
+ end
+
describe 'project pending deletion' do
let!(:project_pending_deletion) do
create(:project,
@@ -960,7 +967,7 @@ describe Project do
it 'is false if avatar is html page' do
project.update_attribute(:avatar, 'uploads/avatar.html')
- expect(project.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff'])
+ expect(project.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico'])
end
end
@@ -1693,6 +1700,31 @@ describe Project do
end
end
+ describe '#human_import_status_name' do
+ context 'when import_state exists' do
+ it 'returns the humanized status name' do
+ project = create(:project)
+ create(:import_state, :started, project: project)
+
+ expect(project.human_import_status_name).to eq("started")
+ end
+ end
+
+ context 'when import_state was not created yet' do
+ let(:project) { create(:project, :import_started) }
+
+ it 'ensures import_state is created and returns humanized status name' do
+ expect do
+ project.human_import_status_name
+ end.to change { ProjectImportState.count }.from(0).to(1)
+ end
+
+ it 'returns humanized status name' do
+ expect(project.human_import_status_name).to eq("started")
+ end
+ end
+ end
+
describe 'Project import job' do
let(:project) { create(:project, import_url: generate(:url)) }
@@ -1701,7 +1733,11 @@ describe Project do
.with(project.repository_storage, project.disk_path, project.import_url)
.and_return(true)
- expect_any_instance_of(Repository).to receive(:after_import)
+ # Works around https://github.com/rspec/rspec-mocks/issues/910
+ allow(described_class).to receive(:find).with(project.id).and_return(project)
+ expect(project.repository).to receive(:after_import)
+ .and_call_original
+ expect(project.wiki.repository).to receive(:after_import)
.and_call_original
end
@@ -2303,6 +2339,22 @@ describe Project do
end
end
+ describe '#any_lfs_file_locks?', :request_store do
+ set(:project) { create(:project) }
+
+ it 'returns false when there are no LFS file locks' do
+ expect(project.any_lfs_file_locks?).to be_falsey
+ end
+
+ it 'returns a cached true when there are LFS file locks' do
+ create(:lfs_file_lock, project: project)
+
+ expect(project.lfs_file_locks).to receive(:any?).once.and_call_original
+
+ 2.times { expect(project.any_lfs_file_locks?).to be_truthy }
+ end
+ end
+
describe '#protected_for?' do
let(:project) { create(:project) }
@@ -2907,7 +2959,7 @@ describe Project do
project.rename_repo
- expect(project.repository.rugged.config['gitlab.fullpath']).to eq(project.full_path)
+ expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
end
end
@@ -3068,7 +3120,7 @@ describe Project do
it 'updates project full path in .git/config' do
project.rename_repo
- expect(project.repository.rugged.config['gitlab.fullpath']).to eq(project.full_path)
+ expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
end
end
@@ -3366,10 +3418,11 @@ describe Project do
end
describe '#after_import' do
- let(:project) { build(:project) }
+ let(:project) { create(:project) }
it 'runs the correct hooks' do
expect(project.repository).to receive(:after_import)
+ expect(project.wiki.repository).to receive(:after_import)
expect(project).to receive(:import_finish)
expect(project).to receive(:update_project_counter_caches)
expect(project).to receive(:remove_import_jid)
@@ -3488,13 +3541,13 @@ describe Project do
it 'writes full path in .git/config when key is missing' do
project.write_repository_config
- expect(project.repository.rugged.config['gitlab.fullpath']).to eq project.full_path
+ expect(rugged_config['gitlab.fullpath']).to eq project.full_path
end
it 'updates full path in .git/config when key is present' do
project.write_repository_config(gl_full_path: 'old/path')
- expect { project.write_repository_config }.to change { project.repository.rugged.config['gitlab.fullpath'] }.from('old/path').to(project.full_path)
+ expect { project.write_repository_config }.to change { rugged_config['gitlab.fullpath'] }.from('old/path').to(project.full_path)
end
it 'does not raise an error with an empty repository' do
@@ -3583,7 +3636,7 @@ describe Project do
target_branch: 'target-branch',
source_project: project,
source_branch: 'awesome-feature-1',
- allow_maintainer_to_push: true
+ allow_collaboration: true
)
end
@@ -3620,9 +3673,14 @@ describe Project do
end
end
- describe '#branch_allows_maintainer_push?' do
+ describe '#branch_allows_collaboration_push?' do
it 'allows access if the user can merge the merge request' do
- expect(project.branch_allows_maintainer_push?(user, 'awesome-feature-1'))
+ expect(project.branch_allows_collaboration?(user, 'awesome-feature-1'))
+ .to be_truthy
+ end
+
+ it 'allows access when there are merge requests open but no branch name is given' do
+ expect(project.branch_allows_collaboration?(user, nil))
.to be_truthy
end
@@ -3630,7 +3688,7 @@ describe Project do
guest = create(:user)
target_project.add_guest(guest)
- expect(project.branch_allows_maintainer_push?(guest, 'awesome-feature-1'))
+ expect(project.branch_allows_collaboration?(guest, 'awesome-feature-1'))
.to be_falsy
end
@@ -3640,31 +3698,31 @@ describe Project do
target_branch: 'target-branch',
source_project: project,
source_branch: 'rejected-feature-1',
- allow_maintainer_to_push: true)
+ allow_collaboration: true)
- expect(project.branch_allows_maintainer_push?(user, 'rejected-feature-1'))
+ expect(project.branch_allows_collaboration?(user, 'rejected-feature-1'))
.to be_falsy
end
it 'does not allow access if the user cannot merge the merge request' do
create(:protected_branch, :masters_can_push, project: target_project, name: 'target-branch')
- expect(project.branch_allows_maintainer_push?(user, 'awesome-feature-1'))
+ expect(project.branch_allows_collaboration?(user, 'awesome-feature-1'))
.to be_falsy
end
it 'caches the result' do
- control = ActiveRecord::QueryRecorder.new { project.branch_allows_maintainer_push?(user, 'awesome-feature-1') }
+ control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') }
- expect { 3.times { project.branch_allows_maintainer_push?(user, 'awesome-feature-1') } }
+ expect { 3.times { project.branch_allows_collaboration?(user, 'awesome-feature-1') } }
.not_to exceed_query_limit(control)
end
context 'when the requeststore is active', :request_store do
it 'only queries per project across instances' do
- control = ActiveRecord::QueryRecorder.new { project.branch_allows_maintainer_push?(user, 'awesome-feature-1') }
+ control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') }
- expect { 2.times { described_class.find(project.id).branch_allows_maintainer_push?(user, 'awesome-feature-1') } }
+ expect { 2.times { described_class.find(project.id).branch_allows_collaboration?(user, 'awesome-feature-1') } }
.not_to exceed_query_limit(control).with_threshold(2)
end
end
@@ -3775,4 +3833,10 @@ describe Project do
let(:uploader_class) { AttachmentUploader }
end
end
+
+ def rugged_config
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.rugged.config
+ end
+ end
end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index e07c522800a..9978f3e9566 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -179,14 +179,14 @@ describe ProjectTeam do
end
describe "#human_max_access" do
- it 'returns Master role' do
+ it 'returns Maintainer role' do
user = create(:user)
group = create(:group)
project = create(:project, namespace: group)
group.add_master(user)
- expect(project.team.human_max_access(user.id)).to eq 'Master'
+ expect(project.team.human_max_access(user.id)).to eq 'Maintainer'
end
it 'returns Owner role' do
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index f1142832f1a..a3c20b3b3c1 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -188,7 +188,11 @@ describe ProjectWiki do
before do
subject.wiki # Make sure the wiki repo exists
- BareRepoOperations.new(subject.repository.path_to_repo).commit_file(image, 'image.png')
+ repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ subject.repository.path_to_repo
+ end
+
+ BareRepoOperations.new(repo_path).commit_file(image, 'image.png')
end
it 'returns the latest version of the file if it exists' do
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index 1d94abe4195..3597b080021 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -15,6 +15,13 @@ describe RemoteMirror do
expect(remote_mirror).not_to be_valid
end
+
+ it 'does not allow url with an invalid user' do
+ remote_mirror = build(:remote_mirror, url: 'http://$user:password@invalid.invalid')
+
+ expect(remote_mirror).to be_invalid
+ expect(remote_mirror.errors[:url].first).to include('Username needs to start with an alphanumeric character')
+ end
end
end
@@ -67,7 +74,9 @@ describe RemoteMirror do
mirror.update_attribute(:url, 'http://foo:baz@test.com')
- config = repo.raw_repository.rugged.config
+ config = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ repo.raw_repository.rugged.config
+ end
expect(config["remote.#{mirror.remote_name}.url"]).to eq('http://foo:baz@test.com')
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 0ccf55bd895..b6df048d4ca 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -136,7 +136,10 @@ describe Repository do
before do
options = { message: 'test tag message\n',
tagger: { name: 'John Smith', email: 'john@gmail.com' } }
- repository.rugged.tags.create(annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', options)
+
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ repository.rugged.tags.create(annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', options)
+ end
double_first = double(committed_date: Time.now - 1.second)
double_last = double(committed_date: Time.now)
@@ -1048,6 +1051,13 @@ describe Repository do
let(:target_project) { project }
let(:target_repository) { target_project.repository }
+ around do |example|
+ # TODO Gitlab::Git::OperationService will be moved to gitaly-ruby and disappear from this repo
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ example.run
+ end
+ end
+
context 'when pre hooks were successful' do
before do
service = Gitlab::Git::HooksService.new
@@ -1185,7 +1195,7 @@ describe Repository do
Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
new_rev
end
- end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
+ end.to raise_error(Gitlab::Git::PreReceiveError)
end
end
@@ -1309,6 +1319,13 @@ describe Repository do
end
describe '#update_autocrlf_option' do
+ around do |example|
+ # TODO Gitlab::Git::OperationService will be moved to gitaly-ruby and disappear from this repo
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ example.run
+ end
+ end
+
describe 'when autocrlf is not already set to :input' do
before do
repository.raw_repository.autocrlf = true
@@ -1802,7 +1819,9 @@ describe Repository do
expect(repository.branch_count).to be_an(Integer)
# NOTE: Until rugged goes away, make sure rugged and gitaly are in sync
- rugged_count = repository.raw_repository.rugged.branches.count
+ rugged_count = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ repository.raw_repository.rugged.branches.count
+ end
expect(repository.branch_count).to eq(rugged_count)
end
@@ -1813,7 +1832,9 @@ describe Repository do
expect(repository.tag_count).to be_an(Integer)
# NOTE: Until rugged goes away, make sure rugged and gitaly are in sync
- rugged_count = repository.raw_repository.rugged.tags.count
+ rugged_count = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ repository.raw_repository.rugged.tags.count
+ end
expect(repository.tag_count).to eq(rugged_count)
end
@@ -1917,13 +1938,13 @@ describe Repository do
context 'when pre hooks failed' do
before do
allow_any_instance_of(Gitlab::GitalyClient::OperationService)
- .to receive(:user_delete_branch).and_raise(Gitlab::Git::HooksService::PreReceiveError)
+ .to receive(:user_delete_branch).and_raise(Gitlab::Git::PreReceiveError)
end
it 'gets an error and does not delete the branch' do
expect do
repository.rm_branch(user, 'feature')
- end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
+ end.to raise_error(Gitlab::Git::PreReceiveError)
expect(repository.find_branch('feature')).not_to be_nil
end
@@ -1959,7 +1980,7 @@ describe Repository do
expect do
repository.rm_branch(user, 'feature')
- end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
+ end.to raise_error(Gitlab::Git::PreReceiveError)
end
it 'does not delete the branch' do
@@ -1967,7 +1988,7 @@ describe Repository do
expect do
repository.rm_branch(user, 'feature')
- end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
+ end.to raise_error(Gitlab::Git::PreReceiveError)
expect(repository.find_branch('feature')).not_to be_nil
end
end
@@ -2073,7 +2094,10 @@ describe Repository do
it "attempting to call keep_around on truncated ref does not fail" do
repository.keep_around(sample_commit.id)
ref = repository.send(:keep_around_ref_name, sample_commit.id)
- path = File.join(repository.path, ref)
+
+ path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ File.join(repository.path, ref)
+ end
# Corrupt the reference
File.truncate(path, 0)
@@ -2088,6 +2112,13 @@ describe Repository do
end
describe '#update_ref' do
+ around do |example|
+ # TODO Gitlab::Git::OperationService will be moved to gitaly-ruby and disappear from this repo
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ example.run
+ end
+ end
+
it 'can create a ref' do
Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_ref, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
@@ -2372,7 +2403,9 @@ describe Repository do
end
def create_remote_branch(remote_name, branch_name, target)
- rugged = repository.rugged
+ rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ repository.rugged
+ end
rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target.id)
end
@@ -2410,6 +2443,32 @@ describe Repository do
end
end
+ describe '#archive_metadata' do
+ let(:ref) { 'master' }
+ let(:storage_path) { '/tmp' }
+
+ let(:prefix) { [project.path, ref].join('-') }
+ let(:filename) { prefix + '.tar.gz' }
+
+ subject(:result) { repository.archive_metadata(ref, storage_path, append_sha: false) }
+
+ context 'with hashed storage disabled' do
+ let(:project) { create(:project, :repository, :legacy_storage) }
+
+ it 'uses the project path to generate the filename' do
+ expect(result['ArchivePrefix']).to eq(prefix)
+ expect(File.basename(result['ArchivePath'])).to eq(filename)
+ end
+ end
+
+ context 'with hashed storage enabled' do
+ it 'uses the project path to generate the filename' do
+ expect(result['ArchivePrefix']).to eq(prefix)
+ expect(File.basename(result['ArchivePath'])).to eq(filename)
+ end
+ end
+ end
+
describe 'commit cache' do
set(:project) { create(:project, :repository) }
diff --git a/spec/models/term_agreement_spec.rb b/spec/models/term_agreement_spec.rb
index a59bf119692..950dfa09a6a 100644
--- a/spec/models/term_agreement_spec.rb
+++ b/spec/models/term_agreement_spec.rb
@@ -5,4 +5,13 @@ describe TermAgreement do
it { is_expected.to validate_presence_of(:term) }
it { is_expected.to validate_presence_of(:user) }
end
+
+ describe '.accepted' do
+ it 'only includes accepted terms' do
+ accepted = create(:term_agreement, :accepted)
+ create(:term_agreement, :declined)
+
+ expect(described_class.accepted).to contain_exactly(accepted)
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 09dfeae6377..097144d04ce 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1260,7 +1260,7 @@ describe User do
it 'is false if avatar is html page' do
user.update_attribute(:avatar, 'uploads/avatar.html')
- expect(user.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff'])
+ expect(user.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico'])
end
end
diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb
index 9ca156deaa0..eead55d33ca 100644
--- a/spec/policies/ci/build_policy_spec.rb
+++ b/spec/policies/ci/build_policy_spec.rb
@@ -101,7 +101,7 @@ describe Ci::BuildPolicy do
it 'enables update_build if user is maintainer' do
allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false)
- allow_any_instance_of(Project).to receive(:branch_allows_maintainer_push?).and_return(true)
+ allow_any_instance_of(Project).to receive(:branch_allows_collaboration?).and_return(true)
expect(policy).to be_allowed :update_build
expect(policy).to be_allowed :update_commit_status
diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb
index a5e509cfa0f..bd32faf06ef 100644
--- a/spec/policies/ci/pipeline_policy_spec.rb
+++ b/spec/policies/ci/pipeline_policy_spec.rb
@@ -69,7 +69,7 @@ describe Ci::PipelinePolicy, :models do
it 'enables update_pipeline if user is maintainer' do
allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false)
- allow_any_instance_of(Project).to receive(:branch_allows_maintainer_push?).and_return(true)
+ allow_any_instance_of(Project).to receive(:branch_allows_collaboration?).and_return(true)
expect(policy).to be_allowed :update_pipeline
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 6609f5f7afd..6ac151f92f3 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -400,7 +400,7 @@ describe ProjectPolicy do
:merge_request,
target_project: target_project,
source_project: project,
- allow_maintainer_to_push: true
+ allow_collaboration: true
)
end
let(:maintainer_abilities) do
diff --git a/spec/requests/api/avatar_spec.rb b/spec/requests/api/avatar_spec.rb
new file mode 100644
index 00000000000..26e0435a6d5
--- /dev/null
+++ b/spec/requests/api/avatar_spec.rb
@@ -0,0 +1,106 @@
+require 'spec_helper'
+
+describe API::Avatar do
+ let(:gravatar_service) { double('GravatarService') }
+
+ describe 'GET /avatar' do
+ context 'avatar uploaded to GitLab' do
+ context 'user with matching public email address' do
+ let(:user) { create(:user, :with_avatar, email: 'public@example.com', public_email: 'public@example.com') }
+
+ before do
+ user
+ end
+
+ it 'returns the avatar url' do
+ get api('/avatar'), { email: 'public@example.com' }
+
+ expect(response.status).to eq 200
+ expect(json_response['avatar_url']).to eql("#{::Settings.gitlab.base_url}#{user.avatar.local_url}")
+ end
+ end
+
+ context 'no user with matching public email address' do
+ before do
+ expect(GravatarService).to receive(:new).and_return(gravatar_service)
+ expect(gravatar_service).to(
+ receive(:execute)
+ .with('private@example.com', nil, 2, { username: nil })
+ .and_return('https://gravatar'))
+ end
+
+ it 'returns the avatar url from Gravatar' do
+ get api('/avatar'), { email: 'private@example.com' }
+
+ expect(response.status).to eq 200
+ expect(json_response['avatar_url']).to eq('https://gravatar')
+ end
+ end
+ end
+
+ context 'avatar uploaded to Gravatar' do
+ context 'user with matching public email address' do
+ let(:user) { create(:user, email: 'public@example.com', public_email: 'public@example.com') }
+
+ before do
+ user
+
+ expect(GravatarService).to receive(:new).and_return(gravatar_service)
+ expect(gravatar_service).to(
+ receive(:execute)
+ .with('public@example.com', nil, 2, { username: user.username })
+ .and_return('https://gravatar'))
+ end
+
+ it 'returns the avatar url from Gravatar' do
+ get api('/avatar'), { email: 'public@example.com' }
+
+ expect(response.status).to eq 200
+ expect(json_response['avatar_url']).to eq('https://gravatar')
+ end
+ end
+
+ context 'no user with matching public email address' do
+ before do
+ expect(GravatarService).to receive(:new).and_return(gravatar_service)
+ expect(gravatar_service).to(
+ receive(:execute)
+ .with('private@example.com', nil, 2, { username: nil })
+ .and_return('https://gravatar'))
+ end
+
+ it 'returns the avatar url from Gravatar' do
+ get api('/avatar'), { email: 'private@example.com' }
+
+ expect(response.status).to eq 200
+ expect(json_response['avatar_url']).to eq('https://gravatar')
+ end
+ end
+
+ context 'public visibility level restricted' do
+ let(:user) { create(:user, :with_avatar, email: 'public@example.com', public_email: 'public@example.com') }
+
+ before do
+ user
+
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ context 'when authenticated' do
+ it 'returns the avatar url' do
+ get api('/avatar', user), { email: 'public@example.com' }
+
+ expect(response.status).to eq 200
+ expect(json_response['avatar_url']).to eql("#{::Settings.gitlab.base_url}#{user.avatar.local_url}")
+ end
+ end
+
+ context 'when unauthenticated' do
+ it_behaves_like '403 response' do
+ let(:request) { get api('/avatar'), { email: 'public@example.com' } }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 8ad19e3f0f5..e73d1a252f5 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -18,14 +18,14 @@ describe API::Commits do
describe 'GET /projects/:id/repository/commits' do
let(:route) { "/projects/#{project_id}/repository/commits" }
- shared_examples_for 'project commits' do
+ shared_examples_for 'project commits' do |schema: 'public_api/v4/commits'|
it "returns project commits" do
commit = project.repository.commit
get api(route, current_user)
expect(response).to have_gitlab_http_status(200)
- expect(response).to match_response_schema('public_api/v4/commits')
+ expect(response).to match_response_schema(schema)
expect(json_response.first['id']).to eq(commit.id)
expect(json_response.first['committer_name']).to eq(commit.committer_name)
expect(json_response.first['committer_email']).to eq(commit.committer_email)
@@ -161,6 +161,23 @@ describe API::Commits do
end
end
+ context 'with_stats optional parameter' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it_behaves_like 'project commits', schema: 'public_api/v4/commits_with_stats' do
+ let(:route) { "/projects/#{project_id}/repository/commits?with_stats=true" }
+
+ it 'include commits details' do
+ commit = project.repository.commit
+ get api(route, current_user)
+
+ expect(json_response.first['stats']['additions']).to eq(commit.stats.additions)
+ expect(json_response.first['stats']['deletions']).to eq(commit.stats.deletions)
+ expect(json_response.first['stats']['total']).to eq(commit.stats.total)
+ end
+ end
+ end
+
context 'with pagination params' do
let(:page) { 1 }
let(:per_page) { 5 }
@@ -247,6 +264,19 @@ describe API::Commits do
]
}
end
+ let!(:valid_utf8_c_params) do
+ {
+ branch: 'master',
+ commit_message: message,
+ actions: [
+ {
+ action: 'create',
+ file_path: 'foo/bar/baz.txt',
+ content: 'puts 🦊'
+ }
+ ]
+ }
+ end
it 'a new file in project repo' do
post api(url, user), valid_c_params
@@ -257,6 +287,15 @@ describe API::Commits do
expect(json_response['committer_email']).to eq(user.email)
end
+ it 'a new file with utf8 chars in project repo' do
+ post api(url, user), valid_utf8_c_params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq(message)
+ expect(json_response['committer_name']).to eq(user.name)
+ expect(json_response['committer_email']).to eq(user.email)
+ end
+
it 'returns a 400 bad request if file exists' do
post api(url, user), invalid_c_params
diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb
index 962c845f36d..e6a61fdcf39 100644
--- a/spec/requests/api/events_spec.rb
+++ b/spec/requests/api/events_spec.rb
@@ -176,7 +176,7 @@ describe API::Events do
end
it 'avoids N+1 queries' do
- control_count = ActiveRecord::QueryRecorder.new do
+ control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
get api("/projects/#{private_project.id}/events", user), target_type: :merge_request
end.count
@@ -184,7 +184,7 @@ describe API::Events do
expect do
get api("/projects/#{private_project.id}/events", user), target_type: :merge_request
- end.not_to exceed_query_limit(control_count)
+ end.not_to exceed_all_query_limit(control_count)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb
new file mode 100644
index 00000000000..796ffc9d569
--- /dev/null
+++ b/spec/requests/api/graphql/project_query_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe 'getting project information' do
+ include GraphqlHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:current_user) { create(:user) }
+
+ let(:query) do
+ graphql_query_for('project', 'fullPath' => project.full_path)
+ end
+
+ context 'when the user has access to the project' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it 'includes the project' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']).not_to be_nil
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+ end
+
+ context 'when requesting a nested merge request' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:merge_request_graphql_data) { graphql_data['project']['mergeRequest'] }
+
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('mergeRequest', iid: merge_request.iid)
+ )
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+ end
+
+ it 'contains merge request information' do
+ post_graphql(query, current_user: current_user)
+
+ expect(merge_request_graphql_data).not_to be_nil
+ end
+
+ # This is a field coming from the `MergeRequestPresenter`
+ it 'includes a web_url' do
+ post_graphql(query, current_user: current_user)
+
+ expect(merge_request_graphql_data['webUrl']).to be_present
+ end
+
+ context 'when the user does not have access to the merge request' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it 'returns nil' do
+ project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
+
+ post_graphql(query)
+
+ expect(merge_request_graphql_data).to be_nil
+ end
+ end
+ end
+ end
+
+ context 'when the user does not have access to the project' do
+ it 'returns an empty field' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']).to be_nil
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 7d923932309..da23fdd7dca 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -138,10 +138,15 @@ describe API::Groups do
context "when using sorting" do
let(:group3) { create(:group, name: "a#{group1.name}", path: "z#{group1.path}") }
+ let(:group4) { create(:group, name: "same-name", path: "y#{group1.path}") }
+ let(:group5) { create(:group, name: "same-name") }
let(:response_groups) { json_response.map { |group| group['name'] } }
+ let(:response_groups_ids) { json_response.map { |group| group['id'] } }
before do
group3.add_owner(user1)
+ group4.add_owner(user1)
+ group5.add_owner(user1)
end
it "sorts by name ascending by default" do
@@ -150,7 +155,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(response_groups).to eq([group3.name, group1.name])
+ expect(response_groups).to eq(Group.visible_to_user(user1).order(:name).pluck(:name))
end
it "sorts in descending order when passed" do
@@ -159,16 +164,52 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(response_groups).to eq([group1.name, group3.name])
+ expect(response_groups).to eq(Group.visible_to_user(user1).order(name: :desc).pluck(:name))
end
- it "sorts by the order_by param" do
+ it "sorts by path in order_by param" do
get api("/groups", user1), order_by: "path"
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(response_groups).to eq([group1.name, group3.name])
+ expect(response_groups).to eq(Group.visible_to_user(user1).order(:path).pluck(:name))
+ end
+
+ it "sorts by id in the order_by param" do
+ get api("/groups", user1), order_by: "id"
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(response_groups).to eq(Group.visible_to_user(user1).order(:id).pluck(:name))
+ end
+
+ it "sorts also by descending id with pagination fix" do
+ get api("/groups", user1), order_by: "id", sort: "desc"
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(response_groups).to eq(Group.visible_to_user(user1).order(id: :desc).pluck(:name))
+ end
+
+ it "sorts identical keys by id for good pagination" do
+ get api("/groups", user1), search: "same-name", order_by: "name"
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(response_groups_ids).to eq(Group.select { |group| group['name'] == 'same-name' }.map { |group| group['id'] }.sort)
+ end
+
+ it "sorts descending identical keys by id for good pagination" do
+ get api("/groups", user1), search: "same-name", order_by: "name", sort: "desc"
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(response_groups_ids).to eq(Group.select { |group| group['name'] == 'same-name' }.map { |group| group['id'] }.sort)
end
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 5dc3ddd4b36..a56b913198c 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -522,7 +522,6 @@ describe API::Internal do
context 'the project path was changed' do
let(:project) { create(:project, :repository, :legacy_storage) }
- let!(:old_path_to_repo) { project.repository.path_to_repo }
let!(:repository) { project.repository }
before do
@@ -835,8 +834,7 @@ describe API::Internal do
end
def push(key, project, protocol = 'ssh', env: nil)
- post(
- api("/internal/allowed"),
+ params = {
changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master',
key_id: key.id,
project: project.full_path,
@@ -845,7 +843,19 @@ describe API::Internal do
secret_token: secret_token,
protocol: protocol,
env: env
- )
+ }
+
+ if Gitlab.rails5?
+ post(
+ api("/internal/allowed"),
+ params: params
+ )
+ else
+ post(
+ api("/internal/allowed"),
+ params
+ )
+ end
end
def archive(key, project)
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 4181f4ebbbe..a15d60aafe0 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -630,15 +630,17 @@ describe API::Issues do
end
it 'avoids N+1 queries' do
- control_count = ActiveRecord::QueryRecorder.new do
+ get api("/projects/#{project.id}/issues", user)
+
+ control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
get api("/projects/#{project.id}/issues", user)
end.count
- create(:issue, author: user, project: project)
+ create_list(:issue, 3, project: project)
expect do
get api("/projects/#{project.id}/issues", user)
- end.not_to exceed_query_limit(control_count)
+ end.not_to exceed_all_query_limit(control_count)
end
it 'returns 404 when project does not exist' do
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 45082e644ca..50d6f4b4d99 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -177,6 +177,18 @@ describe API::Jobs do
json_response.each { |job| expect(job['pipeline']['id']).to eq(pipeline.id) }
end
end
+
+ it 'avoids N+1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
+ end.count
+
+ 3.times { create(:ci_build, :artifacts, pipeline: pipeline) }
+
+ expect do
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
+ end.not_to exceed_all_query_limit(control_count)
+ end
end
context 'unauthorized user' do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 605761867bf..d4ebfc3f782 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -386,12 +386,13 @@ describe API::MergeRequests do
source_project: forked_project,
target_project: project,
source_branch: 'fixes',
- allow_maintainer_to_push: true)
+ allow_collaboration: true)
end
- it 'includes the `allow_maintainer_to_push` field' do
+ it 'includes the `allow_collaboration` field' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
+ expect(json_response['allow_collaboration']).to be_truthy
expect(json_response['allow_maintainer_to_push']).to be_truthy
end
end
@@ -654,11 +655,12 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(400)
end
- it 'allows setting `allow_maintainer_to_push`' do
+ it 'allows setting `allow_collaboration`' do
post api("/projects/#{forked_project.id}/merge_requests", user2),
- title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
- author: user2, target_project_id: project.id, allow_maintainer_to_push: true
+ title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
+ author: user2, target_project_id: project.id, allow_collaboration: true
expect(response).to have_gitlab_http_status(201)
+ expect(json_response['allow_collaboration']).to be_truthy
expect(json_response['allow_maintainer_to_push']).to be_truthy
end
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index 0736329f9fd..78ea77cb3bb 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -285,6 +285,15 @@ describe API::Pipelines do
end
describe 'POST /projects/:id/pipeline ' do
+ def expect_variables(variables, expected_variables)
+ variables.each_with_index do |variable, index|
+ expected_variable = expected_variables[index]
+
+ expect(variable.key).to eq(expected_variable['key'])
+ expect(variable.value).to eq(expected_variable['value'])
+ end
+ end
+
context 'authorized user' do
context 'with gitlab-ci.yml' do
before do
@@ -294,13 +303,62 @@ describe API::Pipelines do
it 'creates and returns a new pipeline' do
expect do
post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch
- end.to change { Ci::Pipeline.count }.by(1)
+ end.to change { project.pipelines.count }.by(1)
expect(response).to have_gitlab_http_status(201)
expect(json_response).to be_a Hash
expect(json_response['sha']).to eq project.commit.id
end
+ context 'variables given' do
+ let(:variables) { [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] }
+
+ it 'creates and returns a new pipeline using the given variables' do
+ expect do
+ post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch, variables: variables
+ end.to change { project.pipelines.count }.by(1)
+ expect_variables(project.pipelines.last.variables, variables)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to be_a Hash
+ expect(json_response['sha']).to eq project.commit.id
+ expect(json_response).not_to have_key('variables')
+ end
+ end
+
+ describe 'using variables conditions' do
+ let(:variables) { [{ 'key' => 'STAGING', 'value' => 'true' }] }
+
+ before do
+ config = YAML.dump(test: { script: 'test', only: { variables: ['$STAGING'] } })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'creates and returns a new pipeline using the given variables' do
+ expect do
+ post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch, variables: variables
+ end.to change { project.pipelines.count }.by(1)
+ expect_variables(project.pipelines.last.variables, variables)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to be_a Hash
+ expect(json_response['sha']).to eq project.commit.id
+ expect(json_response).not_to have_key('variables')
+ end
+
+ context 'condition unmatch' do
+ let(:variables) { [{ 'key' => 'STAGING', 'value' => 'false' }] }
+
+ it "doesn't create a job" do
+ expect do
+ post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch
+ end.not_to change { project.pipelines.count }
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+ end
+
it 'fails when using an invalid ref' do
post api("/projects/#{project.id}/pipeline", user), ref: 'invalid_ref'
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index f8c64f063af..97dffdc9233 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::ProjectImport do
let(:export_path) { "#{Dir.tmpdir}/project_export_spec" }
let(:user) { create(:user) }
- let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
+ let(:file) { File.join('spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
let(:namespace) { create(:group) }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 9b7c3205c1f..99103039f77 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -518,7 +518,7 @@ describe API::Projects do
end
it 'uploads avatar for project a project' do
- project = attributes_for(:project, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif'))
+ project = attributes_for(:project, avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif'))
post api('/projects', user), project
@@ -777,7 +777,7 @@ describe API::Projects do
end
it "uploads the file and returns its info" do
- post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")
+ post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload("spec/fixtures/dk.png", "image/png")
expect(response).to have_gitlab_http_status(201)
expect(json_response['alt']).to eq("dk")
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 9e6d69e3874..cd135dfc32a 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -220,11 +220,10 @@ describe API::Repositories do
expect(response).to have_gitlab_http_status(200)
- repo_name = project.repository.name.gsub("\.git", "")
type, params = workhorse_send_data
expect(type).to eq('git-archive')
- expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
+ expect(params['ArchivePath']).to match(/#{project.path}\-[^\.]+\.tar.gz/)
end
it 'returns the repository archive archive.zip' do
@@ -232,11 +231,10 @@ describe API::Repositories do
expect(response).to have_gitlab_http_status(200)
- repo_name = project.repository.name.gsub("\.git", "")
type, params = workhorse_send_data
expect(type).to eq('git-archive')
- expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
+ expect(params['ArchivePath']).to match(/#{project.path}\-[^\.]+\.zip/)
end
it 'returns the repository archive archive.tar.bz2' do
@@ -244,11 +242,10 @@ describe API::Repositories do
expect(response).to have_gitlab_http_status(200)
- repo_name = project.repository.name.gsub("\.git", "")
type, params = workhorse_send_data
expect(type).to eq('git-archive')
- expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
+ expect(params['ArchivePath']).to match(/#{project.path}\-[^\.]+\.tar.bz2/)
end
context 'when sha does not exist' do
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 319ac389083..e7639599874 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -351,11 +351,13 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when valid token is provided' do
context 'when Runner is not active' do
let(:runner) { create(:ci_runner, :inactive) }
+ let(:update_value) { runner.ensure_runner_queue_value }
it 'returns 204 error' do
request_job
- expect(response).to have_gitlab_http_status 204
+ expect(response).to have_gitlab_http_status(204)
+ expect(response.header['X-GitLab-Last-Update']).to eq(update_value)
end
end
@@ -816,6 +818,18 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(job.reload.trace.raw).to eq 'BUILD TRACE'
end
+
+ context 'when running state is sent' do
+ it 'updates update_at value' do
+ expect { update_job_after_time }.to change { job.reload.updated_at }
+ end
+ end
+
+ context 'when other state is sent' do
+ it "doesn't update update_at value" do
+ expect { update_job_after_time(20.minutes, state: 'success') }.not_to change { job.reload.updated_at }
+ end
+ end
end
context 'when job has been erased' do
@@ -838,6 +852,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
expect(response).to have_gitlab_http_status(403)
+ expect(response.header['Job-Status']).to eq 'failed'
expect(job.trace.raw).to eq 'Job failed'
expect(job).to be_failed
end
@@ -847,6 +862,12 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
new_params = params.merge(token: token)
put api("/jobs/#{job.id}"), new_params
end
+
+ def update_job_after_time(update_interval = 20.minutes, state = 'running')
+ Timecop.travel(job.updated_at + update_interval) do
+ update_job(job.token, state: state)
+ end
+ end
end
describe 'PATCH /api/v4/jobs/:id/trace' do
@@ -979,6 +1000,17 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
end
+
+ context 'when the job is canceled' do
+ before do
+ job.cancel
+ patch_the_trace
+ end
+
+ it 'receives status in header' do
+ expect(response.header['Job-Status']).to eq 'canceled'
+ end
+ end
end
context 'when Runner makes a force-patch' do
@@ -1055,8 +1087,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let(:jwt_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt_token } }
let(:headers_with_token) { headers.merge(API::Helpers::Runner::JOB_TOKEN_HEADER => job.token) }
- let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
- let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
+ let(:file_upload) { fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
+ let(:file_upload2) { fixture_file_upload('spec/fixtures/dk.png', 'image/gif') }
before do
stub_artifacts_object_storage
@@ -1101,6 +1133,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(json_response['RemoteObject']).to have_key('GetURL')
expect(json_response['RemoteObject']).to have_key('StoreURL')
expect(json_response['RemoteObject']).to have_key('DeleteURL')
+ expect(json_response['RemoteObject']).to have_key('MultipartUpload')
end
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index aead8978dd4..57adc3ca7a6 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -35,7 +35,9 @@ describe API::Settings, 'Settings' do
context "custom repository storage type set in the config" do
before do
- storages = { 'custom' => 'tmp/tests/custom_repositories' }
+ # Add a possible storage to the config
+ storages = Gitlab.config.repositories.storages
+ .merge({ 'custom' => 'tmp/tests/custom_repositories' })
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index b3e253befc6..c5456977b60 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -20,6 +20,7 @@ describe API::Snippets do
private_snippet.id)
expect(json_response.last).to have_key('web_url')
expect(json_response.last).to have_key('raw_url')
+ expect(json_response.last).to have_key('visibility')
end
it 'hides private snippets from regular user' do
@@ -112,6 +113,7 @@ describe API::Snippets do
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)
+ expect(json_response['visibility']).to eq(snippet.visibility)
end
it 'returns 404 for invalid snippet id' do
@@ -142,6 +144,7 @@ describe API::Snippets do
expect(json_response['title']).to eq(params[:title])
expect(json_response['description']).to eq(params[:description])
expect(json_response['file_name']).to eq(params[:file_name])
+ expect(json_response['visibility']).to eq(params[:visibility])
end
it 'returns 400 for missing parameters' do
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index e2b19ad59f9..969710d6613 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -287,7 +287,10 @@ describe API::Tags do
context 'annotated tag' do
it 'creates a new annotated tag' do
# Identity must be set in .gitconfig to create annotated tag.
- repo_path = project.repository.path_to_repo
+ repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.path_to_repo
+ end
+
system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name}))
system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email}))
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 05637eb0729..a97c3f3461a 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -512,7 +512,7 @@ describe API::Users do
end
it 'updates user with avatar' do
- put api("/users/#{user.id}", admin), { avatar: fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
+ put api("/users/#{user.id}", admin), { avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
user.reload
@@ -1123,58 +1123,63 @@ describe API::Users do
describe "GET /user" do
let(:personal_access_token) { create(:personal_access_token, user: user).token }
- context 'with regular user' do
- context 'with personal access token' do
- it 'returns 403 without private token when sudo is defined' do
- get api("/user?private_token=#{personal_access_token}&sudo=123")
+ shared_examples 'get user info' do |version|
+ context 'with regular user' do
+ context 'with personal access token' do
+ it 'returns 403 without private token when sudo is defined' do
+ get api("/user?private_token=#{personal_access_token}&sudo=123", version: version)
- expect(response).to have_gitlab_http_status(403)
+ expect(response).to have_gitlab_http_status(403)
+ end
end
- end
- it 'returns current user without private token when sudo not defined' do
- get api("/user", user)
+ it 'returns current user without private token when sudo not defined' do
+ get api("/user", user, version: version)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to match_response_schema('public_api/v4/user/public')
- expect(json_response['id']).to eq(user.id)
- end
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/user/public')
+ expect(json_response['id']).to eq(user.id)
+ end
- context "scopes" do
- let(:path) { "/user" }
- let(:api_call) { method(:api) }
+ context "scopes" do
+ let(:path) { "/user" }
+ let(:api_call) { method(:api) }
- include_examples 'allows the "read_user" scope'
+ include_examples 'allows the "read_user" scope', version
+ end
end
- end
- context 'with admin' do
- let(:admin_personal_access_token) { create(:personal_access_token, user: admin).token }
+ context 'with admin' do
+ let(:admin_personal_access_token) { create(:personal_access_token, user: admin).token }
- context 'with personal access token' do
- it 'returns 403 without private token when sudo defined' do
- get api("/user?private_token=#{admin_personal_access_token}&sudo=#{user.id}")
+ context 'with personal access token' do
+ it 'returns 403 without private token when sudo defined' do
+ get api("/user?private_token=#{admin_personal_access_token}&sudo=#{user.id}", version: version)
- expect(response).to have_gitlab_http_status(403)
- end
+ expect(response).to have_gitlab_http_status(403)
+ end
- it 'returns initial current user without private token but with is_admin when sudo not defined' do
- get api("/user?private_token=#{admin_personal_access_token}")
+ it 'returns initial current user without private token but with is_admin when sudo not defined' do
+ get api("/user?private_token=#{admin_personal_access_token}", version: version)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to match_response_schema('public_api/v4/user/admin')
- expect(json_response['id']).to eq(admin.id)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/user/admin')
+ expect(json_response['id']).to eq(admin.id)
+ end
end
end
- end
- context 'with unauthenticated user' do
- it "returns 401 error if user is unauthenticated" do
- get api("/user")
+ context 'with unauthenticated user' do
+ it "returns 401 error if user is unauthenticated" do
+ get api("/user", version: version)
- expect(response).to have_gitlab_http_status(401)
+ expect(response).to have_gitlab_http_status(401)
+ end
end
end
+
+ it_behaves_like 'get user info', 'v3'
+ it_behaves_like 'get user info', 'v4'
end
describe "GET /user/keys" do
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 2514dab1714..92fcfb65269 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -1,6 +1,7 @@
require "spec_helper"
describe 'Git HTTP requests' do
+ include ProjectForksHelper
include TermsHelper
include GitHttpHelpers
include WorkhorseHelpers
@@ -305,6 +306,22 @@ describe 'Git HTTP requests' do
expect(response.body).to eq(change_access_error(:push_code))
end
end
+
+ context 'when merge requests are open that allow maintainer access' do
+ let(:canonical_project) { create(:project, :public, :repository) }
+ let(:project) { fork_project(canonical_project, nil, repository: true) }
+
+ before do
+ canonical_project.add_master(user)
+ create(:merge_request,
+ source_project: project,
+ target_project: canonical_project,
+ source_branch: 'fixes',
+ allow_collaboration: true)
+ end
+
+ it_behaves_like 'pushes are allowed'
+ end
end
end
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 79672fe1cc5..4d30b99262e 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -1021,6 +1021,7 @@ describe 'Git LFS API and storage' do
expect(json_response['RemoteObject']).to have_key('GetURL')
expect(json_response['RemoteObject']).to have_key('StoreURL')
expect(json_response['RemoteObject']).to have_key('DeleteURL')
+ expect(json_response['RemoteObject']).not_to have_key('MultipartUpload')
expect(json_response['LfsOid']).to eq(sample_oid)
expect(json_response['LfsSize']).to eq(sample_size)
end
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index be286c490fe..bcb8d6c2bfc 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('spec/fixtures/dk.png')
)
end
diff --git a/spec/routing/api_routing_spec.rb b/spec/routing/api_routing_spec.rb
new file mode 100644
index 00000000000..5fde4bd885b
--- /dev/null
+++ b/spec/routing/api_routing_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe 'api', 'routing' do
+ context 'when graphql is disabled' do
+ before do
+ stub_feature_flags(graphql: false)
+ end
+
+ it 'does not route to the GraphqlController' do
+ expect(get('/api/graphql')).not_to route_to('graphql#execute')
+ end
+
+ it 'does not expose graphiql' do
+ expect(get('/-/graphql-explorer')).not_to route_to('graphiql/rails/editors#show')
+ end
+ end
+
+ context 'when graphql is disabled' do
+ before do
+ stub_feature_flags(graphql: true)
+ end
+
+ it 'routes to the GraphqlController' do
+ expect(get('/api/graphql')).not_to route_to('graphql#execute')
+ end
+
+ it 'exposes graphiql' do
+ expect(get('/-/graphql-explorer')).not_to route_to('graphiql/rails/editors#show')
+ end
+ end
+end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index e1b4e618092..56d93095a85 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -36,33 +36,36 @@ describe 'project routing' do
shared_examples 'RESTful project resources' do
let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] }
let(:controller_path) { controller }
+ let(:id) { { id: '1' } }
+ let(:format) { {} } # response format, e.g. { format: :html }
+ let(:params) { { namespace_id: 'gitlab', project_id: 'gitlabhq' } }
it 'to #index' do
- expect(get("/gitlab/gitlabhq/#{controller_path}")).to route_to("projects/#{controller}#index", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:index)
+ expect(get("/gitlab/gitlabhq/#{controller_path}")).to route_to("projects/#{controller}#index", params) if actions.include?(:index)
end
it 'to #create' do
- expect(post("/gitlab/gitlabhq/#{controller_path}")).to route_to("projects/#{controller}#create", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:create)
+ expect(post("/gitlab/gitlabhq/#{controller_path}")).to route_to("projects/#{controller}#create", params) if actions.include?(:create)
end
it 'to #new' do
- expect(get("/gitlab/gitlabhq/#{controller_path}/new")).to route_to("projects/#{controller}#new", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:new)
+ expect(get("/gitlab/gitlabhq/#{controller_path}/new")).to route_to("projects/#{controller}#new", params) if actions.include?(:new)
end
it 'to #edit' do
- expect(get("/gitlab/gitlabhq/#{controller_path}/1/edit")).to route_to("projects/#{controller}#edit", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:edit)
+ expect(get("/gitlab/gitlabhq/#{controller_path}/1/edit")).to route_to("projects/#{controller}#edit", params.merge(**id, **format)) if actions.include?(:edit)
end
it 'to #show' do
- expect(get("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#show", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:show)
+ expect(get("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#show", params.merge(**id, **format)) if actions.include?(:show)
end
it 'to #update' do
- expect(put("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#update", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:update)
+ expect(put("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#update", params.merge(id)) if actions.include?(:update)
end
it 'to #destroy' do
- expect(delete("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#destroy", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:destroy)
+ expect(delete("/gitlab/gitlabhq/#{controller_path}/1")).to route_to("projects/#{controller}#destroy", params.merge(**id, **format)) if actions.include?(:destroy)
end
end
@@ -150,12 +153,13 @@ describe 'project routing' do
end
it 'to #history' do
- expect(get('/gitlab/gitlabhq/wikis/1/history')).to route_to('projects/wikis#history', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+ expect(get('/gitlab/gitlabhq/wikis/1/history')).to route_to('projects/wikis#history', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: :html)
end
it_behaves_like 'RESTful project resources' do
let(:actions) { [:create, :edit, :show, :destroy] }
let(:controller) { 'wikis' }
+ let(:format) { { format: :html } }
end
end
diff --git a/spec/serializers/build_serializer_spec.rb b/spec/serializers/build_serializer_spec.rb
index 98cd15e248b..52459cd369d 100644
--- a/spec/serializers/build_serializer_spec.rb
+++ b/spec/serializers/build_serializer_spec.rb
@@ -39,7 +39,7 @@ describe BuildSerializer do
expect(subject[:label]).to eq('failed')
expect(subject[:tooltip]).to eq('failed <br> (unknown failure)')
expect(subject[:icon]).to eq(status.icon)
- expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico")
+ expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.png")
end
end
@@ -54,7 +54,7 @@ describe BuildSerializer do
expect(subject[:label]).to eq('passed')
expect(subject[:tooltip]).to eq('passed')
expect(subject[:icon]).to eq(status.icon)
- expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico")
+ expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.png")
end
end
end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index b741308e2c5..eb4235e3ee6 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -8,6 +8,10 @@ describe PipelineSerializer do
described_class.new(current_user: user)
end
+ before do
+ stub_feature_flags(ci_pipeline_persisted_stages: true)
+ end
+
subject { serializer.represent(resource) }
describe '#represent' do
@@ -99,7 +103,8 @@ describe PipelineSerializer do
end
end
- context 'number of queries' do
+ describe 'number of queries when preloaded' do
+ subject { serializer.represent(resource, preload: true) }
let(:resource) { Ci::Pipeline.all }
before do
@@ -107,7 +112,7 @@ describe PipelineSerializer do
# gitaly calls in this block
# Issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/37772
Gitlab::GitalyClient.allow_n_plus_1_calls do
- Ci::Pipeline::AVAILABLE_STATUSES.each do |status|
+ Ci::Pipeline::COMPLETED_STATUSES.each do |status|
create_pipeline(status)
end
end
@@ -120,7 +125,7 @@ describe PipelineSerializer do
it 'verifies number of queries', :request_store do
recorded = ActiveRecord::QueryRecorder.new { subject }
- expect(recorded.count).to be_within(1).of(44)
+ expect(recorded.count).to be_within(2).of(27)
expect(recorded.cached_count).to eq(0)
end
end
@@ -139,7 +144,7 @@ describe PipelineSerializer do
# pipeline. With the same ref this check is cached but if refs are
# different then there is an extra query per ref
# https://gitlab.com/gitlab-org/gitlab-ce/issues/46368
- expect(recorded.count).to be_within(1).of(51)
+ expect(recorded.count).to be_within(2).of(30)
expect(recorded.cached_count).to eq(0)
end
end
@@ -174,7 +179,7 @@ describe PipelineSerializer do
expect(subject[:text]).to eq(status.text)
expect(subject[:label]).to eq(status.label)
expect(subject[:icon]).to eq(status.icon)
- expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico")
+ expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.png")
end
end
end
diff --git a/spec/serializers/status_entity_spec.rb b/spec/serializers/status_entity_spec.rb
index 559475e571c..0b010ebd507 100644
--- a/spec/serializers/status_entity_spec.rb
+++ b/spec/serializers/status_entity_spec.rb
@@ -18,17 +18,7 @@ describe StatusEntity do
it 'contains status details' do
expect(subject).to include :text, :icon, :favicon, :label, :group, :tooltip
expect(subject).to include :has_details, :details_path
- expect(subject[:favicon]).to match_asset_path('/assets/ci_favicons/favicon_status_success.ico')
- end
-
- it 'contains a dev namespaced favicon if dev env' do
- allow(Rails.env).to receive(:development?) { true }
- expect(entity.as_json[:favicon]).to match_asset_path('/assets/ci_favicons/dev/favicon_status_success.ico')
- end
-
- it 'contains a canary namespaced favicon if canary env' do
- stub_env('CANARY', 'true')
- expect(entity.as_json[:favicon]).to match_asset_path('/assets/ci_favicons/canary/favicon_status_success.ico')
+ expect(subject[:favicon]).to match_asset_path('/assets/ci_favicons/favicon_status_success.png')
end
end
end
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index a73bd7a0268..688d3b8c038 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -280,12 +280,12 @@ describe Ci::RetryPipelineService, '#execute' do
source_project: forked_project,
target_project: project,
source_branch: 'fixes',
- allow_maintainer_to_push: true)
+ allow_collaboration: true)
create_build('rspec 1', :failed, 1)
end
it 'allows to retry failed pipeline' do
- allow_any_instance_of(Project).to receive(:fetch_branch_allows_maintainer_push?).and_return(true)
+ allow_any_instance_of(Project).to receive(:fetch_branch_allows_collaboration?).and_return(true)
allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false)
service.execute(pipeline)
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index 33405d7a7ec..92159e1e372 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -118,7 +118,9 @@ describe GitTagPushService do
before do
# Create the lightweight tag
- project.repository.raw_repository.rugged.tags.create(tag_name, newrev)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.raw_repository.rugged.tags.create(tag_name, newrev)
+ end
# Clear tag list cache
project.repository.expire_tags_cache
diff --git a/spec/services/lfs/unlock_file_service_spec.rb b/spec/services/lfs/unlock_file_service_spec.rb
index 4bea112b9c6..948401d7bdc 100644
--- a/spec/services/lfs/unlock_file_service_spec.rb
+++ b/spec/services/lfs/unlock_file_service_spec.rb
@@ -80,12 +80,12 @@ describe Lfs::UnlockFileService do
result = subject.execute
expect(result[:status]).to eq(:error)
- expect(result[:message]).to match(/You must have master access/)
+ expect(result[:message]).to match(/You must have maintainer access/)
expect(result[:http_status]).to eq(403)
end
end
- context 'by a master user' do
+ context 'by a maintainer user' do
let(:current_user) { master }
let(:params) do
{ id: lock.id,
diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb
index 38d84cf0ceb..b1882df732d 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -125,9 +125,14 @@ describe MergeRequests::CreateFromIssueService do
end
context 'when ref branch does not exist' do
- it 'does not create a merge request' do
- expect { described_class.new(project, user, issue_iid: issue.iid, ref: 'nobr').execute }
- .not_to change { project.merge_requests.count }
+ subject { described_class.new(project, user, issue_iid: issue.iid, ref: 'no-such-branch').execute }
+
+ it 'creates a merge request' do
+ expect { subject }.to change(project.merge_requests, :count).by(1)
+ end
+
+ it 'sets the merge request target branch to the project default branch' do
+ expect(subject[:merge_request].target_branch).to eq(project.default_branch)
end
end
end
diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb
index 5ef6365fcc9..28f56d19657 100644
--- a/spec/services/merge_requests/ff_merge_service_spec.rb
+++ b/spec/services/merge_requests/ff_merge_service_spec.rb
@@ -72,7 +72,7 @@ describe MergeRequests::FfMergeService do
it 'logs and saves error if there is an PreReceiveError exception' do
error_message = 'error message'
- allow(service).to receive(:repository).and_raise(Gitlab::Git::HooksService::PreReceiveError, error_message)
+ allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, error_message)
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index dc30a9bccc1..ef2738ef504 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -226,7 +226,7 @@ describe MergeRequests::MergeService do
it 'logs and saves error if there is an PreReceiveError exception' do
error_message = 'error message'
- allow(service).to receive(:repository).and_raise(Gitlab::Git::HooksService::PreReceiveError, error_message)
+ allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, error_message)
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
diff --git a/spec/services/merge_requests/squash_service_spec.rb b/spec/services/merge_requests/squash_service_spec.rb
index bd884787425..ded17fa92a4 100644
--- a/spec/services/merge_requests/squash_service_spec.rb
+++ b/spec/services/merge_requests/squash_service_spec.rb
@@ -63,7 +63,9 @@ describe MergeRequests::SquashService do
end
it 'has the same diff as the merge request, but a different SHA' do
- rugged = project.repository.rugged
+ rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.rugged
+ end
mr_diff = rugged.diff(merge_request.diff_base_sha, merge_request.diff_head_sha)
squash_diff = rugged.diff(merge_request.diff_start_sha, squash_sha)
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index bd2e91f1f7a..443dcd92a8b 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -547,7 +547,7 @@ describe MergeRequests::UpdateService, :mailer do
let(:closed_issuable) { create(:closed_merge_request, source_project: project) }
end
- context 'setting `allow_maintainer_to_push`' do
+ context 'setting `allow_collaboration`' do
let(:target_project) { create(:project, :public) }
let(:source_project) { fork_project(target_project) }
let(:user) { create(:user) }
@@ -562,23 +562,23 @@ describe MergeRequests::UpdateService, :mailer do
allow(ProtectedBranch).to receive(:protected?).with(source_project, 'fixes') { false }
end
- it 'does not allow a maintainer of the target project to set `allow_maintainer_to_push`' do
+ it 'does not allow a maintainer of the target project to set `allow_collaboration`' do
target_project.add_developer(user)
- update_merge_request(allow_maintainer_to_push: true, title: 'Updated title')
+ update_merge_request(allow_collaboration: true, title: 'Updated title')
expect(merge_request.title).to eq('Updated title')
- expect(merge_request.allow_maintainer_to_push).to be_falsy
+ expect(merge_request.allow_collaboration).to be_falsy
end
it 'is allowed by a user that can push to the source and can update the merge request' do
merge_request.update!(assignee: user)
source_project.add_developer(user)
- update_merge_request(allow_maintainer_to_push: true, title: 'Updated title')
+ update_merge_request(allow_collaboration: true, title: 'Updated title')
expect(merge_request.title).to eq('Updated title')
- expect(merge_request.allow_maintainer_to_push).to be_truthy
+ expect(merge_request.allow_collaboration).to be_truthy
end
end
end
diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb
new file mode 100644
index 00000000000..7f536ce4e68
--- /dev/null
+++ b/spec/services/notification_recipient_service_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe NotificationRecipientService do
+ let(:service) { described_class }
+ let(:assignee) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:other_projects) { create_list(:project, 5, :public) }
+
+ describe '#build_new_note_recipients' do
+ let(:issue) { create(:issue, project: project, assignees: [assignee]) }
+ let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id) }
+
+ def create_watcher
+ watcher = create(:user)
+ create(:notification_setting, project: project, user: watcher, level: :watch)
+
+ other_projects.each do |other_project|
+ create(:notification_setting, project: other_project, user: watcher, level: :watch)
+ end
+ end
+
+ it 'avoids N+1 queries', :request_store do
+ Gitlab::GitalyClient.allow_n_plus_1_calls { create_watcher }
+
+ service.build_new_note_recipients(note)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ service.build_new_note_recipients(note)
+ end
+
+ Gitlab::GitalyClient.allow_n_plus_1_calls { create_watcher }
+
+ expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count)
+ end
+ end
+end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 831678b949d..0eadc83bfe3 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -59,6 +59,20 @@ describe NotificationService, :mailer do
should_email(participant)
end
+
+ context 'for subgroups', :nested_groups do
+ before do
+ build_group(project)
+ end
+
+ it 'emails the participant' do
+ create(:note_on_issue, noteable: issuable, project_id: project.id, note: 'anything', author: @pg_participant)
+
+ notification_trigger
+
+ should_email_nested_group_user(@pg_participant)
+ end
+ end
end
shared_examples 'participating by assignee notification' do
@@ -239,34 +253,56 @@ describe NotificationService, :mailer do
end
describe 'new note on issue in project that belongs to a group' do
- let(:group) { create(:group) }
-
before do
note.project.namespace_id = group.id
- note.project.group.add_user(@u_watcher, GroupMember::MASTER)
- note.project.group.add_user(@u_custom_global, GroupMember::MASTER)
+ group.add_user(@u_watcher, GroupMember::MASTER)
+ group.add_user(@u_custom_global, GroupMember::MASTER)
note.project.save
@u_watcher.notification_settings_for(note.project).participating!
- @u_watcher.notification_settings_for(note.project.group).global!
+ @u_watcher.notification_settings_for(group).global!
update_custom_notification(:new_note, @u_custom_global)
reset_delivered_emails!
end
- it do
- notification.new_note(note)
+ shared_examples 'new note notifications' do
+ it do
+ notification.new_note(note)
+
+ should_email(note.noteable.author)
+ should_email(note.noteable.assignees.first)
+ should_email(@u_mentioned)
+ should_email(@u_custom_global)
+ should_not_email(@u_guest_custom)
+ should_not_email(@u_guest_watcher)
+ should_not_email(@u_watcher)
+ should_not_email(note.author)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
+ should_not_email(@u_lazy_participant)
+ end
+ end
- should_email(note.noteable.author)
- should_email(note.noteable.assignees.first)
- should_email(@u_mentioned)
- should_email(@u_custom_global)
- should_not_email(@u_guest_custom)
- should_not_email(@u_guest_watcher)
- should_not_email(@u_watcher)
- should_not_email(note.author)
- should_not_email(@u_participating)
- should_not_email(@u_disabled)
- should_not_email(@u_lazy_participant)
+ let(:group) { create(:group) }
+
+ it_behaves_like 'new note notifications'
+
+ context 'which is a subgroup', :nested_groups do
+ let!(:parent) { create(:group) }
+ let!(:group) { create(:group, parent: parent) }
+
+ it_behaves_like 'new note notifications'
+
+ it 'overrides child objects with global level' do
+ user = create(:user)
+ parent.add_developer(user)
+ user.notification_settings_for(parent).watch!
+ reset_delivered_emails!
+
+ notification.new_note(note)
+
+ should_email(user)
+ end
end
end
end
@@ -301,6 +337,31 @@ describe NotificationService, :mailer do
should_email(member)
should_email(admin)
end
+
+ context 'on project that belongs to subgroup', :nested_groups do
+ let(:group_reporter) { create(:user) }
+ let(:group_guest) { create(:user) }
+ let(:parent_group) { create(:group) }
+ let(:child_group) { create(:group, parent: parent_group) }
+ let(:project) { create(:project, namespace: child_group) }
+
+ context 'when user is group guest member' do
+ before do
+ parent_group.add_reporter(group_reporter)
+ parent_group.add_guest(group_guest)
+ group_guest.notification_settings_for(parent_group).watch!
+ group_reporter.notification_settings_for(parent_group).watch!
+ reset_delivered_emails!
+ end
+
+ it 'does not email guest user' do
+ notification.new_note(note)
+
+ should_email(group_reporter)
+ should_not_email(group_guest)
+ end
+ end
+ end
end
context 'issue note mention' do
@@ -311,6 +372,7 @@ describe NotificationService, :mailer do
before do
build_team(note.project)
+ build_group(note.project)
note.project.add_master(note.author)
add_users_with_subscription(note.project, issue)
reset_delivered_emails!
@@ -336,10 +398,20 @@ describe NotificationService, :mailer do
should_email(@u_guest_watcher)
should_email(note.noteable.author)
should_email(note.noteable.assignees.first)
- should_not_email(note.author)
+ should_email_nested_group_user(@pg_watcher)
should_email(@u_mentioned)
- should_not_email(@u_disabled)
should_email(@u_not_mentioned)
+ should_not_email(note.author)
+ should_not_email(@u_disabled)
+ should_not_email_nested_group_user(@pg_disabled)
+ end
+
+ it 'notifies parent group members with mention level', :nested_groups do
+ note = create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: "@#{@pg_mention.username}")
+
+ notification.new_note(note)
+
+ should_email_nested_group_user(@pg_mention)
end
it 'filters out "mentioned in" notes' do
@@ -352,17 +424,18 @@ describe NotificationService, :mailer do
end
context 'project snippet note' do
- let(:project) { create(:project, :public) }
+ let!(:project) { create(:project, :public) }
let(:snippet) { create(:project_snippet, project: project, author: create(:user)) }
- let(:note) { create(:note_on_project_snippet, noteable: snippet, project_id: snippet.project.id, note: '@all mentioned') }
+ let(:note) { create(:note_on_project_snippet, noteable: snippet, project_id: project.id, note: '@all mentioned') }
before do
- build_team(note.project)
+ build_team(project)
+ build_group(project)
# make sure these users can read the project snippet!
project.add_guest(@u_guest_watcher)
project.add_guest(@u_guest_custom)
-
+ add_member_for_parent_group(@pg_watcher, project)
note.project.add_master(note.author)
reset_delivered_emails!
end
@@ -370,7 +443,6 @@ describe NotificationService, :mailer do
describe '#new_note' do
it 'notifies the team members' do
notification.new_note(note)
-
# Notify all team members
note.project.team.members.each do |member|
# User with disabled notification should not be notified
@@ -449,6 +521,7 @@ describe NotificationService, :mailer do
before do
build_team(note.project)
+ build_group(project)
reset_delivered_emails!
allow(note.noteable).to receive(:author).and_return(@u_committer)
update_custom_notification(:new_note, @u_guest_custom, resource: project)
@@ -463,11 +536,13 @@ describe NotificationService, :mailer do
should_email(@u_guest_custom)
should_email(@u_committer)
should_email(@u_watcher)
+ should_email_nested_group_user(@pg_watcher)
should_not_email(@u_mentioned)
should_not_email(note.author)
should_not_email(@u_participating)
should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
+ should_not_email_nested_group_user(@pg_disabled)
end
it do
@@ -478,10 +553,12 @@ describe NotificationService, :mailer do
should_email(@u_committer)
should_email(@u_watcher)
should_email(@u_mentioned)
+ should_email_nested_group_user(@pg_watcher)
should_not_email(note.author)
should_not_email(@u_participating)
should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
+ should_not_email_nested_group_user(@pg_disabled)
end
it do
@@ -548,10 +625,13 @@ describe NotificationService, :mailer do
should_email(@g_global_watcher)
should_email(@g_watcher)
should_email(@unsubscribed_mentioned)
+ should_email_nested_group_user(@pg_watcher)
should_not_email(@u_mentioned)
should_not_email(@u_participating)
should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
+ should_not_email_nested_group_user(@pg_disabled)
+ should_not_email_nested_group_user(@pg_mention)
end
it do
@@ -1922,19 +2002,69 @@ describe NotificationService, :mailer do
# Users in the project's group but not part of project's team
# with different notification settings
def build_group(project)
- group = create(:group, :public)
- project.group = group
+ group = create_nested_group
+ project.update(namespace_id: group.id)
# Group member: global=disabled, group=watch
- @g_watcher = create_user_with_notification(:watch, 'group_watcher', project.group)
+ @g_watcher ||= create_user_with_notification(:watch, 'group_watcher', project.group)
@g_watcher.notification_settings_for(nil).disabled!
# Group member: global=watch, group=global
- @g_global_watcher = create_global_setting_for(create(:user), :watch)
+ @g_global_watcher ||= create_global_setting_for(create(:user), :watch)
group.add_users([@g_watcher, @g_global_watcher], :master)
+
group
end
+ # Creates a nested group only if supported
+ # to avoid errors on MySQL
+ def create_nested_group
+ if Group.supports_nested_groups?
+ parent_group = create(:group, :public)
+ child_group = create(:group, :public, parent: parent_group)
+
+ # Parent group member: global=disabled, parent_group=watch, child_group=global
+ @pg_watcher ||= create_user_with_notification(:watch, 'parent_group_watcher', parent_group)
+ @pg_watcher.notification_settings_for(nil).disabled!
+
+ # Parent group member: global=global, parent_group=disabled, child_group=global
+ @pg_disabled ||= create_user_with_notification(:disabled, 'parent_group_disabled', parent_group)
+ @pg_disabled.notification_settings_for(nil).global!
+
+ # Parent group member: global=global, parent_group=mention, child_group=global
+ @pg_mention ||= create_user_with_notification(:mention, 'parent_group_mention', parent_group)
+ @pg_mention.notification_settings_for(nil).global!
+
+ # Parent group member: global=global, parent_group=participating, child_group=global
+ @pg_participant ||= create_user_with_notification(:participating, 'parent_group_participant', parent_group)
+ @pg_mention.notification_settings_for(nil).global!
+
+ child_group
+ else
+ create(:group, :public)
+ end
+ end
+
+ def add_member_for_parent_group(user, project)
+ return unless Group.supports_nested_groups?
+
+ project.reload
+
+ project.group.parent.add_master(user)
+ end
+
+ def should_email_nested_group_user(user, times: 1, recipients: email_recipients)
+ return unless Group.supports_nested_groups?
+
+ should_email(user, times: 1, recipients: email_recipients)
+ end
+
+ def should_not_email_nested_group_user(user, recipients: email_recipients)
+ return unless Group.supports_nested_groups?
+
+ should_not_email(user, recipients: email_recipients)
+ end
+
def add_users_with_subscription(project, issuable)
@subscriber = create :user
@unsubscriber = create :user
diff --git a/spec/services/pages_service_spec.rb b/spec/services/pages_service_spec.rb
deleted file mode 100644
index f8db6900a0a..00000000000
--- a/spec/services/pages_service_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-require 'spec_helper'
-
-describe PagesService do
- let(:build) { create(:ci_build) }
- let(:data) { Gitlab::DataBuilder::Build.build(build) }
- let(:service) { described_class.new(data) }
-
- before do
- allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
- end
-
- context 'execute asynchronously for pages job' do
- before do
- build.name = 'pages'
- end
-
- context 'on success' do
- before do
- build.success
- end
-
- it 'executes worker' do
- expect(PagesWorker).to receive(:perform_async)
- service.execute
- end
- end
-
- %w(pending running failed canceled).each do |status|
- context "on #{status}" do
- before do
- build.status = status
- end
-
- it 'does not execute worker' do
- expect(PagesWorker).not_to receive(:perform_async)
- service.execute
- end
- end
- end
- end
-
- context 'for other jobs' do
- before do
- build.name = 'other job'
- build.success
- end
-
- it 'does not execute worker' do
- expect(PagesWorker).not_to receive(:perform_async)
- service.execute
- end
- end
-end
diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb
index c6678fc1f5c..cd52bc88f4c 100644
--- a/spec/services/projects/after_import_service_spec.rb
+++ b/spec/services/projects/after_import_service_spec.rb
@@ -32,7 +32,7 @@ describe Projects::AfterImportService do
end
it 'removes refs/pull/**/*' do
- expect(repository.rugged.references.map(&:name))
+ expect(rugged.references.map(&:name))
.not_to include(%r{\Arefs/pull/})
end
end
@@ -46,10 +46,14 @@ describe Projects::AfterImportService do
end
it "does not remove refs/#{name}/tmp" do
- expect(repository.rugged.references.map(&:name))
+ expect(rugged.references.map(&:name))
.to include("refs/#{name}/tmp")
end
end
end
+
+ def rugged
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository.rugged }
+ end
end
end
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index f7ff8b80bd7..6fd73a50511 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -115,5 +115,20 @@ describe Projects::AutocompleteService do
expect(milestone_titles).to eq([group_milestone2.title, group_milestone1.title])
end
+
+ context 'with nested groups', :nested_groups do
+ let(:subgroup) { create(:group, :public, parent: group) }
+ let!(:subgroup_milestone) { create(:milestone, group: subgroup) }
+
+ before do
+ project.update(namespace: subgroup)
+ end
+
+ it 'includes project milestones and all acestors milestones' do
+ expect(milestone_titles).to match_array(
+ [project_milestone.title, group_milestone2.title, group_milestone1.title, subgroup_milestone.title]
+ )
+ end
+ end
end
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index a8f003b1073..e8cbf84e3be 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -272,8 +272,11 @@ describe Projects::CreateService, '#execute' do
it 'writes project full path to .git/config' do
project = create_project(user, opts)
+ rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.rugged
+ end
- expect(project.repository.rugged.config['gitlab.fullpath']).to eq project.full_path
+ expect(rugged.config['gitlab.fullpath']).to eq project.full_path
end
def create_project(user, opts)
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index b63f409579e..38660ad7a01 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -5,7 +5,11 @@ describe Projects::DestroyService do
let!(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace) }
- let!(:path) { project.repository.path_to_repo }
+ let!(:path) do
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.path_to_repo
+ end
+ end
let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") }
let!(:async) { false } # execute or async_execute
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index a93f6f1ddc2..c15f5120b8a 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -8,7 +8,7 @@ describe Projects::ForkService do
before do
@from_user = create(:user)
@from_namespace = @from_user.namespace
- avatar = fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")
+ avatar = fixture_file_upload("spec/fixtures/dk.png", "image/png")
@from_project = create(:project,
:repository,
creator_id: @from_user.id,
diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb
index ee1a886f5d6..0a898e9b89b 100644
--- a/spec/services/projects/gitlab_projects_import_service_spec.rb
+++ b/spec/services/projects/gitlab_projects_import_service_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Projects::GitlabProjectsImportService do
set(:namespace) { create(:namespace) }
let(:path) { 'test-path' }
- let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+ let(:file) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
let(:overwrite) { false }
let(:import_params) { { namespace_id: namespace.id, path: path, file: file, overwrite: overwrite } }
subject { described_class.new(namespace.owner, import_params) }
diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
index 7dca81eb59e..ed4930313c5 100644
--- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
@@ -37,7 +37,11 @@ describe Projects::HashedStorage::MigrateRepositoryService do
it 'writes project full path to .git/config' do
service.execute
- expect(project.repository.rugged.config['gitlab.fullpath']).to eq project.full_path
+ rugged_config = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.rugged.config['gitlab.fullpath']
+ end
+
+ expect(rugged_config).to eq project.full_path
end
end
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index b7b5de07380..1cf373d1d72 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Projects::HousekeepingService do
subject { described_class.new(project) }
- let(:project) { create(:project, :repository) }
+ set(:project) { create(:project, :repository) }
before do
project.reset_pushes_since_gc
@@ -16,12 +16,12 @@ describe Projects::HousekeepingService do
it 'enqueues a sidekiq job' do
expect(subject).to receive(:try_obtain_lease).and_return(:the_uuid)
expect(subject).to receive(:lease_key).and_return(:the_lease_key)
- expect(subject).to receive(:task).and_return(:the_task)
- expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :the_task, :the_lease_key, :the_uuid)
+ expect(subject).to receive(:task).and_return(:incremental_repack)
+ expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid).and_call_original
- subject.execute
-
- expect(project.reload.pushes_since_gc).to eq(0)
+ Sidekiq::Testing.fake! do
+ expect { subject.execute }.to change(GitGarbageCollectWorker.jobs, :size).by(1)
+ end
end
it 'yields the block if given' do
@@ -30,6 +30,16 @@ describe Projects::HousekeepingService do
end.to yield_with_no_args
end
+ it 'resets counter after execution' do
+ expect(subject).to receive(:try_obtain_lease).and_return(:the_uuid)
+ allow(subject).to receive(:gc_period).and_return(1)
+ project.increment_pushes_since_gc
+
+ Sidekiq::Testing.inline! do
+ expect { subject.execute }.to change { project.pushes_since_gc }.to(0)
+ end
+ end
+
context 'when no lease can be obtained' do
before do
expect(subject).to receive(:try_obtain_lease).and_return(false)
@@ -54,6 +64,30 @@ describe Projects::HousekeepingService do
end.not_to yield_with_no_args
end
end
+
+ context 'task type' do
+ it 'goes through all three housekeeping tasks, executing only the highest task when there is overlap' do
+ allow(subject).to receive(:try_obtain_lease).and_return(:the_uuid)
+ allow(subject).to receive(:lease_key).and_return(:the_lease_key)
+
+ # At push 200
+ expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :the_lease_key, :the_uuid)
+ .exactly(1).times
+ # At push 50, 100, 150
+ expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :full_repack, :the_lease_key, :the_uuid)
+ .exactly(3).times
+ # At push 10, 20, ... (except those above)
+ expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid)
+ .exactly(16).times
+
+ 201.times do
+ subject.increment!
+ subject.execute if subject.needed?
+ end
+
+ expect(project.pushes_since_gc).to eq(1)
+ end
+ end
end
describe '#needed?' do
@@ -69,31 +103,7 @@ describe Projects::HousekeepingService do
describe '#increment!' do
it 'increments the pushes_since_gc counter' do
- expect do
- subject.increment!
- end.to change { project.pushes_since_gc }.from(0).to(1)
+ expect { subject.increment! }.to change { project.pushes_since_gc }.by(1)
end
end
-
- it 'goes through all three housekeeping tasks, executing only the highest task when there is overlap' do
- allow(subject).to receive(:try_obtain_lease).and_return(:the_uuid)
- allow(subject).to receive(:lease_key).and_return(:the_lease_key)
-
- # At push 200
- expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :the_lease_key, :the_uuid)
- .exactly(1).times
- # At push 50, 100, 150
- expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :full_repack, :the_lease_key, :the_uuid)
- .exactly(3).times
- # At push 10, 20, ... (except those above)
- expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid)
- .exactly(16).times
-
- 201.times do
- subject.increment!
- subject.execute if subject.needed?
- end
-
- expect(project.pushes_since_gc).to eq(1)
- end
end
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index 30c89ebd821..b3815045792 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -3,9 +3,17 @@ require 'spec_helper'
describe Projects::ImportService do
let!(:project) { create(:project) }
let(:user) { project.creator }
+ let(:import_url) { 'http://www.gitlab.com/demo/repo.git' }
+ let(:oid_download_links) { { 'oid1' => "#{import_url}/gitlab-lfs/objects/oid1", 'oid2' => "#{import_url}/gitlab-lfs/objects/oid2" } }
subject { described_class.new(project, user) }
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
+ allow_any_instance_of(Projects::LfsPointers::LfsDownloadService).to receive(:execute)
+ allow_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(oid_download_links)
+ end
+
describe '#async?' do
it 'returns true for an asynchronous importer' do
importer_class = double(:importer, async?: true)
@@ -63,6 +71,15 @@ describe Projects::ImportService do
expect(result[:status]).to eq :error
expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.full_path} - The repository could not be created."
end
+
+ context 'when repository creation succeeds' do
+ it 'does not download lfs files' do
+ expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute)
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).not_to receive(:execute)
+
+ subject.execute
+ end
+ end
end
context 'with known url' do
@@ -91,6 +108,15 @@ describe Projects::ImportService do
expect(result[:status]).to eq :error
end
+
+ context 'when repository import scheduled' do
+ it 'does not download lfs objects' do
+ expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute)
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).not_to receive(:execute)
+
+ subject.execute
+ end
+ end
end
context 'with a non Github repository' do
@@ -99,9 +125,10 @@ describe Projects::ImportService do
project.import_type = 'bitbucket'
end
- it 'succeeds if repository import is successfully' do
+ it 'succeeds if repository import is successfull' do
expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_return(true)
expect_any_instance_of(Gitlab::BitbucketImport::Importer).to receive(:execute).and_return(true)
+ expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return({})
result = subject.execute
@@ -116,6 +143,29 @@ describe Projects::ImportService do
expect(result[:status]).to eq :error
expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.full_path} - Failed to import the repository"
end
+
+ context 'when repository import scheduled' do
+ before do
+ allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_return(true)
+ allow(subject).to receive(:import_data)
+ end
+
+ it 'downloads lfs objects if lfs_enabled is enabled for project' do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
+ expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(oid_download_links)
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).to receive(:execute).twice
+
+ subject.execute
+ end
+
+ it 'does not download lfs objects if lfs_enabled is not enabled for project' do
+ allow(project).to receive(:lfs_enabled?).and_return(false)
+ expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute)
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).not_to receive(:execute)
+
+ subject.execute
+ end
+ end
end
end
@@ -147,6 +197,26 @@ describe Projects::ImportService do
expect(result[:status]).to eq :error
end
+
+ context 'when importer' do
+ it 'has a custom repository importer it does not download lfs objects' do
+ allow(Gitlab::GithubImport::ParallelImporter).to receive(:imports_repository?).and_return(true)
+
+ expect_any_instance_of(Projects::LfsPointers::LfsImportService).not_to receive(:execute)
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).not_to receive(:execute)
+
+ subject.execute
+ end
+
+ it 'does not have a custom repository importer downloads lfs objects' do
+ allow(Gitlab::GithubImport::ParallelImporter).to receive(:imports_repository?).and_return(false)
+
+ expect_any_instance_of(Projects::LfsPointers::LfsImportService).to receive(:execute).and_return(oid_download_links)
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadService).to receive(:execute)
+
+ subject.execute
+ end
+ end
end
context 'with blocked import_URL' do
diff --git a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
new file mode 100644
index 00000000000..d7a2829d5f8
--- /dev/null
+++ b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+
+describe Projects::LfsPointers::LfsDownloadLinkListService do
+ let(:import_url) { 'http://www.gitlab.com/demo/repo.git' }
+ let(:lfs_endpoint) { "#{import_url}/info/lfs/objects/batch" }
+ let!(:project) { create(:project, import_url: import_url) }
+ let(:new_oids) { { 'oid1' => 123, 'oid2' => 125 } }
+ let(:remote_uri) { URI.parse(lfs_endpoint) }
+
+ let(:objects_response) do
+ body = new_oids.map do |oid, size|
+ {
+ 'oid' => oid,
+ 'size' => size,
+ 'actions' => {
+ 'download' => { 'href' => "#{import_url}/gitlab-lfs/objects/#{oid}" }
+ }
+ }
+ end
+
+ Struct.new(:success?, :objects).new(true, body)
+ end
+
+ let(:invalid_object_response) do
+ [
+ 'oid' => 'whatever',
+ 'size' => 123
+ ]
+ end
+
+ subject { described_class.new(project, remote_uri: remote_uri) }
+
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
+ allow(Gitlab::HTTP).to receive(:post).and_return(objects_response)
+ end
+
+ describe '#execute' do
+ it 'retrieves each download link of every non existent lfs object' do
+ subject.execute(new_oids).each do |oid, link|
+ expect(link).to eq "#{import_url}/gitlab-lfs/objects/#{oid}"
+ end
+ end
+
+ context 'credentials' do
+ context 'when the download link and the lfs_endpoint have the same host' do
+ context 'when lfs_endpoint has credentials' do
+ let(:import_url) { 'http://user:password@www.gitlab.com/demo/repo.git' }
+
+ it 'adds credentials to the download_link' do
+ result = subject.execute(new_oids)
+
+ result.each do |oid, link|
+ expect(link.starts_with?('http://user:password@')).to be_truthy
+ end
+ end
+ end
+
+ context 'when lfs_endpoint does not have any credentials' do
+ it 'does not add any credentials' do
+ result = subject.execute(new_oids)
+
+ result.each do |oid, link|
+ expect(link.starts_with?('http://user:password@')).to be_falsey
+ end
+ end
+ end
+ end
+
+ context 'when the download link and the lfs_endpoint have different hosts' do
+ let(:import_url_with_credentials) { 'http://user:password@www.otherdomain.com/demo/repo.git' }
+ let(:lfs_endpoint) { "#{import_url_with_credentials}/info/lfs/objects/batch" }
+
+ it 'downloads without any credentials' do
+ result = subject.execute(new_oids)
+
+ result.each do |oid, link|
+ expect(link.starts_with?('http://user:password@')).to be_falsey
+ end
+ end
+ end
+ end
+ end
+
+ describe '#get_download_links' do
+ it 'raise errorif request fails' do
+ allow(Gitlab::HTTP).to receive(:post).and_return(Struct.new(:success?, :message).new(false, 'Failed request'))
+
+ expect { subject.send(:get_download_links, new_oids) }.to raise_error(described_class::DownloadLinksError)
+ end
+ end
+
+ describe '#parse_response_links' do
+ it 'does not add oid entry if href not found' do
+ expect(Rails.logger).to receive(:error).with("Link for Lfs Object with oid whatever not found or invalid.")
+
+ result = subject.send(:parse_response_links, invalid_object_response)
+
+ expect(result).to be_empty
+ end
+ end
+end
diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
new file mode 100644
index 00000000000..6af5bfc7689
--- /dev/null
+++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe Projects::LfsPointers::LfsDownloadService do
+ let(:project) { create(:project) }
+ let(:oid) { '9e548e25631dd9ce6b43afd6359ab76da2819d6a5b474e66118c7819e1d8b3e8' }
+ let(:download_link) { "http://gitlab.com/#{oid}" }
+ let(:lfs_content) do
+ <<~HEREDOC
+ whatever
+ HEREDOC
+ end
+
+ subject { described_class.new(project) }
+
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
+ WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
+ end
+
+ describe '#execute' do
+ context 'when file download succeeds' do
+ it 'a new lfs object is created' do
+ expect { subject.execute(oid, download_link) }.to change { LfsObject.count }.from(0).to(1)
+ end
+
+ it 'has the same oid' do
+ subject.execute(oid, download_link)
+
+ expect(LfsObject.first.oid).to eq oid
+ end
+
+ it 'stores the content' do
+ subject.execute(oid, download_link)
+
+ expect(File.read(LfsObject.first.file.file.file)).to eq lfs_content
+ end
+ end
+
+ context 'when file download fails' do
+ it 'no lfs object is created' do
+ expect { subject.execute(oid, download_link) }.to change { LfsObject.count }
+ end
+ end
+
+ context 'when credentials present' do
+ let(:download_link_with_credentials) { "http://user:password@gitlab.com/#{oid}" }
+
+ before do
+ WebMock.stub_request(:get, download_link).with(headers: { 'Authorization' => 'Basic dXNlcjpwYXNzd29yZA==' }).to_return(body: lfs_content)
+ end
+
+ it 'the request adds authorization headers' do
+ subject.execute(oid, download_link_with_credentials)
+ end
+ end
+
+ context 'when an lfs object with the same oid already exists' do
+ before do
+ create(:lfs_object, oid: 'oid')
+ end
+
+ it 'does not download the file' do
+ expect(subject).not_to receive(:download_and_save_file)
+
+ subject.execute('oid', download_link)
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb
new file mode 100644
index 00000000000..5a75fb38dec
--- /dev/null
+++ b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb
@@ -0,0 +1,146 @@
+require 'spec_helper'
+
+describe Projects::LfsPointers::LfsImportService do
+ let(:import_url) { 'http://www.gitlab.com/demo/repo.git' }
+ let(:default_endpoint) { "#{import_url}/info/lfs/objects/batch"}
+ let(:group) { create(:group, lfs_enabled: true)}
+ let!(:project) { create(:project, namespace: group, import_url: import_url, lfs_enabled: true) }
+ let!(:lfs_objects_project) { create_list(:lfs_objects_project, 2, project: project) }
+ let!(:existing_lfs_objects) { LfsObject.pluck(:oid, :size).to_h }
+ let(:oids) { { 'oid1' => 123, 'oid2' => 125 } }
+ let(:oid_download_links) { { 'oid1' => "#{import_url}/gitlab-lfs/objects/oid1", 'oid2' => "#{import_url}/gitlab-lfs/objects/oid2" } }
+ let(:all_oids) { existing_lfs_objects.merge(oids) }
+ let(:remote_uri) { URI.parse(lfs_endpoint) }
+
+ subject { described_class.new(project) }
+
+ before do
+ allow(project.repository).to receive(:lfsconfig_for).and_return(nil)
+ allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+ allow_any_instance_of(Projects::LfsPointers::LfsListService).to receive(:execute).and_return(all_oids)
+ end
+
+ describe '#execute' do
+ context 'when no lfs pointer is linked' do
+ before do
+ allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return([])
+ allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).and_return(oid_download_links)
+ expect(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:new).with(project, remote_uri: URI.parse(default_endpoint)).and_call_original
+ end
+
+ it 'retrieves all lfs pointers in the project repository' do
+ expect_any_instance_of(Projects::LfsPointers::LfsListService).to receive(:execute)
+
+ subject.execute
+ end
+
+ it 'links existent lfs objects to the project' do
+ expect_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute)
+
+ subject.execute
+ end
+
+ it 'retrieves the download links of non existent objects' do
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with(all_oids)
+
+ subject.execute
+ end
+ end
+
+ context 'when some lfs objects are linked' do
+ before do
+ allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return(existing_lfs_objects.keys)
+ allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).and_return(oid_download_links)
+ end
+
+ it 'retrieves the download links of non existent objects' do
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with(oids)
+
+ subject.execute
+ end
+ end
+
+ context 'when all lfs objects are linked' do
+ before do
+ allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return(all_oids.keys)
+ allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute)
+ end
+
+ it 'retrieves no download links' do
+ expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with({}).and_call_original
+
+ expect(subject.execute).to be_empty
+ end
+ end
+
+ context 'when lfsconfig file exists' do
+ before do
+ allow(project.repository).to receive(:lfsconfig_for).and_return("[lfs]\n\turl = #{lfs_endpoint}\n")
+ end
+
+ context 'when url points to the same import url host' do
+ let(:lfs_endpoint) { "#{import_url}/different_endpoint" }
+ let(:service) { double }
+
+ before do
+ allow(service).to receive(:execute)
+ end
+ it 'downloads lfs object using the new endpoint' do
+ expect(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:new).with(project, remote_uri: remote_uri).and_return(service)
+
+ subject.execute
+ end
+
+ context 'when import url has credentials' do
+ let(:import_url) { 'http://user:password@www.gitlab.com/demo/repo.git'}
+
+ it 'adds the credentials to the new endpoint' do
+ expect(Projects::LfsPointers::LfsDownloadLinkListService)
+ .to receive(:new).with(project, remote_uri: URI.parse("http://user:password@www.gitlab.com/demo/repo.git/different_endpoint"))
+ .and_return(service)
+
+ subject.execute
+ end
+
+ context 'when url has its own credentials' do
+ let(:lfs_endpoint) { "http://user1:password1@www.gitlab.com/demo/repo.git/different_endpoint" }
+
+ it 'does not add the import url credentials' do
+ expect(Projects::LfsPointers::LfsDownloadLinkListService)
+ .to receive(:new).with(project, remote_uri: remote_uri)
+ .and_return(service)
+
+ subject.execute
+ end
+ end
+ end
+ end
+
+ context 'when url points to a third party service' do
+ let(:lfs_endpoint) { 'http://third_party_service.com/info/lfs/objects/' }
+
+ it 'disables lfs from the project' do
+ expect(project.lfs_enabled?).to be_truthy
+
+ subject.execute
+
+ expect(project.lfs_enabled?).to be_falsey
+ end
+
+ it 'does not download anything' do
+ expect_any_instance_of(Projects::LfsPointers::LfsListService).not_to receive(:execute)
+
+ subject.execute
+ end
+ end
+ end
+ end
+
+ describe '#default_endpoint_uri' do
+ let(:import_url) { 'http://www.gitlab.com/demo/repo' }
+
+ it 'adds suffix .git if the url does not have it' do
+ expect(subject.send(:default_endpoint_uri).path).to match(/repo.git/)
+ end
+ end
+end
diff --git a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
new file mode 100644
index 00000000000..b7b153655db
--- /dev/null
+++ b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Projects::LfsPointers::LfsLinkService do
+ let!(:project) { create(:project, lfs_enabled: true) }
+ let!(:lfs_objects_project) { create_list(:lfs_objects_project, 2, project: project) }
+ let(:new_oids) { { 'oid1' => 123, 'oid2' => 125 } }
+ let(:all_oids) { LfsObject.pluck(:oid, :size).to_h.merge(new_oids) }
+ let(:new_lfs_object) { create(:lfs_object) }
+ let(:new_oid_list) { all_oids.merge(new_lfs_object.oid => new_lfs_object.size) }
+
+ subject { described_class.new(project) }
+
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
+ end
+
+ describe '#execute' do
+ it 'links existing lfs objects to the project' do
+ expect(project.all_lfs_objects.count).to eq 2
+
+ linked = subject.execute(new_oid_list.keys)
+
+ expect(project.all_lfs_objects.count).to eq 3
+ expect(linked.size).to eq 3
+ end
+
+ it 'returns linked oids' do
+ linked = lfs_objects_project.map(&:lfs_object).map(&:oid) << new_lfs_object.oid
+
+ expect(subject.execute(new_oid_list.keys)).to eq linked
+ end
+ end
+end
diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb
index 0d18ceb8ff8..6040f9100f8 100644
--- a/spec/services/projects/participants_service_spec.rb
+++ b/spec/services/projects/participants_service_spec.rb
@@ -4,7 +4,7 @@ describe Projects::ParticipantsService do
describe '#groups' do
describe 'avatar_url' do
let(:project) { create(:project, :public) }
- let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png')) }
+ let(:group) { create(:group, avatar: fixture_file_upload('spec/fixtures/dk.png')) }
let(:user) { create(:user) }
let!(:group_member) { create(:group_member, group: group, user: user) }
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 3e6483d7e28..5100987c2fe 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -64,7 +64,7 @@ describe Projects::TransferService do
it 'updates project full path in .git/config' do
transfer_project(project, user, group)
- expect(project.repository.rugged.config['gitlab.fullpath']).to eq "#{group.full_path}/#{project.path}"
+ expect(rugged_config['gitlab.fullpath']).to eq "#{group.full_path}/#{project.path}"
end
end
@@ -84,7 +84,9 @@ describe Projects::TransferService do
end
def project_path(project)
- project.repository.path_to_repo
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.path_to_repo
+ end
end
def current_path
@@ -101,7 +103,7 @@ describe Projects::TransferService do
it 'rolls back project full path in .git/config' do
attempt_project_transfer
- expect(project.repository.rugged.config['gitlab.fullpath']).to eq project.full_path
+ expect(rugged_config['gitlab.fullpath']).to eq project.full_path
end
it "doesn't send move notifications" do
@@ -264,4 +266,10 @@ describe Projects::TransferService do
transfer_project(project, owner, group)
end
end
+
+ def rugged_config
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.rugged.config
+ end
+ end
end
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index 347ac13828c..1bffeee6790 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -4,13 +4,13 @@ describe Projects::UpdatePagesService do
set(:project) { create(:project, :repository) }
set(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) }
set(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD') }
- let(:invalid_file) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png') }
+ let(:invalid_file) { fixture_file_upload('spec/fixtures/dk.png') }
let(:extension) { 'zip' }
- let(:file) { fixture_file_upload(Rails.root + "spec/fixtures/pages.#{extension}") }
- let(:empty_file) { fixture_file_upload(Rails.root + "spec/fixtures/pages_empty.#{extension}") }
+ let(:file) { fixture_file_upload("spec/fixtures/pages.#{extension}") }
+ let(:empty_file) { fixture_file_upload("spec/fixtures/pages_empty.#{extension}") }
let(:metadata) do
- filename = Rails.root + "spec/fixtures/pages.#{extension}.meta"
+ filename = "spec/fixtures/pages.#{extension}.meta"
fixture_file_upload(filename) if File.exist?(filename)
end
@@ -196,8 +196,8 @@ describe Projects::UpdatePagesService do
let(:metadata) { spy('metadata') }
before do
- file = fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip')
- metafile = fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip.meta')
+ file = fixture_file_upload('spec/fixtures/pages.zip')
+ metafile = fixture_file_upload('spec/fixtures/pages.zip.meta')
build.update_attributes(legacy_artifacts_file: file)
build.update_attributes(legacy_artifacts_metadata: metafile)
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 3e6073b9861..ecf1ba05618 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -125,7 +125,7 @@ describe Projects::UpdateService do
context 'when we update project but not enabling a wiki' do
it 'does not try to create an empty wiki' do
- FileUtils.rm_rf(project.wiki.repository.path)
+ Gitlab::Shell.new.rm_directory(project.repository_storage, project.wiki.path)
result = update_project(project, user, { name: 'test1' })
@@ -146,7 +146,7 @@ describe Projects::UpdateService do
context 'when enabling a wiki' do
it 'creates a wiki' do
project.project_feature.update(wiki_access_level: ProjectFeature::DISABLED)
- FileUtils.rm_rf(project.wiki.repository.path)
+ Gitlab::Shell.new.rm_directory(project.repository_storage, project.wiki.path)
result = update_project(project, user, project_feature_attributes: { wiki_access_level: ProjectFeature::ENABLED })
@@ -275,6 +275,10 @@ describe Projects::UpdateService do
it { is_expected.to eq(false) }
end
+ context 'when auto devops is nil' do
+ it { is_expected.to eq(false) }
+ end
+
context 'when auto devops is explicitly enabled' do
before do
project.create_auto_devops!(enabled: true)
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index bd835a1fca6..743e281183e 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -323,6 +323,14 @@ describe QuickActions::InterpretService do
end
end
+ shared_examples 'confidential command' do
+ it 'marks issue as confidential if content contains /confidential' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(confidential: true)
+ end
+ end
+
shared_examples 'shrug command' do
it 'appends ¯\_(ツ)_/¯ to the comment' do
new_content, _ = service.execute(content, issuable)
@@ -774,6 +782,11 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
+ it_behaves_like 'confidential command' do
+ let(:content) { '/confidential' }
+ let(:issuable) { issue }
+ end
+
context '/copy_metadata command' do
let(:todo_label) { create(:label, project: project, title: 'To Do') }
let(:inreview_label) { create(:label, project: project, title: 'In Review') }
@@ -919,6 +932,11 @@ describe QuickActions::InterpretService do
end
it_behaves_like 'empty command' do
+ let(:content) { '/confidential' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
let(:content) { '/duplicate #{issue.to_reference}' }
let(:issuable) { issue }
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index e28b0ea5cf2..57d081cffb3 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe SystemNoteService do
include Gitlab::Routing
include RepoHelpers
+ include AssetsHelpers
set(:group) { create(:group) }
set(:project) { create(:project, :repository, group: group) }
@@ -769,6 +770,8 @@ describe SystemNoteService do
end
describe "new reference" do
+ let(:favicon_path) { "http://localhost/assets/#{find_asset('favicon.png').digest_path}" }
+
before do
allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
end
@@ -789,7 +792,7 @@ describe SystemNoteService do
object: {
url: project_commit_url(project, commit),
title: "GitLab: Mentioned on commit - #{commit.title}",
- icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" },
+ icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: false }
}
)
@@ -815,7 +818,7 @@ describe SystemNoteService do
object: {
url: project_issue_url(project, issue),
title: "GitLab: Mentioned on issue - #{issue.title}",
- icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" },
+ icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: false }
}
)
@@ -841,7 +844,7 @@ describe SystemNoteService do
object: {
url: project_snippet_url(project, snippet),
title: "GitLab: Mentioned on snippet - #{snippet.title}",
- icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" },
+ icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: false }
}
)
diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb
index e7e9080b6b0..0cbe57352be 100644
--- a/spec/services/tags/create_service_spec.rb
+++ b/spec/services/tags/create_service_spec.rb
@@ -41,7 +41,7 @@ describe Tags::CreateService do
it 'returns an error' do
expect(repository).to receive(:add_tag)
.with(user, 'v1.1.0', 'master', 'Foo')
- .and_raise(Gitlab::Git::HooksService::PreReceiveError, 'something went wrong')
+ .and_raise(Gitlab::Git::PreReceiveError, 'something went wrong')
response = service.execute('v1.1.0', 'master', 'Foo')
diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb
index 962b9f40c4f..19e1c5ff3b2 100644
--- a/spec/services/test_hooks/project_service_spec.rb
+++ b/spec/services/test_hooks/project_service_spec.rb
@@ -6,13 +6,19 @@ describe TestHooks::ProjectService do
describe '#execute' do
let(:project) { create(:project, :repository) }
let(:hook) { create(:project_hook, project: project) }
+ let(:trigger) { 'not_implemented_events' }
let(:service) { described_class.new(hook, current_user, trigger) }
let(:sample_data) { { data: 'sample' } }
let(:success_result) { { status: :success, http_status: 200, message: 'ok' } }
- context 'hook with not implemented test' do
- let(:trigger) { 'not_implemented_events' }
+ it 'allows to set a custom project' do
+ project = double
+ service.project = project
+
+ expect(service.project).to eq(project)
+ end
+ context 'hook with not implemented test' do
it 'returns error message' do
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Testing not available for this hook' })
diff --git a/spec/services/upload_service_spec.rb b/spec/services/upload_service_spec.rb
index 24f3a5c5ff0..9b232a52efa 100644
--- a/spec/services/upload_service_spec.rb
+++ b/spec/services/upload_service_spec.rb
@@ -9,7 +9,7 @@ describe UploadService do
context 'for valid gif file' do
before do
- gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif')
+ gif = fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif')
@link_to_file = upload_file(@project, gif)
end
@@ -21,7 +21,7 @@ describe UploadService do
context 'for valid png file' do
before do
- png = fixture_file_upload(Rails.root + 'spec/fixtures/dk.png',
+ png = fixture_file_upload('spec/fixtures/dk.png',
'image/png')
@link_to_file = upload_file(@project, png)
end
@@ -34,7 +34,7 @@ describe UploadService do
context 'for valid jpg file' do
before do
- jpg = fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg')
+ jpg = fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg')
@link_to_file = upload_file(@project, jpg)
end
@@ -46,7 +46,7 @@ describe UploadService do
context 'for txt file' do
before do
- txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain')
+ txt = fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain')
@link_to_file = upload_file(@project, txt)
end
@@ -58,7 +58,7 @@ describe UploadService do
context 'for too large a file' do
before do
- txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain')
+ txt = fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain')
allow(txt).to receive(:size) { 1000.megabytes.to_i }
@link_to_file = upload_file(@project, txt)
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index d3de2331244..dac609e2545 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -87,6 +87,7 @@ RSpec.configure do |config|
config.include LiveDebugger, :js
config.include MigrationsHelpers, :migration
config.include RedisHelpers
+ config.include Rails.application.routes.url_helpers, type: :routing
if ENV['CI']
# This includes the first try, i.e. tests will be run 4 times before failing.
@@ -107,19 +108,6 @@ RSpec.configure do |config|
end
config.before(:example) do
- # Skip pre-receive hook check so we can use the web editor and merge.
- allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
-
- allow_any_instance_of(Gitlab::Git::GitlabProjects).to receive(:fork_repository).and_wrap_original do |m, *args|
- m.call(*args)
-
- shard_name, repository_relative_path = args
- # We can't leave the hooks in place after a fork, as those would fail in tests
- # The "internal" API is not available
- Gitlab::Shell.new.rm_directory(shard_name,
- File.join(repository_relative_path, 'hooks'))
- end
-
# Enable all features by default for testing
allow(Feature).to receive(:enabled?) { true }
end
@@ -133,6 +121,10 @@ RSpec.configure do |config|
RequestStore.clear!
end
+ config.after(:example) do
+ Fog.unmock! if Fog.mock?
+ end
+
config.before(:example, :mailer) do
reset_delivered_emails!
end
diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb
index 06ae8792c61..d7cef137989 100644
--- a/spec/support/api/scopes/read_user_shared_examples.rb
+++ b/spec/support/api/scopes/read_user_shared_examples.rb
@@ -1,10 +1,12 @@
-shared_examples_for 'allows the "read_user" scope' do
+shared_examples_for 'allows the "read_user" scope' do |api_version|
+ let(:version) { api_version || 'v4' }
+
context 'for personal access tokens' do
context 'when the requesting token has the "api" scope' do
let(:token) { create(:personal_access_token, scopes: ['api'], user: user) }
it 'returns a "200" response' do
- get api_call.call(path, user, personal_access_token: token)
+ get api_call.call(path, user, personal_access_token: token, version: version)
expect(response).to have_gitlab_http_status(200)
end
@@ -14,7 +16,7 @@ shared_examples_for 'allows the "read_user" scope' do
let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) }
it 'returns a "200" response' do
- get api_call.call(path, user, personal_access_token: token)
+ get api_call.call(path, user, personal_access_token: token, version: version)
expect(response).to have_gitlab_http_status(200)
end
@@ -28,7 +30,7 @@ shared_examples_for 'allows the "read_user" scope' do
end
it 'returns a "403" response' do
- get api_call.call(path, user, personal_access_token: token)
+ get api_call.call(path, user, personal_access_token: token, version: version)
expect(response).to have_gitlab_http_status(403)
end
diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb
index 368439aa5b0..1c1b68c12a2 100644
--- a/spec/support/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb
@@ -118,14 +118,19 @@ shared_examples 'a GitHub-ish import controller: POST create' do
expect(response).to have_gitlab_http_status(200)
end
- it 'returns 422 response when the project could not be imported' do
+ it 'returns 422 response with the base error when the project could not be imported' do
+ project = build(:project)
+ project.errors.add(:name, 'is invalid')
+ project.errors.add(:path, 'is old')
+
allow(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
- .and_return(double(execute: build(:project)))
+ .and_return(double(execute: project))
post :create, format: :json
expect(response).to have_gitlab_http_status(422)
+ expect(json_response['errors']).to eq('Name is invalid, Path is old')
end
context "when the repository owner is the provider user" do
diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb
index 836e5e7be23..b4c71d69119 100644
--- a/spec/support/features/reportable_note_shared_examples.rb
+++ b/spec/support/features/reportable_note_shared_examples.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
shared_examples 'reportable note' do |type|
+ include MobileHelpers
include NotesHelper
let(:comment) { find("##{ActionView::RecordIdentifier.dom_id(note)}") }
@@ -39,6 +40,9 @@ shared_examples 'reportable note' do |type|
end
def open_dropdown(dropdown)
+ # make window wide enough that tooltip doesn't trigger horizontal scrollbar
+ resize_window(1200, 800)
+
dropdown.find('.more-actions-toggle').click
dropdown.find('.dropdown-menu li', match: :first)
end
diff --git a/spec/support/gitaly.rb b/spec/support/gitaly.rb
index 5a1dd44bc9d..614aaa73693 100644
--- a/spec/support/gitaly.rb
+++ b/spec/support/gitaly.rb
@@ -9,7 +9,7 @@ RSpec.configure do |config|
# Use 'and_wrap_original' to make sure the arguments are valid
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_wrap_original do |m, *args|
m.call(*args)
- !Gitlab::GitalyClient::EXPLICIT_OPT_IN_REQUIRED.include?(args.first)
+ !Gitlab::GitalyClient.explicit_opt_in_required.include?(args.first)
end
end
end
diff --git a/spec/support/helpers/assets_helpers.rb b/spec/support/helpers/assets_helpers.rb
new file mode 100644
index 00000000000..09bbf451671
--- /dev/null
+++ b/spec/support/helpers/assets_helpers.rb
@@ -0,0 +1,15 @@
+module AssetsHelpers
+ # In a CI environment the assets are not compiled, as there is a CI job
+ # `compile-assets` that compiles them in the prepare stage for all following
+ # specs.
+ # Locally the assets are precompiled dynamically.
+ #
+ # Sprockets doesn't provide one method to access an asset for both cases.
+ def find_asset(asset_name)
+ if ENV['CI']
+ Sprockets::Railtie.build_environment(Rails.application, true)[asset_name]
+ else
+ Rails.application.assets.find_asset(asset_name)
+ end
+ end
+end
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index 06a76d53354..32d9807f06a 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -123,7 +123,11 @@ module CycleAnalyticsHelpers
if branch_update.newrev
_, opts = args
- commit = raw_repository.commit(branch_update.newrev).rugged_commit
+
+ commit = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ raw_repository.commit(branch_update.newrev).rugged_commit
+ end
+
branch_update.newrev = commit.amend(
update_ref: "#{Gitlab::Git::BRANCH_REF_PREFIX}#{opts[:branch_name]}",
author: commit.author.merge(time: new_date),
diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb
index 1a1d5853a7a..2b9f8b30c60 100644
--- a/spec/support/helpers/features/notes_helpers.rb
+++ b/spec/support/helpers/features/notes_helpers.rb
@@ -13,7 +13,7 @@ module Spec
module Features
module NotesHelpers
def add_note(text)
- Sidekiq::Testing.fake! do
+ perform_enqueued_jobs do
page.within(".js-main-target-form") do
fill_in("note[note]", with: text)
find(".js-comment-submit-button").click
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
new file mode 100644
index 00000000000..0930b9da368
--- /dev/null
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -0,0 +1,99 @@
+module GraphqlHelpers
+ # makes an underscored string look like a fieldname
+ # "merge_request" => "mergeRequest"
+ def self.fieldnamerize(underscored_field_name)
+ graphql_field_name = underscored_field_name.to_s.camelize
+ graphql_field_name[0] = graphql_field_name[0].downcase
+
+ graphql_field_name
+ end
+
+ # Run a loader's named resolver
+ def resolve(resolver_class, obj: nil, args: {}, ctx: {})
+ resolver_class.new(object: obj, context: ctx).resolve(args)
+ end
+
+ # Runs a block inside a BatchLoader::Executor wrapper
+ def batch(max_queries: nil, &blk)
+ wrapper = proc do
+ begin
+ BatchLoader::Executor.ensure_current
+ yield
+ ensure
+ BatchLoader::Executor.clear_current
+ end
+ end
+
+ if max_queries
+ result = nil
+ expect { result = wrapper.call }.not_to exceed_query_limit(max_queries)
+ result
+ else
+ wrapper.call
+ end
+ end
+
+ def graphql_query_for(name, attributes = {}, fields = nil)
+ <<~QUERY
+ {
+ #{query_graphql_field(name, attributes, fields)}
+ }
+ QUERY
+ end
+
+ def query_graphql_field(name, attributes = {}, fields = nil)
+ fields ||= all_graphql_fields_for(name.classify)
+ attributes = attributes_to_graphql(attributes)
+ <<~QUERY
+ #{name}(#{attributes}) {
+ #{fields}
+ }
+ QUERY
+ end
+
+ def all_graphql_fields_for(class_name)
+ type = GitlabSchema.types[class_name.to_s]
+ return "" unless type
+
+ type.fields.map do |name, field|
+ # We can't guess arguments, so skip fields that require them
+ next if field.arguments.any?
+
+ if scalar?(field)
+ name
+ else
+ "#{name} { #{all_graphql_fields_for(field_type(field))} }"
+ end
+ end.compact.join("\n")
+ end
+
+ def attributes_to_graphql(attributes)
+ attributes.map do |name, value|
+ "#{GraphqlHelpers.fieldnamerize(name.to_s)}: \"#{value}\""
+ end.join(", ")
+ end
+
+ def post_graphql(query, current_user: nil)
+ post api('/', current_user, version: 'graphql'), query: query
+ end
+
+ def graphql_data
+ json_response['data']
+ end
+
+ def graphql_errors
+ json_response['data']
+ end
+
+ def scalar?(field)
+ field_type(field).kind.scalar?
+ end
+
+ def field_type(field)
+ if field.type.respond_to?(:of_type)
+ field.type.of_type
+ else
+ field.type
+ end
+ end
+end
diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb
index 84abec75c26..0bc235701eb 100644
--- a/spec/support/helpers/migrations_helpers.rb
+++ b/spec/support/helpers/migrations_helpers.rb
@@ -10,10 +10,6 @@ module MigrationsHelpers
ActiveRecord::Migrator.migrations_paths
end
- def table_exists?(name)
- ActiveRecord::Base.connection.table_exists?(name)
- end
-
def migrations
ActiveRecord::Migrator.migrations(migrations_paths)
end
diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb
index 28536bbef5e..7ce63375d34 100644
--- a/spec/support/helpers/query_recorder.rb
+++ b/spec/support/helpers/query_recorder.rb
@@ -1,10 +1,11 @@
module ActiveRecord
class QueryRecorder
- attr_reader :log, :cached
+ attr_reader :log, :skip_cached, :cached
- def initialize(&block)
+ def initialize(skip_cached: true, &block)
@log = []
@cached = []
+ @skip_cached = skip_cached
ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block)
end
@@ -16,7 +17,7 @@ module ActiveRecord
def callback(name, start, finish, message_id, values)
show_backtrace(values) if ENV['QUERY_RECORDER_DEBUG']
- if values[:name]&.include?("CACHE")
+ if values[:name]&.include?("CACHE") && skip_cached
@cached << values[:sql]
elsif !values[:name]&.include?("SCHEMA")
@log << values[:sql]
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index 19d744b959a..bceaf8277ee 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -45,4 +45,16 @@ module StubObjectStorage
remote_directory: 'uploads',
**params)
end
+
+ def stub_object_storage_multipart_init(endpoint, upload_id = "upload_id")
+ stub_request(:post, %r{\A#{endpoint}tmp/uploads/[a-z0-9-]*\?uploads\z})
+ .to_return status: 200, body: <<-EOS.strip_heredoc
+ <?xml version="1.0" encoding="UTF-8"?>
+ <InitiateMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
+ <Bucket>example-bucket</Bucket>
+ <Key>example-object</Key>
+ <UploadId>#{upload_id}</UploadId>
+ </InitiateMultipartUploadResult>
+ EOS
+ end
end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 1fef50a52ec..05a8e6206ae 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -135,6 +135,16 @@ module TestEnv
install_dir: Gitlab.config.gitlab_shell.path,
version: Gitlab::Shell.version_required,
task: 'gitlab:shell:install')
+
+ create_fake_git_hooks
+ end
+
+ def create_fake_git_hooks
+ # gitlab-shell hooks don't work in our test environment because they try to make internal API calls
+ hooks_dir = File.join(Gitlab.config.gitlab_shell.path, 'hooks')
+ %w[pre-receive post-receive update].each do |hook|
+ File.open(File.join(hooks_dir, hook), 'w', 0755) { |f| f.puts '#!/bin/sh' }
+ end
end
def setup_gitaly
diff --git a/spec/support/matchers/email_matchers.rb b/spec/support/matchers/email_matchers.rb
deleted file mode 100644
index d9d59ec12ec..00000000000
--- a/spec/support/matchers/email_matchers.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-RSpec::Matchers.define :have_html_escaped_body_text do |expected|
- match do |actual|
- expect(actual).to have_body_text(ERB::Util.html_escape(expected))
- end
-end
diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb
index 88d22a3ddd9..cd042401f3a 100644
--- a/spec/support/matchers/exceed_query_limit.rb
+++ b/spec/support/matchers/exceed_query_limit.rb
@@ -1,17 +1,4 @@
-RSpec::Matchers.define :exceed_query_limit do |expected|
- supports_block_expectations
-
- match do |block|
- @subject_block = block
- actual_count > expected_count + threshold
- end
-
- failure_message_when_negated do |actual|
- threshold_message = threshold > 0 ? " (+#{@threshold})" : ''
- counts = "#{expected_count}#{threshold_message}"
- "Expected a maximum of #{counts} queries, got #{actual_count}:\n\n#{log_message}"
- end
-
+module ExceedQueryLimitHelpers
def with_threshold(threshold)
@threshold = threshold
self
@@ -43,7 +30,7 @@ RSpec::Matchers.define :exceed_query_limit do |expected|
end
def recorder
- @recorder ||= ActiveRecord::QueryRecorder.new(&@subject_block)
+ @recorder ||= ActiveRecord::QueryRecorder.new(skip_cached: skip_cached, &@subject_block)
end
def count_queries(queries)
@@ -61,4 +48,52 @@ RSpec::Matchers.define :exceed_query_limit do |expected|
@recorder.log_message
end
end
+
+ def skip_cached
+ true
+ end
+
+ def verify_count(&block)
+ @subject_block = block
+ actual_count > expected_count + threshold
+ end
+
+ def failure_message
+ threshold_message = threshold > 0 ? " (+#{@threshold})" : ''
+ counts = "#{expected_count}#{threshold_message}"
+ "Expected a maximum of #{counts} queries, got #{actual_count}:\n\n#{log_message}"
+ end
+end
+
+RSpec::Matchers.define :exceed_all_query_limit do |expected|
+ supports_block_expectations
+
+ include ExceedQueryLimitHelpers
+
+ match do |block|
+ verify_count(&block)
+ end
+
+ failure_message_when_negated do |actual|
+ failure_message
+ end
+
+ def skip_cached
+ false
+ end
+end
+
+# Excludes cached methods from the query count
+RSpec::Matchers.define :exceed_query_limit do |expected|
+ supports_block_expectations
+
+ include ExceedQueryLimitHelpers
+
+ match do |block|
+ verify_count(&block)
+ end
+
+ failure_message_when_negated do |actual|
+ failure_message
+ end
end
diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb
new file mode 100644
index 00000000000..d23cbaf4beb
--- /dev/null
+++ b/spec/support/matchers/graphql_matchers.rb
@@ -0,0 +1,46 @@
+RSpec::Matchers.define :require_graphql_authorizations do |*expected|
+ match do |field|
+ field_definition = field.metadata[:type_class]
+ expect(field_definition).to respond_to(:required_permissions)
+ expect(field_definition.required_permissions).to contain_exactly(*expected)
+ end
+end
+
+RSpec::Matchers.define :have_graphql_fields do |*expected|
+ match do |kls|
+ field_names = expected.map { |name| GraphqlHelpers.fieldnamerize(name) }
+ expect(kls.fields.keys).to contain_exactly(*field_names)
+ end
+end
+
+RSpec::Matchers.define :have_graphql_field do |field_name|
+ match do |kls|
+ expect(kls.fields.keys).to include(GraphqlHelpers.fieldnamerize(field_name))
+ end
+end
+
+RSpec::Matchers.define :have_graphql_arguments do |*expected|
+ include GraphqlHelpers
+
+ match do |field|
+ argument_names = expected.map { |name| GraphqlHelpers.fieldnamerize(name) }
+ expect(field.arguments.keys).to contain_exactly(*argument_names)
+ end
+end
+
+RSpec::Matchers.define :have_graphql_type do |expected|
+ match do |field|
+ expect(field.type).to eq(expected.to_graphql)
+ end
+end
+
+RSpec::Matchers.define :have_graphql_resolver do |expected|
+ match do |field|
+ case expected
+ when Method
+ expect(field.metadata[:type_class].resolve_proc).to eq(expected)
+ else
+ expect(field.metadata[:type_class].resolver).to eq(expected)
+ end
+ end
+end
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index dffab22d8b5..54b8df7aa19 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -9,4 +9,6 @@ RSpec.configure do |config|
config.include StubConfiguration
config.include StubObjectStorage
config.include StubENV
+
+ config.fixture_path = Rails.root if defined?(Rails)
end
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index 21c6f3c829f..6dbe0f6f980 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -227,6 +227,42 @@ shared_examples_for 'common trace features' do
end
end
end
+
+ describe '#archive!' do
+ subject { trace.archive! }
+
+ context 'when build status is success' do
+ let!(:build) { create(:ci_build, :success, :trace_live) }
+
+ it 'does not have an archived trace yet' do
+ expect(build.job_artifacts_trace).to be_nil
+ end
+
+ context 'when archives' do
+ it 'has an archived trace' do
+ subject
+
+ build.reload
+ expect(build.job_artifacts_trace).to be_exist
+ end
+
+ context 'when another process has already been archiving', :clean_gitlab_redis_shared_state do
+ before do
+ Gitlab::ExclusiveLease.new("trace:archive:#{trace.job.id}", timeout: 1.hour).try_obtain
+ end
+
+ it 'blocks concurrent archiving' do
+ expect(Rails.logger).to receive(:error).with('Cannot obtain an exclusive lease. There must be another instance already in execution.')
+
+ subject
+
+ build.reload
+ expect(build.job_artifacts_trace).to be_nil
+ end
+ end
+ end
+ end
+ end
end
shared_examples_for 'trace with disabled live trace feature' do
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index ea7dbade171..bbbad86dcd5 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -1,7 +1,7 @@
shared_examples 'handle uploads' do
let(:user) { create(:user) }
- let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
- let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+ let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
+ let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
let(:secret) { FileUploader.generate_secret }
let(:uploader_class) { FileUploader }
diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb
index 43fdaddf545..d176d3fa425 100644
--- a/spec/support/shared_examples/notify_shared_examples.rb
+++ b/spec/support/shared_examples/notify_shared_examples.rb
@@ -212,7 +212,7 @@ shared_examples 'a note email' do
end
it 'contains the message from the note' do
- is_expected.to have_html_escaped_body_text note.note
+ is_expected.to have_body_text note.note
end
it 'does not contain note author' do
@@ -225,7 +225,7 @@ shared_examples 'a note email' do
end
it 'contains a link to note author' do
- is_expected.to have_html_escaped_body_text note.author_name
+ is_expected.to have_body_text note.author_name
end
end
end
diff --git a/spec/support/shared_examples/requests/graphql_shared_examples.rb b/spec/support/shared_examples/requests/graphql_shared_examples.rb
new file mode 100644
index 00000000000..9b2b74593a5
--- /dev/null
+++ b/spec/support/shared_examples/requests/graphql_shared_examples.rb
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+shared_examples 'a working graphql query' do
+ include GraphqlHelpers
+
+ it 'is returns a successfull response', :aggregate_failures do
+ expect(response).to be_success
+ expect(graphql_errors['errors']).to be_nil
+ expect(json_response.keys).to include('data')
+ end
+end
diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
index 2228e872926..7c34c7b4977 100644
--- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
@@ -245,6 +245,70 @@ RSpec.shared_examples 'slack or mattermost notifications' do
end
end
+ describe 'Push events' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, creator: user) }
+
+ before do
+ allow(chat_service).to receive_messages(
+ project: project,
+ service_hook: true,
+ webhook: webhook_url
+ )
+
+ WebMock.stub_request(:post, webhook_url)
+ end
+
+ context 'only notify for the default branch' do
+ context 'when enabled' do
+ before do
+ chat_service.notify_only_default_branch = true
+ end
+
+ it 'does not notify push events if they are not for the default branch' do
+ ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test"
+ push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, [])
+
+ chat_service.execute(push_sample_data)
+
+ expect(WebMock).not_to have_requested(:post, webhook_url)
+ end
+
+ it 'notifies about push events for the default branch' do
+ push_sample_data = Gitlab::DataBuilder::Push.build_sample(project, user)
+
+ chat_service.execute(push_sample_data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
+
+ it 'still notifies about pushed tags' do
+ ref = "#{Gitlab::Git::TAG_REF_PREFIX}test"
+ push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, [])
+
+ chat_service.execute(push_sample_data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
+ end
+
+ context 'when disabled' do
+ before do
+ chat_service.notify_only_default_branch = false
+ end
+
+ it 'notifies about all push events' do
+ ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test"
+ push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, [])
+
+ chat_service.execute(push_sample_data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
+ end
+ end
+ end
+
describe "Note events" do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, creator: user) }
@@ -394,23 +458,6 @@ RSpec.shared_examples 'slack or mattermost notifications' do
expect(result).to be_falsy
end
-
- it 'does not notify push events if they are not for the default branch' do
- ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test"
- push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, [])
-
- chat_service.execute(push_sample_data)
-
- expect(WebMock).not_to have_requested(:post, webhook_url)
- end
-
- it 'notifies about push events for the default branch' do
- push_sample_data = Gitlab::DataBuilder::Push.build_sample(project, user)
-
- chat_service.execute(push_sample_data)
-
- expect(WebMock).to have_requested(:post, webhook_url).once
- end
end
context 'when disabled' do
diff --git a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
index 6352f1527cd..19800c6638f 100644
--- a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
@@ -76,26 +76,24 @@ shared_examples "migrates" do |to_store:, from_store: nil|
end
context 'when migrate! is occupied by another process' do
- let(:exclusive_lease_key) { "object_storage_migrate:#{subject.model.class}:#{subject.model.id}" }
-
before do
- @uuid = Gitlab::ExclusiveLease.new(exclusive_lease_key, timeout: 1.hour.to_i).try_obtain
+ @uuid = Gitlab::ExclusiveLease.new(subject.exclusive_lease_key, timeout: 1.hour.to_i).try_obtain
end
it 'does not execute migrate!' do
expect(subject).not_to receive(:unsafe_migrate!)
- expect { migrate(to) }.to raise_error('exclusive lease already taken')
+ expect { migrate(to) }.to raise_error(ObjectStorage::ExclusiveLeaseTaken)
end
it 'does not execute use_file' do
expect(subject).not_to receive(:unsafe_use_file)
- expect { subject.use_file }.to raise_error('exclusive lease already taken')
+ expect { subject.use_file }.to raise_error(ObjectStorage::ExclusiveLeaseTaken)
end
after do
- Gitlab::ExclusiveLease.cancel(exclusive_lease_key, @uuid)
+ Gitlab::ExclusiveLease.cancel(subject.exclusive_lease_key, @uuid)
end
end
diff --git a/spec/support/shoulda/matchers/rails_shim.rb b/spec/support/shoulda/matchers/rails_shim.rb
new file mode 100644
index 00000000000..8d70598beb5
--- /dev/null
+++ b/spec/support/shoulda/matchers/rails_shim.rb
@@ -0,0 +1,27 @@
+# monkey patch which fixes serialization matcher in Rails 5
+# https://github.com/thoughtbot/shoulda-matchers/issues/913
+# This can be removed when a new version of shoulda-matchers
+# is released
+module Shoulda
+ module Matchers
+ class RailsShim
+ def self.serialized_attributes_for(model)
+ if defined?(::ActiveRecord::Type::Serialized)
+ # Rails 5+
+ serialized_columns = model.columns.select do |column|
+ model.type_for_attribute(column.name).is_a?(
+ ::ActiveRecord::Type::Serialized
+ )
+ end
+
+ serialized_columns.inject({}) do |hash, column| # rubocop:disable Style/EachWithObject
+ hash[column.name.to_s] = model.type_for_attribute(column.name).coder
+ hash
+ end
+ else
+ model.serialized_attributes
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/trace/trace_helpers.rb b/spec/support/trace/trace_helpers.rb
new file mode 100644
index 00000000000..c7802bbcb94
--- /dev/null
+++ b/spec/support/trace/trace_helpers.rb
@@ -0,0 +1,27 @@
+module TraceHelpers
+ def create_legacy_trace(build, content)
+ File.open(legacy_trace_path(build), 'wb') { |stream| stream.write(content) }
+ end
+
+ def create_legacy_trace_in_db(build, content)
+ build.update_column(:trace, content)
+ end
+
+ def legacy_trace_path(build)
+ legacy_trace_dir = File.join(Settings.gitlab_ci.builds_path,
+ build.created_at.utc.strftime("%Y_%m"),
+ build.project_id.to_s)
+
+ FileUtils.mkdir_p(legacy_trace_dir)
+
+ File.join(legacy_trace_dir, "#{build.id}.log")
+ end
+
+ def archived_trace_path(job_artifact)
+ disk_hash = Digest::SHA2.hexdigest(job_artifact.project_id.to_s)
+ creation_date = job_artifact.created_at.utc.strftime('%Y_%m_%d')
+
+ File.join(Gitlab.config.artifacts.path, disk_hash[0..1], disk_hash[2..3], disk_hash,
+ creation_date, job_artifact.job_id.to_s, job_artifact.id.to_s, 'job.log')
+ end
+end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index c9252bebb2e..93a436cb2b5 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -101,7 +101,9 @@ describe 'gitlab:app namespace rake task' do
before do
stub_env('SKIP', 'db')
- path = File.join(project.repository.path_to_repo, 'custom_hooks')
+ path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ File.join(project.repository.path_to_repo, 'custom_hooks')
+ end
FileUtils.mkdir_p(path)
FileUtils.touch(File.join(path, "dummy.txt"))
end
@@ -122,7 +124,10 @@ describe 'gitlab:app namespace rake task' do
expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout
expect { run_rake_task('gitlab:backup:restore') }.to output.to_stdout
- expect(Dir.entries(File.join(project.repository.path, 'custom_hooks'))).to include("dummy.txt")
+ repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.path
+ end
+ expect(Dir.entries(File.join(repo_path, 'custom_hooks'))).to include("dummy.txt")
end
end
@@ -243,10 +248,12 @@ describe 'gitlab:app namespace rake task' do
FileUtils.mkdir_p(b_storage_dir)
# Even when overriding the storage, we have to move it there, so it exists
- FileUtils.mv(
- File.join(Settings.absolute(storages['default'].legacy_disk_path), project_b.repository.disk_path + '.git'),
- Rails.root.join(storages['test_second_storage'].legacy_disk_path, project_b.repository.disk_path + '.git')
- )
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ FileUtils.mv(
+ File.join(Settings.absolute(storages['default'].legacy_disk_path), project_b.repository.disk_path + '.git'),
+ Rails.root.join(storages['test_second_storage'].legacy_disk_path, project_b.repository.disk_path + '.git')
+ )
+ end
expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index 1e507c0236e..4545226d78c 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -134,7 +134,9 @@ describe 'gitlab:gitaly namespace rake task' do
parsed_output = TomlRB.parse(expected_output)
config.each do |name, params|
- expect(parsed_output['storage']).to include({ 'name' => name, 'path' => params.legacy_disk_path })
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ expect(parsed_output['storage']).to include({ 'name' => name, 'path' => params.legacy_disk_path })
+ end
end
end
end
diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb
index 4a756c5742d..0ed5d3e27b9 100644
--- a/spec/tasks/gitlab/shell_rake_spec.rb
+++ b/spec/tasks/gitlab/shell_rake_spec.rb
@@ -7,11 +7,17 @@ describe 'gitlab:shell rake tasks' do
stub_warn_user_is_not_gitlab
end
+ after do
+ TestEnv.create_fake_git_hooks
+ end
+
describe 'install task' do
it 'invokes create_hooks task' do
expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke)
- storages = Gitlab.config.repositories.storages.values.map(&:legacy_disk_path)
+ storages = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ Gitlab.config.repositories.storages.values.map(&:legacy_disk_path)
+ end
expect(Kernel).to receive(:system).with('bin/install', *storages).and_call_original
expect(Kernel).to receive(:system).with('bin/compile').and_call_original
diff --git a/spec/tasks/gitlab/storage_rake_spec.rb b/spec/tasks/gitlab/storage_rake_spec.rb
index 35e451b2f9a..233076ad6fa 100644
--- a/spec/tasks/gitlab/storage_rake_spec.rb
+++ b/spec/tasks/gitlab/storage_rake_spec.rb
@@ -1,6 +1,6 @@
require 'rake_helper'
-describe 'gitlab:storage:*' do
+describe 'rake gitlab:storage:*' do
before do
Rake.application.rake_require 'tasks/gitlab/storage'
@@ -44,16 +44,18 @@ describe 'gitlab:storage:*' do
end
describe 'gitlab:storage:migrate_to_hashed' do
+ let(:task) { 'gitlab:storage:migrate_to_hashed' }
+
context '0 legacy projects' do
it 'does nothing' do
expect(StorageMigratorWorker).not_to receive(:perform_async)
- run_rake_task('gitlab:storage:migrate_to_hashed')
+ run_rake_task(task)
end
end
context '3 legacy projects' do
- let(:projects) { create_list(:project, 3, storage_version: 0) }
+ let(:projects) { create_list(:project, 3, :legacy_storage) }
context 'in batches of 1' do
before do
@@ -65,7 +67,7 @@ describe 'gitlab:storage:*' do
expect(StorageMigratorWorker).to receive(:perform_async).with(project.id, project.id)
end
- run_rake_task('gitlab:storage:migrate_to_hashed')
+ run_rake_task(task)
end
end
@@ -80,23 +82,48 @@ describe 'gitlab:storage:*' do
expect(StorageMigratorWorker).to receive(:perform_async).with(first, last)
end
- run_rake_task('gitlab:storage:migrate_to_hashed')
+ run_rake_task(task)
end
end
end
+
+ context 'with same id in range' do
+ it 'displays message when project cant be found' do
+ stub_env('ID_FROM', 99999)
+ stub_env('ID_TO', 99999)
+
+ expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=99999/).to_stdout
+ end
+
+ it 'displays a message when project exists but its already migrated' do
+ project = create(:project)
+ stub_env('ID_FROM', project.id)
+ stub_env('ID_TO', project.id)
+
+ expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=#{project.id}/).to_stdout
+ end
+
+ it 'enqueues migration when project can be found' do
+ project = create(:project, :legacy_storage)
+ stub_env('ID_FROM', project.id)
+ stub_env('ID_TO', project.id)
+
+ expect { run_rake_task(task) }.to output(/Enqueueing storage migration .* \(ID=#{project.id}\)/).to_stdout
+ end
+ end
end
describe 'gitlab:storage:legacy_projects' do
it_behaves_like 'rake entities summary', 'projects', 'Legacy' do
let(:task) { 'gitlab:storage:legacy_projects' }
- let(:create_collection) { create_list(:project, 3, storage_version: 0) }
+ let(:create_collection) { create_list(:project, 3, :legacy_storage) }
end
end
describe 'gitlab:storage:list_legacy_projects' do
it_behaves_like 'rake listing entities', 'projects', 'Legacy' do
let(:task) { 'gitlab:storage:list_legacy_projects' }
- let(:create_collection) { create_list(:project, 3, storage_version: 0) }
+ let(:create_collection) { create_list(:project, 3, :legacy_storage) }
end
end
@@ -133,7 +160,7 @@ describe 'gitlab:storage:*' do
describe 'gitlab:storage:hashed_attachments' do
it_behaves_like 'rake entities summary', 'attachments', 'Hashed' do
let(:task) { 'gitlab:storage:hashed_attachments' }
- let(:project) { create(:project, storage_version: 2) }
+ let(:project) { create(:project) }
let(:create_collection) { create_list(:upload, 3, model: project) }
end
end
@@ -141,7 +168,7 @@ describe 'gitlab:storage:*' do
describe 'gitlab:storage:list_hashed_attachments' do
it_behaves_like 'rake listing entities', 'attachments', 'Hashed' do
let(:task) { 'gitlab:storage:list_hashed_attachments' }
- let(:project) { create(:project, storage_version: 2) }
+ let(:project) { create(:project) }
let(:create_collection) { create_list(:upload, 3, model: project) }
end
end
diff --git a/spec/uploaders/attachment_uploader_spec.rb b/spec/uploaders/attachment_uploader_spec.rb
index d302c14efb9..a9415854d25 100644
--- a/spec/uploaders/attachment_uploader_spec.rb
+++ b/spec/uploaders/attachment_uploader_spec.rb
@@ -26,7 +26,7 @@ describe AttachmentUploader do
describe "#migrate!" do
before do
- uploader.store!(fixture_file_upload(Rails.root.join('spec/fixtures/doc_sample.txt')))
+ uploader.store!(fixture_file_upload(File.join('spec/fixtures/doc_sample.txt')))
stub_uploads_object_storage
end
diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb
index 68b7e24776d..de29d0c943f 100644
--- a/spec/uploaders/file_mover_spec.rb
+++ b/spec/uploaders/file_mover_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe FileMover do
let(:filename) { 'banana_sample.gif' }
- let(:file) { fixture_file_upload(Rails.root.join('spec', 'fixtures', filename)) }
+ let(:file) { fixture_file_upload(File.join('spec', 'fixtures', filename)) }
let(:temp_file_path) { File.join('uploads/-/system/temp', 'secret55', filename) }
let(:temp_description) do
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index db2810bbe1d..59013a02938 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -89,7 +89,7 @@ describe FileUploader do
describe "#migrate!" do
before do
- uploader.store!(fixture_file_upload(Rails.root.join('spec/fixtures/dk.png')))
+ uploader.store!(fixture_file_upload('spec/fixtures/dk.png'))
stub_uploads_object_storage
end
diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb
index 4fba122cce1..362f89424d4 100644
--- a/spec/uploaders/gitlab_uploader_spec.rb
+++ b/spec/uploaders/gitlab_uploader_spec.rb
@@ -62,7 +62,7 @@ describe GitlabUploader do
expect(FileUtils).to receive(:mv).with(anything, /^#{subject.work_dir}/).and_call_original
expect(FileUtils).to receive(:mv).with(/^#{subject.work_dir}/, /#{subject.cache_dir}/).and_call_original
- fixture = Rails.root.join('spec', 'fixtures', 'rails_sample.jpg')
+ fixture = File.join('spec', 'fixtures', 'rails_sample.jpg')
subject.cache!(fixture_file_upload(fixture))
expect(subject.file.path).to match(/#{subject.cache_dir}/)
diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb
index 42036d67f3d..026e4356ed6 100644
--- a/spec/uploaders/job_artifact_uploader_spec.rb
+++ b/spec/uploaders/job_artifact_uploader_spec.rb
@@ -29,8 +29,7 @@ describe JobArtifactUploader do
context 'when trace is stored in File storage' do
context 'when file exists' do
let(:file) do
- fixture_file_upload(
- Rails.root.join('spec/fixtures/trace/sample_trace'), 'text/plain')
+ fixture_file_upload('spec/fixtures/trace/sample_trace', 'text/plain')
end
before do
@@ -63,8 +62,7 @@ describe JobArtifactUploader do
context 'file is stored in valid local_path' do
let(:file) do
- fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip')
+ fixture_file_upload('spec/fixtures/ci_build_artifacts.zip', 'application/zip')
end
before do
@@ -81,7 +79,7 @@ describe JobArtifactUploader do
describe "#migrate!" do
before do
- uploader.store!(fixture_file_upload(Rails.root.join('spec/fixtures/trace/sample_trace')))
+ uploader.store!(fixture_file_upload('spec/fixtures/trace/sample_trace'))
stub_artifacts_object_storage
end
diff --git a/spec/uploaders/legacy_artifact_uploader_spec.rb b/spec/uploaders/legacy_artifact_uploader_spec.rb
index eeb6fd90c9d..0589563b502 100644
--- a/spec/uploaders/legacy_artifact_uploader_spec.rb
+++ b/spec/uploaders/legacy_artifact_uploader_spec.rb
@@ -44,8 +44,7 @@ describe LegacyArtifactUploader do
context 'file is stored in valid path' do
let(:file) do
- fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip')
+ fixture_file_upload('spec/fixtures/ci_build_artifacts.zip', 'application/zip')
end
before do
diff --git a/spec/uploaders/namespace_file_uploader_spec.rb b/spec/uploaders/namespace_file_uploader_spec.rb
index a8ba01d70b8..71fe2c353c0 100644
--- a/spec/uploaders/namespace_file_uploader_spec.rb
+++ b/spec/uploaders/namespace_file_uploader_spec.rb
@@ -28,7 +28,7 @@ describe NamespaceFileUploader do
describe "#migrate!" do
before do
- uploader.store!(fixture_file_upload(Rails.root.join('spec/fixtures/doc_sample.txt')))
+ uploader.store!(fixture_file_upload(File.join('spec/fixtures/doc_sample.txt')))
stub_uploads_object_storage
end
diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb
index 2dd0925a8e6..c7f5694ff43 100644
--- a/spec/uploaders/object_storage_spec.rb
+++ b/spec/uploaders/object_storage_spec.rb
@@ -321,7 +321,7 @@ describe ObjectStorage do
when_file_is_in_use do
expect(uploader).not_to receive(:unsafe_migrate!)
- expect { uploader.migrate!(described_class::Store::REMOTE) }.to raise_error('exclusive lease already taken')
+ expect { uploader.migrate!(described_class::Store::REMOTE) }.to raise_error(ObjectStorage::ExclusiveLeaseTaken)
end
end
@@ -329,7 +329,19 @@ describe ObjectStorage do
when_file_is_in_use do
expect(uploader).not_to receive(:unsafe_use_file)
- expect { uploader.use_file }.to raise_error('exclusive lease already taken')
+ expect { uploader.use_file }.to raise_error(ObjectStorage::ExclusiveLeaseTaken)
+ end
+ end
+
+ it 'can still migrate other files of the same model' do
+ uploader2 = uploader_class.new(object, :file)
+ uploader2.upload = create(:upload)
+ uploader.upload = create(:upload)
+
+ when_file_is_in_use do
+ expect(uploader2).to receive(:unsafe_migrate!)
+
+ uploader2.migrate!(described_class::Store::REMOTE)
end
end
end
@@ -355,14 +367,10 @@ describe ObjectStorage do
end
describe '.workhorse_authorize' do
- subject { uploader_class.workhorse_authorize }
+ let(:has_length) { true }
+ let(:maximum_size) { nil }
- before do
- # ensure that we use regular Fog libraries
- # other tests might call `Fog.mock!` and
- # it will make tests to fail
- Fog.unmock!
- end
+ subject { uploader_class.workhorse_authorize(has_length: has_length, maximum_size: maximum_size) }
shared_examples 'uses local storage' do
it "returns temporary path" do
@@ -371,10 +379,6 @@ describe ObjectStorage do
expect(subject[:TempPath]).to start_with(uploader_class.root)
expect(subject[:TempPath]).to include(described_class::TMP_UPLOAD_PATH)
end
-
- it "does not return remote store" do
- is_expected.not_to have_key('RemoteObject')
- end
end
shared_examples 'uses remote storage' do
@@ -383,7 +387,7 @@ describe ObjectStorage do
expect(subject[:RemoteObject]).to have_key(:ID)
expect(subject[:RemoteObject]).to include(Timeout: a_kind_of(Integer))
- expect(subject[:RemoteObject][:Timeout]).to be(ObjectStorage::DIRECT_UPLOAD_TIMEOUT)
+ expect(subject[:RemoteObject][:Timeout]).to be(ObjectStorage::DirectUpload::TIMEOUT)
expect(subject[:RemoteObject]).to have_key(:GetURL)
expect(subject[:RemoteObject]).to have_key(:DeleteURL)
expect(subject[:RemoteObject]).to have_key(:StoreURL)
@@ -391,9 +395,31 @@ describe ObjectStorage do
expect(subject[:RemoteObject][:DeleteURL]).to include(described_class::TMP_UPLOAD_PATH)
expect(subject[:RemoteObject][:StoreURL]).to include(described_class::TMP_UPLOAD_PATH)
end
+ end
- it "does not return local store" do
- is_expected.not_to have_key('TempPath')
+ shared_examples 'uses remote storage with multipart uploads' do
+ it_behaves_like 'uses remote storage' do
+ it "returns multipart upload" do
+ is_expected.to have_key(:RemoteObject)
+
+ expect(subject[:RemoteObject]).to have_key(:MultipartUpload)
+ expect(subject[:RemoteObject][:MultipartUpload]).to have_key(:PartSize)
+ expect(subject[:RemoteObject][:MultipartUpload]).to have_key(:PartURLs)
+ expect(subject[:RemoteObject][:MultipartUpload]).to have_key(:CompleteURL)
+ expect(subject[:RemoteObject][:MultipartUpload]).to have_key(:AbortURL)
+ expect(subject[:RemoteObject][:MultipartUpload][:PartURLs]).to all(include(described_class::TMP_UPLOAD_PATH))
+ expect(subject[:RemoteObject][:MultipartUpload][:CompleteURL]).to include(described_class::TMP_UPLOAD_PATH)
+ expect(subject[:RemoteObject][:MultipartUpload][:AbortURL]).to include(described_class::TMP_UPLOAD_PATH)
+ end
+ end
+ end
+
+ shared_examples 'uses remote storage without multipart uploads' do
+ it_behaves_like 'uses remote storage' do
+ it "does not return multipart upload" do
+ is_expected.to have_key(:RemoteObject)
+ expect(subject[:RemoteObject]).not_to have_key(:MultipartUpload)
+ end
end
end
@@ -416,6 +442,8 @@ describe ObjectStorage do
end
context 'uses AWS' do
+ let(:storage_url) { "https://uploads.s3-eu-central-1.amazonaws.com/" }
+
before do
expect(uploader_class).to receive(:object_store_credentials) do
{ provider: "AWS",
@@ -425,18 +453,40 @@ describe ObjectStorage do
end
end
- it_behaves_like 'uses remote storage' do
- let(:storage_url) { "https://uploads.s3-eu-central-1.amazonaws.com/" }
+ context 'for known length' do
+ it_behaves_like 'uses remote storage without multipart uploads' do
+ it 'returns links for S3' do
+ expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url)
+ end
+ end
+ end
+
+ context 'for unknown length' do
+ let(:has_length) { false }
+ let(:maximum_size) { 1.gigabyte }
+
+ before do
+ stub_object_storage_multipart_init(storage_url)
+ end
- it 'returns links for S3' do
- expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url)
- expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url)
- expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url)
+ it_behaves_like 'uses remote storage with multipart uploads' do
+ it 'returns links for S3' do
+ expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:MultipartUpload][:PartURLs]).to all(start_with(storage_url))
+ expect(subject[:RemoteObject][:MultipartUpload][:CompleteURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:MultipartUpload][:AbortURL]).to start_with(storage_url)
+ end
end
end
end
context 'uses Google' do
+ let(:storage_url) { "https://storage.googleapis.com/uploads/" }
+
before do
expect(uploader_class).to receive(:object_store_credentials) do
{ provider: "Google",
@@ -445,36 +495,71 @@ describe ObjectStorage do
end
end
- it_behaves_like 'uses remote storage' do
- let(:storage_url) { "https://storage.googleapis.com/uploads/" }
+ context 'for known length' do
+ it_behaves_like 'uses remote storage without multipart uploads' do
+ it 'returns links for Google Cloud' do
+ expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url)
+ end
+ end
+ end
+
+ context 'for unknown length' do
+ let(:has_length) { false }
+ let(:maximum_size) { 1.gigabyte }
- it 'returns links for Google Cloud' do
- expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url)
- expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url)
- expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url)
+ it_behaves_like 'uses remote storage without multipart uploads' do
+ it 'returns links for Google Cloud' do
+ expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url)
+ end
end
end
end
context 'uses GDK/minio' do
+ let(:storage_url) { "http://minio:9000/uploads/" }
+
before do
expect(uploader_class).to receive(:object_store_credentials) do
{ provider: "AWS",
aws_access_key_id: "AWS_ACCESS_KEY_ID",
aws_secret_access_key: "AWS_SECRET_ACCESS_KEY",
- endpoint: 'http://127.0.0.1:9000',
+ endpoint: 'http://minio:9000',
path_style: true,
region: "gdk" }
end
end
- it_behaves_like 'uses remote storage' do
- let(:storage_url) { "http://127.0.0.1:9000/uploads/" }
+ context 'for known length' do
+ it_behaves_like 'uses remote storage without multipart uploads' do
+ it 'returns links for S3' do
+ expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url)
+ end
+ end
+ end
+
+ context 'for unknown length' do
+ let(:has_length) { false }
+ let(:maximum_size) { 1.gigabyte }
+
+ before do
+ stub_object_storage_multipart_init(storage_url)
+ end
- it 'returns links for S3' do
- expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url)
- expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url)
- expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url)
+ it_behaves_like 'uses remote storage with multipart uploads' do
+ it 'returns links for S3' do
+ expect(subject[:RemoteObject][:GetURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:DeleteURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:StoreURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:MultipartUpload][:PartURLs]).to all(start_with(storage_url))
+ expect(subject[:RemoteObject][:MultipartUpload][:CompleteURL]).to start_with(storage_url)
+ expect(subject[:RemoteObject][:MultipartUpload][:AbortURL]).to start_with(storage_url)
+ end
end
end
end
@@ -498,7 +583,7 @@ describe ObjectStorage do
context 'when local file is used' do
context 'when valid file is used' do
let(:uploaded_file) do
- fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg')
+ fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg')
end
it "properly caches the file" do
@@ -659,4 +744,26 @@ describe ObjectStorage do
end
end
end
+
+ describe '#retrieve_from_store!' do
+ [:group, :project, :user].each do |model|
+ context "for #{model}s" do
+ let(:models) { create_list(model, 3, :with_avatar).map(&:reload) }
+ let(:avatars) { models.map(&:avatar) }
+
+ it 'batches fetching uploads from the database' do
+ # Ensure that these are all created and fully loaded before we start
+ # running queries for avatars
+ models
+
+ expect { avatars }.not_to exceed_query_limit(1)
+ end
+
+ it 'fetches a unique upload for each model' do
+ expect(avatars.map(&:url).uniq).to eq(avatars.map(&:url))
+ expect(avatars.map(&:upload).uniq).to eq(avatars.map(&:upload))
+ end
+ end
+ end
+ end
end
diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb
index c70521d90dc..7700b14ce6b 100644
--- a/spec/uploaders/personal_file_uploader_spec.rb
+++ b/spec/uploaders/personal_file_uploader_spec.rb
@@ -45,7 +45,7 @@ describe PersonalFileUploader do
describe "#migrate!" do
before do
- uploader.store!(fixture_file_upload(Rails.root.join('spec/fixtures/doc_sample.txt')))
+ uploader.store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
stub_uploads_object_storage
end
diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb
index 9a3e5d83e01..3592a11360d 100644
--- a/spec/uploaders/records_uploads_spec.rb
+++ b/spec/uploaders/records_uploads_spec.rb
@@ -16,7 +16,7 @@ describe RecordsUploads do
end
def upload_fixture(filename)
- fixture_file_upload(Rails.root.join('spec', 'fixtures', filename))
+ fixture_file_upload(File.join('spec', 'fixtures', filename))
end
describe 'callbacks' do
diff --git a/spec/uploaders/uploader_helper_spec.rb b/spec/uploaders/uploader_helper_spec.rb
index c47f09adb6d..33da93cc9d0 100644
--- a/spec/uploaders/uploader_helper_spec.rb
+++ b/spec/uploaders/uploader_helper_spec.rb
@@ -12,7 +12,7 @@ describe UploaderHelper do
end
def upload_fixture(filename)
- fixture_file_upload(Rails.root.join('spec', 'fixtures', filename))
+ fixture_file_upload(File.join('spec', 'fixtures', filename))
end
describe '#image_or_video?' do
diff --git a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
index b34f427fd8a..95813d15e52 100644
--- a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
+++ b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
@@ -125,8 +125,10 @@ describe ObjectStorage::BackgroundMoveWorker do
it "migrates file to remote storage" do
perform
+ project.reload
+ BatchLoader::Executor.clear_current
- expect(project.reload.avatar.file_storage?).to be_falsey
+ expect(project.avatar).not_to be_file_storage
end
end
@@ -137,7 +139,7 @@ describe ObjectStorage::BackgroundMoveWorker do
it "migrates file to remote storage" do
perform
- expect(project.reload.avatar.file_storage?).to be_falsey
+ expect(project.reload.avatar).not_to be_file_storage
end
end
end
diff --git a/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb b/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
index aed62f97448..da490cb02af 100644
--- a/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
+++ b/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb
@@ -11,6 +11,12 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
let(:uploads) { Upload.all }
let(:to_store) { ObjectStorage::Store::REMOTE }
+ def perform(uploads)
+ described_class.new.perform(uploads.ids, model_class.to_s, mounted_as, to_store)
+ rescue ObjectStorage::MigrateUploadsWorker::Report::MigrationFailures
+ # swallow
+ end
+
shared_examples "uploads migration worker" do
describe '.enqueue!' do
def enqueue!
@@ -69,12 +75,6 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
end
describe '#perform' do
- def perform
- described_class.new.perform(uploads.ids, model_class.to_s, mounted_as, to_store)
- rescue ObjectStorage::MigrateUploadsWorker::Report::MigrationFailures
- # swallow
- end
-
shared_examples 'outputs correctly' do |success: 0, failures: 0|
total = success + failures
@@ -82,7 +82,7 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
it 'outputs the reports' do
expect(Rails.logger).to receive(:info).with(%r{Migrated #{success}/#{total} files})
- perform
+ perform(uploads)
end
end
@@ -90,7 +90,7 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
it 'outputs upload failures' do
expect(Rails.logger).to receive(:warn).with(/Error .* I am a teapot/)
- perform
+ perform(uploads)
end
end
end
@@ -98,7 +98,7 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
it_behaves_like 'outputs correctly', success: 10
it 'migrates files' do
- perform
+ perform(uploads)
expect(Upload.where(store: ObjectStorage::Store::LOCAL).count).to eq(0)
end
@@ -123,6 +123,17 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
end
it_behaves_like "uploads migration worker"
+
+ describe "limits N+1 queries" do
+ it "to N*5" do
+ query_count = ActiveRecord::QueryRecorder.new { perform(uploads) }
+
+ more_projects = create_list(:project, 3, :with_avatar)
+
+ expected_queries_per_migration = 5 * more_projects.count
+ expect { perform(Upload.all) }.not_to exceed_query_limit(query_count).with_threshold(expected_queries_per_migration)
+ end
+ end
end
context "for FileUploader" do
@@ -130,15 +141,29 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do
let(:secret) { SecureRandom.hex }
let(:mounted_as) { nil }
+ def upload_file(project)
+ uploader = FileUploader.new(project)
+ uploader.store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
+ end
+
before do
stub_uploads_object_storage(FileUploader)
- projects.map do |project|
- uploader = FileUploader.new(project)
- uploader.store!(fixture_file_upload('spec/fixtures/doc_sample.txt'))
- end
+ projects.map(&method(:upload_file))
end
it_behaves_like "uploads migration worker"
+
+ describe "limits N+1 queries" do
+ it "to N*5" do
+ query_count = ActiveRecord::QueryRecorder.new { perform(uploads) }
+
+ more_projects = create_list(:project, 3)
+ more_projects.map(&method(:upload_file))
+
+ expected_queries_per_migration = 5 * more_projects.count
+ expect { perform(Upload.all) }.not_to exceed_query_limit(query_count).with_threshold(expected_queries_per_migration)
+ end
+ end
end
end
diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb
index 2d719263fc8..93fe013d11c 100644
--- a/spec/validators/url_validator_spec.rb
+++ b/spec/validators/url_validator_spec.rb
@@ -50,13 +50,56 @@ describe UrlValidator do
end
end
- context 'when ports is set' do
- let(:validator) { described_class.new(attributes: [:link_url], ports: [443]) }
+ context 'when ports is' do
+ let(:validator) { described_class.new(attributes: [:link_url], ports: ports) }
- it 'blocks urls with a different port' do
- subject
+ context 'empty' do
+ let(:ports) { [] }
- expect(badge.errors.empty?).to be false
+ it 'does not block any port' do
+ subject
+
+ expect(badge.errors.empty?).to be true
+ end
+ end
+
+ context 'set' do
+ let(:ports) { [443] }
+
+ it 'blocks urls with a different port' do
+ subject
+
+ expect(badge.errors.empty?).to be false
+ end
+ end
+ end
+
+ context 'when enforce_user is' do
+ let(:url) { 'http://$user@example.com'}
+ let(:validator) { described_class.new(attributes: [:link_url], enforce_user: enforce_user) }
+
+ context 'true' do
+ let(:enforce_user) { true }
+
+ it 'checks user format' do
+ badge.link_url = url
+
+ subject
+
+ expect(badge.errors.empty?).to be false
+ end
+ end
+
+ context 'false (default)' do
+ let(:enforce_user) { false }
+
+ it 'does not check user format' do
+ badge.link_url = url
+
+ subject
+
+ expect(badge.errors.empty?).to be true
+ end
end
end
end
diff --git a/spec/views/errors/access_denied.html.haml_spec.rb b/spec/views/errors/access_denied.html.haml_spec.rb
new file mode 100644
index 00000000000..bde2f6f0169
--- /dev/null
+++ b/spec/views/errors/access_denied.html.haml_spec.rb
@@ -0,0 +1,7 @@
+require 'spec_helper'
+
+describe 'errors/access_denied' do
+ it 'does not fail to render when there is no message provided' do
+ expect { render }.not_to raise_error
+ end
+end
diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
index d15391911c1..cb1b9e6f5fb 100644
--- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
+++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
@@ -12,7 +12,7 @@ describe 'projects/settings/ci_cd/_autodevops_form' do
it 'shows warning message' do
render
- expect(rendered).to have_css('.settings-message')
+ expect(rendered).to have_css('.auto-devops-warning-message')
expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name and a')
expect(rendered).to have_link('Kubernetes cluster')
end
@@ -26,7 +26,7 @@ describe 'projects/settings/ci_cd/_autodevops_form' do
it 'shows warning message' do
render
- expect(rendered).to have_css('.settings-message')
+ expect(rendered).to have_css('.auto-devops-warning-message')
expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a')
expect(rendered).to have_link('Kubernetes cluster')
end
@@ -42,7 +42,7 @@ describe 'projects/settings/ci_cd/_autodevops_form' do
it 'shows warning message' do
render
- expect(rendered).to have_css('.settings-message')
+ expect(rendered).to have_css('.auto-devops-warning-message')
expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name to work correctly.')
end
end
@@ -55,7 +55,7 @@ describe 'projects/settings/ci_cd/_autodevops_form' do
it 'does not show warning message' do
render
- expect(rendered).not_to have_css('.settings-message')
+ expect(rendered).not_to have_css('.auto-devops-warning-message')
end
end
end
diff --git a/spec/workers/ci/archive_traces_cron_worker_spec.rb b/spec/workers/ci/archive_traces_cron_worker_spec.rb
new file mode 100644
index 00000000000..9af51b7d4d8
--- /dev/null
+++ b/spec/workers/ci/archive_traces_cron_worker_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe Ci::ArchiveTracesCronWorker do
+ subject { described_class.new.perform }
+
+ before do
+ stub_feature_flags(ci_enable_live_trace: true)
+ end
+
+ shared_examples_for 'archives trace' do
+ it do
+ subject
+
+ build.reload
+ expect(build.job_artifacts_trace).to be_exist
+ end
+ end
+
+ shared_examples_for 'does not archive trace' do
+ it do
+ subject
+
+ build.reload
+ expect(build.job_artifacts_trace).to be_nil
+ end
+ end
+
+ context 'when a job was succeeded' do
+ let!(:build) { create(:ci_build, :success, :trace_live) }
+
+ it_behaves_like 'archives trace'
+
+ context 'when archive raised an exception' do
+ let!(:build) { create(:ci_build, :success, :trace_artifact, :trace_live) }
+ let!(:build2) { create(:ci_build, :success, :trace_live) }
+
+ it 'archives valid targets' do
+ expect(Rails.logger).to receive(:error).with("Failed to archive stale live trace. id: #{build.id} message: Already archived")
+
+ subject
+
+ build2.reload
+ expect(build2.job_artifacts_trace).to be_exist
+ end
+ end
+ end
+
+ context 'when a job was cancelled' do
+ let!(:build) { create(:ci_build, :canceled, :trace_live) }
+
+ it_behaves_like 'archives trace'
+ end
+
+ context 'when a job is running' do
+ let!(:build) { create(:ci_build, :running, :trace_live) }
+
+ it_behaves_like 'does not archive trace'
+ end
+end
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 9e3b99b3502..2106959e23c 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -13,7 +13,7 @@ describe 'Every Sidekiq worker' do
file_worker_queues = Gitlab::SidekiqConfig.worker_queues.to_set
worker_queues = Gitlab::SidekiqConfig.workers.map(&:queue).to_set
- worker_queues << ActionMailer::DeliveryJob.queue_name
+ worker_queues << ActionMailer::DeliveryJob.new.queue_name
worker_queues << 'default'
missing_from_file = worker_queues - file_worker_queues
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 74539a7e493..e39dec556fc 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -11,36 +11,63 @@ describe GitGarbageCollectWorker do
subject { described_class.new }
describe "#perform" do
- shared_examples 'flushing ref caches' do |gitaly|
- context 'with active lease_uuid' do
+ context 'with active lease_uuid' do
+ before do
+ allow(subject).to receive(:get_lease_uuid).and_return(lease_uuid)
+ end
+
+ it "flushes ref caches when the task if 'gc'" do
+ expect(subject).to receive(:renew_lease).with(lease_key, lease_uuid).and_call_original
+ expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:garbage_collect)
+ .and_return(nil)
+ expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original
+ expect_any_instance_of(Repository).to receive(:branch_names).and_call_original
+ expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original
+ expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original
+
+ subject.perform(project.id, :gc, lease_key, lease_uuid)
+ end
+ end
+
+ context 'with different lease than the active one' do
+ before do
+ allow(subject).to receive(:get_lease_uuid).and_return(SecureRandom.uuid)
+ end
+
+ it 'returns silently' do
+ expect_any_instance_of(Repository).not_to receive(:after_create_branch).and_call_original
+ expect_any_instance_of(Repository).not_to receive(:branch_names).and_call_original
+ expect_any_instance_of(Repository).not_to receive(:has_visible_content?).and_call_original
+
+ subject.perform(project.id, :gc, lease_key, lease_uuid)
+ end
+ end
+
+ context 'with no active lease' do
+ before do
+ allow(subject).to receive(:get_lease_uuid).and_return(false)
+ end
+
+ context 'when is able to get the lease' do
before do
- allow(subject).to receive(:get_lease_uuid).and_return(lease_uuid)
+ allow(subject).to receive(:try_obtain_lease).and_return(SecureRandom.uuid)
end
it "flushes ref caches when the task if 'gc'" do
- expect(subject).to receive(:renew_lease).with(lease_key, lease_uuid).and_call_original
- expect(subject).to receive(:command).with(:gc).and_return([:the, :command])
-
- if gitaly
- expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:garbage_collect)
- .and_return(nil)
- else
- expect(Gitlab::Popen).to receive(:popen)
- .with([:the, :command], project.repository.path_to_repo).and_return(["", 0])
- end
-
+ expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:garbage_collect)
+ .and_return(nil)
expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original
expect_any_instance_of(Repository).to receive(:branch_names).and_call_original
expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original
expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original
- subject.perform(project.id, :gc, lease_key, lease_uuid)
+ subject.perform(project.id)
end
end
- context 'with different lease than the active one' do
+ context 'when no lease can be obtained' do
before do
- allow(subject).to receive(:get_lease_uuid).and_return(SecureRandom.uuid)
+ expect(subject).to receive(:try_obtain_lease).and_return(false)
end
it 'returns silently' do
@@ -49,65 +76,11 @@ describe GitGarbageCollectWorker do
expect_any_instance_of(Repository).not_to receive(:branch_names).and_call_original
expect_any_instance_of(Repository).not_to receive(:has_visible_content?).and_call_original
- subject.perform(project.id, :gc, lease_key, lease_uuid)
- end
- end
-
- context 'with no active lease' do
- before do
- allow(subject).to receive(:get_lease_uuid).and_return(false)
- end
-
- context 'when is able to get the lease' do
- before do
- allow(subject).to receive(:try_obtain_lease).and_return(SecureRandom.uuid)
- end
-
- it "flushes ref caches when the task if 'gc'" do
- expect(subject).to receive(:command).with(:gc).and_return([:the, :command])
-
- if gitaly
- expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:garbage_collect)
- .and_return(nil)
- else
- expect(Gitlab::Popen).to receive(:popen)
- .with([:the, :command], project.repository.path_to_repo).and_return(["", 0])
- end
-
- expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original
- expect_any_instance_of(Repository).to receive(:branch_names).and_call_original
- expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original
- expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original
-
- subject.perform(project.id)
- end
- end
-
- context 'when no lease can be obtained' do
- before do
- expect(subject).to receive(:try_obtain_lease).and_return(false)
- end
-
- it 'returns silently' do
- expect(subject).not_to receive(:command)
- expect_any_instance_of(Repository).not_to receive(:after_create_branch).and_call_original
- expect_any_instance_of(Repository).not_to receive(:branch_names).and_call_original
- expect_any_instance_of(Repository).not_to receive(:has_visible_content?).and_call_original
-
- subject.perform(project.id)
- end
+ subject.perform(project.id)
end
end
end
- context "with Gitaly turned on" do
- it_should_behave_like 'flushing ref caches', true
- end
-
- context "with Gitaly turned off", :skip_gitaly_mock do
- it_should_behave_like 'flushing ref caches', false
- end
-
context "repack_full" do
before do
expect(subject).to receive(:get_lease_uuid).and_return(lease_uuid)
@@ -218,7 +191,9 @@ describe GitGarbageCollectWorker do
# Create a new commit on a random new branch
def create_objects(project)
- rugged = project.repository.rugged
+ rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.rugged
+ end
old_commit = rugged.branches.first.target
new_commit_sha = Rugged::Commit.create(
rugged,
@@ -237,7 +212,9 @@ describe GitGarbageCollectWorker do
end
def packs(project)
- Dir["#{project.repository.path_to_repo}/objects/pack/*.pack"]
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ Dir["#{project.repository.path_to_repo}/objects/pack/*.pack"]
+ end
end
def packed_refs(project)
diff --git a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
new file mode 100644
index 00000000000..b19884d7991
--- /dev/null
+++ b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::Stage::ImportLfsObjectsWorker do
+ let(:project) { create(:project) }
+ let(:worker) { described_class.new }
+
+ describe '#import' do
+ it 'imports all the lfs objects' do
+ importer = double(:importer)
+ waiter = Gitlab::JobWaiter.new(2, '123')
+
+ expect(Gitlab::GithubImport::Importer::LfsObjectsImporter)
+ .to receive(:new)
+ .with(project, nil)
+ .and_return(importer)
+
+ expect(importer)
+ .to receive(:execute)
+ .and_return(waiter)
+
+ expect(Gitlab::GithubImport::AdvanceStageWorker)
+ .to receive(:perform_async)
+ .with(project.id, { '123' => 2 }, :finish)
+
+ worker.import(project)
+ end
+ end
+end
diff --git a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
index 098d2d55386..94cff9e4e80 100644
--- a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
@@ -21,7 +21,7 @@ describe Gitlab::GithubImport::Stage::ImportNotesWorker do
expect(Gitlab::GithubImport::AdvanceStageWorker)
.to receive(:perform_async)
- .with(project.id, { '123' => 2 }, :finish)
+ .with(project.id, { '123' => 2 }, :lfs_objects)
worker.import(client, project)
end
diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb
index f19c9dff941..42e1d86e3bb 100644
--- a/spec/workers/project_destroy_worker_spec.rb
+++ b/spec/workers/project_destroy_worker_spec.rb
@@ -2,7 +2,11 @@ require 'spec_helper'
describe ProjectDestroyWorker do
let(:project) { create(:project, :repository, pending_delete: true) }
- let(:path) { project.repository.path_to_repo }
+ let(:path) do
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.path_to_repo
+ end
+ end
subject { described_class.new }
diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb
index a021235aed6..22fc64c1536 100644
--- a/spec/workers/repository_check/single_repository_worker_spec.rb
+++ b/spec/workers/repository_check/single_repository_worker_spec.rb
@@ -88,7 +88,9 @@ describe RepositoryCheck::SingleRepositoryWorker do
end
def break_wiki(project)
- break_repo(wiki_path(project))
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ break_repo(wiki_path(project))
+ end
end
def wiki_path(project)
@@ -96,7 +98,9 @@ describe RepositoryCheck::SingleRepositoryWorker do
end
def break_project(project)
- break_repo(project.repository.path_to_repo)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ break_repo(project.repository.path_to_repo)
+ end
end
def break_repo(repo)
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 4b3c1736ea0..ac8716ecfb1 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -55,10 +55,15 @@ describe RepositoryForkWorker do
it 'flushes various caches' do
expect_fork_repository.and_return(true)
- expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
+ # Works around https://github.com/rspec/rspec-mocks/issues/910
+ expect(Project).to receive(:find).with(fork_project.id).and_return(fork_project)
+ expect(fork_project.repository).to receive(:expire_emptiness_caches)
.and_call_original
-
- expect_any_instance_of(Repository).to receive(:expire_exists_cache)
+ expect(fork_project.repository).to receive(:expire_exists_cache)
+ .and_call_original
+ expect(fork_project.wiki.repository).to receive(:expire_emptiness_caches)
+ .and_call_original
+ expect(fork_project.wiki.repository).to receive(:expire_exists_cache)
.and_call_original
perform!
@@ -87,13 +92,6 @@ describe RepositoryForkWorker do
end
it_behaves_like 'RepositoryForkWorker performing'
-
- it 'logs a message about forking with old-style arguments' do
- allow(Rails.logger).to receive(:info).with(anything) # To compensate for other logs
- expect(Rails.logger).to receive(:info).with("Project #{fork_project.id} is being forked using old-style arguments.")
-
- perform!
- end
end
end
end
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index 84d1b38ef19..f0884ad0aff 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -22,8 +22,11 @@ describe RepositoryImportWorker do
expect_any_instance_of(Projects::ImportService).to receive(:execute)
.and_return({ status: :ok })
- expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
- expect_any_instance_of(Project).to receive(:import_finish)
+ # Works around https://github.com/rspec/rspec-mocks/issues/910
+ expect(Project).to receive(:find).with(project.id).and_return(project)
+ expect(project.repository).to receive(:expire_emptiness_caches)
+ expect(project.wiki.repository).to receive(:expire_emptiness_caches)
+ expect(project).to receive(:import_finish)
subject.perform(project.id)
end
@@ -34,9 +37,11 @@ describe RepositoryImportWorker do
expect_any_instance_of(Projects::ImportService).to receive(:execute)
.and_return({ status: :ok })
- expect_any_instance_of(Project).to receive(:after_import).and_call_original
- expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
- expect_any_instance_of(Project).to receive(:import_finish)
+ # Works around https://github.com/rspec/rspec-mocks/issues/910
+ expect(Project).to receive(:find).with(project.id).and_return(project)
+ expect(project.repository).to receive(:expire_emptiness_caches)
+ expect(project.wiki.repository).to receive(:expire_emptiness_caches)
+ expect(project).to receive(:import_finish)
subject.perform(project.id)
end
diff --git a/spec/workers/repository_remove_remote_worker_spec.rb b/spec/workers/repository_remove_remote_worker_spec.rb
index f22d7c1d073..5968c5da3c9 100644
--- a/spec/workers/repository_remove_remote_worker_spec.rb
+++ b/spec/workers/repository_remove_remote_worker_spec.rb
@@ -44,7 +44,9 @@ describe RepositoryRemoveRemoteWorker do
end
def create_remote_branch(remote_name, branch_name, target)
- rugged = project.repository.rugged
+ rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project.repository.rugged
+ end
rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target.id)
end
end
diff --git a/spec/workers/storage_migrator_worker_spec.rb b/spec/workers/storage_migrator_worker_spec.rb
index ff625164142..815432aacce 100644
--- a/spec/workers/storage_migrator_worker_spec.rb
+++ b/spec/workers/storage_migrator_worker_spec.rb
@@ -2,29 +2,24 @@ require 'spec_helper'
describe StorageMigratorWorker do
subject(:worker) { described_class.new }
- let(:projects) { create_list(:project, 2, :legacy_storage) }
+ let(:projects) { create_list(:project, 2, :legacy_storage, :empty_repo) }
+ let(:ids) { projects.map(&:id) }
describe '#perform' do
- let(:ids) { projects.map(&:id) }
+ it 'delegates to MigratorService' do
+ expect_any_instance_of(Gitlab::HashedStorage::Migrator).to receive(:bulk_migrate).with(5, 10)
- it 'enqueue jobs to ProjectMigrateHashedStorageWorker' do
- expect(ProjectMigrateHashedStorageWorker).to receive(:perform_async).twice
-
- worker.perform(ids.min, ids.max)
+ worker.perform(5, 10)
end
- it 'sets projects as read only' do
- allow(ProjectMigrateHashedStorageWorker).to receive(:perform_async).twice
- worker.perform(ids.min, ids.max)
+ it 'migrates projects in the specified range' do
+ Sidekiq::Testing.inline! do
+ worker.perform(ids.min, ids.max)
+ end
projects.each do |project|
- expect(project.reload.repository_read_only?).to be_truthy
+ expect(project.reload.hashed_storage?(:attachments)).to be_truthy
end
end
-
- it 'rescues and log exceptions' do
- allow_any_instance_of(Project).to receive(:migrate_to_hashed_storage!).and_raise(StandardError)
- expect { worker.perform(ids.min, ids.max) }.not_to raise_error
- end
end
end
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
index bdc64c6785b..2605c14334f 100644
--- a/spec/workers/stuck_ci_jobs_worker_spec.rb
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -132,8 +132,10 @@ describe StuckCiJobsWorker do
end
it 'cancels exclusive lease after worker perform' do
- expect(Gitlab::ExclusiveLease).to receive(:cancel).with(described_class::EXCLUSIVE_LEASE_KEY, exclusive_lease_uuid)
worker.perform
+
+ expect(Gitlab::ExclusiveLease.new(described_class::EXCLUSIVE_LEASE_KEY, timeout: 1.hour))
+ .not_to be_exists
end
end
end