From 023d4f6f2f3d88d0966fe01e6ef921fd03a309fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 29 Mar 2018 11:47:54 +0200 Subject: Move spec helpers/matchers/shared examples/contexts to their relevant folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .rubocop_todo.yml | 8 +- db/fixtures/development/17_cycle_analytics.rb | 2 +- .../testing_guide/testing_rake_tasks.md | 2 +- features/support/env.rb | 6 +- spec/factories/commits.rb | 2 +- spec/factories/gpg_key_subkeys.rb | 2 - spec/factories/gpg_keys.rb | 2 +- spec/factories/gpg_signature.rb | 2 - spec/factories/notes.rb | 2 +- spec/factories/projects.rb | 2 +- spec/spec_helper.rb | 59 +-- spec/support/api_helpers.rb | 50 -- spec/support/background_migrations_matchers.rb | 26 -- spec/support/bare_repo_operations.rb | 62 --- spec/support/board_helpers.rb | 16 - spec/support/capybara.rb | 2 + spec/support/capybara_helpers.rb | 47 -- .../support/chat_slash_commands_shared_examples.rb | 97 ---- spec/support/cookie_helper.rb | 34 -- spec/support/cycle_analytics_helpers.rb | 141 ------ spec/support/database_connection_helpers.rb | 9 - spec/support/devise_helpers.rb | 17 - spec/support/drag_to_helper.rb | 13 - spec/support/dropzone_helper.rb | 76 --- spec/support/email_format_shared_examples.rb | 44 -- spec/support/email_helpers.rb | 37 -- spec/support/factory_bot.rb | 3 - spec/support/fake_migration_classes.rb | 11 - spec/support/fake_u2f_device.rb | 40 -- spec/support/filter_item_select_helper.rb | 19 - spec/support/filter_spec_helper.rb | 87 ---- spec/support/filtered_search_helpers.rb | 148 ------ spec/support/fixture_helpers.rb | 15 - spec/support/generate-seed-repo-rb | 2 +- spec/support/git_http_helpers.rb | 68 --- spec/support/gitlab-git-test.git/README.md | 2 +- spec/support/gitlab_verify.rb | 45 -- spec/support/gpg_helpers.rb | 517 --------------------- spec/support/group_members_shared_example.rb | 27 -- spec/support/helpers/api_helpers.rb | 50 ++ spec/support/helpers/bare_repo_operations.rb | 62 +++ spec/support/helpers/board_helpers.rb | 16 + spec/support/helpers/capybara_helpers.rb | 43 ++ spec/support/helpers/cookie_helper.rb | 34 ++ spec/support/helpers/cycle_analytics_helpers.rb | 137 ++++++ .../support/helpers/database_connection_helpers.rb | 9 + spec/support/helpers/devise_helpers.rb | 17 + spec/support/helpers/drag_to_helper.rb | 13 + spec/support/helpers/dropzone_helper.rb | 76 +++ spec/support/helpers/email_helpers.rb | 37 ++ spec/support/helpers/fake_migration_classes.rb | 11 + spec/support/helpers/fake_u2f_device.rb | 40 ++ spec/support/helpers/filter_item_select_helper.rb | 19 + spec/support/helpers/filter_spec_helper.rb | 87 ++++ spec/support/helpers/filtered_search_helpers.rb | 148 ++++++ spec/support/helpers/fixture_helpers.rb | 11 + spec/support/helpers/git_http_helpers.rb | 68 +++ spec/support/helpers/gitlab_verify_helpers.rb | 25 + spec/support/helpers/gpg_helpers.rb | 517 +++++++++++++++++++++ spec/support/helpers/import_spec_helper.rb | 33 ++ spec/support/helpers/input_helper.rb | 7 + spec/support/helpers/inspect_requests.rb | 17 + spec/support/helpers/issue_helpers.rb | 13 + .../support/helpers/javascript_fixtures_helpers.rb | 66 +++ spec/support/helpers/jira_service_helper.rb | 88 ++++ spec/support/helpers/kubernetes_helpers.rb | 104 +++++ spec/support/helpers/ldap_helpers.rb | 49 ++ spec/support/helpers/live_debugger.rb | 17 + spec/support/helpers/login_helpers.rb | 162 +++++++ spec/support/helpers/markdown_feature.rb | 128 +++++ spec/support/helpers/merge_request_helpers.rb | 22 + spec/support/helpers/migrations_helpers.rb | 92 ++++ spec/support/helpers/mobile_helpers.rb | 17 + spec/support/helpers/project_forks_helper.rb | 54 +++ spec/support/helpers/prometheus_helpers.rb | 202 ++++++++ spec/support/helpers/query_recorder.rb | 38 ++ spec/support/helpers/quick_actions_helpers.rb | 10 + spec/support/helpers/rake_helpers.rb | 19 + spec/support/helpers/reactive_caching_helpers.rb | 48 ++ spec/support/helpers/redis_without_keys.rb | 8 + spec/support/helpers/reference_parser_helpers.rb | 39 ++ spec/support/helpers/repo_helpers.rb | 118 +++++ spec/support/helpers/search_helpers.rb | 5 + spec/support/helpers/seed_helper.rb | 110 +++++ spec/support/helpers/seed_repo.rb | 153 ++++++ spec/support/helpers/select2_helper.rb | 35 ++ spec/support/helpers/selection_helper.rb | 6 + spec/support/helpers/sorting_helper.rb | 18 + spec/support/helpers/stub_configuration.rb | 102 ++++ spec/support/helpers/stub_env.rb | 36 ++ spec/support/helpers/stub_feature_flags.rb | 8 + spec/support/helpers/stub_gitlab_calls.rb | 129 +++++ spec/support/helpers/stub_gitlab_data.rb | 5 + spec/support/helpers/stub_object_storage.rb | 48 ++ spec/support/helpers/test_env.rb | 366 +++++++++++++++ spec/support/helpers/upload_helpers.rb | 16 + spec/support/helpers/user_activities_helpers.rb | 7 + spec/support/helpers/wait_for_requests.rb | 78 ++++ spec/support/helpers/workhorse_helpers.rb | 21 + spec/support/import_spec_helper.rb | 33 -- spec/support/input_helper.rb | 7 - spec/support/inspect_requests.rb | 17 - spec/support/issuable_shared_examples.rb | 38 -- .../issuables_list_metadata_shared_examples.rb | 46 -- spec/support/issue_helpers.rb | 13 - .../issue_tracker_service_shared_example.rb | 22 - spec/support/javascript_fixtures_helpers.rb | 66 --- spec/support/jira_service_helper.rb | 88 ---- spec/support/json_response.rb | 5 + spec/support/json_response_helpers.rb | 9 - spec/support/kubernetes_helpers.rb | 104 ----- spec/support/ldap_helpers.rb | 49 -- spec/support/ldap_shared_examples.rb | 69 --- .../legacy_path_redirect_shared_examples.rb | 13 - spec/support/live_debugger.rb | 17 - spec/support/login_helpers.rb | 162 ------- spec/support/malicious_regexp_shared_examples.rb | 8 - spec/support/markdown_feature.rb | 128 ----- .../matchers/background_migrations_matchers.rb | 26 ++ spec/support/matchers/exceed_query_limit.rb | 64 +++ spec/support/mentionable_shared_examples.rb | 144 ------ spec/support/merge_request_helpers.rb | 22 - spec/support/migrations_helpers.rb | 92 ---- spec/support/milestone_tabs_examples.rb | 84 ---- spec/support/mobile_helpers.rb | 17 - spec/support/notify_shared_examples.rb | 199 -------- spec/support/prepare-gitlab-git-test-for-commit | 2 +- spec/support/project_forks_helper.rb | 54 --- spec/support/prometheus_helpers.rb | 202 -------- spec/support/query_recorder.rb | 103 ---- spec/support/rake_helpers.rb | 19 - spec/support/reactive_caching_helpers.rb | 48 -- spec/support/redis_without_keys.rb | 8 - spec/support/reference_parser_helpers.rb | 39 -- spec/support/reference_parser_shared_examples.rb | 47 -- spec/support/repo_helpers.rb | 118 ----- spec/support/routing_helpers.rb | 3 - spec/support/rspec.rb | 12 + spec/support/search_helpers.rb | 5 - spec/support/seed.rb | 7 + spec/support/seed_helper.rb | 118 ----- spec/support/seed_repo.rb | 153 ------ spec/support/select2_helper.rb | 35 -- spec/support/selection_helper.rb | 6 - spec/support/services_shared_context.rb | 37 -- .../json_response_shared_context.rb | 3 + .../shared_contexts/services_shared_context.rb | 37 ++ .../chat_slash_commands_shared_examples.rb | 97 ++++ .../email_format_shared_examples.rb | 44 ++ spec/support/shared_examples/gitlab_verify.rb | 19 + .../group_members_shared_example.rb | 27 ++ .../shared_examples/issuable_shared_examples.rb | 38 ++ .../issuables_list_metadata_shared_examples.rb | 46 ++ .../issue_tracker_service_shared_example.rb | 22 + .../shared_examples/ldap_shared_examples.rb | 69 +++ .../legacy_path_redirect_shared_examples.rb | 13 + .../malicious_regexp_shared_examples.rb | 8 + .../shared_examples/mentionable_shared_examples.rb | 144 ++++++ .../shared_examples/milestone_tabs_examples.rb | 84 ++++ .../shared_examples/notify_shared_examples.rb | 199 ++++++++ .../reference_parser_shared_examples.rb | 47 ++ ...ack_mattermost_notifications_shared_examples.rb | 429 +++++++++++++++++ spec/support/shared_examples/snippet_visibility.rb | 322 +++++++++++++ .../shared_examples/snippets_shared_examples.rb | 18 + .../shared_examples/taskable_shared_examples.rb | 108 +++++ .../time_tracking_shared_examples.rb | 85 ++++ .../unique_ip_check_shared_examples.rb | 68 +++ .../shared_examples/update_invalid_issuable.rb | 57 +++ .../updating_mentions_shared_examples.rb | 36 ++ ...ack_mattermost_notifications_shared_examples.rb | 429 ----------------- spec/support/snippet_visibility.rb | 322 ------------- spec/support/snippets_shared_examples.rb | 18 - spec/support/stub_configuration.rb | 99 ---- spec/support/stub_env.rb | 36 -- spec/support/stub_feature_flags.rb | 8 - spec/support/stub_gitlab_calls.rb | 129 ----- spec/support/stub_gitlab_data.rb | 5 - spec/support/stub_object_storage.rb | 48 -- spec/support/taskable_shared_examples.rb | 108 ----- spec/support/test_env.rb | 366 --------------- spec/support/time_tracking_shared_examples.rb | 85 ---- spec/support/unique_ip_check_shared_examples.rb | 68 --- spec/support/update_invalid_issuable.rb | 57 --- spec/support/updating_mentions_shared_examples.rb | 36 -- spec/support/upload_helpers.rb | 16 - spec/support/user_activities_helpers.rb | 7 - spec/support/wait_for_requests.rb | 78 ---- spec/support/workhorse_helpers.rb | 21 - 188 files changed, 6099 insertions(+), 6073 deletions(-) delete mode 100644 spec/support/api_helpers.rb delete mode 100644 spec/support/background_migrations_matchers.rb delete mode 100644 spec/support/bare_repo_operations.rb delete mode 100644 spec/support/board_helpers.rb delete mode 100644 spec/support/capybara_helpers.rb delete mode 100644 spec/support/chat_slash_commands_shared_examples.rb delete mode 100644 spec/support/cookie_helper.rb delete mode 100644 spec/support/cycle_analytics_helpers.rb delete mode 100644 spec/support/database_connection_helpers.rb delete mode 100644 spec/support/devise_helpers.rb delete mode 100644 spec/support/drag_to_helper.rb delete mode 100644 spec/support/dropzone_helper.rb delete mode 100644 spec/support/email_format_shared_examples.rb delete mode 100644 spec/support/email_helpers.rb delete mode 100644 spec/support/factory_bot.rb delete mode 100644 spec/support/fake_migration_classes.rb delete mode 100644 spec/support/fake_u2f_device.rb delete mode 100644 spec/support/filter_item_select_helper.rb delete mode 100644 spec/support/filter_spec_helper.rb delete mode 100644 spec/support/filtered_search_helpers.rb delete mode 100644 spec/support/fixture_helpers.rb delete mode 100644 spec/support/git_http_helpers.rb delete mode 100644 spec/support/gitlab_verify.rb delete mode 100644 spec/support/gpg_helpers.rb delete mode 100644 spec/support/group_members_shared_example.rb create mode 100644 spec/support/helpers/api_helpers.rb create mode 100644 spec/support/helpers/bare_repo_operations.rb create mode 100644 spec/support/helpers/board_helpers.rb create mode 100644 spec/support/helpers/capybara_helpers.rb create mode 100644 spec/support/helpers/cookie_helper.rb create mode 100644 spec/support/helpers/cycle_analytics_helpers.rb create mode 100644 spec/support/helpers/database_connection_helpers.rb create mode 100644 spec/support/helpers/devise_helpers.rb create mode 100644 spec/support/helpers/drag_to_helper.rb create mode 100644 spec/support/helpers/dropzone_helper.rb create mode 100644 spec/support/helpers/email_helpers.rb create mode 100644 spec/support/helpers/fake_migration_classes.rb create mode 100644 spec/support/helpers/fake_u2f_device.rb create mode 100644 spec/support/helpers/filter_item_select_helper.rb create mode 100644 spec/support/helpers/filter_spec_helper.rb create mode 100644 spec/support/helpers/filtered_search_helpers.rb create mode 100644 spec/support/helpers/fixture_helpers.rb create mode 100644 spec/support/helpers/git_http_helpers.rb create mode 100644 spec/support/helpers/gitlab_verify_helpers.rb create mode 100644 spec/support/helpers/gpg_helpers.rb create mode 100644 spec/support/helpers/import_spec_helper.rb create mode 100644 spec/support/helpers/input_helper.rb create mode 100644 spec/support/helpers/inspect_requests.rb create mode 100644 spec/support/helpers/issue_helpers.rb create mode 100644 spec/support/helpers/javascript_fixtures_helpers.rb create mode 100644 spec/support/helpers/jira_service_helper.rb create mode 100644 spec/support/helpers/kubernetes_helpers.rb create mode 100644 spec/support/helpers/ldap_helpers.rb create mode 100644 spec/support/helpers/live_debugger.rb create mode 100644 spec/support/helpers/login_helpers.rb create mode 100644 spec/support/helpers/markdown_feature.rb create mode 100644 spec/support/helpers/merge_request_helpers.rb create mode 100644 spec/support/helpers/migrations_helpers.rb create mode 100644 spec/support/helpers/mobile_helpers.rb create mode 100644 spec/support/helpers/project_forks_helper.rb create mode 100644 spec/support/helpers/prometheus_helpers.rb create mode 100644 spec/support/helpers/query_recorder.rb create mode 100644 spec/support/helpers/quick_actions_helpers.rb create mode 100644 spec/support/helpers/rake_helpers.rb create mode 100644 spec/support/helpers/reactive_caching_helpers.rb create mode 100644 spec/support/helpers/redis_without_keys.rb create mode 100644 spec/support/helpers/reference_parser_helpers.rb create mode 100644 spec/support/helpers/repo_helpers.rb create mode 100644 spec/support/helpers/search_helpers.rb create mode 100644 spec/support/helpers/seed_helper.rb create mode 100644 spec/support/helpers/seed_repo.rb create mode 100644 spec/support/helpers/select2_helper.rb create mode 100644 spec/support/helpers/selection_helper.rb create mode 100644 spec/support/helpers/sorting_helper.rb create mode 100644 spec/support/helpers/stub_configuration.rb create mode 100644 spec/support/helpers/stub_env.rb create mode 100644 spec/support/helpers/stub_feature_flags.rb create mode 100644 spec/support/helpers/stub_gitlab_calls.rb create mode 100644 spec/support/helpers/stub_gitlab_data.rb create mode 100644 spec/support/helpers/stub_object_storage.rb create mode 100644 spec/support/helpers/test_env.rb create mode 100644 spec/support/helpers/upload_helpers.rb create mode 100644 spec/support/helpers/user_activities_helpers.rb create mode 100644 spec/support/helpers/wait_for_requests.rb create mode 100644 spec/support/helpers/workhorse_helpers.rb delete mode 100644 spec/support/import_spec_helper.rb delete mode 100644 spec/support/input_helper.rb delete mode 100644 spec/support/inspect_requests.rb delete mode 100644 spec/support/issuable_shared_examples.rb delete mode 100644 spec/support/issuables_list_metadata_shared_examples.rb delete mode 100644 spec/support/issue_helpers.rb delete mode 100644 spec/support/issue_tracker_service_shared_example.rb delete mode 100644 spec/support/javascript_fixtures_helpers.rb delete mode 100644 spec/support/jira_service_helper.rb create mode 100644 spec/support/json_response.rb delete mode 100644 spec/support/json_response_helpers.rb delete mode 100644 spec/support/kubernetes_helpers.rb delete mode 100644 spec/support/ldap_helpers.rb delete mode 100644 spec/support/ldap_shared_examples.rb delete mode 100644 spec/support/legacy_path_redirect_shared_examples.rb delete mode 100644 spec/support/live_debugger.rb delete mode 100644 spec/support/login_helpers.rb delete mode 100644 spec/support/malicious_regexp_shared_examples.rb delete mode 100644 spec/support/markdown_feature.rb create mode 100644 spec/support/matchers/background_migrations_matchers.rb create mode 100644 spec/support/matchers/exceed_query_limit.rb delete mode 100644 spec/support/mentionable_shared_examples.rb delete mode 100644 spec/support/merge_request_helpers.rb delete mode 100644 spec/support/migrations_helpers.rb delete mode 100644 spec/support/milestone_tabs_examples.rb delete mode 100644 spec/support/mobile_helpers.rb delete mode 100644 spec/support/notify_shared_examples.rb delete mode 100644 spec/support/project_forks_helper.rb delete mode 100644 spec/support/prometheus_helpers.rb delete mode 100644 spec/support/query_recorder.rb delete mode 100644 spec/support/rake_helpers.rb delete mode 100644 spec/support/reactive_caching_helpers.rb delete mode 100644 spec/support/redis_without_keys.rb delete mode 100644 spec/support/reference_parser_helpers.rb delete mode 100644 spec/support/reference_parser_shared_examples.rb delete mode 100644 spec/support/repo_helpers.rb delete mode 100644 spec/support/routing_helpers.rb create mode 100644 spec/support/rspec.rb delete mode 100644 spec/support/search_helpers.rb create mode 100644 spec/support/seed.rb delete mode 100644 spec/support/seed_helper.rb delete mode 100644 spec/support/seed_repo.rb delete mode 100644 spec/support/select2_helper.rb delete mode 100644 spec/support/selection_helper.rb delete mode 100644 spec/support/services_shared_context.rb create mode 100644 spec/support/shared_contexts/json_response_shared_context.rb create mode 100644 spec/support/shared_contexts/services_shared_context.rb create mode 100644 spec/support/shared_examples/chat_slash_commands_shared_examples.rb create mode 100644 spec/support/shared_examples/email_format_shared_examples.rb create mode 100644 spec/support/shared_examples/gitlab_verify.rb create mode 100644 spec/support/shared_examples/group_members_shared_example.rb create mode 100644 spec/support/shared_examples/issuable_shared_examples.rb create mode 100644 spec/support/shared_examples/issuables_list_metadata_shared_examples.rb create mode 100644 spec/support/shared_examples/issue_tracker_service_shared_example.rb create mode 100644 spec/support/shared_examples/ldap_shared_examples.rb create mode 100644 spec/support/shared_examples/legacy_path_redirect_shared_examples.rb create mode 100644 spec/support/shared_examples/malicious_regexp_shared_examples.rb create mode 100644 spec/support/shared_examples/mentionable_shared_examples.rb create mode 100644 spec/support/shared_examples/milestone_tabs_examples.rb create mode 100644 spec/support/shared_examples/notify_shared_examples.rb create mode 100644 spec/support/shared_examples/reference_parser_shared_examples.rb create mode 100644 spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb create mode 100644 spec/support/shared_examples/snippet_visibility.rb create mode 100644 spec/support/shared_examples/snippets_shared_examples.rb create mode 100644 spec/support/shared_examples/taskable_shared_examples.rb create mode 100644 spec/support/shared_examples/time_tracking_shared_examples.rb create mode 100644 spec/support/shared_examples/unique_ip_check_shared_examples.rb create mode 100644 spec/support/shared_examples/update_invalid_issuable.rb create mode 100644 spec/support/shared_examples/updating_mentions_shared_examples.rb delete mode 100644 spec/support/slack_mattermost_notifications_shared_examples.rb delete mode 100644 spec/support/snippet_visibility.rb delete mode 100644 spec/support/snippets_shared_examples.rb delete mode 100644 spec/support/stub_configuration.rb delete mode 100644 spec/support/stub_env.rb delete mode 100644 spec/support/stub_feature_flags.rb delete mode 100644 spec/support/stub_gitlab_calls.rb delete mode 100644 spec/support/stub_gitlab_data.rb delete mode 100644 spec/support/stub_object_storage.rb delete mode 100644 spec/support/taskable_shared_examples.rb delete mode 100644 spec/support/test_env.rb delete mode 100644 spec/support/time_tracking_shared_examples.rb delete mode 100644 spec/support/unique_ip_check_shared_examples.rb delete mode 100644 spec/support/update_invalid_issuable.rb delete mode 100644 spec/support/updating_mentions_shared_examples.rb delete mode 100644 spec/support/upload_helpers.rb delete mode 100644 spec/support/user_activities_helpers.rb delete mode 100644 spec/support/wait_for_requests.rb delete mode 100644 spec/support/workhorse_helpers.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d443238b9e1..16b0b5c95e2 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -143,7 +143,7 @@ Lint/MissingCopEnableDirective: Lint/NestedPercentLiteral: Exclude: - 'lib/gitlab/git/repository.rb' - - 'spec/support/email_format_shared_examples.rb' + - 'spec/support/shared_examples/email_format_shared_examples.rb' # Offense count: 1 Lint/ReturnInVoidContext: @@ -195,8 +195,8 @@ Naming/HeredocDelimiterCase: - 'spec/lib/gitlab/diff/parser_spec.rb' - 'spec/lib/json_web_token/rsa_token_spec.rb' - 'spec/models/commit_spec.rb' - - 'spec/support/repo_helpers.rb' - - 'spec/support/seed_repo.rb' + - 'spec/support/helpers/repo_helpers.rb' + - 'spec/support/helpers/seed_repo.rb' # Offense count: 112 # Configuration parameters: Blacklist. @@ -496,7 +496,7 @@ Style/EmptyLiteral: - 'spec/lib/gitlab/request_context_spec.rb' - 'spec/lib/gitlab/workhorse_spec.rb' - 'spec/requests/api/jobs_spec.rb' - - 'spec/support/chat_slash_commands_shared_examples.rb' + - 'spec/support/shared_examples/chat_slash_commands_shared_examples.rb' # Offense count: 102 # Cop supports --auto-correct. diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb index d7be6f5950f..7b9a4bad449 100644 --- a/db/fixtures/development/17_cycle_analytics.rb +++ b/db/fixtures/development/17_cycle_analytics.rb @@ -1,5 +1,5 @@ require './spec/support/sidekiq' -require './spec/support/test_env' +require './spec/support/helpers/test_env' class Gitlab::Seeder::CycleAnalytics def initialize(project, perf: false) diff --git a/doc/development/testing_guide/testing_rake_tasks.md b/doc/development/testing_guide/testing_rake_tasks.md index 60163f1a230..db8ca87e9f8 100644 --- a/doc/development/testing_guide/testing_rake_tasks.md +++ b/doc/development/testing_guide/testing_rake_tasks.md @@ -9,7 +9,7 @@ At a minimum, requiring the Rake helper will redirect `stdout`, include the runtime task helpers, and include the `RakeHelpers` Spec support module. The `RakeHelpers` module exposes a `run_rake_task()` method to make -executing tasks simple. See `spec/support/rake_helpers.rb` for all available +executing tasks simple. See `spec/support/helpers/rake_helpers.rb` for all available methods. Example: diff --git a/features/support/env.rb b/features/support/env.rb index 15211995918..8fa2fcb6e3e 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -12,7 +12,11 @@ end WebMock.enable! -%w(select2_helper test_env repo_helpers wait_for_requests sidekiq project_forks_helper webmock).each do |f| +%w(select2_helper test_env repo_helpers wait_for_requests project_forks_helper).each do |f| + require Rails.root.join('spec', 'support', 'helpers', f) +end + +%w(sidekiq webmock).each do |f| require Rails.root.join('spec', 'support', f) end diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb index d5d819d862a..818f7b046f6 100644 --- a/spec/factories/commits.rb +++ b/spec/factories/commits.rb @@ -1,4 +1,4 @@ -require_relative '../support/repo_helpers' +require_relative '../support/helpers/repo_helpers' FactoryBot.define do factory :commit do diff --git a/spec/factories/gpg_key_subkeys.rb b/spec/factories/gpg_key_subkeys.rb index 57eaaee345f..6c7db5379a9 100644 --- a/spec/factories/gpg_key_subkeys.rb +++ b/spec/factories/gpg_key_subkeys.rb @@ -1,5 +1,3 @@ -require_relative '../support/gpg_helpers' - FactoryBot.define do factory :gpg_key_subkey do gpg_key diff --git a/spec/factories/gpg_keys.rb b/spec/factories/gpg_keys.rb index b8aabf74221..51b8ddc9934 100644 --- a/spec/factories/gpg_keys.rb +++ b/spec/factories/gpg_keys.rb @@ -1,4 +1,4 @@ -require_relative '../support/gpg_helpers' +require_relative '../support/helpers/gpg_helpers' FactoryBot.define do factory :gpg_key do diff --git a/spec/factories/gpg_signature.rb b/spec/factories/gpg_signature.rb index 4620caff823..b89e6ffc4b3 100644 --- a/spec/factories/gpg_signature.rb +++ b/spec/factories/gpg_signature.rb @@ -1,5 +1,3 @@ -require_relative '../support/gpg_helpers' - FactoryBot.define do factory :gpg_signature do commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) } diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 857333f222d..40f3fa7d69b 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -1,4 +1,4 @@ -require_relative '../support/repo_helpers' +require_relative '../support/helpers/repo_helpers' include ActionDispatch::TestProcess diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 1761b6e2a3b..1ae6152a1f0 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -1,4 +1,4 @@ -require_relative '../support/test_env' +require_relative '../support/helpers/test_env' FactoryBot.define do # Project without repository diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 83664bae046..53045815a6a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,42 +32,19 @@ require 'rainbow/ext/string' # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. +# Requires helpers, and shared contexts/examples first since they're used in other support files +Dir[Rails.root.join("spec/support/helpers/*.rb")].each { |f| require f } +Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f } +Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } RSpec.configure do |config| config.use_transactional_fixtures = false config.use_instantiated_fixtures = false - config.mock_with :rspec config.verbose_retry = true config.display_try_failure_messages = true - config.include Devise::Test::ControllerHelpers, type: :controller - config.include Devise::Test::ControllerHelpers, type: :view - config.include Devise::Test::IntegrationHelpers, type: :feature - config.include Warden::Test::Helpers, type: :request - config.include LoginHelpers, type: :feature - config.include SearchHelpers, type: :feature - config.include CookieHelper, :js - config.include InputHelper, :js - config.include SelectionHelper, :js - config.include InspectRequests, :js - config.include WaitForRequests, :js - config.include LiveDebugger, :js - config.include StubConfiguration - config.include EmailHelpers, :mailer, type: :mailer - config.include TestEnv - config.include ActiveJob::TestHelper - config.include ActiveSupport::Testing::TimeHelpers - config.include StubGitlabCalls - config.include StubGitlabData - config.include ApiHelpers, :api - config.include Gitlab::Routing, type: :routing - config.include MigrationsHelpers, :migration - config.include StubFeatureFlags - config.include StubENV - config.include ExpectOffense - config.infer_spec_type_from_file_location! config.define_derived_metadata(file_path: %r{/spec/}) do |metadata| @@ -82,7 +59,33 @@ RSpec.configure do |config| metadata[:type] = match[1].singularize.to_sym if match end - config.raise_errors_for_deprecations! + config.include ActiveJob::TestHelper + config.include ActiveSupport::Testing::TimeHelpers + config.include CycleAnalyticsHelpers + config.include ExpectOffense + config.include FactoryBot::Syntax::Methods + config.include FixtureHelpers + config.include GitlabRoutingHelper + config.include StubFeatureFlags + config.include StubGitlabCalls + config.include StubGitlabData + config.include TestEnv + config.include Devise::Test::ControllerHelpers, type: :controller + config.include Devise::Test::IntegrationHelpers, type: :feature + config.include LoginHelpers, type: :feature + config.include SearchHelpers, type: :feature + config.include EmailHelpers, :mailer, type: :mailer + config.include Warden::Test::Helpers, type: :request + config.include Gitlab::Routing, type: :routing + config.include Devise::Test::ControllerHelpers, type: :view + config.include ApiHelpers, :api + config.include CookieHelper, :js + config.include InputHelper, :js + config.include SelectionHelper, :js + config.include InspectRequests, :js + config.include WaitForRequests, :js + config.include LiveDebugger, :js + config.include MigrationsHelpers, :migration if ENV['CI'] # This includes the first try, i.e. tests will be run 4 times before failing. diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb deleted file mode 100644 index ac0c7a9b493..00000000000 --- a/spec/support/api_helpers.rb +++ /dev/null @@ -1,50 +0,0 @@ -module ApiHelpers - # Public: Prepend a request path with the path to the API - # - # path - Path to append - # user - User object - If provided, automatically appends private_token query - # string for authenticated requests - # - # Examples - # - # >> api('/issues') - # => "/api/v2/issues" - # - # >> api('/issues', User.last) - # => "/api/v2/issues?private_token=..." - # - # >> api('/issues?foo=bar', User.last) - # => "/api/v2/issues?foo=bar&private_token=..." - # - # Returns the relative path to the requested API resource - def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil) - full_path = "/api/#{version}#{path}" - - if oauth_access_token - query_string = "access_token=#{oauth_access_token.token}" - elsif personal_access_token - query_string = "private_token=#{personal_access_token.token}" - elsif user - personal_access_token = create(:personal_access_token, user: user) - query_string = "private_token=#{personal_access_token.token}" - end - - if query_string - full_path << (path.index('?') ? '&' : '?') - full_path << query_string - end - - full_path - end - - # Temporary helper method for simplifying V3 exclusive API specs - def v3_api(path, user = nil, personal_access_token: nil, oauth_access_token: nil) - api( - path, - user, - version: 'v3', - personal_access_token: personal_access_token, - oauth_access_token: oauth_access_token - ) - end -end diff --git a/spec/support/background_migrations_matchers.rb b/spec/support/background_migrations_matchers.rb deleted file mode 100644 index f4127efc6ae..00000000000 --- a/spec/support/background_migrations_matchers.rb +++ /dev/null @@ -1,26 +0,0 @@ -RSpec::Matchers.define :be_scheduled_delayed_migration do |delay, *expected| - match do |migration| - BackgroundMigrationWorker.jobs.any? do |job| - job['args'] == [migration, expected] && - job['at'].to_i == (delay.to_i + Time.now.to_i) - end - end - - failure_message do |migration| - "Migration `#{migration}` with args `#{expected.inspect}` " \ - 'not scheduled in expected time!' - end -end - -RSpec::Matchers.define :be_scheduled_migration do |*expected| - match do |migration| - BackgroundMigrationWorker.jobs.any? do |job| - args = job['args'].size == 1 ? [BackgroundMigrationWorker.jobs[0]['args'][0], []] : job['args'] - args == [migration, expected] - end - end - - failure_message do |migration| - "Migration `#{migration}` with args `#{expected.inspect}` not scheduled!" - end -end diff --git a/spec/support/bare_repo_operations.rb b/spec/support/bare_repo_operations.rb deleted file mode 100644 index 3f4a4243cb6..00000000000 --- a/spec/support/bare_repo_operations.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'zlib' - -class BareRepoOperations - include Gitlab::Popen - - def initialize(path_to_repo) - @path_to_repo = path_to_repo - end - - def commit_tree(tree_id, msg, parent: Gitlab::Git::EMPTY_TREE_ID) - commit_tree_args = ['commit-tree', tree_id, '-m', msg] - commit_tree_args += ['-p', parent] unless parent == Gitlab::Git::EMPTY_TREE_ID - commit_id = execute(commit_tree_args) - - commit_id[0] - end - - # Based on https://stackoverflow.com/a/25556917/1856239 - def commit_file(file, dst_path, branch = 'master') - head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || Gitlab::Git::EMPTY_TREE_ID - - execute(['read-tree', '--empty']) - execute(['read-tree', head_id]) - - blob_id = execute(['hash-object', '--stdin', '-w']) do |stdin| - stdin.write(file.read) - end - - execute(['update-index', '--add', '--cacheinfo', '100644', blob_id[0], dst_path]) - - tree_id = execute(['write-tree']) - - commit_id = commit_tree(tree_id[0], "Add #{dst_path}", parent: head_id) - - execute(['update-ref', "refs/heads/#{branch}", commit_id]) - end - - private - - def execute(args, allow_failure: false) - output, status = popen(base_args + args, nil) do |stdin| - yield stdin if block_given? - end - - unless status.zero? - if allow_failure - return [] - else - raise "Got a non-zero exit code while calling out `#{args.join(' ')}`: #{output}" - end - end - - output.split("\n") - end - - def base_args - [ - Gitlab.config.git.bin_path, - "--git-dir=#{@path_to_repo}" - ] - end -end diff --git a/spec/support/board_helpers.rb b/spec/support/board_helpers.rb deleted file mode 100644 index 507d0432d7f..00000000000 --- a/spec/support/board_helpers.rb +++ /dev/null @@ -1,16 +0,0 @@ -module BoardHelpers - def click_card(card) - within card do - first('.card-number').click - end - - wait_for_sidebar - end - - def wait_for_sidebar - # loop until the CSS transition is complete - Timeout.timeout(0.5) do - loop until evaluate_script('$(".right-sidebar").outerWidth()') == 290 - end - end -end diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 9ddcc5f2fbf..c0ceb0f6605 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -60,6 +60,8 @@ Capybara::Screenshot.register_driver(:chrome) do |driver, path| end RSpec.configure do |config| + config.include CapybaraHelpers, type: :feature + config.before(:context, :js) do next if $capybara_server_already_started diff --git a/spec/support/capybara_helpers.rb b/spec/support/capybara_helpers.rb deleted file mode 100644 index 868233416bf..00000000000 --- a/spec/support/capybara_helpers.rb +++ /dev/null @@ -1,47 +0,0 @@ -module CapybaraHelpers - # Execute a block a certain number of times before considering it a failure - # - # The given block is called, and if it raises a `Capybara::ExpectationNotMet` - # error, we wait `interval` seconds and then try again, until `retries` is - # met. - # - # This allows for better handling of timing-sensitive expectations in a - # sketchy CI environment, for example. - # - # interval - Delay between retries in seconds (default: 0.5) - # retries - Number of times to execute before failing (default: 5) - def allowing_for_delay(interval: 0.5, retries: 5) - tries = 0 - - begin - sleep interval - - yield - rescue Capybara::ExpectationNotMet => ex - if tries <= retries - tries += 1 - sleep interval - retry - else - raise ex - end - end - end - - # Refresh the page. Calling `visit current_url` doesn't seem to work consistently. - # - def refresh - url = current_url - visit 'about:blank' - visit url - end - - # Simulate a browser restart by clearing the session cookie. - def clear_browser_session - page.driver.browser.manage.delete_cookie('_gitlab_session') - end -end - -RSpec.configure do |config| - config.include CapybaraHelpers, type: :feature -end diff --git a/spec/support/chat_slash_commands_shared_examples.rb b/spec/support/chat_slash_commands_shared_examples.rb deleted file mode 100644 index dc97a39f051..00000000000 --- a/spec/support/chat_slash_commands_shared_examples.rb +++ /dev/null @@ -1,97 +0,0 @@ -RSpec.shared_examples 'chat slash commands service' do - describe "Associations" do - it { is_expected.to respond_to :token } - it { is_expected.to have_many :chat_names } - end - - describe '#valid_token?' do - subject { described_class.new } - - context 'when the token is empty' do - it 'is false' do - expect(subject.valid_token?('wer')).to be_falsey - end - end - - context 'when there is a token' do - before do - subject.token = '123' - end - - it 'accepts equal tokens' do - expect(subject.valid_token?('123')).to be_truthy - end - end - end - - describe '#trigger' do - subject { described_class.new } - - context 'no token is passed' do - let(:params) { Hash.new } - - it 'returns nil' do - expect(subject.trigger(params)).to be_nil - end - end - - context 'with a token passed' do - let(:project) { create(:project) } - let(:params) { { token: 'token' } } - - before do - allow(subject).to receive(:token).and_return('token') - end - - context 'no user can be found' do - context 'when no url can be generated' do - it 'responds with the authorize url' do - response = subject.trigger(params) - - expect(response[:response_type]).to eq :ephemeral - expect(response[:text]).to start_with ":sweat_smile: Couldn't identify you" - end - end - - context 'when an auth url can be generated' do - let(:params) do - { - team_domain: 'http://domain.tld', - team_id: 'T3423423', - user_id: 'U234234', - user_name: 'mepmep', - token: 'token' - } - end - - let(:service) do - project.create_mattermost_slash_commands_service( - properties: { token: 'token' } - ) - end - - it 'generates the url' do - response = service.trigger(params) - - expect(response[:text]).to start_with(':wave: Hi there!') - end - end - end - - context 'when the user is authenticated' do - let!(:chat_name) { create(:chat_name, service: subject) } - let(:params) { { token: 'token', team_id: chat_name.team_id, user_id: chat_name.chat_id } } - - subject do - described_class.create(project: project, properties: { token: 'token' }) - end - - it 'triggers the command' do - expect_any_instance_of(Gitlab::SlashCommands::Command).to receive(:execute) - - subject.trigger(params) - end - end - end - end -end diff --git a/spec/support/cookie_helper.rb b/spec/support/cookie_helper.rb deleted file mode 100644 index 5ff7b0b68c9..00000000000 --- a/spec/support/cookie_helper.rb +++ /dev/null @@ -1,34 +0,0 @@ -# Helper for setting cookies in Selenium/WebDriver -# -module CookieHelper - def set_cookie(name, value, options = {}) - case page.driver - when Capybara::RackTest::Driver - rack_set_cookie(name, value) - else - selenium_set_cookie(name, value, options) - end - end - - def selenium_set_cookie(name, value, options = {}) - # Selenium driver will not set cookies for a given domain when the browser is at `about:blank`. - # It also doesn't appear to allow overriding the cookie path. loading `/` is the most inclusive. - visit options.fetch(:path, '/') unless on_a_page? - page.driver.browser.manage.add_cookie(name: name, value: value, **options) - end - - def rack_set_cookie(name, value) - page.driver.browser.set_cookie("#{name}=#{value}") - end - - def get_cookie(name) - page.driver.browser.manage.cookie_named(name) - end - - private - - def on_a_page? - current_url = Capybara.current_session.driver.browser.current_url - current_url && current_url != '' && current_url != 'about:blank' && current_url != 'data:,' - end -end diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb deleted file mode 100644 index 73cc64c0b74..00000000000 --- a/spec/support/cycle_analytics_helpers.rb +++ /dev/null @@ -1,141 +0,0 @@ -module CycleAnalyticsHelpers - def create_commit_referencing_issue(issue, branch_name: generate(:branch)) - project.repository.add_branch(user, branch_name, 'master') - create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name) - end - - def create_commit(message, project, user, branch_name, count: 1) - repository = project.repository - oldrev = repository.commit(branch_name).sha - - if Timecop.frozen? && Gitlab::GitalyClient.feature_enabled?(:operation_user_commit_files) - mock_gitaly_multi_action_dates(repository.raw) - end - - commit_shas = Array.new(count) do |index| - commit_sha = repository.create_file(user, generate(:branch), "content", message: message, branch_name: branch_name) - repository.commit(commit_sha) - - commit_sha - end - - GitPushService.new(project, - user, - oldrev: oldrev, - newrev: commit_shas.last, - ref: 'refs/heads/master').execute - end - - def create_cycle(user, project, issue, mr, milestone, pipeline) - issue.update(milestone: milestone) - pipeline.run - - ci_build = create(:ci_build, pipeline: pipeline, status: :success, author: user) - - merge_merge_requests_closing_issue(user, project, issue) - ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.to_hash) - - ci_build - end - - def create_merge_request_closing_issue(user, project, issue, message: nil, source_branch: nil, commit_message: 'commit message') - if !source_branch || project.repository.commit(source_branch).blank? - source_branch = generate(:branch) - project.repository.add_branch(user, source_branch, 'master') - end - - sha = project.repository.create_file( - user, - generate(:branch), - 'content', - message: commit_message, - branch_name: source_branch) - project.repository.commit(sha) - - opts = { - title: 'Awesome merge_request', - description: message || "Fixes #{issue.to_reference}", - source_branch: source_branch, - target_branch: 'master' - } - - mr = MergeRequests::CreateService.new(project, user, opts).execute - NewMergeRequestWorker.new.perform(mr, user) - mr - end - - def merge_merge_requests_closing_issue(user, project, issue) - merge_requests = issue.closed_by_merge_requests(user) - - merge_requests.each { |merge_request| MergeRequests::MergeService.new(project, user).execute(merge_request) } - end - - def deploy_master(user, project, environment: 'production') - dummy_job = - case environment - when 'production' - dummy_production_job(user, project) - when 'staging' - dummy_staging_job(user, project) - else - raise ArgumentError - end - - CreateDeploymentService.new(dummy_job).execute - end - - def dummy_production_job(user, project) - new_dummy_job(user, project, 'production') - end - - def dummy_staging_job(user, project) - new_dummy_job(user, project, 'staging') - end - - def dummy_pipeline(project) - Ci::Pipeline.new( - sha: project.repository.commit('master').sha, - ref: 'master', - source: :push, - project: project, - protected: false) - end - - def new_dummy_job(user, project, environment) - project.environments.find_or_create_by(name: environment) - - Ci::Build.new( - project: project, - user: user, - environment: environment, - ref: 'master', - tag: false, - name: 'dummy', - stage: 'dummy', - pipeline: dummy_pipeline(project), - protected: false) - end - - def mock_gitaly_multi_action_dates(raw_repository) - allow(raw_repository).to receive(:multi_action).and_wrap_original do |m, *args| - new_date = Time.now - branch_update = m.call(*args) - - if branch_update.newrev - _, opts = args - commit = raw_repository.commit(branch_update.newrev).rugged_commit - branch_update.newrev = commit.amend( - update_ref: "#{Gitlab::Git::BRANCH_REF_PREFIX}#{opts[:branch_name]}", - author: commit.author.merge(time: new_date), - committer: commit.committer.merge(time: new_date) - ) - end - - branch_update - end - end -end - -RSpec.configure do |config| - config.include CycleAnalyticsHelpers -end diff --git a/spec/support/database_connection_helpers.rb b/spec/support/database_connection_helpers.rb deleted file mode 100644 index 763329499f0..00000000000 --- a/spec/support/database_connection_helpers.rb +++ /dev/null @@ -1,9 +0,0 @@ -module DatabaseConnectionHelpers - def run_with_new_database_connection - pool = ActiveRecord::Base.connection_pool - conn = pool.checkout - yield conn - ensure - pool.checkin(conn) - end -end diff --git a/spec/support/devise_helpers.rb b/spec/support/devise_helpers.rb deleted file mode 100644 index 66874e10f38..00000000000 --- a/spec/support/devise_helpers.rb +++ /dev/null @@ -1,17 +0,0 @@ -module DeviseHelpers - # explicitly tells Devise which mapping to use - # this is needed when we are testing a Devise controller bypassing the router - def set_devise_mapping(context:) - env = env_from_context(context) - - env['devise.mapping'] = Devise.mappings[:user] if env - end - - def env_from_context(context) - if context.respond_to?(:env_config) - context.env_config - elsif context.respond_to?(:env) - context.env - end - end -end diff --git a/spec/support/drag_to_helper.rb b/spec/support/drag_to_helper.rb deleted file mode 100644 index ae149631ed9..00000000000 --- a/spec/support/drag_to_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -module DragTo - def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body') - evaluate_script("simulateDrag({scrollable: $('#{scrollable}').get(0), from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{from_index}}, to: {el: $('#{selector}').eq(#{list_to_index}).get(0), index: #{to_index}}});") - - Timeout.timeout(Capybara.default_max_wait_time) do - loop while drag_active? - end - end - - def drag_active? - page.evaluate_script('window.SIMULATE_DRAG_ACTIVE').nonzero? - end -end diff --git a/spec/support/dropzone_helper.rb b/spec/support/dropzone_helper.rb deleted file mode 100644 index fe72d320fcf..00000000000 --- a/spec/support/dropzone_helper.rb +++ /dev/null @@ -1,76 +0,0 @@ -module DropzoneHelper - # Provides a way to perform `attach_file` for a Dropzone-based file input - # - # This is accomplished by creating a standard HTML file input on the page, - # performing `attach_file` on that field, and then triggering the appropriate - # Dropzone events to perform the actual upload. - # - # This method waits for the upload to complete before returning. - # max_file_size is an optional parameter. - # If it's not 0, then it used in dropzone.maxFilesize parameter. - # wait_for_queuecomplete is an optional parameter. - # If it's 'false', then the helper will NOT wait for backend response - # It lets to test behaviors while AJAX is processing. - def dropzone_file(files, max_file_size = 0, wait_for_queuecomplete = true) - # Generate a fake file input that Capybara can attach to - page.execute_script <<-JS.strip_heredoc - $('#fakeFileInput').remove(); - var fakeFileInput = window.$('').attr( - {id: 'fakeFileInput', type: 'file', multiple: true} - ).appendTo('body'); - - window._dropzoneComplete = false; - JS - - # Attach files to the fake input selector with Capybara - attach_file('fakeFileInput', files) - - # Manually trigger a Dropzone "drop" event with the fake input's file list - page.execute_script <<-JS.strip_heredoc - var dropzone = $('.div-dropzone')[0].dropzone; - dropzone.options.autoProcessQueue = false; - - if (#{max_file_size} > 0) { - dropzone.options.maxFilesize = #{max_file_size}; - } - - dropzone.on('queuecomplete', function() { - window._dropzoneComplete = true; - }); - - var fileList = [$('#fakeFileInput')[0].files]; - - $.map(fileList, function(file){ - var e = jQuery.Event('drop', { dataTransfer : { files : file } }); - - dropzone.listeners[0].events.drop(e); - }); - - dropzone.processQueue(); - JS - - if wait_for_queuecomplete - # Wait until Dropzone's fired `queuecomplete` - loop until page.evaluate_script('window._dropzoneComplete === true') - end - end - - def drop_in_dropzone(file_path) - # Generate a fake input selector - page.execute_script <<-JS - var fakeFileInput = window.$('').attr( - {id: 'fakeFileInput', type: 'file'} - ).appendTo('body'); - JS - - # Attach the file to the fake input selector with Capybara - attach_file('fakeFileInput', file_path) - - # Add the file to a fileList array and trigger the fake drop event - page.execute_script <<-JS - var fileList = [$('#fakeFileInput')[0].files[0]]; - var e = jQuery.Event('drop', { dataTransfer : { files : fileList } }); - $('.dropzone')[0].dropzone.listeners[0].events.drop(e); - JS - end -end diff --git a/spec/support/email_format_shared_examples.rb b/spec/support/email_format_shared_examples.rb deleted file mode 100644 index b924a208e71..00000000000 --- a/spec/support/email_format_shared_examples.rb +++ /dev/null @@ -1,44 +0,0 @@ -# Specifications for behavior common to all objects with an email attribute. -# Takes a list of email-format attributes and requires: -# - subject { "the object with a attribute= setter" } -# Note: You have access to `email_value` which is the email address value -# being currently tested). - -shared_examples 'an object with email-formated attributes' do |*attributes| - attributes.each do |attribute| - describe "specifically its :#{attribute} attribute" do - %w[ - info@example.com - info+test@example.com - o'reilly@example.com - mailto:test@example.com - lol!'+=?><#$%^&*()@gmail.com - ].each do |valid_email| - context "with a value of '#{valid_email}'" do - let(:email_value) { valid_email } - - it 'is valid' do - subject.send("#{attribute}=", valid_email) - - expect(subject).to be_valid - end - end - end - - %w[ - foobar - test@test@example.com - ].each do |invalid_email| - context "with a value of '#{invalid_email}'" do - let(:email_value) { invalid_email } - - it 'is invalid' do - subject.send("#{attribute}=", invalid_email) - - expect(subject).to be_invalid - end - end - end - end - end -end diff --git a/spec/support/email_helpers.rb b/spec/support/email_helpers.rb deleted file mode 100644 index 1fb8252459f..00000000000 --- a/spec/support/email_helpers.rb +++ /dev/null @@ -1,37 +0,0 @@ -module EmailHelpers - def sent_to_user(user, recipients: email_recipients) - recipients.count { |to| to == user.notification_email } - end - - def reset_delivered_emails! - ActionMailer::Base.deliveries.clear - end - - def should_only_email(*users, kind: :to) - recipients = email_recipients(kind: kind) - - users.each { |user| should_email(user, recipients: recipients) } - - expect(recipients.count).to eq(users.count) - end - - def should_email(user, times: 1, recipients: email_recipients) - expect(sent_to_user(user, recipients: recipients)).to eq(times) - end - - def should_not_email(user, recipients: email_recipients) - should_email(user, times: 0, recipients: recipients) - end - - def should_not_email_anyone - expect(ActionMailer::Base.deliveries).to be_empty - end - - def email_recipients(kind: :to) - ActionMailer::Base.deliveries.flat_map(&kind) - end - - def find_email_for(user) - ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email) } - end -end diff --git a/spec/support/factory_bot.rb b/spec/support/factory_bot.rb deleted file mode 100644 index c7890e49c66..00000000000 --- a/spec/support/factory_bot.rb +++ /dev/null @@ -1,3 +0,0 @@ -RSpec.configure do |config| - config.include FactoryBot::Syntax::Methods -end diff --git a/spec/support/fake_migration_classes.rb b/spec/support/fake_migration_classes.rb deleted file mode 100644 index b0fc8422857..00000000000 --- a/spec/support/fake_migration_classes.rb +++ /dev/null @@ -1,11 +0,0 @@ -class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration - include Gitlab::Database::RenameReservedPathsMigration::V1 - - def version - '20170316163845' - end - - def name - "FakeRenameReservedPathMigrationV1" - end -end diff --git a/spec/support/fake_u2f_device.rb b/spec/support/fake_u2f_device.rb deleted file mode 100644 index a7605cd483a..00000000000 --- a/spec/support/fake_u2f_device.rb +++ /dev/null @@ -1,40 +0,0 @@ -class FakeU2fDevice - attr_reader :name - - def initialize(page, name) - @page = page - @name = name - end - - def respond_to_u2f_registration - app_id = @page.evaluate_script('gon.u2f.app_id') - challenges = @page.evaluate_script('gon.u2f.challenges') - - json_response = u2f_device(app_id).register_response(challenges[0]) - - @page.execute_script(" - u2f.register = function(appId, registerRequests, signRequests, callback) { - callback(#{json_response}); - }; - ") - end - - def respond_to_u2f_authentication - app_id = @page.evaluate_script('gon.u2f.app_id') - challenge = @page.evaluate_script('gon.u2f.challenge') - json_response = u2f_device(app_id).sign_response(challenge) - - @page.execute_script(" - u2f.sign = function(appId, challenges, signRequests, callback) { - callback(#{json_response}); - }; - window.gl.u2fAuthenticate.start(); - ") - end - - private - - def u2f_device(app_id) - @u2f_device ||= U2F::FakeU2F.new(app_id) - end -end diff --git a/spec/support/filter_item_select_helper.rb b/spec/support/filter_item_select_helper.rb deleted file mode 100644 index 519e84d359e..00000000000 --- a/spec/support/filter_item_select_helper.rb +++ /dev/null @@ -1,19 +0,0 @@ -# Helper allows you to select value from filter-items -# -# Params -# value - value for select -# selector - css selector of item -# -# Usage: -# -# filter_item_select('Any Author', '.js-author-search') -# -module FilterItemSelectHelper - def filter_item_select(value, selector) - find(selector).click - wait_for_requests - page.within('.dropdown-content') do - click_link value - end - end -end diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb deleted file mode 100644 index 721d359c2ee..00000000000 --- a/spec/support/filter_spec_helper.rb +++ /dev/null @@ -1,87 +0,0 @@ -# Helper methods for Banzai filter specs -# -# Must be included into specs manually -module FilterSpecHelper - extend ActiveSupport::Concern - - # Perform `call` on the described class - # - # Automatically passes the current `project` value, if defined, to the context - # if none is provided. - # - # html - HTML String to pass to the filter's `call` method. - # context - Hash context for the filter. (default: {project: project}) - # - # Returns a Nokogiri::XML::DocumentFragment - def filter(html, context = {}) - if defined?(project) - context.reverse_merge!(project: project) - end - - render_context = Banzai::RenderContext - .new(context[:project], context[:current_user]) - - context = context.merge(render_context: render_context) - - described_class.call(html, context) - end - - # Run text through HTML::Pipeline with the current filter and return the - # result Hash - # - # body - String text to run through the pipeline - # context - Hash context for the filter. (default: {project: project}) - # - # Returns the Hash - def pipeline_result(body, context = {}) - context.reverse_merge!(project: project) if defined?(project) - - pipeline = HTML::Pipeline.new([described_class], context) - pipeline.call(body) - end - - def reference_pipeline(context = {}) - context.reverse_merge!(project: project) if defined?(project) - - filters = [ - Banzai::Filter::AutolinkFilter, - described_class - ] - - HTML::Pipeline.new(filters, context) - end - - def reference_pipeline_result(body, context = {}) - reference_pipeline(context).call(body) - end - - def reference_filter(html, context = {}) - reference_pipeline(context).to_document(html) - end - - # Modify a String reference to make it invalid - # - # Commit SHAs get reversed, IDs get incremented by 1, all other Strings get - # their word characters reversed. - # - # reference - String reference to modify - # - # Returns a String - def invalidate_reference(reference) - if reference =~ /\A(.+)?[^\d]\d+\z/ - # Integer-based reference with optional project prefix - reference.gsub(/\d+\z/) { |i| i.to_i + 10_000 } - elsif reference =~ /\A(.+@)?(\h{7,40}\z)/ - # SHA-based reference with optional prefix - reference.gsub(/\h{7,40}\z/) { |v| v.reverse } - else - reference.gsub(/\w+\z/) { |v| v.reverse } - end - end - - # Shortcut to Rails' auto-generated routes helpers, to avoid including the - # module - def urls - Gitlab::Routing.url_helpers - end -end diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/filtered_search_helpers.rb deleted file mode 100644 index 5f42ff77fb2..00000000000 --- a/spec/support/filtered_search_helpers.rb +++ /dev/null @@ -1,148 +0,0 @@ -module FilteredSearchHelpers - def filtered_search - page.find('.filtered-search') - end - - # Enables input to be set (similar to copy and paste) - def input_filtered_search(search_term, submit: true, extra_space: true) - search = search_term - if extra_space - # Add an extra space to engage visual tokens - search = "#{search_term} " - end - - filtered_search.set(search) - - if submit - # Wait for the lazy author/assignee tokens that - # swap out the username with an avatar and name - wait_for_requests - filtered_search.send_keys(:enter) - end - end - - # Select a label clicking in the search dropdown instead - # of entering label names on the input. - def select_label_on_dropdown(label_title) - input_filtered_search("label:", submit: false) - - within('#js-dropdown-label') do - wait_for_requests - - find('li', text: label_title).click - end - - filtered_search.send_keys(:enter) - end - - def expect_issues_list_count(open_count, closed_count = 0) - all_count = open_count + closed_count - - expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count) - page.within '.issues-list' do - expect(page).to have_selector('.issue', count: open_count) - end - end - - # Enables input to be added character by character - def input_filtered_search_keys(search_term) - # Add an extra space to engage visual tokens - filtered_search.send_keys("#{search_term} ") - filtered_search.send_keys(:enter) - end - - def expect_filtered_search_input(input) - expect(find('.filtered-search').value).to eq(input) - end - - def clear_search_field - find('.filtered-search-box .clear-search').click - end - - def reset_filters - clear_search_field - filtered_search.send_keys(:enter) - end - - def init_label_search - filtered_search.set('label:') - # This ensures the dropdown is shown - expect(find('#js-dropdown-label')).not_to have_css('.filter-dropdown-loading') - end - - def expect_filtered_search_input_empty - expect(find('.filtered-search').value).to eq('') - end - - # Iterates through each visual token inside - # .tokens-container to make sure the correct names and values are rendered - def expect_tokens(tokens) - page.within '.filtered-search-box .tokens-container' do - page.all(:css, '.tokens-container li .selectable').each_with_index do |el, index| - token_name = tokens[index][:name] - token_value = tokens[index][:value] - token_emoji = tokens[index][:emoji_name] - - expect(el.find('.name')).to have_content(token_name) - - if token_value - expect(el.find('.value')).to have_content(token_value) - end - - # gl-emoji content is blank when the emoji unicode is not supported - if token_emoji - selector = %(gl-emoji[data-name="#{token_emoji}"]) - expect(el.find('.value')).to have_css(selector) - end - end - end - end - - def create_token(token_name, token_value = nil, symbol = nil) - { name: token_name, value: "#{symbol}#{token_value}" } - end - - def author_token(author_name = nil) - create_token('Author', author_name) - end - - def assignee_token(assignee_name = nil) - create_token('Assignee', assignee_name) - end - - def milestone_token(milestone_name = nil, has_symbol = true) - symbol = has_symbol ? '%' : nil - create_token('Milestone', milestone_name, symbol) - end - - def label_token(label_name = nil, has_symbol = true) - symbol = has_symbol ? '~' : nil - create_token('Label', label_name, symbol) - end - - def emoji_token(emoji_name = nil) - { name: 'My-Reaction', emoji_name: emoji_name } - end - - def default_placeholder - 'Search or filter results...' - end - - def get_filtered_search_placeholder - find('.filtered-search')['placeholder'] - end - - def remove_recent_searches - execute_script('window.localStorage.clear();') - end - - def set_recent_searches(key, input) - execute_script("window.localStorage.setItem('#{key}', '#{input}');") - end - - def wait_for_filtered_search(text) - Timeout.timeout(Capybara.default_max_wait_time) do - loop until find('.filtered-search').value.strip == text - end - end -end diff --git a/spec/support/fixture_helpers.rb b/spec/support/fixture_helpers.rb deleted file mode 100644 index 8854382dc6b..00000000000 --- a/spec/support/fixture_helpers.rb +++ /dev/null @@ -1,15 +0,0 @@ -module FixtureHelpers - def fixture_file(filename, dir: '') - return '' if filename.blank? - - File.read(expand_fixture_path(filename, dir: dir)) - end - - def expand_fixture_path(filename, dir: '') - File.expand_path(Rails.root.join(dir, 'spec', 'fixtures', filename)) - end -end - -RSpec.configure do |config| - config.include FixtureHelpers -end diff --git a/spec/support/generate-seed-repo-rb b/spec/support/generate-seed-repo-rb index 876b3b8242d..44b3de23b99 100755 --- a/spec/support/generate-seed-repo-rb +++ b/spec/support/generate-seed-repo-rb @@ -8,7 +8,7 @@ # # Usage: # -# ./spec/support/generate-seed-repo-rb > spec/support/seed_repo.rb +# ./spec/support/generate-seed-repo-rb > spec/support/helpers/seed_repo.rb # # diff --git a/spec/support/git_http_helpers.rb b/spec/support/git_http_helpers.rb deleted file mode 100644 index b8289e6c5f1..00000000000 --- a/spec/support/git_http_helpers.rb +++ /dev/null @@ -1,68 +0,0 @@ -module GitHttpHelpers - def clone_get(project, options = {}) - get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token)) - end - - def clone_post(project, options = {}) - post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token)) - end - - def push_get(project, options = {}) - get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token)) - end - - def push_post(project, options = {}) - post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token)) - end - - def download(project, user: nil, password: nil, spnego_request_token: nil) - args = [project, { user: user, password: password, spnego_request_token: spnego_request_token }] - - clone_get(*args) - yield response - - clone_post(*args) - yield response - end - - def upload(project, user: nil, password: nil, spnego_request_token: nil) - args = [project, { user: user, password: password, spnego_request_token: spnego_request_token }] - - push_get(*args) - yield response - - push_post(*args) - yield response - end - - def download_or_upload(*args, &block) - download(*args, &block) - upload(*args, &block) - end - - def auth_env(user, password, spnego_request_token) - env = workhorse_internal_api_request_header - if user - env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password) - elsif spnego_request_token - env['HTTP_AUTHORIZATION'] = "Negotiate #{::Base64.strict_encode64('opaque_request_token')}" - end - - env - end - - def git_access_error(error_key) - message = Gitlab::GitAccess::ERROR_MESSAGES[error_key] - message || raise("GitAccess error message key '#{error_key}' not found") - end - - def git_access_wiki_error(error_key) - message = Gitlab::GitAccessWiki::ERROR_MESSAGES[error_key] - message || raise("GitAccessWiki error message key '#{error_key}' not found") - end - - def change_access_error(error_key) - message = Gitlab::Checks::ChangeAccess::ERROR_MESSAGES[error_key] - message || raise("ChangeAccess error message key '#{error_key}' not found") - end -end diff --git a/spec/support/gitlab-git-test.git/README.md b/spec/support/gitlab-git-test.git/README.md index f072cd421be..f757e613ee6 100644 --- a/spec/support/gitlab-git-test.git/README.md +++ b/spec/support/gitlab-git-test.git/README.md @@ -12,5 +12,5 @@ inflate the size of the gitlab-ce repository. - make changes in your local clone of gitlab-git-test - run `git push` which will push to your local source `gitlab-ce/spec/support/gitlab-git-test.git` - in gitlab-ce: run `spec/support/prepare-gitlab-git-test-for-commit` -- in gitlab-ce: `git add spec/support/seed_repo.rb spec/support/gitlab-git-test.git` +- in gitlab-ce: `git add spec/support/helpers/seed_repo.rb spec/support/gitlab-git-test.git` - commit your changes in gitlab-ce diff --git a/spec/support/gitlab_verify.rb b/spec/support/gitlab_verify.rb deleted file mode 100644 index 13e2e37624d..00000000000 --- a/spec/support/gitlab_verify.rb +++ /dev/null @@ -1,45 +0,0 @@ -RSpec.shared_examples 'Gitlab::Verify::BatchVerifier subclass' do - describe 'batching' do - let(:first_batch) { objects[0].id..objects[0].id } - let(:second_batch) { objects[1].id..objects[1].id } - let(:third_batch) { objects[2].id..objects[2].id } - - it 'iterates through objects in batches' do - expect(collect_ranges).to eq([first_batch, second_batch, third_batch]) - end - - it 'allows the starting ID to be specified' do - expect(collect_ranges(start: second_batch.first)).to eq([second_batch, third_batch]) - end - - it 'allows the finishing ID to be specified' do - expect(collect_ranges(finish: second_batch.last)).to eq([first_batch, second_batch]) - end - end -end - -module GitlabVerifyHelpers - def collect_ranges(args = {}) - verifier = described_class.new(args.merge(batch_size: 1)) - - collect_results(verifier).map { |range, _| range } - end - - def collect_failures - verifier = described_class.new(batch_size: 1) - - out = {} - - collect_results(verifier).map { |_, failures| out.merge!(failures) } - - out - end - - def collect_results(verifier) - out = [] - - verifier.run_batches { |*args| out << args } - - out - end -end diff --git a/spec/support/gpg_helpers.rb b/spec/support/gpg_helpers.rb deleted file mode 100644 index 3f7279a50e0..00000000000 --- a/spec/support/gpg_helpers.rb +++ /dev/null @@ -1,517 +0,0 @@ -module GpgHelpers - SIGNED_COMMIT_SHA = '8a852d50dda17cc8fd1408d2fd0c5b0f24c76ca4'.freeze - - module User1 - extend self - - def signed_commit_signature - <<~SIGNATURE - -----BEGIN PGP SIGNATURE----- - Version: GnuPG v1 - - iJwEAAECAAYFAliu264ACgkQzPvhnwCsix1VXgP9F6zwAMb3OXKZzqGxJ4MQIBoL - OdiUSJpL/4sIA9uhFeIv3GIA+uhsG1BHHsG627+sDy7b8W9VWEd7tbcoz4Mvhf3P - 8g0AIt9/KJuStQZDrXwP1uP6Rrl759nDcNpoOKdSQ5EZ1zlRzeDROlZeDp7Ckfvw - GLmN/74Gl3pk0wfgHFY= - =wSgS - -----END PGP SIGNATURE----- - SIGNATURE - end - - def signed_commit_base_data - <<~SIGNEDDATA - tree ed60cfd202644fda1abaf684e7d965052db18c13 - parent caf6a0334a855e12f30205fff3d7333df1f65127 - author Nannie Bernhard 1487854510 +0100 - committer Nannie Bernhard 1487854510 +0100 - - signed commit, verified key/email - SIGNEDDATA - end - - def secret_key - <<~KEY.strip - -----BEGIN PGP PRIVATE KEY BLOCK----- - Version: GnuPG v1 - - lQHYBFiu1ScBBADUhWsrlWHp5e7ASlI5iMcA0XN43fivhVlGYJJy4Ii3Hr2i4f5s - VffHS8QyhgxxzSnPwe2OKnZWWL9cHzUFbiG3fHalEBTjpB+7pG4HBgU8R/tiDOu8 - vkAR+tfJbkuRs9XeG3dGKBX/8WRhIfRucYnM+04l2Myyo5zIx7thJmxXjwARAQAB - AAP/XUtcqrtfSnDYCK4Xvo4e3msUSAEZxOPDNzP51lhfbBQgp7qSGDj9Fw5ZyNwz - 5llse3nksT5OyMUY7HX+rq2UOs12a/piLqvhtX1okp/oTAETmKXNYkZLenv6t94P - NqLi0o2AnXAvL9ueXa7WUY3l4DkvuLcjT4+9Ut2Y71zIjeECAN7q9ohNL7E8tNkf - Elsbx+8KfyHRQXiSUYaQLlvDRq2lYCKIS7sogTqjZMEgbZx2mRX1fakRlvcmqOwB - QoX34zcCAPQPd+yTteNUV12uvDaj8V9DICktPPhbHdYYaUoHjF8RrIHCTRUPzk9E - KzCL9dUP8eXPPBV/ty+zjUwl69IgCmkB/3pnNZ0D4EJsNgu24UgI0N+c8H/PE1D6 - K+bGQ/jK83uYPMXJUsiojssCHLGNp7eBGHFn1PpEqZphgVI50ZMrZQWhJbQtTmFu - bmllIEJlcm5oYXJkIDxuYW5uaWUuYmVybmhhcmRAZXhhbXBsZS5jb20+iLgEEwEC - ACIFAliu1ScCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEMz74Z8ArIsd - p5ID/32hRalvTY+V+QAtzHlGdxugweSBzNgRT3A4UiC9chF6zBOEIw689lqmK6L4 - i3Il9XeKMl87wi9tsVy9TuOMYDTvcFvu1vMAQ5AsDXqZaAEtCUZpFZscNbi7AXG+ - QkoDQbMSxp0Rd6eIRJpk9zis5co87f78xJBZLZua+8awFMS6nQHYBFiu1ScBBADI - XkITf+kKCkD+n8tMsdTLInefu8KrJ8p7YRYCCabEXnWRsDb5zxUAG2VXCVUhYl6Q - XQybkNiBaduS+uxilz7gtYZUMFJvQ09+fV7D2N9B7u/1bGdIYz+cDFJnEJitLY4w - /nju2Sno5CL5Ead8sZuslKetSXPYHR/kbW462EOw5wARAQABAAP+IoZfU1XUdVbr - +RPWp3ny5SekviDPu8co9BZ4ANTh5+8wyfA3oNbGUxTlYthoU07MZYqq+/k63R28 - 6HgVGC3gdvCiRMGmryIQ6roLLRXkfzjXrI7Lgnhx4OtVjo62pAKDqdl45wEa1Q+M - v08CQF6XNpb5R9Xszz4aBC4eV0KjtjkCANlGSQHZ1B81g+iltj1FAhRHkyUFlrc1 - cqLVhNgxtHZ96+R57Uk2A7dIJBsE00eIYaHOfk5X5GD/95s1QvPcQskCAOwUk5xj - NeQ6VV/1+cI91TrWU6VnT2Yj8632fM/JlKKfaS15pp8t5Ha6pNFr3xD4KgQutchq - fPsEOjaU7nwQ/i8B/1rDPTYfNXFpRNt33WAB1XtpgOIHlpmOfaYYqf6lneTlZWBc - TgyO+j+ZsHAvP18ugIRkU8D192NflzgAGwXLryijyYifBBgBAgAJBQJYrtUnAhsM - AAoJEMz74Z8ArIsdlkUEALTl6QUutJsqwVF4ZXKmmw0IEk8PkqW4G+tYRDHJMs6Z - O0nzDS89BG2DL4/UlOs5wRvERnlJYz01TMTxq/ciKaBTEjygFIv9CgIEZh97VacZ - TIqcF40k9SbpJNnh3JLf94xsNxNRJTEhbVC3uruaeILue/IR7pBMEyCs49Gcguwy - =b6UD - -----END PGP PRIVATE KEY BLOCK----- - KEY - end - - def public_key - <<~KEY.strip - -----BEGIN PGP PUBLIC KEY BLOCK----- - Version: GnuPG v1 - - mI0EWK7VJwEEANSFayuVYenl7sBKUjmIxwDRc3jd+K+FWUZgknLgiLcevaLh/mxV - 98dLxDKGDHHNKc/B7Y4qdlZYv1wfNQVuIbd8dqUQFOOkH7ukbgcGBTxH+2IM67y+ - QBH618luS5Gz1d4bd0YoFf/xZGEh9G5xicz7TiXYzLKjnMjHu2EmbFePABEBAAG0 - LU5hbm5pZSBCZXJuaGFyZCA8bmFubmllLmJlcm5oYXJkQGV4YW1wbGUuY29tPoi4 - BBMBAgAiBQJYrtUnAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDM++Gf - AKyLHaeSA/99oUWpb02PlfkALcx5RncboMHkgczYEU9wOFIgvXIReswThCMOvPZa - piui+ItyJfV3ijJfO8IvbbFcvU7jjGA073Bb7tbzAEOQLA16mWgBLQlGaRWbHDW4 - uwFxvkJKA0GzEsadEXeniESaZPc4rOXKPO3+/MSQWS2bmvvGsBTEuriNBFiu1ScB - BADIXkITf+kKCkD+n8tMsdTLInefu8KrJ8p7YRYCCabEXnWRsDb5zxUAG2VXCVUh - Yl6QXQybkNiBaduS+uxilz7gtYZUMFJvQ09+fV7D2N9B7u/1bGdIYz+cDFJnEJit - LY4w/nju2Sno5CL5Ead8sZuslKetSXPYHR/kbW462EOw5wARAQABiJ8EGAECAAkF - Aliu1ScCGwwACgkQzPvhnwCsix2WRQQAtOXpBS60myrBUXhlcqabDQgSTw+Spbgb - 61hEMckyzpk7SfMNLz0EbYMvj9SU6znBG8RGeUljPTVMxPGr9yIpoFMSPKAUi/0K - AgRmH3tVpxlMipwXjST1Jukk2eHckt/3jGw3E1ElMSFtULe6u5p4gu578hHukEwT - IKzj0ZyC7DI= - =Ug0r - -----END PGP PUBLIC KEY BLOCK----- - KEY - end - - def public_key_with_extra_signing_key - <<~KEY.strip - -----BEGIN PGP PUBLIC KEY BLOCK----- - Version: GnuPG v1 - - mI0EWK7VJwEEANSFayuVYenl7sBKUjmIxwDRc3jd+K+FWUZgknLgiLcevaLh/mxV - 98dLxDKGDHHNKc/B7Y4qdlZYv1wfNQVuIbd8dqUQFOOkH7ukbgcGBTxH+2IM67y+ - QBH618luS5Gz1d4bd0YoFf/xZGEh9G5xicz7TiXYzLKjnMjHu2EmbFePABEBAAG0 - LU5hbm5pZSBCZXJuaGFyZCA8bmFubmllLmJlcm5oYXJkQGV4YW1wbGUuY29tPoi4 - BBMBAgAiBQJYrtUnAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDM++Gf - AKyLHaeSA/99oUWpb02PlfkALcx5RncboMHkgczYEU9wOFIgvXIReswThCMOvPZa - piui+ItyJfV3ijJfO8IvbbFcvU7jjGA073Bb7tbzAEOQLA16mWgBLQlGaRWbHDW4 - uwFxvkJKA0GzEsadEXeniESaZPc4rOXKPO3+/MSQWS2bmvvGsBTEuriNBFiu1ScB - BADIXkITf+kKCkD+n8tMsdTLInefu8KrJ8p7YRYCCabEXnWRsDb5zxUAG2VXCVUh - Yl6QXQybkNiBaduS+uxilz7gtYZUMFJvQ09+fV7D2N9B7u/1bGdIYz+cDFJnEJit - LY4w/nju2Sno5CL5Ead8sZuslKetSXPYHR/kbW462EOw5wARAQABiJ8EGAECAAkF - Aliu1ScCGwwACgkQzPvhnwCsix2WRQQAtOXpBS60myrBUXhlcqabDQgSTw+Spbgb - 61hEMckyzpk7SfMNLz0EbYMvj9SU6znBG8RGeUljPTVMxPGr9yIpoFMSPKAUi/0K - AgRmH3tVpxlMipwXjST1Jukk2eHckt/3jGw3E1ElMSFtULe6u5p4gu578hHukEwT - IKzj0ZyC7DK5AQ0EWcx23AEIANwpAq85bT10JCBuNhOMyF2jKVt5wHbI9wBtjWYG - fgJFBkRvm6IsbmR0Y5DSBvF/of0UX1iGMfx6mvCDJkb1okquhCUef6MONWRpzXYE - CIZDm1TXu6yv0D35tkLfPo+/sY9UHHp1zGRcPAU46e8ztRwoD+zEJwy7lobLHGOL - 9OdWtCGjsutLOTqKRK4jsifr8n3rePU09rejhDkRONNs7ufn9GRcWMN7RWiFDtpU - gNe84AJ38qaXPU8GHNTrDtDtRRPmn68ezMmE1qTNsxQxD4Isexe5Wsfc4+ElaP9s - zaHgij7npX1HS9RpmhnOa2h1ESroM9cqDh3IJVhf+eP6/uMAEQEAAYkBxAQYAQIA - DwUCWcx23AIbAgUJAeEzgAEpCRDM++GfAKyLHcBdIAQZAQIABgUCWcx23AAKCRDk - garE0uOuES7DCAC2Kgl6zO+NqIBIS6McgcEN0sGyvOvZ8Ps4hBiMwCyDAnsIRAUi - v4KZMtQMAyl9njJ3YjPWBsdieuTz45O06DDnrzJpZO5rUGJjAcEue4zvRRWIyu3H - qHC8MsvkslsNCygJHoWlknm+HucroskTNtxHQ+FdKZ6Tey+twl1u+PhV8PQVyFkl - 4G1chO90EP4dvYrye26CC+ik2JkvC7Vy5M+U0PJikme8pFMjcdNks25BnAKcdqKU - AU8RTkSjoYvb8qSmZyldJjYjQRkTPRX1ZdaOID1EdiWl+s5cn0Oypo3z7BChcEMx - IWB/gmXQJQXVr5qNQnJObyMO/RczXYi9qNnyGMED/2EJJERLR0nefjHQalwDKQVP - s5lX1OKAcf2CrV6ZarckqaQgtqjZbuV2C2mrOHUs5uojlXaopj5gA4yJSGDcYhj1 - Rg9jdHWBtkHBj3eL32ZqrHDs3ap8ErZMmPE8A+mn9TTnQS+FY2QF7vBjJKM3qPT7 - DMVGWrg4m1NF8N6yMPMP - =RB1y - -----END PGP PUBLIC KEY BLOCK----- - KEY - end - - def primary_keyid - fingerprint[-16..-1] - end - - def fingerprint - '5F7EA3981A5845B141ABD522CCFBE19F00AC8B1D' - end - - def names - ['Nannie Bernhard'] - end - - def emails - ['nannie.bernhard@example.com'] - end - end - - module User2 - extend self - - def private_key - <<~KEY.strip - -----BEGIN PGP PRIVATE KEY BLOCK----- - Version: GnuPG v1 - - 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 - =yEqv - -----END PGP PRIVATE KEY BLOCK----- - KEY - end - - def public_key - <<~KEY.strip - -----BEGIN PGP PUBLIC KEY BLOCK----- - Version: GnuPG v1 - - mI0EWK6qKgEEAODjqOSIBNYzL23WenEVYnvvGJs9d1SjfosBmBnq4a6eBLNwUvjn - Rt0TIwwablEmnoMJiRmXBAOUz/9gSrB/QKar8CJsLmb1sevuF/mwzGXjSywnh+N3 - NPqh2KE7S/EPYBFKu2LCQRLMXGMnnWaM590+8AZ/S5zHOB5F9SUKszeTABEBAAG0 - L0JldHRlIENhcnR3cmlnaHQgPGJldHRlLmNhcnR3cmlnaHRAZXhhbXBsZS5jb20+ - iLgEEwECACIFAliuqioCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEL+d - kl+RHv1lQhsD/0LDi008hCeoZdGMuwy2N5FMUusYg/X1tRQ6E0JcYYjvvE5oIZoX - bVN0yLLD99P0rt6TSlt05CC7RPVPCNpKxKbF+TZMjMJiOi13XmY5Yti77ZMdBZtD - 7HeBzjkWCVuR6k8eIwy83niHGI6p6G87Q6XxvDjQ7wdBCNBpwkzmTYIBtC9CZXR0 - ZSBDYXJ0d3JpZ2h0IDxiZXR0ZS5jYXJ0d3JpZ2h0QGV4YW1wbGUubmV0Poi4BBMB - AgAiBQJYrwLFAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRC/nZJfkR79 - ZVzsBADYe3Hv71I7+lTJDvKOTmV7M4ShOfjEpmDvL/5JB3FzXHAucCOlReCv/6+N - afAspPTc1uys47dvtePwQalroAsd8Y1grn/PCh+Tx27kpn1WG8yZjUsuq3z5rrGR - uXj6g8aeZdqkMTHUVF9Gd+g/5KDODdJbOjwH+l63b/bOiATDFbiNBFiuqioBBADe - 5nUdVOcbZlnxOjl0KBAT+A5bmyBLUT0BmLPsmA4PuXDSth7WvibPC8wcCdCYVk0I - RMYneZUiWq/o5c4rthfLR4jg8kruvomQ4E4d4hyI6R0MLxXYZ3XMu67VuScFgbLU - Rw1eRZ16ANd3Nc1VuFW7ms0vCG0idB8iSZBoULaK8QARAQABiJ8EGAECAAkFAliu - qioCGwwACgkQv52SX5Ee/WVCCAQA1bBDCuVPYEUtZXwXH7CWDfX6ZyOROrQtpa7e - i54CW6hKX3fYHpOiu4yHromjGlINTjjHclsJpn/qmiJizkB9/xl7IRWV+RVpDshZ - 3mymOldpLiKgI5+Rl6mZtO4TtM9BYziIEC24h+IkaLBylZYz4ogfbzXKF8JxtHPM - UuwJVDE= - =0vYo - -----END PGP PUBLIC KEY BLOCK----- - KEY - end - - def primary_keyid - fingerprint[-16..-1] - end - - def fingerprint - '6D494CA6FC90C0CAE0910E42BF9D925F911EFD65' - end - - def names - ['Bette Cartwright', 'Bette Cartwright'] - end - - def emails - ['bette.cartwright@example.com', 'bette.cartwright@example.net'] - end - end - - # GPG Key with extra signing key - module User3 - extend self - - def signed_commit_signature - <<~SIGNATURE - -----BEGIN PGP SIGNATURE----- - - iQEzBAABCAAdFiEEBSLdKbmPFnzYQhdS44/8r3Wr2SoFAlnNlT8ACgkQ44/8r3Wr - 2SqP1wf9FC4J2S8LIHs/fpxgkYzsyCp5lCbS7JuoD4pqmI2KWyBx+vi9/3mZPCsm - Fj9f0vFEtNOb39GNGZbaA8DdGw30/WAS6kI6yco0WSK53KHrLw9Kqd+3e/NAVSsl - 991Gq4n8X1U5izSH+gZOMtEEUBGqIlZKgRrEh7lhNcz0G7JTF2VCE4NNtZdq7GDA - N6jOQxDGUwi9wQBYORQzIBc3NihfhGloII1hXf0XzrgUY3zNYHTT7QipCxKAmH54 - skwW+wi8RpBedar4saf7fs5xZbP/0yyVz98MJMdHBL68++Xt1AIHoqrb7eWszqnd - PCo/fnz1iHKCig602KLj0/zhADcNkg== - =LsTi - -----END PGP SIGNATURE----- - SIGNATURE - end - - def signed_commit_base_data - <<~SIGNEDDATA - tree 86ec18bfe87ad42a782fdabd8310f9b7ac750f51 - parent 2d1096e3a0ecf1d2baf6dee036cc80775d4940ba - author John Doe 1506645311 -0500 - committer John Doe 1506645311 -0500 - - Commit signed with subkey by John Doe - SIGNEDDATA - end - - def public_key - <<~KEY.strip - -----BEGIN PGP PUBLIC KEY BLOCK----- - - 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 - /jQYeOgFDKq20x86WulkvsUtBoaZJg== - =Q5Z7 - -----END PGP PUBLIC KEY BLOCK----- - KEY - end - - def secret_key - <<~SECRET - -----BEGIN PGP PRIVATE KEY BLOCK----- - - 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 - Jg== - =TKlF - -----END PGP PRIVATE KEY BLOCK----- - SECRET - end - - def fingerprint - 'EA3F8B88972A8FD474872F2F115B3AF1AAD3EDBA' - end - - def subkey_fingerprints - %w(159AD5DDF199591D67D2B87AA3CEC5C0A7C270EC 0522DD29B98F167CD8421752E38FFCAF75ABD92A) - end - - def names - ['John Doe'] - end - - def emails - ['john.doe@example.com'] - end - end - - # GPG Key containing just the main key - module User4 - extend self - - def public_key - <<~KEY.strip - -----BEGIN PGP PUBLIC KEY BLOCK----- - - mQENBFnWcesBCAC6Y8FXl9ZJ9HPa6dIYcgQrvjIQcwoQCUEsaXNRpc+206RPCIXK - aIYr0nTD8GeovMuUONXTj+DdueQU2GAAqHHOqvDDVXqRrW3xfWnSwix7sTuhG1Ew - PLHYmjLENqaTsdyliEo3N8VWy2k0QRbC3R6xvop4Ooa87D5vcATIl0gYFtSiHIL+ - TervYvTG9Eq1qSLZHbe2x4IzeqX2luikPKokL7j8FTZaCmC5MezIUur1ulfyYY/j - SkST/1aUFc5QXJJSZA0MYJWZX6x7Y3l7yl0dkHqmK8OTuo8RPWd3ybEiuvRsOL8K - GAv/PmVJRGDAf7GGbwXXsE9MiZ5GzVPxHnexABEBAAG0G0pvaG4gRG9lIDxqb2hu - QGV4YW1wbGUuY29tPokBTgQTAQgAOBYhBAh0izYM0lwuzJnVlAcBbPnhOj+bBQJZ - 1nHrAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEAcBbPnhOj+bkywH/i4w - OwpDxoTjUQlPlqGAGuzvWaPzSJndawgmMTr68oRsD+wlQmQQTR5eqxCpUIyV4aYb - D697RYzoqbT4mlU49ymzfKSAxFe88r1XQWdm81DcofHVPmw2GBrIqaX3Du4Z7xkI - Q9/S43orwknh5FoVwU8Nau7qBuv9vbw2apSkuA1oBj3spQ8hqwLavACyQ+fQloAT - hSDNqPiCZj6L0dwM1HYiqVoN3Q7qjgzzeBzlXzljJoWblhxllvMK20bVoa7H+uR2 - lczFHfsX8VTIMjyTGP7R3oHN91DEahlQybVVNLmNSDKZM2P/0d28BRUmWxQJ4Ws3 - J4hOWDKnLMed3VOIWzM= - =xVuW - -----END PGP PUBLIC KEY BLOCK----- - KEY - end - - def secret_key - <<~KEY.strip - -----BEGIN PGP PRIVATE KEY BLOCK----- - - lQPGBFnWcesBCAC6Y8FXl9ZJ9HPa6dIYcgQrvjIQcwoQCUEsaXNRpc+206RPCIXK - aIYr0nTD8GeovMuUONXTj+DdueQU2GAAqHHOqvDDVXqRrW3xfWnSwix7sTuhG1Ew - PLHYmjLENqaTsdyliEo3N8VWy2k0QRbC3R6xvop4Ooa87D5vcATIl0gYFtSiHIL+ - TervYvTG9Eq1qSLZHbe2x4IzeqX2luikPKokL7j8FTZaCmC5MezIUur1ulfyYY/j - SkST/1aUFc5QXJJSZA0MYJWZX6x7Y3l7yl0dkHqmK8OTuo8RPWd3ybEiuvRsOL8K - GAv/PmVJRGDAf7GGbwXXsE9MiZ5GzVPxHnexABEBAAH+BwMC4UwgHgH5Cp7meY39 - G5Q3GV2xtwADoaAvlOvPOLPK2fQqxQfb4WN4eZECp2wQuMRBMj52c4i9yphab1mQ - vOzoPIRGvkcJoxG++OxQ0kRk0C0gX6wM6SGVdb1nQnfZnoJCCU3IwCaSGktkLDs1 - jwdI+VmXJbSugUbd25bakHQcE2BaNHuRBlQWQfFbhGBy0+uMfNDBZ6FRipBu47hO - f/wm/xXuV8N8BSgvNR/qtAqSQI34CdsnWAhMYm9rqmTNyt0nq4dveX+E0YzVn4lH - lOEa7cpYeuBwIL8L3EvSPNCICiJlF3gVqiYzyqRElnCkv1OGc0x3W5onY/agHgGZ - KYyi/ubOdqqDgBR+eMt0JKSGH2EPxUAGFPY5F37u4erdxH86GzIinAExLSmADiVR - KtxluZP6S2KLbETN5uVbrfa+HVcMbbUZaBHHtL+YbY8PqaFUIvIUR1HM2SK7IrFw - KuQ8ibRgooyP7VgMNiPzlFpY4NXUv+FXIrNJ6ELuIaENi0izJ7aIbVBM8SijDz6u - 5EEmodnDvmU2hmQNZJ17TxggE7oeT0rKdDGHM5zBvqZ3deqE9sgKx/aTKcj61ID3 - M80ZkHPDFazUCohLpYgFN20bYYSmxU4LeNFy8YEiuic8QQKaAFxSf9Lf87UFQwyF - dduI1RWEbjMsbEJXwlmGM02ssQHsgoVKwZxijq5A5R1Ul6LowazQ8obPiwRS4NZ4 - Z+QKDon79MMXiFEeh1jeG/MKKWPxFg3pdtCWhC7WdH4hfkBsCVKf+T58yB2Gzziy - fOHvAl7v3PtdZgf1xikF8spGYGCWo4B2lxC79xIflKAb2U6myb5I4dpUYxzxoMxT - zxHwxEie3NxzZGUyXSt3LqYe2r4CxWnOCXWjIxxRlLue1BE5Za1ycnDRjgUO24+Z - uDQne6KLkhAotBtKb2huIERvZSA8am9obkBleGFtcGxlLmNvbT6JAU4EEwEIADgW - IQQIdIs2DNJcLsyZ1ZQHAWz54To/mwUCWdZx6wIbAwULCQgHAgYVCAkKCwIEFgID - AQIeAQIXgAAKCRAHAWz54To/m5MsB/4uMDsKQ8aE41EJT5ahgBrs71mj80iZ3WsI - JjE6+vKEbA/sJUJkEE0eXqsQqVCMleGmGw+ve0WM6Km0+JpVOPcps3ykgMRXvPK9 - V0FnZvNQ3KHx1T5sNhgayKml9w7uGe8ZCEPf0uN6K8JJ4eRaFcFPDWru6gbr/b28 - NmqUpLgNaAY97KUPIasC2rwAskPn0JaAE4Ugzaj4gmY+i9HcDNR2IqlaDd0O6o4M - 83gc5V85YyaFm5YcZZbzCttG1aGux/rkdpXMxR37F/FUyDI8kxj+0d6BzfdQxGoZ - UMm1VTS5jUgymTNj/9HdvAUVJlsUCeFrNyeITlgypyzHnd1TiFsz - =/37z - -----END PGP PRIVATE KEY BLOCK----- - KEY - end - - def primary_keyid - fingerprint[-16..-1] - end - - def fingerprint - '08748B360CD25C2ECC99D59407016CF9E13A3F9B' - end - end -end diff --git a/spec/support/group_members_shared_example.rb b/spec/support/group_members_shared_example.rb deleted file mode 100644 index 547c83c7955..00000000000 --- a/spec/support/group_members_shared_example.rb +++ /dev/null @@ -1,27 +0,0 @@ -RSpec.shared_examples 'members and requesters associations' do - describe '#members_and_requesters' do - it 'includes members and requesters' do - member_and_requester_user_ids = namespace.members_and_requesters.pluck(:user_id) - - expect(member_and_requester_user_ids).to include(requester.id, developer.id) - end - end - - describe '#members' do - it 'includes members and exclude requesters' do - member_user_ids = namespace.members.pluck(:user_id) - - expect(member_user_ids).to include(developer.id) - expect(member_user_ids).not_to include(requester.id) - end - end - - describe '#requesters' do - it 'does not include requesters' do - requester_user_ids = namespace.requesters.pluck(:user_id) - - expect(requester_user_ids).to include(requester.id) - expect(requester_user_ids).not_to include(developer.id) - end - end -end diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb new file mode 100644 index 00000000000..ac0c7a9b493 --- /dev/null +++ b/spec/support/helpers/api_helpers.rb @@ -0,0 +1,50 @@ +module ApiHelpers + # Public: Prepend a request path with the path to the API + # + # path - Path to append + # user - User object - If provided, automatically appends private_token query + # string for authenticated requests + # + # Examples + # + # >> api('/issues') + # => "/api/v2/issues" + # + # >> api('/issues', User.last) + # => "/api/v2/issues?private_token=..." + # + # >> api('/issues?foo=bar', User.last) + # => "/api/v2/issues?foo=bar&private_token=..." + # + # Returns the relative path to the requested API resource + def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil) + full_path = "/api/#{version}#{path}" + + if oauth_access_token + query_string = "access_token=#{oauth_access_token.token}" + elsif personal_access_token + query_string = "private_token=#{personal_access_token.token}" + elsif user + personal_access_token = create(:personal_access_token, user: user) + query_string = "private_token=#{personal_access_token.token}" + end + + if query_string + full_path << (path.index('?') ? '&' : '?') + full_path << query_string + end + + full_path + end + + # Temporary helper method for simplifying V3 exclusive API specs + def v3_api(path, user = nil, personal_access_token: nil, oauth_access_token: nil) + api( + path, + user, + version: 'v3', + personal_access_token: personal_access_token, + oauth_access_token: oauth_access_token + ) + end +end diff --git a/spec/support/helpers/bare_repo_operations.rb b/spec/support/helpers/bare_repo_operations.rb new file mode 100644 index 00000000000..3f4a4243cb6 --- /dev/null +++ b/spec/support/helpers/bare_repo_operations.rb @@ -0,0 +1,62 @@ +require 'zlib' + +class BareRepoOperations + include Gitlab::Popen + + def initialize(path_to_repo) + @path_to_repo = path_to_repo + end + + def commit_tree(tree_id, msg, parent: Gitlab::Git::EMPTY_TREE_ID) + commit_tree_args = ['commit-tree', tree_id, '-m', msg] + commit_tree_args += ['-p', parent] unless parent == Gitlab::Git::EMPTY_TREE_ID + commit_id = execute(commit_tree_args) + + commit_id[0] + end + + # Based on https://stackoverflow.com/a/25556917/1856239 + def commit_file(file, dst_path, branch = 'master') + head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || Gitlab::Git::EMPTY_TREE_ID + + execute(['read-tree', '--empty']) + execute(['read-tree', head_id]) + + blob_id = execute(['hash-object', '--stdin', '-w']) do |stdin| + stdin.write(file.read) + end + + execute(['update-index', '--add', '--cacheinfo', '100644', blob_id[0], dst_path]) + + tree_id = execute(['write-tree']) + + commit_id = commit_tree(tree_id[0], "Add #{dst_path}", parent: head_id) + + execute(['update-ref', "refs/heads/#{branch}", commit_id]) + end + + private + + def execute(args, allow_failure: false) + output, status = popen(base_args + args, nil) do |stdin| + yield stdin if block_given? + end + + unless status.zero? + if allow_failure + return [] + else + raise "Got a non-zero exit code while calling out `#{args.join(' ')}`: #{output}" + end + end + + output.split("\n") + end + + def base_args + [ + Gitlab.config.git.bin_path, + "--git-dir=#{@path_to_repo}" + ] + end +end diff --git a/spec/support/helpers/board_helpers.rb b/spec/support/helpers/board_helpers.rb new file mode 100644 index 00000000000..507d0432d7f --- /dev/null +++ b/spec/support/helpers/board_helpers.rb @@ -0,0 +1,16 @@ +module BoardHelpers + def click_card(card) + within card do + first('.card-number').click + end + + wait_for_sidebar + end + + def wait_for_sidebar + # loop until the CSS transition is complete + Timeout.timeout(0.5) do + loop until evaluate_script('$(".right-sidebar").outerWidth()') == 290 + end + end +end diff --git a/spec/support/helpers/capybara_helpers.rb b/spec/support/helpers/capybara_helpers.rb new file mode 100644 index 00000000000..bcc2df44708 --- /dev/null +++ b/spec/support/helpers/capybara_helpers.rb @@ -0,0 +1,43 @@ +module CapybaraHelpers + # Execute a block a certain number of times before considering it a failure + # + # The given block is called, and if it raises a `Capybara::ExpectationNotMet` + # error, we wait `interval` seconds and then try again, until `retries` is + # met. + # + # This allows for better handling of timing-sensitive expectations in a + # sketchy CI environment, for example. + # + # interval - Delay between retries in seconds (default: 0.5) + # retries - Number of times to execute before failing (default: 5) + def allowing_for_delay(interval: 0.5, retries: 5) + tries = 0 + + begin + sleep interval + + yield + rescue Capybara::ExpectationNotMet => ex + if tries <= retries + tries += 1 + sleep interval + retry + else + raise ex + end + end + end + + # Refresh the page. Calling `visit current_url` doesn't seem to work consistently. + # + def refresh + url = current_url + visit 'about:blank' + visit url + end + + # Simulate a browser restart by clearing the session cookie. + def clear_browser_session + page.driver.browser.manage.delete_cookie('_gitlab_session') + end +end diff --git a/spec/support/helpers/cookie_helper.rb b/spec/support/helpers/cookie_helper.rb new file mode 100644 index 00000000000..5ff7b0b68c9 --- /dev/null +++ b/spec/support/helpers/cookie_helper.rb @@ -0,0 +1,34 @@ +# Helper for setting cookies in Selenium/WebDriver +# +module CookieHelper + def set_cookie(name, value, options = {}) + case page.driver + when Capybara::RackTest::Driver + rack_set_cookie(name, value) + else + selenium_set_cookie(name, value, options) + end + end + + def selenium_set_cookie(name, value, options = {}) + # Selenium driver will not set cookies for a given domain when the browser is at `about:blank`. + # It also doesn't appear to allow overriding the cookie path. loading `/` is the most inclusive. + visit options.fetch(:path, '/') unless on_a_page? + page.driver.browser.manage.add_cookie(name: name, value: value, **options) + end + + def rack_set_cookie(name, value) + page.driver.browser.set_cookie("#{name}=#{value}") + end + + def get_cookie(name) + page.driver.browser.manage.cookie_named(name) + end + + private + + def on_a_page? + current_url = Capybara.current_session.driver.browser.current_url + current_url && current_url != '' && current_url != 'about:blank' && current_url != 'data:,' + end +end diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb new file mode 100644 index 00000000000..55359d36597 --- /dev/null +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -0,0 +1,137 @@ +module CycleAnalyticsHelpers + def create_commit_referencing_issue(issue, branch_name: generate(:branch)) + project.repository.add_branch(user, branch_name, 'master') + create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name) + end + + def create_commit(message, project, user, branch_name, count: 1) + repository = project.repository + oldrev = repository.commit(branch_name).sha + + if Timecop.frozen? && Gitlab::GitalyClient.feature_enabled?(:operation_user_commit_files) + mock_gitaly_multi_action_dates(repository.raw) + end + + commit_shas = Array.new(count) do |index| + commit_sha = repository.create_file(user, generate(:branch), "content", message: message, branch_name: branch_name) + repository.commit(commit_sha) + + commit_sha + end + + GitPushService.new(project, + user, + oldrev: oldrev, + newrev: commit_shas.last, + ref: 'refs/heads/master').execute + end + + def create_cycle(user, project, issue, mr, milestone, pipeline) + issue.update(milestone: milestone) + pipeline.run + + ci_build = create(:ci_build, pipeline: pipeline, status: :success, author: user) + + merge_merge_requests_closing_issue(user, project, issue) + ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.to_hash) + + ci_build + end + + def create_merge_request_closing_issue(user, project, issue, message: nil, source_branch: nil, commit_message: 'commit message') + if !source_branch || project.repository.commit(source_branch).blank? + source_branch = generate(:branch) + project.repository.add_branch(user, source_branch, 'master') + end + + sha = project.repository.create_file( + user, + generate(:branch), + 'content', + message: commit_message, + branch_name: source_branch) + project.repository.commit(sha) + + opts = { + title: 'Awesome merge_request', + description: message || "Fixes #{issue.to_reference}", + source_branch: source_branch, + target_branch: 'master' + } + + mr = MergeRequests::CreateService.new(project, user, opts).execute + NewMergeRequestWorker.new.perform(mr, user) + mr + end + + def merge_merge_requests_closing_issue(user, project, issue) + merge_requests = issue.closed_by_merge_requests(user) + + merge_requests.each { |merge_request| MergeRequests::MergeService.new(project, user).execute(merge_request) } + end + + def deploy_master(user, project, environment: 'production') + dummy_job = + case environment + when 'production' + dummy_production_job(user, project) + when 'staging' + dummy_staging_job(user, project) + else + raise ArgumentError + end + + CreateDeploymentService.new(dummy_job).execute + end + + def dummy_production_job(user, project) + new_dummy_job(user, project, 'production') + end + + def dummy_staging_job(user, project) + new_dummy_job(user, project, 'staging') + end + + def dummy_pipeline(project) + Ci::Pipeline.new( + sha: project.repository.commit('master').sha, + ref: 'master', + source: :push, + project: project, + protected: false) + end + + def new_dummy_job(user, project, environment) + project.environments.find_or_create_by(name: environment) + + Ci::Build.new( + project: project, + user: user, + environment: environment, + ref: 'master', + tag: false, + name: 'dummy', + stage: 'dummy', + pipeline: dummy_pipeline(project), + protected: false) + end + + def mock_gitaly_multi_action_dates(raw_repository) + allow(raw_repository).to receive(:multi_action).and_wrap_original do |m, *args| + new_date = Time.now + branch_update = m.call(*args) + + if branch_update.newrev + _, opts = args + commit = raw_repository.commit(branch_update.newrev).rugged_commit + branch_update.newrev = commit.amend( + update_ref: "#{Gitlab::Git::BRANCH_REF_PREFIX}#{opts[:branch_name]}", + author: commit.author.merge(time: new_date), + committer: commit.committer.merge(time: new_date) + ) + end + + branch_update + end + end +end diff --git a/spec/support/helpers/database_connection_helpers.rb b/spec/support/helpers/database_connection_helpers.rb new file mode 100644 index 00000000000..763329499f0 --- /dev/null +++ b/spec/support/helpers/database_connection_helpers.rb @@ -0,0 +1,9 @@ +module DatabaseConnectionHelpers + def run_with_new_database_connection + pool = ActiveRecord::Base.connection_pool + conn = pool.checkout + yield conn + ensure + pool.checkin(conn) + end +end diff --git a/spec/support/helpers/devise_helpers.rb b/spec/support/helpers/devise_helpers.rb new file mode 100644 index 00000000000..66874e10f38 --- /dev/null +++ b/spec/support/helpers/devise_helpers.rb @@ -0,0 +1,17 @@ +module DeviseHelpers + # explicitly tells Devise which mapping to use + # this is needed when we are testing a Devise controller bypassing the router + def set_devise_mapping(context:) + env = env_from_context(context) + + env['devise.mapping'] = Devise.mappings[:user] if env + end + + def env_from_context(context) + if context.respond_to?(:env_config) + context.env_config + elsif context.respond_to?(:env) + context.env + end + end +end diff --git a/spec/support/helpers/drag_to_helper.rb b/spec/support/helpers/drag_to_helper.rb new file mode 100644 index 00000000000..ae149631ed9 --- /dev/null +++ b/spec/support/helpers/drag_to_helper.rb @@ -0,0 +1,13 @@ +module DragTo + def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body') + evaluate_script("simulateDrag({scrollable: $('#{scrollable}').get(0), from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{from_index}}, to: {el: $('#{selector}').eq(#{list_to_index}).get(0), index: #{to_index}}});") + + Timeout.timeout(Capybara.default_max_wait_time) do + loop while drag_active? + end + end + + def drag_active? + page.evaluate_script('window.SIMULATE_DRAG_ACTIVE').nonzero? + end +end diff --git a/spec/support/helpers/dropzone_helper.rb b/spec/support/helpers/dropzone_helper.rb new file mode 100644 index 00000000000..fe72d320fcf --- /dev/null +++ b/spec/support/helpers/dropzone_helper.rb @@ -0,0 +1,76 @@ +module DropzoneHelper + # Provides a way to perform `attach_file` for a Dropzone-based file input + # + # This is accomplished by creating a standard HTML file input on the page, + # performing `attach_file` on that field, and then triggering the appropriate + # Dropzone events to perform the actual upload. + # + # This method waits for the upload to complete before returning. + # max_file_size is an optional parameter. + # If it's not 0, then it used in dropzone.maxFilesize parameter. + # wait_for_queuecomplete is an optional parameter. + # If it's 'false', then the helper will NOT wait for backend response + # It lets to test behaviors while AJAX is processing. + def dropzone_file(files, max_file_size = 0, wait_for_queuecomplete = true) + # Generate a fake file input that Capybara can attach to + page.execute_script <<-JS.strip_heredoc + $('#fakeFileInput').remove(); + var fakeFileInput = window.$('').attr( + {id: 'fakeFileInput', type: 'file', multiple: true} + ).appendTo('body'); + + window._dropzoneComplete = false; + JS + + # Attach files to the fake input selector with Capybara + attach_file('fakeFileInput', files) + + # Manually trigger a Dropzone "drop" event with the fake input's file list + page.execute_script <<-JS.strip_heredoc + var dropzone = $('.div-dropzone')[0].dropzone; + dropzone.options.autoProcessQueue = false; + + if (#{max_file_size} > 0) { + dropzone.options.maxFilesize = #{max_file_size}; + } + + dropzone.on('queuecomplete', function() { + window._dropzoneComplete = true; + }); + + var fileList = [$('#fakeFileInput')[0].files]; + + $.map(fileList, function(file){ + var e = jQuery.Event('drop', { dataTransfer : { files : file } }); + + dropzone.listeners[0].events.drop(e); + }); + + dropzone.processQueue(); + JS + + if wait_for_queuecomplete + # Wait until Dropzone's fired `queuecomplete` + loop until page.evaluate_script('window._dropzoneComplete === true') + end + end + + def drop_in_dropzone(file_path) + # Generate a fake input selector + page.execute_script <<-JS + var fakeFileInput = window.$('').attr( + {id: 'fakeFileInput', type: 'file'} + ).appendTo('body'); + JS + + # Attach the file to the fake input selector with Capybara + attach_file('fakeFileInput', file_path) + + # Add the file to a fileList array and trigger the fake drop event + page.execute_script <<-JS + var fileList = [$('#fakeFileInput')[0].files[0]]; + var e = jQuery.Event('drop', { dataTransfer : { files : fileList } }); + $('.dropzone')[0].dropzone.listeners[0].events.drop(e); + JS + end +end diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb new file mode 100644 index 00000000000..1fb8252459f --- /dev/null +++ b/spec/support/helpers/email_helpers.rb @@ -0,0 +1,37 @@ +module EmailHelpers + def sent_to_user(user, recipients: email_recipients) + recipients.count { |to| to == user.notification_email } + end + + def reset_delivered_emails! + ActionMailer::Base.deliveries.clear + end + + def should_only_email(*users, kind: :to) + recipients = email_recipients(kind: kind) + + users.each { |user| should_email(user, recipients: recipients) } + + expect(recipients.count).to eq(users.count) + end + + def should_email(user, times: 1, recipients: email_recipients) + expect(sent_to_user(user, recipients: recipients)).to eq(times) + end + + def should_not_email(user, recipients: email_recipients) + should_email(user, times: 0, recipients: recipients) + end + + def should_not_email_anyone + expect(ActionMailer::Base.deliveries).to be_empty + end + + def email_recipients(kind: :to) + ActionMailer::Base.deliveries.flat_map(&kind) + end + + def find_email_for(user) + ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email) } + end +end diff --git a/spec/support/helpers/fake_migration_classes.rb b/spec/support/helpers/fake_migration_classes.rb new file mode 100644 index 00000000000..b0fc8422857 --- /dev/null +++ b/spec/support/helpers/fake_migration_classes.rb @@ -0,0 +1,11 @@ +class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration + include Gitlab::Database::RenameReservedPathsMigration::V1 + + def version + '20170316163845' + end + + def name + "FakeRenameReservedPathMigrationV1" + end +end diff --git a/spec/support/helpers/fake_u2f_device.rb b/spec/support/helpers/fake_u2f_device.rb new file mode 100644 index 00000000000..a7605cd483a --- /dev/null +++ b/spec/support/helpers/fake_u2f_device.rb @@ -0,0 +1,40 @@ +class FakeU2fDevice + attr_reader :name + + def initialize(page, name) + @page = page + @name = name + end + + def respond_to_u2f_registration + app_id = @page.evaluate_script('gon.u2f.app_id') + challenges = @page.evaluate_script('gon.u2f.challenges') + + json_response = u2f_device(app_id).register_response(challenges[0]) + + @page.execute_script(" + u2f.register = function(appId, registerRequests, signRequests, callback) { + callback(#{json_response}); + }; + ") + end + + def respond_to_u2f_authentication + app_id = @page.evaluate_script('gon.u2f.app_id') + challenge = @page.evaluate_script('gon.u2f.challenge') + json_response = u2f_device(app_id).sign_response(challenge) + + @page.execute_script(" + u2f.sign = function(appId, challenges, signRequests, callback) { + callback(#{json_response}); + }; + window.gl.u2fAuthenticate.start(); + ") + end + + private + + def u2f_device(app_id) + @u2f_device ||= U2F::FakeU2F.new(app_id) + end +end diff --git a/spec/support/helpers/filter_item_select_helper.rb b/spec/support/helpers/filter_item_select_helper.rb new file mode 100644 index 00000000000..519e84d359e --- /dev/null +++ b/spec/support/helpers/filter_item_select_helper.rb @@ -0,0 +1,19 @@ +# Helper allows you to select value from filter-items +# +# Params +# value - value for select +# selector - css selector of item +# +# Usage: +# +# filter_item_select('Any Author', '.js-author-search') +# +module FilterItemSelectHelper + def filter_item_select(value, selector) + find(selector).click + wait_for_requests + page.within('.dropdown-content') do + click_link value + end + end +end diff --git a/spec/support/helpers/filter_spec_helper.rb b/spec/support/helpers/filter_spec_helper.rb new file mode 100644 index 00000000000..721d359c2ee --- /dev/null +++ b/spec/support/helpers/filter_spec_helper.rb @@ -0,0 +1,87 @@ +# Helper methods for Banzai filter specs +# +# Must be included into specs manually +module FilterSpecHelper + extend ActiveSupport::Concern + + # Perform `call` on the described class + # + # Automatically passes the current `project` value, if defined, to the context + # if none is provided. + # + # html - HTML String to pass to the filter's `call` method. + # context - Hash context for the filter. (default: {project: project}) + # + # Returns a Nokogiri::XML::DocumentFragment + def filter(html, context = {}) + if defined?(project) + context.reverse_merge!(project: project) + end + + render_context = Banzai::RenderContext + .new(context[:project], context[:current_user]) + + context = context.merge(render_context: render_context) + + described_class.call(html, context) + end + + # Run text through HTML::Pipeline with the current filter and return the + # result Hash + # + # body - String text to run through the pipeline + # context - Hash context for the filter. (default: {project: project}) + # + # Returns the Hash + def pipeline_result(body, context = {}) + context.reverse_merge!(project: project) if defined?(project) + + pipeline = HTML::Pipeline.new([described_class], context) + pipeline.call(body) + end + + def reference_pipeline(context = {}) + context.reverse_merge!(project: project) if defined?(project) + + filters = [ + Banzai::Filter::AutolinkFilter, + described_class + ] + + HTML::Pipeline.new(filters, context) + end + + def reference_pipeline_result(body, context = {}) + reference_pipeline(context).call(body) + end + + def reference_filter(html, context = {}) + reference_pipeline(context).to_document(html) + end + + # Modify a String reference to make it invalid + # + # Commit SHAs get reversed, IDs get incremented by 1, all other Strings get + # their word characters reversed. + # + # reference - String reference to modify + # + # Returns a String + def invalidate_reference(reference) + if reference =~ /\A(.+)?[^\d]\d+\z/ + # Integer-based reference with optional project prefix + reference.gsub(/\d+\z/) { |i| i.to_i + 10_000 } + elsif reference =~ /\A(.+@)?(\h{7,40}\z)/ + # SHA-based reference with optional prefix + reference.gsub(/\h{7,40}\z/) { |v| v.reverse } + else + reference.gsub(/\w+\z/) { |v| v.reverse } + end + end + + # Shortcut to Rails' auto-generated routes helpers, to avoid including the + # module + def urls + Gitlab::Routing.url_helpers + end +end diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb new file mode 100644 index 00000000000..5f42ff77fb2 --- /dev/null +++ b/spec/support/helpers/filtered_search_helpers.rb @@ -0,0 +1,148 @@ +module FilteredSearchHelpers + def filtered_search + page.find('.filtered-search') + end + + # Enables input to be set (similar to copy and paste) + def input_filtered_search(search_term, submit: true, extra_space: true) + search = search_term + if extra_space + # Add an extra space to engage visual tokens + search = "#{search_term} " + end + + filtered_search.set(search) + + if submit + # Wait for the lazy author/assignee tokens that + # swap out the username with an avatar and name + wait_for_requests + filtered_search.send_keys(:enter) + end + end + + # Select a label clicking in the search dropdown instead + # of entering label names on the input. + def select_label_on_dropdown(label_title) + input_filtered_search("label:", submit: false) + + within('#js-dropdown-label') do + wait_for_requests + + find('li', text: label_title).click + end + + filtered_search.send_keys(:enter) + end + + def expect_issues_list_count(open_count, closed_count = 0) + all_count = open_count + closed_count + + expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count) + page.within '.issues-list' do + expect(page).to have_selector('.issue', count: open_count) + end + end + + # Enables input to be added character by character + def input_filtered_search_keys(search_term) + # Add an extra space to engage visual tokens + filtered_search.send_keys("#{search_term} ") + filtered_search.send_keys(:enter) + end + + def expect_filtered_search_input(input) + expect(find('.filtered-search').value).to eq(input) + end + + def clear_search_field + find('.filtered-search-box .clear-search').click + end + + def reset_filters + clear_search_field + filtered_search.send_keys(:enter) + end + + def init_label_search + filtered_search.set('label:') + # This ensures the dropdown is shown + expect(find('#js-dropdown-label')).not_to have_css('.filter-dropdown-loading') + end + + def expect_filtered_search_input_empty + expect(find('.filtered-search').value).to eq('') + end + + # Iterates through each visual token inside + # .tokens-container to make sure the correct names and values are rendered + def expect_tokens(tokens) + page.within '.filtered-search-box .tokens-container' do + page.all(:css, '.tokens-container li .selectable').each_with_index do |el, index| + token_name = tokens[index][:name] + token_value = tokens[index][:value] + token_emoji = tokens[index][:emoji_name] + + expect(el.find('.name')).to have_content(token_name) + + if token_value + expect(el.find('.value')).to have_content(token_value) + end + + # gl-emoji content is blank when the emoji unicode is not supported + if token_emoji + selector = %(gl-emoji[data-name="#{token_emoji}"]) + expect(el.find('.value')).to have_css(selector) + end + end + end + end + + def create_token(token_name, token_value = nil, symbol = nil) + { name: token_name, value: "#{symbol}#{token_value}" } + end + + def author_token(author_name = nil) + create_token('Author', author_name) + end + + def assignee_token(assignee_name = nil) + create_token('Assignee', assignee_name) + end + + def milestone_token(milestone_name = nil, has_symbol = true) + symbol = has_symbol ? '%' : nil + create_token('Milestone', milestone_name, symbol) + end + + def label_token(label_name = nil, has_symbol = true) + symbol = has_symbol ? '~' : nil + create_token('Label', label_name, symbol) + end + + def emoji_token(emoji_name = nil) + { name: 'My-Reaction', emoji_name: emoji_name } + end + + def default_placeholder + 'Search or filter results...' + end + + def get_filtered_search_placeholder + find('.filtered-search')['placeholder'] + end + + def remove_recent_searches + execute_script('window.localStorage.clear();') + end + + def set_recent_searches(key, input) + execute_script("window.localStorage.setItem('#{key}', '#{input}');") + end + + def wait_for_filtered_search(text) + Timeout.timeout(Capybara.default_max_wait_time) do + loop until find('.filtered-search').value.strip == text + end + end +end diff --git a/spec/support/helpers/fixture_helpers.rb b/spec/support/helpers/fixture_helpers.rb new file mode 100644 index 00000000000..611d19f36a0 --- /dev/null +++ b/spec/support/helpers/fixture_helpers.rb @@ -0,0 +1,11 @@ +module FixtureHelpers + def fixture_file(filename, dir: '') + return '' if filename.blank? + + File.read(expand_fixture_path(filename, dir: dir)) + end + + def expand_fixture_path(filename, dir: '') + File.expand_path(Rails.root.join(dir, 'spec', 'fixtures', filename)) + end +end diff --git a/spec/support/helpers/git_http_helpers.rb b/spec/support/helpers/git_http_helpers.rb new file mode 100644 index 00000000000..b8289e6c5f1 --- /dev/null +++ b/spec/support/helpers/git_http_helpers.rb @@ -0,0 +1,68 @@ +module GitHttpHelpers + def clone_get(project, options = {}) + get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token)) + end + + def clone_post(project, options = {}) + post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token)) + end + + def push_get(project, options = {}) + get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token)) + end + + def push_post(project, options = {}) + post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token)) + end + + def download(project, user: nil, password: nil, spnego_request_token: nil) + args = [project, { user: user, password: password, spnego_request_token: spnego_request_token }] + + clone_get(*args) + yield response + + clone_post(*args) + yield response + end + + def upload(project, user: nil, password: nil, spnego_request_token: nil) + args = [project, { user: user, password: password, spnego_request_token: spnego_request_token }] + + push_get(*args) + yield response + + push_post(*args) + yield response + end + + def download_or_upload(*args, &block) + download(*args, &block) + upload(*args, &block) + end + + def auth_env(user, password, spnego_request_token) + env = workhorse_internal_api_request_header + if user + env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password) + elsif spnego_request_token + env['HTTP_AUTHORIZATION'] = "Negotiate #{::Base64.strict_encode64('opaque_request_token')}" + end + + env + end + + def git_access_error(error_key) + message = Gitlab::GitAccess::ERROR_MESSAGES[error_key] + message || raise("GitAccess error message key '#{error_key}' not found") + end + + def git_access_wiki_error(error_key) + message = Gitlab::GitAccessWiki::ERROR_MESSAGES[error_key] + message || raise("GitAccessWiki error message key '#{error_key}' not found") + end + + def change_access_error(error_key) + message = Gitlab::Checks::ChangeAccess::ERROR_MESSAGES[error_key] + message || raise("ChangeAccess error message key '#{error_key}' not found") + end +end diff --git a/spec/support/helpers/gitlab_verify_helpers.rb b/spec/support/helpers/gitlab_verify_helpers.rb new file mode 100644 index 00000000000..5df4bf24ec2 --- /dev/null +++ b/spec/support/helpers/gitlab_verify_helpers.rb @@ -0,0 +1,25 @@ +module GitlabVerifyHelpers + def collect_ranges(args = {}) + verifier = described_class.new(args.merge(batch_size: 1)) + + collect_results(verifier).map { |range, _| range } + end + + def collect_failures + verifier = described_class.new(batch_size: 1) + + out = {} + + collect_results(verifier).map { |_, failures| out.merge!(failures) } + + out + end + + def collect_results(verifier) + out = [] + + verifier.run_batches { |*args| out << args } + + out + end +end diff --git a/spec/support/helpers/gpg_helpers.rb b/spec/support/helpers/gpg_helpers.rb new file mode 100644 index 00000000000..3f7279a50e0 --- /dev/null +++ b/spec/support/helpers/gpg_helpers.rb @@ -0,0 +1,517 @@ +module GpgHelpers + SIGNED_COMMIT_SHA = '8a852d50dda17cc8fd1408d2fd0c5b0f24c76ca4'.freeze + + module User1 + extend self + + def signed_commit_signature + <<~SIGNATURE + -----BEGIN PGP SIGNATURE----- + Version: GnuPG v1 + + iJwEAAECAAYFAliu264ACgkQzPvhnwCsix1VXgP9F6zwAMb3OXKZzqGxJ4MQIBoL + OdiUSJpL/4sIA9uhFeIv3GIA+uhsG1BHHsG627+sDy7b8W9VWEd7tbcoz4Mvhf3P + 8g0AIt9/KJuStQZDrXwP1uP6Rrl759nDcNpoOKdSQ5EZ1zlRzeDROlZeDp7Ckfvw + GLmN/74Gl3pk0wfgHFY= + =wSgS + -----END PGP SIGNATURE----- + SIGNATURE + end + + def signed_commit_base_data + <<~SIGNEDDATA + tree ed60cfd202644fda1abaf684e7d965052db18c13 + parent caf6a0334a855e12f30205fff3d7333df1f65127 + author Nannie Bernhard 1487854510 +0100 + committer Nannie Bernhard 1487854510 +0100 + + signed commit, verified key/email + SIGNEDDATA + end + + def secret_key + <<~KEY.strip + -----BEGIN PGP PRIVATE KEY BLOCK----- + Version: GnuPG v1 + + lQHYBFiu1ScBBADUhWsrlWHp5e7ASlI5iMcA0XN43fivhVlGYJJy4Ii3Hr2i4f5s + VffHS8QyhgxxzSnPwe2OKnZWWL9cHzUFbiG3fHalEBTjpB+7pG4HBgU8R/tiDOu8 + vkAR+tfJbkuRs9XeG3dGKBX/8WRhIfRucYnM+04l2Myyo5zIx7thJmxXjwARAQAB + AAP/XUtcqrtfSnDYCK4Xvo4e3msUSAEZxOPDNzP51lhfbBQgp7qSGDj9Fw5ZyNwz + 5llse3nksT5OyMUY7HX+rq2UOs12a/piLqvhtX1okp/oTAETmKXNYkZLenv6t94P + NqLi0o2AnXAvL9ueXa7WUY3l4DkvuLcjT4+9Ut2Y71zIjeECAN7q9ohNL7E8tNkf + Elsbx+8KfyHRQXiSUYaQLlvDRq2lYCKIS7sogTqjZMEgbZx2mRX1fakRlvcmqOwB + QoX34zcCAPQPd+yTteNUV12uvDaj8V9DICktPPhbHdYYaUoHjF8RrIHCTRUPzk9E + KzCL9dUP8eXPPBV/ty+zjUwl69IgCmkB/3pnNZ0D4EJsNgu24UgI0N+c8H/PE1D6 + K+bGQ/jK83uYPMXJUsiojssCHLGNp7eBGHFn1PpEqZphgVI50ZMrZQWhJbQtTmFu + bmllIEJlcm5oYXJkIDxuYW5uaWUuYmVybmhhcmRAZXhhbXBsZS5jb20+iLgEEwEC + ACIFAliu1ScCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEMz74Z8ArIsd + p5ID/32hRalvTY+V+QAtzHlGdxugweSBzNgRT3A4UiC9chF6zBOEIw689lqmK6L4 + i3Il9XeKMl87wi9tsVy9TuOMYDTvcFvu1vMAQ5AsDXqZaAEtCUZpFZscNbi7AXG+ + QkoDQbMSxp0Rd6eIRJpk9zis5co87f78xJBZLZua+8awFMS6nQHYBFiu1ScBBADI + XkITf+kKCkD+n8tMsdTLInefu8KrJ8p7YRYCCabEXnWRsDb5zxUAG2VXCVUhYl6Q + XQybkNiBaduS+uxilz7gtYZUMFJvQ09+fV7D2N9B7u/1bGdIYz+cDFJnEJitLY4w + /nju2Sno5CL5Ead8sZuslKetSXPYHR/kbW462EOw5wARAQABAAP+IoZfU1XUdVbr + +RPWp3ny5SekviDPu8co9BZ4ANTh5+8wyfA3oNbGUxTlYthoU07MZYqq+/k63R28 + 6HgVGC3gdvCiRMGmryIQ6roLLRXkfzjXrI7Lgnhx4OtVjo62pAKDqdl45wEa1Q+M + v08CQF6XNpb5R9Xszz4aBC4eV0KjtjkCANlGSQHZ1B81g+iltj1FAhRHkyUFlrc1 + cqLVhNgxtHZ96+R57Uk2A7dIJBsE00eIYaHOfk5X5GD/95s1QvPcQskCAOwUk5xj + NeQ6VV/1+cI91TrWU6VnT2Yj8632fM/JlKKfaS15pp8t5Ha6pNFr3xD4KgQutchq + fPsEOjaU7nwQ/i8B/1rDPTYfNXFpRNt33WAB1XtpgOIHlpmOfaYYqf6lneTlZWBc + TgyO+j+ZsHAvP18ugIRkU8D192NflzgAGwXLryijyYifBBgBAgAJBQJYrtUnAhsM + AAoJEMz74Z8ArIsdlkUEALTl6QUutJsqwVF4ZXKmmw0IEk8PkqW4G+tYRDHJMs6Z + O0nzDS89BG2DL4/UlOs5wRvERnlJYz01TMTxq/ciKaBTEjygFIv9CgIEZh97VacZ + TIqcF40k9SbpJNnh3JLf94xsNxNRJTEhbVC3uruaeILue/IR7pBMEyCs49Gcguwy + =b6UD + -----END PGP PRIVATE KEY BLOCK----- + KEY + end + + def public_key + <<~KEY.strip + -----BEGIN PGP PUBLIC KEY BLOCK----- + Version: GnuPG v1 + + mI0EWK7VJwEEANSFayuVYenl7sBKUjmIxwDRc3jd+K+FWUZgknLgiLcevaLh/mxV + 98dLxDKGDHHNKc/B7Y4qdlZYv1wfNQVuIbd8dqUQFOOkH7ukbgcGBTxH+2IM67y+ + QBH618luS5Gz1d4bd0YoFf/xZGEh9G5xicz7TiXYzLKjnMjHu2EmbFePABEBAAG0 + LU5hbm5pZSBCZXJuaGFyZCA8bmFubmllLmJlcm5oYXJkQGV4YW1wbGUuY29tPoi4 + BBMBAgAiBQJYrtUnAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDM++Gf + AKyLHaeSA/99oUWpb02PlfkALcx5RncboMHkgczYEU9wOFIgvXIReswThCMOvPZa + piui+ItyJfV3ijJfO8IvbbFcvU7jjGA073Bb7tbzAEOQLA16mWgBLQlGaRWbHDW4 + uwFxvkJKA0GzEsadEXeniESaZPc4rOXKPO3+/MSQWS2bmvvGsBTEuriNBFiu1ScB + BADIXkITf+kKCkD+n8tMsdTLInefu8KrJ8p7YRYCCabEXnWRsDb5zxUAG2VXCVUh + Yl6QXQybkNiBaduS+uxilz7gtYZUMFJvQ09+fV7D2N9B7u/1bGdIYz+cDFJnEJit + LY4w/nju2Sno5CL5Ead8sZuslKetSXPYHR/kbW462EOw5wARAQABiJ8EGAECAAkF + Aliu1ScCGwwACgkQzPvhnwCsix2WRQQAtOXpBS60myrBUXhlcqabDQgSTw+Spbgb + 61hEMckyzpk7SfMNLz0EbYMvj9SU6znBG8RGeUljPTVMxPGr9yIpoFMSPKAUi/0K + AgRmH3tVpxlMipwXjST1Jukk2eHckt/3jGw3E1ElMSFtULe6u5p4gu578hHukEwT + IKzj0ZyC7DI= + =Ug0r + -----END PGP PUBLIC KEY BLOCK----- + KEY + end + + def public_key_with_extra_signing_key + <<~KEY.strip + -----BEGIN PGP PUBLIC KEY BLOCK----- + Version: GnuPG v1 + + mI0EWK7VJwEEANSFayuVYenl7sBKUjmIxwDRc3jd+K+FWUZgknLgiLcevaLh/mxV + 98dLxDKGDHHNKc/B7Y4qdlZYv1wfNQVuIbd8dqUQFOOkH7ukbgcGBTxH+2IM67y+ + QBH618luS5Gz1d4bd0YoFf/xZGEh9G5xicz7TiXYzLKjnMjHu2EmbFePABEBAAG0 + LU5hbm5pZSBCZXJuaGFyZCA8bmFubmllLmJlcm5oYXJkQGV4YW1wbGUuY29tPoi4 + BBMBAgAiBQJYrtUnAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDM++Gf + AKyLHaeSA/99oUWpb02PlfkALcx5RncboMHkgczYEU9wOFIgvXIReswThCMOvPZa + piui+ItyJfV3ijJfO8IvbbFcvU7jjGA073Bb7tbzAEOQLA16mWgBLQlGaRWbHDW4 + uwFxvkJKA0GzEsadEXeniESaZPc4rOXKPO3+/MSQWS2bmvvGsBTEuriNBFiu1ScB + BADIXkITf+kKCkD+n8tMsdTLInefu8KrJ8p7YRYCCabEXnWRsDb5zxUAG2VXCVUh + Yl6QXQybkNiBaduS+uxilz7gtYZUMFJvQ09+fV7D2N9B7u/1bGdIYz+cDFJnEJit + LY4w/nju2Sno5CL5Ead8sZuslKetSXPYHR/kbW462EOw5wARAQABiJ8EGAECAAkF + Aliu1ScCGwwACgkQzPvhnwCsix2WRQQAtOXpBS60myrBUXhlcqabDQgSTw+Spbgb + 61hEMckyzpk7SfMNLz0EbYMvj9SU6znBG8RGeUljPTVMxPGr9yIpoFMSPKAUi/0K + AgRmH3tVpxlMipwXjST1Jukk2eHckt/3jGw3E1ElMSFtULe6u5p4gu578hHukEwT + IKzj0ZyC7DK5AQ0EWcx23AEIANwpAq85bT10JCBuNhOMyF2jKVt5wHbI9wBtjWYG + fgJFBkRvm6IsbmR0Y5DSBvF/of0UX1iGMfx6mvCDJkb1okquhCUef6MONWRpzXYE + CIZDm1TXu6yv0D35tkLfPo+/sY9UHHp1zGRcPAU46e8ztRwoD+zEJwy7lobLHGOL + 9OdWtCGjsutLOTqKRK4jsifr8n3rePU09rejhDkRONNs7ufn9GRcWMN7RWiFDtpU + gNe84AJ38qaXPU8GHNTrDtDtRRPmn68ezMmE1qTNsxQxD4Isexe5Wsfc4+ElaP9s + zaHgij7npX1HS9RpmhnOa2h1ESroM9cqDh3IJVhf+eP6/uMAEQEAAYkBxAQYAQIA + DwUCWcx23AIbAgUJAeEzgAEpCRDM++GfAKyLHcBdIAQZAQIABgUCWcx23AAKCRDk + garE0uOuES7DCAC2Kgl6zO+NqIBIS6McgcEN0sGyvOvZ8Ps4hBiMwCyDAnsIRAUi + v4KZMtQMAyl9njJ3YjPWBsdieuTz45O06DDnrzJpZO5rUGJjAcEue4zvRRWIyu3H + qHC8MsvkslsNCygJHoWlknm+HucroskTNtxHQ+FdKZ6Tey+twl1u+PhV8PQVyFkl + 4G1chO90EP4dvYrye26CC+ik2JkvC7Vy5M+U0PJikme8pFMjcdNks25BnAKcdqKU + AU8RTkSjoYvb8qSmZyldJjYjQRkTPRX1ZdaOID1EdiWl+s5cn0Oypo3z7BChcEMx + IWB/gmXQJQXVr5qNQnJObyMO/RczXYi9qNnyGMED/2EJJERLR0nefjHQalwDKQVP + s5lX1OKAcf2CrV6ZarckqaQgtqjZbuV2C2mrOHUs5uojlXaopj5gA4yJSGDcYhj1 + Rg9jdHWBtkHBj3eL32ZqrHDs3ap8ErZMmPE8A+mn9TTnQS+FY2QF7vBjJKM3qPT7 + DMVGWrg4m1NF8N6yMPMP + =RB1y + -----END PGP PUBLIC KEY BLOCK----- + KEY + end + + def primary_keyid + fingerprint[-16..-1] + end + + def fingerprint + '5F7EA3981A5845B141ABD522CCFBE19F00AC8B1D' + end + + def names + ['Nannie Bernhard'] + end + + def emails + ['nannie.bernhard@example.com'] + end + end + + module User2 + extend self + + def private_key + <<~KEY.strip + -----BEGIN PGP PRIVATE KEY BLOCK----- + Version: GnuPG v1 + + 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 + =yEqv + -----END PGP PRIVATE KEY BLOCK----- + KEY + end + + def public_key + <<~KEY.strip + -----BEGIN PGP PUBLIC KEY BLOCK----- + Version: GnuPG v1 + + mI0EWK6qKgEEAODjqOSIBNYzL23WenEVYnvvGJs9d1SjfosBmBnq4a6eBLNwUvjn + Rt0TIwwablEmnoMJiRmXBAOUz/9gSrB/QKar8CJsLmb1sevuF/mwzGXjSywnh+N3 + NPqh2KE7S/EPYBFKu2LCQRLMXGMnnWaM590+8AZ/S5zHOB5F9SUKszeTABEBAAG0 + L0JldHRlIENhcnR3cmlnaHQgPGJldHRlLmNhcnR3cmlnaHRAZXhhbXBsZS5jb20+ + iLgEEwECACIFAliuqioCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEL+d + kl+RHv1lQhsD/0LDi008hCeoZdGMuwy2N5FMUusYg/X1tRQ6E0JcYYjvvE5oIZoX + bVN0yLLD99P0rt6TSlt05CC7RPVPCNpKxKbF+TZMjMJiOi13XmY5Yti77ZMdBZtD + 7HeBzjkWCVuR6k8eIwy83niHGI6p6G87Q6XxvDjQ7wdBCNBpwkzmTYIBtC9CZXR0 + ZSBDYXJ0d3JpZ2h0IDxiZXR0ZS5jYXJ0d3JpZ2h0QGV4YW1wbGUubmV0Poi4BBMB + AgAiBQJYrwLFAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRC/nZJfkR79 + ZVzsBADYe3Hv71I7+lTJDvKOTmV7M4ShOfjEpmDvL/5JB3FzXHAucCOlReCv/6+N + afAspPTc1uys47dvtePwQalroAsd8Y1grn/PCh+Tx27kpn1WG8yZjUsuq3z5rrGR + uXj6g8aeZdqkMTHUVF9Gd+g/5KDODdJbOjwH+l63b/bOiATDFbiNBFiuqioBBADe + 5nUdVOcbZlnxOjl0KBAT+A5bmyBLUT0BmLPsmA4PuXDSth7WvibPC8wcCdCYVk0I + RMYneZUiWq/o5c4rthfLR4jg8kruvomQ4E4d4hyI6R0MLxXYZ3XMu67VuScFgbLU + Rw1eRZ16ANd3Nc1VuFW7ms0vCG0idB8iSZBoULaK8QARAQABiJ8EGAECAAkFAliu + qioCGwwACgkQv52SX5Ee/WVCCAQA1bBDCuVPYEUtZXwXH7CWDfX6ZyOROrQtpa7e + i54CW6hKX3fYHpOiu4yHromjGlINTjjHclsJpn/qmiJizkB9/xl7IRWV+RVpDshZ + 3mymOldpLiKgI5+Rl6mZtO4TtM9BYziIEC24h+IkaLBylZYz4ogfbzXKF8JxtHPM + UuwJVDE= + =0vYo + -----END PGP PUBLIC KEY BLOCK----- + KEY + end + + def primary_keyid + fingerprint[-16..-1] + end + + def fingerprint + '6D494CA6FC90C0CAE0910E42BF9D925F911EFD65' + end + + def names + ['Bette Cartwright', 'Bette Cartwright'] + end + + def emails + ['bette.cartwright@example.com', 'bette.cartwright@example.net'] + end + end + + # GPG Key with extra signing key + module User3 + extend self + + def signed_commit_signature + <<~SIGNATURE + -----BEGIN PGP SIGNATURE----- + + iQEzBAABCAAdFiEEBSLdKbmPFnzYQhdS44/8r3Wr2SoFAlnNlT8ACgkQ44/8r3Wr + 2SqP1wf9FC4J2S8LIHs/fpxgkYzsyCp5lCbS7JuoD4pqmI2KWyBx+vi9/3mZPCsm + Fj9f0vFEtNOb39GNGZbaA8DdGw30/WAS6kI6yco0WSK53KHrLw9Kqd+3e/NAVSsl + 991Gq4n8X1U5izSH+gZOMtEEUBGqIlZKgRrEh7lhNcz0G7JTF2VCE4NNtZdq7GDA + N6jOQxDGUwi9wQBYORQzIBc3NihfhGloII1hXf0XzrgUY3zNYHTT7QipCxKAmH54 + skwW+wi8RpBedar4saf7fs5xZbP/0yyVz98MJMdHBL68++Xt1AIHoqrb7eWszqnd + PCo/fnz1iHKCig602KLj0/zhADcNkg== + =LsTi + -----END PGP SIGNATURE----- + SIGNATURE + end + + def signed_commit_base_data + <<~SIGNEDDATA + tree 86ec18bfe87ad42a782fdabd8310f9b7ac750f51 + parent 2d1096e3a0ecf1d2baf6dee036cc80775d4940ba + author John Doe 1506645311 -0500 + committer John Doe 1506645311 -0500 + + Commit signed with subkey by John Doe + SIGNEDDATA + end + + def public_key + <<~KEY.strip + -----BEGIN PGP PUBLIC KEY BLOCK----- + + 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 + /jQYeOgFDKq20x86WulkvsUtBoaZJg== + =Q5Z7 + -----END PGP PUBLIC KEY BLOCK----- + KEY + end + + def secret_key + <<~SECRET + -----BEGIN PGP PRIVATE KEY BLOCK----- + + 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 + Jg== + =TKlF + -----END PGP PRIVATE KEY BLOCK----- + SECRET + end + + def fingerprint + 'EA3F8B88972A8FD474872F2F115B3AF1AAD3EDBA' + end + + def subkey_fingerprints + %w(159AD5DDF199591D67D2B87AA3CEC5C0A7C270EC 0522DD29B98F167CD8421752E38FFCAF75ABD92A) + end + + def names + ['John Doe'] + end + + def emails + ['john.doe@example.com'] + end + end + + # GPG Key containing just the main key + module User4 + extend self + + def public_key + <<~KEY.strip + -----BEGIN PGP PUBLIC KEY BLOCK----- + + mQENBFnWcesBCAC6Y8FXl9ZJ9HPa6dIYcgQrvjIQcwoQCUEsaXNRpc+206RPCIXK + aIYr0nTD8GeovMuUONXTj+DdueQU2GAAqHHOqvDDVXqRrW3xfWnSwix7sTuhG1Ew + PLHYmjLENqaTsdyliEo3N8VWy2k0QRbC3R6xvop4Ooa87D5vcATIl0gYFtSiHIL+ + TervYvTG9Eq1qSLZHbe2x4IzeqX2luikPKokL7j8FTZaCmC5MezIUur1ulfyYY/j + SkST/1aUFc5QXJJSZA0MYJWZX6x7Y3l7yl0dkHqmK8OTuo8RPWd3ybEiuvRsOL8K + GAv/PmVJRGDAf7GGbwXXsE9MiZ5GzVPxHnexABEBAAG0G0pvaG4gRG9lIDxqb2hu + QGV4YW1wbGUuY29tPokBTgQTAQgAOBYhBAh0izYM0lwuzJnVlAcBbPnhOj+bBQJZ + 1nHrAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEAcBbPnhOj+bkywH/i4w + OwpDxoTjUQlPlqGAGuzvWaPzSJndawgmMTr68oRsD+wlQmQQTR5eqxCpUIyV4aYb + D697RYzoqbT4mlU49ymzfKSAxFe88r1XQWdm81DcofHVPmw2GBrIqaX3Du4Z7xkI + Q9/S43orwknh5FoVwU8Nau7qBuv9vbw2apSkuA1oBj3spQ8hqwLavACyQ+fQloAT + hSDNqPiCZj6L0dwM1HYiqVoN3Q7qjgzzeBzlXzljJoWblhxllvMK20bVoa7H+uR2 + lczFHfsX8VTIMjyTGP7R3oHN91DEahlQybVVNLmNSDKZM2P/0d28BRUmWxQJ4Ws3 + J4hOWDKnLMed3VOIWzM= + =xVuW + -----END PGP PUBLIC KEY BLOCK----- + KEY + end + + def secret_key + <<~KEY.strip + -----BEGIN PGP PRIVATE KEY BLOCK----- + + lQPGBFnWcesBCAC6Y8FXl9ZJ9HPa6dIYcgQrvjIQcwoQCUEsaXNRpc+206RPCIXK + aIYr0nTD8GeovMuUONXTj+DdueQU2GAAqHHOqvDDVXqRrW3xfWnSwix7sTuhG1Ew + PLHYmjLENqaTsdyliEo3N8VWy2k0QRbC3R6xvop4Ooa87D5vcATIl0gYFtSiHIL+ + TervYvTG9Eq1qSLZHbe2x4IzeqX2luikPKokL7j8FTZaCmC5MezIUur1ulfyYY/j + SkST/1aUFc5QXJJSZA0MYJWZX6x7Y3l7yl0dkHqmK8OTuo8RPWd3ybEiuvRsOL8K + GAv/PmVJRGDAf7GGbwXXsE9MiZ5GzVPxHnexABEBAAH+BwMC4UwgHgH5Cp7meY39 + G5Q3GV2xtwADoaAvlOvPOLPK2fQqxQfb4WN4eZECp2wQuMRBMj52c4i9yphab1mQ + vOzoPIRGvkcJoxG++OxQ0kRk0C0gX6wM6SGVdb1nQnfZnoJCCU3IwCaSGktkLDs1 + jwdI+VmXJbSugUbd25bakHQcE2BaNHuRBlQWQfFbhGBy0+uMfNDBZ6FRipBu47hO + f/wm/xXuV8N8BSgvNR/qtAqSQI34CdsnWAhMYm9rqmTNyt0nq4dveX+E0YzVn4lH + lOEa7cpYeuBwIL8L3EvSPNCICiJlF3gVqiYzyqRElnCkv1OGc0x3W5onY/agHgGZ + KYyi/ubOdqqDgBR+eMt0JKSGH2EPxUAGFPY5F37u4erdxH86GzIinAExLSmADiVR + KtxluZP6S2KLbETN5uVbrfa+HVcMbbUZaBHHtL+YbY8PqaFUIvIUR1HM2SK7IrFw + KuQ8ibRgooyP7VgMNiPzlFpY4NXUv+FXIrNJ6ELuIaENi0izJ7aIbVBM8SijDz6u + 5EEmodnDvmU2hmQNZJ17TxggE7oeT0rKdDGHM5zBvqZ3deqE9sgKx/aTKcj61ID3 + M80ZkHPDFazUCohLpYgFN20bYYSmxU4LeNFy8YEiuic8QQKaAFxSf9Lf87UFQwyF + dduI1RWEbjMsbEJXwlmGM02ssQHsgoVKwZxijq5A5R1Ul6LowazQ8obPiwRS4NZ4 + Z+QKDon79MMXiFEeh1jeG/MKKWPxFg3pdtCWhC7WdH4hfkBsCVKf+T58yB2Gzziy + fOHvAl7v3PtdZgf1xikF8spGYGCWo4B2lxC79xIflKAb2U6myb5I4dpUYxzxoMxT + zxHwxEie3NxzZGUyXSt3LqYe2r4CxWnOCXWjIxxRlLue1BE5Za1ycnDRjgUO24+Z + uDQne6KLkhAotBtKb2huIERvZSA8am9obkBleGFtcGxlLmNvbT6JAU4EEwEIADgW + IQQIdIs2DNJcLsyZ1ZQHAWz54To/mwUCWdZx6wIbAwULCQgHAgYVCAkKCwIEFgID + AQIeAQIXgAAKCRAHAWz54To/m5MsB/4uMDsKQ8aE41EJT5ahgBrs71mj80iZ3WsI + JjE6+vKEbA/sJUJkEE0eXqsQqVCMleGmGw+ve0WM6Km0+JpVOPcps3ykgMRXvPK9 + V0FnZvNQ3KHx1T5sNhgayKml9w7uGe8ZCEPf0uN6K8JJ4eRaFcFPDWru6gbr/b28 + NmqUpLgNaAY97KUPIasC2rwAskPn0JaAE4Ugzaj4gmY+i9HcDNR2IqlaDd0O6o4M + 83gc5V85YyaFm5YcZZbzCttG1aGux/rkdpXMxR37F/FUyDI8kxj+0d6BzfdQxGoZ + UMm1VTS5jUgymTNj/9HdvAUVJlsUCeFrNyeITlgypyzHnd1TiFsz + =/37z + -----END PGP PRIVATE KEY BLOCK----- + KEY + end + + def primary_keyid + fingerprint[-16..-1] + end + + def fingerprint + '08748B360CD25C2ECC99D59407016CF9E13A3F9B' + end + end +end diff --git a/spec/support/helpers/import_spec_helper.rb b/spec/support/helpers/import_spec_helper.rb new file mode 100644 index 00000000000..d4eced724fa --- /dev/null +++ b/spec/support/helpers/import_spec_helper.rb @@ -0,0 +1,33 @@ +require 'ostruct' + +# Helper methods for controller specs in the Import namespace +# +# Must be included manually. +module ImportSpecHelper + # Stub `controller` to return a null object double with the provided messages + # when `client` is called + # + # Examples: + # + # stub_client(foo: %w(foo)) + # + # controller.client.foo # => ["foo"] + # controller.client.bar.baz.foo # => ["foo"] + # + # Returns the client double + def stub_client(messages = {}) + client = double('client', messages).as_null_object + allow(controller).to receive(:client).and_return(client) + + client + end + + def stub_omniauth_provider(name) + provider = OpenStruct.new( + name: name, + app_id: 'asd123', + app_secret: 'asd123' + ) + stub_omniauth_setting(providers: [provider]) + end +end diff --git a/spec/support/helpers/input_helper.rb b/spec/support/helpers/input_helper.rb new file mode 100644 index 00000000000..acbb42274ec --- /dev/null +++ b/spec/support/helpers/input_helper.rb @@ -0,0 +1,7 @@ +# see app/assets/javascripts/test_utils/simulate_input.js + +module InputHelper + def simulate_input(selector, input = '') + evaluate_script("window.simulateInput(#{selector.to_json}, #{input.to_json});") + end +end diff --git a/spec/support/helpers/inspect_requests.rb b/spec/support/helpers/inspect_requests.rb new file mode 100644 index 00000000000..88ddc5c7f6c --- /dev/null +++ b/spec/support/helpers/inspect_requests.rb @@ -0,0 +1,17 @@ +require_relative './wait_for_requests' + +module InspectRequests + extend self + include WaitForRequests + + def inspect_requests(inject_headers: {}) + Gitlab::Testing::RequestInspectorMiddleware.log_requests!(inject_headers) + + yield + + wait_for_all_requests + Gitlab::Testing::RequestInspectorMiddleware.requests + ensure + Gitlab::Testing::RequestInspectorMiddleware.stop_logging! + end +end diff --git a/spec/support/helpers/issue_helpers.rb b/spec/support/helpers/issue_helpers.rb new file mode 100644 index 00000000000..ffd72515f37 --- /dev/null +++ b/spec/support/helpers/issue_helpers.rb @@ -0,0 +1,13 @@ +module IssueHelpers + def visit_issues(project, opts = {}) + visit project_issues_path project, opts + end + + def first_issue + page.all('ul.issues-list > li').first.text + end + + def last_issue + page.all('ul.issues-list > li').last.text + end +end diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb new file mode 100644 index 00000000000..2197bc9d853 --- /dev/null +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -0,0 +1,66 @@ +require 'action_dispatch/testing/test_request' +require 'fileutils' + +module JavaScriptFixturesHelpers + include Gitlab::Popen + + FIXTURE_PATH = 'spec/javascripts/fixtures'.freeze + + # Public: Removes all fixture files from given directory + # + # directory_name - directory of the fixtures (relative to FIXTURE_PATH) + # + def clean_frontend_fixtures(directory_name) + directory_name = File.expand_path(directory_name, FIXTURE_PATH) + Dir[File.expand_path('*.html.raw', directory_name)].each do |file_name| + FileUtils.rm(file_name) + end + end + + # Public: Store a response object as fixture file + # + # response - string or response object to store + # fixture_file_name - file name to store the fixture in (relative to FIXTURE_PATH) + # + def store_frontend_fixture(response, fixture_file_name) + fixture_file_name = File.expand_path(fixture_file_name, FIXTURE_PATH) + fixture = response.respond_to?(:body) ? parse_response(response) : response + + FileUtils.mkdir_p(File.dirname(fixture_file_name)) + File.write(fixture_file_name, fixture) + end + + def remove_repository(project) + Gitlab::Shell.new.remove_repository(project.repository_storage_path, project.disk_path) + end + + private + + # Private: Prepare a response object for use as a frontend fixture + # + # response - response object to prepare + # + def parse_response(response) + fixture = response.body + fixture.force_encoding("utf-8") + + response_mime_type = Mime::Type.lookup(response.content_type) + if response_mime_type.html? + doc = Nokogiri::HTML::DocumentFragment.parse(fixture) + + link_tags = doc.css('link') + link_tags.remove + + scripts = doc.css("script:not([type='text/template']):not([type='text/x-template'])") + scripts.remove + + fixture = doc.to_html + + # replace relative links + test_host = ActionDispatch::TestRequest::DEFAULT_ENV['HTTP_HOST'] + fixture.gsub!(%r{="/}, "=\"http://#{test_host}/") + end + + fixture + end +end diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb new file mode 100644 index 00000000000..88a7aeba461 --- /dev/null +++ b/spec/support/helpers/jira_service_helper.rb @@ -0,0 +1,88 @@ +module JiraServiceHelper + JIRA_URL = "http://jira.example.net".freeze + JIRA_API = JIRA_URL + "/rest/api/2" + + def jira_service_settings + properties = { + title: "JIRA tracker", + url: JIRA_URL, + username: 'jira-user', + password: 'my-secret-password', + project_key: "JIRA", + jira_issue_transition_id: '1' + } + + jira_tracker.update_attributes(properties: properties, active: true) + end + + def jira_issue_comments + "{\"startAt\":0,\"maxResults\":11,\"total\":11, + \"comments\":[{\"self\":\"http://0.0.0.0:4567/rest/api/2/issue/10002/comment/10609\", + \"id\":\"10609\",\"author\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\", + \"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", + \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", + \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", + \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", + \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"}, + \"displayName\":\"GitLab\",\"active\":true}, + \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned JIRA-1 in Merge request of [gitlab-org/gitlab-test|http://localhost:3000/gitlab-org/gitlab-test/merge_requests/2].\", + \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", + \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", + \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", + \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", + \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true}, + \"created\":\"2015-02-12T22:47:07.826+0100\", + \"updated\":\"2015-02-12T22:47:07.826+0100\"}, + {\"self\":\"http://0.0.0.0:4567/rest/api/2/issue/10002/comment/10700\", + \"id\":\"10700\",\"author\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\", + \"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", + \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", + \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", + \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", + \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true}, + \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned this issue in [a commit of h5bp/html5-boilerplate|http://localhost:3000/h5bp/html5-boilerplate/commit/2439f77897122fbeee3bfd9bb692d3608848433e].\", + \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", + \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", + \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", + \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", + \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true}, + \"created\":\"2015-04-01T03:45:55.667+0200\", + \"updated\":\"2015-04-01T03:45:55.667+0200\" + } + ]}" + end + + def jira_project_url + JIRA_API + "/project" + end + + def jira_api_comment_url(issue_id) + JIRA_API + "/issue/#{issue_id}/comment" + end + + def jira_api_remote_link_url(issue_id) + JIRA_API + "/issue/#{issue_id}/remotelink" + end + + def jira_api_transition_url(issue_id) + JIRA_API + "/issue/#{issue_id}/transitions" + end + + def jira_api_test_url + JIRA_API + "/myself" + end + + def jira_issue_url(issue_id) + JIRA_API + "/issue/#{issue_id}" + end + + def stub_jira_urls(issue_id) + WebMock.stub_request(:get, jira_project_url) + WebMock.stub_request(:get, jira_api_comment_url(issue_id)).to_return(body: jira_issue_comments) + WebMock.stub_request(:get, jira_issue_url(issue_id)) + WebMock.stub_request(:get, jira_api_test_url) + WebMock.stub_request(:post, jira_api_comment_url(issue_id)) + WebMock.stub_request(:post, jira_api_remote_link_url(issue_id)) + WebMock.stub_request(:post, jira_api_transition_url(issue_id)) + end +end diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb new file mode 100644 index 00000000000..e46b61b6461 --- /dev/null +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -0,0 +1,104 @@ +module KubernetesHelpers + include Gitlab::Kubernetes + + def kube_response(body) + { body: body.to_json } + end + + def kube_pods_response + kube_response(kube_pods_body) + end + + def stub_kubeclient_discover(api_url) + WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body)) + end + + def stub_kubeclient_pods(response = nil) + stub_kubeclient_discover(service.api_url) + pods_url = service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods" + + WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response) + end + + def stub_kubeclient_get_secrets(api_url, **options) + WebMock.stub_request(:get, api_url + '/api/v1/secrets') + .to_return(kube_response(kube_v1_secrets_body(options))) + end + + def stub_kubeclient_get_secrets_error(api_url) + WebMock.stub_request(:get, api_url + '/api/v1/secrets') + .to_return(status: [404, "Internal Server Error"]) + end + + def kube_v1_secrets_body(**options) + { + "kind" => "SecretList", + "apiVersion": "v1", + "items" => [ + { + "metadata": { + "name": options[:metadata_name] || "default-token-1", + "namespace": "kube-system" + }, + "data": { + "token": options[:token] || Base64.encode64('token-sample-123') + } + } + ] + } + end + + def kube_v1_discovery_body + { + "kind" => "APIResourceList", + "resources" => [ + { "name" => "pods", "namespaced" => true, "kind" => "Pod" }, + { "name" => "secrets", "namespaced" => true, "kind" => "Secret" } + ] + } + end + + def kube_pods_body + { + "kind" => "PodList", + "items" => [kube_pod] + } + end + + # This is a partial response, it will have many more elements in reality but + # these are the ones we care about at the moment + def kube_pod(name: "kube-pod", app: "valid-pod-label") + { + "metadata" => { + "name" => name, + "creationTimestamp" => "2016-11-25T19:55:19Z", + "labels" => { "app" => app } + }, + "spec" => { + "containers" => [ + { "name" => "container-0" }, + { "name" => "container-1" } + ] + }, + "status" => { "phase" => "Running" } + } + end + + def kube_terminals(service, pod) + pod_name = pod['metadata']['name'] + containers = pod['spec']['containers'] + + containers.map do |container| + terminal = { + selectors: { pod: pod_name, container: container['name'] }, + url: container_exec_url(service.api_url, service.actual_namespace, pod_name, container['name']), + subprotocols: ['channel.k8s.io'], + headers: { 'Authorization' => ["Bearer #{service.token}"] }, + created_at: DateTime.parse(pod['metadata']['creationTimestamp']), + max_session_time: 0 + } + terminal[:ca_pem] = service.ca_pem if service.ca_pem.present? + terminal + end + end +end diff --git a/spec/support/helpers/ldap_helpers.rb b/spec/support/helpers/ldap_helpers.rb new file mode 100644 index 00000000000..0e87b3d359d --- /dev/null +++ b/spec/support/helpers/ldap_helpers.rb @@ -0,0 +1,49 @@ +module LdapHelpers + def ldap_adapter(provider = 'ldapmain', ldap = double(:ldap)) + ::Gitlab::Auth::LDAP::Adapter.new(provider, ldap) + end + + def user_dn(uid) + "uid=#{uid},ou=users,dc=example,dc=com" + end + + # Accepts a hash of Gitlab::Auth::LDAP::Config keys and values. + # + # Example: + # stub_ldap_config( + # group_base: 'ou=groups,dc=example,dc=com', + # admin_group: 'my-admin-group' + # ) + def stub_ldap_config(messages) + allow_any_instance_of(::Gitlab::Auth::LDAP::Config).to receive_messages(messages) + end + + # Stub an LDAP person search and provide the return entry. Specify `nil` for + # `entry` to simulate when an LDAP person is not found + # + # Example: + # adapter = ::Gitlab::Auth::LDAP::Adapter.new('ldapmain', double(:ldap)) + # ldap_user_entry = ldap_user_entry('john_doe') + # + # stub_ldap_person_find_by_uid('john_doe', ldap_user_entry, adapter) + def stub_ldap_person_find_by_uid(uid, entry, provider = 'ldapmain') + return_value = ::Gitlab::Auth::LDAP::Person.new(entry, provider) if entry.present? + + allow(::Gitlab::Auth::LDAP::Person) + .to receive(:find_by_uid).with(uid, any_args).and_return(return_value) + end + + # Create a simple LDAP user entry. + def ldap_user_entry(uid) + entry = Net::LDAP::Entry.new + entry['dn'] = user_dn(uid) + entry['uid'] = uid + + entry + end + + def raise_ldap_connection_error + allow_any_instance_of(Gitlab::Auth::LDAP::Adapter) + .to receive(:ldap_search).and_raise(Gitlab::Auth::LDAP::LDAPConnectionError) + end +end diff --git a/spec/support/helpers/live_debugger.rb b/spec/support/helpers/live_debugger.rb new file mode 100644 index 00000000000..911eb48a8ca --- /dev/null +++ b/spec/support/helpers/live_debugger.rb @@ -0,0 +1,17 @@ +require 'io/console' + +module LiveDebugger + def live_debug + puts + puts "Current example is paused for live debugging." + puts "Opening #{current_url} in your default browser..." + puts "The current user credentials are: #{@current_user.username} / #{@current_user.password}" if @current_user + puts "Press any key to resume the execution of the example!!" + + `open #{current_url}` + + loop until $stdin.getch + + puts "Back to the example!" + end +end diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb new file mode 100644 index 00000000000..db34090e971 --- /dev/null +++ b/spec/support/helpers/login_helpers.rb @@ -0,0 +1,162 @@ +require_relative 'devise_helpers' + +module LoginHelpers + include DeviseHelpers + + # Overriding Devise::Test::IntegrationHelpers#sign_in to store @current_user + # since we may need it in LiveDebugger#live_debug. + def sign_in(resource, scope: nil) + super + + @current_user = resource + end + + # Overriding Devise::Test::IntegrationHelpers#sign_out to clear @current_user. + def sign_out(resource_or_scope) + super + + @current_user = nil + end + + # Internal: Log in as a specific user or a new user of a specific role + # + # user_or_role - User object, or a role to create (e.g., :admin, :user) + # + # Examples: + # + # # Create a user automatically + # gitlab_sign_in(:user) + # + # # Create an admin automatically + # gitlab_sign_in(:admin) + # + # # Provide an existing User record + # user = create(:user) + # gitlab_sign_in(user) + def gitlab_sign_in(user_or_role, **kwargs) + user = + if user_or_role.is_a?(User) + user_or_role + else + create(user_or_role) + end + + gitlab_sign_in_with(user, **kwargs) + + @current_user = user + end + + def gitlab_sign_in_via(provider, user, uid) + mock_auth_hash(provider, uid, user.email) + visit new_user_session_path + click_link provider + end + + # Requires Javascript driver. + def gitlab_sign_out + find(".header-user-dropdown-toggle").click + click_link "Sign out" + @current_user = nil + + expect(page).to have_button('Sign in') + end + + private + + # Private: Login as the specified user + # + # user - User instance to login with + # remember - Whether or not to check "Remember me" (default: false) + def gitlab_sign_in_with(user, remember: false) + visit new_user_session_path + + fill_in "user_login", with: user.email + fill_in "user_password", with: "12345678" + check 'user_remember_me' if remember + + click_button "Sign in" + end + + def login_via(provider, user, uid, remember_me: false) + mock_auth_hash(provider, uid, user.email) + visit new_user_session_path + expect(page).to have_content('Sign in with') + + check 'remember_me' if remember_me + + click_link "oauth-login-#{provider}" + end + + def mock_auth_hash(provider, uid, email) + # The mock_auth configuration allows you to set per-provider (or default) + # authentication hashes to return during integration testing. + OmniAuth.config.mock_auth[provider.to_sym] = OmniAuth::AuthHash.new({ + provider: provider, + uid: uid, + info: { + name: 'mockuser', + email: email, + image: 'mock_user_thumbnail_url' + }, + credentials: { + token: 'mock_token', + secret: 'mock_secret' + }, + extra: { + raw_info: { + info: { + name: 'mockuser', + email: email, + image: 'mock_user_thumbnail_url' + } + } + } + }) + Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:saml] + end + + def mock_saml_config + OpenStruct.new(name: 'saml', label: 'saml', args: { + assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback', + idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52', + idp_sso_target_url: 'https://idp.example.com/sso/saml', + issuer: 'https://localhost:3443/', + name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' + }) + end + + def stub_omniauth_provider(provider, context: Rails.application) + env = env_from_context(context) + + set_devise_mapping(context: context) + env['omniauth.auth'] = OmniAuth.config.mock_auth[provider] + end + + def stub_omniauth_saml_config(messages) + set_devise_mapping(context: Rails.application) + Rails.application.routes.disable_clear_and_finalize = true + Rails.application.routes.draw do + post '/users/auth/saml' => 'omniauth_callbacks#saml' + end + allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config) + stub_omniauth_setting(messages) + stub_saml_authorize_path_helpers + end + + def stub_saml_authorize_path_helpers + allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml') + allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml') + end + + def stub_omniauth_config(messages) + allow(Gitlab.config.omniauth).to receive_messages(messages) + end + + def stub_basic_saml_config + allow(Gitlab::Auth::Saml::Config).to receive_messages({ options: { name: 'saml', args: {} } }) + end + + def stub_saml_group_config(groups) + allow(Gitlab::Auth::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } }) + end +end diff --git a/spec/support/helpers/markdown_feature.rb b/spec/support/helpers/markdown_feature.rb new file mode 100644 index 00000000000..39e94ad53de --- /dev/null +++ b/spec/support/helpers/markdown_feature.rb @@ -0,0 +1,128 @@ +# This is a helper class used by the GitLab Markdown feature spec +# +# Because the feature spec only cares about the output of the Markdown, and the +# test setup and teardown and parsing is fairly expensive, we only want to do it +# once. Unfortunately RSpec will not let you access `let`s in a `before(:all)` +# block, so we fake it by encapsulating all the shared setup in this class. +# +# The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for +# reference to the factory-created objects. +class MarkdownFeature + include FactoryBot::Syntax::Methods + + def user + @user ||= create(:user) + end + + def group + @group ||= create(:group).tap do |group| + group.add_developer(user) + end + end + + # Direct references ---------------------------------------------------------- + + def project + @project ||= create(:project, :repository, group: group).tap do |project| + project.add_master(user) + end + end + + def project_wiki + @project_wiki ||= ProjectWiki.new(project, user) + end + + def project_wiki_page + @project_wiki_page ||= build(:wiki_page, wiki: project_wiki) + end + + def issue + @issue ||= create(:issue, project: project) + end + + def merge_request + @merge_request ||= create(:merge_request, :simple, source_project: project) + end + + def snippet + @snippet ||= create(:project_snippet, project: project) + end + + def commit + @commit ||= project.commit + end + + def commit_range + @commit_range ||= begin + commit2 = project.commit('HEAD~3') + CommitRange.new("#{commit.id}...#{commit2.id}", project) + end + end + + def simple_label + @simple_label ||= create(:label, name: 'gfm', project: project) + end + + def label + @label ||= create(:label, name: 'awaiting feedback', project: project) + end + + def simple_milestone + @simple_milestone ||= create(:milestone, name: 'gfm-milestone', project: project) + end + + def milestone + @milestone ||= create(:milestone, name: 'next goal', project: project) + end + + def group_milestone + @group_milestone ||= create(:milestone, name: 'group-milestone', group: group) + end + + # Cross-references ----------------------------------------------------------- + + def xproject + @xproject ||= begin + group = create(:group, :nested) + create(:project, :repository, namespace: group) do |project| + project.add_developer(user) + end + end + end + + def xissue + @xissue ||= create(:issue, project: xproject) + end + + def xmerge_request + @xmerge_request ||= create(:merge_request, :simple, source_project: xproject) + end + + def xsnippet + @xsnippet ||= create(:project_snippet, project: xproject) + end + + def xcommit + @xcommit ||= xproject.commit + end + + def xcommit_range + @xcommit_range ||= begin + xcommit2 = xproject.commit('HEAD~2') + CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject) + end + end + + def xmilestone + @xmilestone ||= create(:milestone, project: xproject) + end + + def urls + Gitlab::Routing.url_helpers + end + + def raw_markdown + markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb')) + ERB.new(markdown).result(binding) + end +end diff --git a/spec/support/helpers/merge_request_helpers.rb b/spec/support/helpers/merge_request_helpers.rb new file mode 100644 index 00000000000..772adff4626 --- /dev/null +++ b/spec/support/helpers/merge_request_helpers.rb @@ -0,0 +1,22 @@ +module MergeRequestHelpers + def visit_merge_requests(project, opts = {}) + visit project_merge_requests_path project, opts + end + + def first_merge_request + page.all('ul.mr-list > li').first.text + end + + def last_merge_request + page.all('ul.mr-list > li').last.text + end + + def expect_mr_list_count(open_count, closed_count = 0) + all_count = open_count + closed_count + + expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count) + page.within '.mr-list' do + expect(page).to have_selector('.merge-request', count: open_count) + end + end +end diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb new file mode 100644 index 00000000000..5d6f662e8fe --- /dev/null +++ b/spec/support/helpers/migrations_helpers.rb @@ -0,0 +1,92 @@ +module MigrationsHelpers + def table(name) + Class.new(ActiveRecord::Base) do + self.table_name = name + self.inheritance_column = :_type_disabled + end + end + + def migrations_paths + ActiveRecord::Migrator.migrations_paths + end + + def table_exists?(name) + ActiveRecord::Base.connection.table_exists?(name) + end + + def migrations + ActiveRecord::Migrator.migrations(migrations_paths) + end + + def clear_schema_cache! + ActiveRecord::Base.connection_pool.connections.each do |conn| + conn.schema_cache.clear! + end + end + + def reset_column_in_all_models + clear_schema_cache! + + # Reset column information for the most offending classes **after** we + # migrated the schema up, otherwise, column information could be + # outdated. We have a separate method for this so we can override it in EE. + ActiveRecord::Base.descendants.each(&method(:reset_column_information)) + + # Without that, we get errors because of missing attributes, e.g. + # super: no superclass method `elasticsearch_indexing' for # + ApplicationSetting.define_attribute_methods + end + + def reset_column_information(klass) + klass.reset_column_information + end + + def previous_migration + migrations.each_cons(2) do |previous, migration| + break previous if migration.name == described_class.name + end + end + + def migration_schema_version + metadata_schema = self.class.metadata[:schema] + + if metadata_schema == :latest + migrations.last.version + else + metadata_schema || previous_migration.version + end + end + + def schema_migrate_down! + disable_migrations_output do + ActiveRecord::Migrator.migrate(migrations_paths, + migration_schema_version) + end + + reset_column_in_all_models + end + + def schema_migrate_up! + reset_column_in_all_models + + disable_migrations_output do + ActiveRecord::Migrator.migrate(migrations_paths) + end + + reset_column_in_all_models + end + + def disable_migrations_output + ActiveRecord::Migration.verbose = false + + yield + ensure + ActiveRecord::Migration.verbose = true + end + + def migrate! + ActiveRecord::Migrator.up(migrations_paths) do |migration| + migration.name == described_class.name + end + end +end diff --git a/spec/support/helpers/mobile_helpers.rb b/spec/support/helpers/mobile_helpers.rb new file mode 100644 index 00000000000..3b9eb84e824 --- /dev/null +++ b/spec/support/helpers/mobile_helpers.rb @@ -0,0 +1,17 @@ +module MobileHelpers + def resize_screen_xs + resize_window(767, 768) + end + + def resize_screen_sm + resize_window(900, 768) + end + + def restore_window_size + resize_window(1366, 768) + end + + def resize_window(width, height) + Capybara.current_session.current_window.resize_to(width, height) + end +end diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb new file mode 100644 index 00000000000..2c501a2a27c --- /dev/null +++ b/spec/support/helpers/project_forks_helper.rb @@ -0,0 +1,54 @@ +module ProjectForksHelper + def fork_project(project, user = nil, params = {}) + # Load the `fork_network` for the project to fork as there might be one that + # wasn't loaded yet. + project.reload unless project.fork_network + + unless user + user = create(:user) + project.add_developer(user) + end + + unless params[:namespace] || params[:namespace_id] + params[:namespace] = create(:group) + params[:namespace].add_owner(user) + end + + service = Projects::ForkService.new(project, user, params) + + create_repository = params.delete(:repository) + # Avoid creating a repository + unless create_repository + allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) + shell = double('gitlab_shell', fork_repository: true) + allow(service).to receive(:gitlab_shell).and_return(shell) + end + + forked_project = service.execute + + # Reload the both projects so they know about their newly created fork_network + if forked_project.persisted? + project.reload + forked_project.reload + end + + if create_repository + # The call to project.repository.after_import in RepositoryForkWorker does + # not reset the @exists variable of this forked_project.repository + # so we have to explicitely call this method to clear the @exists variable. + # of the instance we're returning here. + forked_project.repository.after_import + end + + forked_project + end + + def fork_project_with_submodules(project, user = nil, params = {}) + forked_project = fork_project(project, user, params) + TestEnv.copy_repo(forked_project, + bare_repo: TestEnv.forked_repo_path_bare, + refs: TestEnv::FORKED_BRANCH_SHA) + forked_project.repository.after_import + forked_project + end +end diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb new file mode 100644 index 00000000000..4212be2cc88 --- /dev/null +++ b/spec/support/helpers/prometheus_helpers.rb @@ -0,0 +1,202 @@ +module PrometheusHelpers + def prometheus_memory_query(environment_slug) + %{avg(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / 2^20} + end + + def prometheus_cpu_query(environment_slug) + %{avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) * 100} + end + + def prometheus_ping_url(prometheus_query) + query = { query: prometheus_query }.to_query + + "https://prometheus.example.com/api/v1/query?#{query}" + end + + def prometheus_query_url(prometheus_query) + query = { query: prometheus_query }.to_query + + "https://prometheus.example.com/api/v1/query?#{query}" + end + + def prometheus_query_with_time_url(prometheus_query, time) + query = { query: prometheus_query, time: time.to_f }.to_query + + "https://prometheus.example.com/api/v1/query?#{query}" + end + + def prometheus_query_range_url(prometheus_query, start: 8.hours.ago, stop: Time.now.to_f) + query = { + query: prometheus_query, + start: start.to_f, + end: stop, + step: 1.minute.to_i + }.to_query + + "https://prometheus.example.com/api/v1/query_range?#{query}" + end + + def prometheus_label_values_url(name) + "https://prometheus.example.com/api/v1/label/#{name}/values" + end + + def prometheus_series_url(*matches, start: 8.hours.ago, stop: Time.now) + query = { + match: matches, + start: start.to_f, + end: stop.to_f + }.to_query + "https://prometheus.example.com/api/v1/series?#{query}" + end + + def stub_prometheus_request(url, body: {}, status: 200) + WebMock.stub_request(:get, url) + .to_return({ + status: status, + headers: { 'Content-Type' => 'application/json' }, + body: body.to_json + }) + end + + def stub_prometheus_request_with_exception(url, exception_type) + WebMock.stub_request(:get, url).to_raise(exception_type) + end + + def stub_all_prometheus_requests(environment_slug, body: nil, status: 200) + stub_prometheus_request( + prometheus_query_with_time_url(prometheus_memory_query(environment_slug), Time.now.utc), + status: status, + body: body || prometheus_value_body + ) + stub_prometheus_request( + prometheus_query_with_time_url(prometheus_memory_query(environment_slug), 8.hours.ago), + status: status, + body: body || prometheus_value_body + ) + stub_prometheus_request( + prometheus_query_range_url(prometheus_memory_query(environment_slug)), + status: status, + body: body || prometheus_values_body + ) + stub_prometheus_request( + prometheus_query_with_time_url(prometheus_cpu_query(environment_slug), Time.now.utc), + status: status, + body: body || prometheus_value_body + ) + stub_prometheus_request( + prometheus_query_with_time_url(prometheus_cpu_query(environment_slug), 8.hours.ago), + status: status, + body: body || prometheus_value_body + ) + stub_prometheus_request( + prometheus_query_range_url(prometheus_cpu_query(environment_slug)), + status: status, + body: body || prometheus_values_body + ) + end + + def prometheus_data(last_update: Time.now.utc) + { + success: true, + data: { + memory_values: prometheus_values_body('matrix').dig(:data, :result), + memory_current: prometheus_value_body('vector').dig(:data, :result), + cpu_values: prometheus_values_body('matrix').dig(:data, :result), + cpu_current: prometheus_value_body('vector').dig(:data, :result) + }, + last_update: last_update + } + end + + def prometheus_metrics_data(last_update: Time.now.utc) + { + success: true, + metrics: { + memory_values: prometheus_values_body('matrix').dig(:data, :result), + memory_current: prometheus_value_body('vector').dig(:data, :result), + cpu_values: prometheus_values_body('matrix').dig(:data, :result), + cpu_current: prometheus_value_body('vector').dig(:data, :result) + }, + last_update: last_update + } + end + + def prometheus_empty_body(type) + { + "status": "success", + "data": { + "resultType": type, + "result": [] + } + } + end + + def prometheus_value_body(type = 'vector') + { + "status": "success", + "data": { + "resultType": type, + "result": [ + { + "metric": {}, + "value": [ + 1488772511.004, + "0.000041021495238095323" + ] + } + ] + } + } + end + + def prometheus_values_body(type = 'matrix') + { + "status": "success", + "data": { + "resultType": type, + "result": [ + { + "metric": {}, + "values": [ + [1488758662.506, "0.00002996364761904785"], + [1488758722.506, "0.00003090239047619091"] + ] + } + ] + } + } + end + + def prometheus_label_values + { + 'status': 'success', + 'data': %w(job_adds job_controller_rate_limiter_use job_depth job_queue_latency job_work_duration_sum up) + } + end + + def prometheus_series(name) + { + 'status': 'success', + 'data': [ + { + '__name__': name, + 'container_name': 'gitlab', + 'environment': 'mattermost', + 'id': '/docker/9953982f95cf5010dfc59d7864564d5f188aaecddeda343699783009f89db667', + 'image': 'gitlab/gitlab-ce:8.15.4-ce.1', + 'instance': 'minikube', + 'job': 'kubernetes-nodes', + 'name': 'k8s_gitlab.e6611886_mattermost-4210310111-77z8r_gitlab_2298ae6b-da24-11e6-baee-8e7f67d0eb3a_43536cb6', + 'namespace': 'gitlab', + 'pod_name': 'mattermost-4210310111-77z8r' + }, + { + '__name__': name, + 'id': '/docker', + 'instance': 'minikube', + 'job': 'kubernetes-nodes' + } + ] + } + end +end diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb new file mode 100644 index 00000000000..28536bbef5e --- /dev/null +++ b/spec/support/helpers/query_recorder.rb @@ -0,0 +1,38 @@ +module ActiveRecord + class QueryRecorder + attr_reader :log, :cached + + def initialize(&block) + @log = [] + @cached = [] + ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block) + end + + def show_backtrace(values) + Rails.logger.debug("QueryRecorder SQL: #{values[:sql]}") + caller.each { |line| Rails.logger.debug(" --> #{line}") } + end + + def callback(name, start, finish, message_id, values) + show_backtrace(values) if ENV['QUERY_RECORDER_DEBUG'] + + if values[:name]&.include?("CACHE") + @cached << values[:sql] + elsif !values[:name]&.include?("SCHEMA") + @log << values[:sql] + end + end + + def count + @log.count + end + + def cached_count + @cached.count + end + + def log_message + @log.join("\n\n") + end + end +end diff --git a/spec/support/helpers/quick_actions_helpers.rb b/spec/support/helpers/quick_actions_helpers.rb new file mode 100644 index 00000000000..361190aa352 --- /dev/null +++ b/spec/support/helpers/quick_actions_helpers.rb @@ -0,0 +1,10 @@ +module QuickActionsHelpers + def write_note(text) + Sidekiq::Testing.fake! do + page.within('.js-main-target-form') do + fill_in 'note[note]', with: text + find('.js-comment-submit-button').click + end + end + end +end diff --git a/spec/support/helpers/rake_helpers.rb b/spec/support/helpers/rake_helpers.rb new file mode 100644 index 00000000000..86bfeed107c --- /dev/null +++ b/spec/support/helpers/rake_helpers.rb @@ -0,0 +1,19 @@ +module RakeHelpers + def run_rake_task(task_name, *args) + Rake::Task[task_name].reenable + Rake.application.invoke_task("#{task_name}[#{args.join(',')}]") + end + + def stub_warn_user_is_not_gitlab + allow(main_object).to receive(:warn_user_is_not_gitlab) + end + + def silence_output + allow(main_object).to receive(:puts) + allow(main_object).to receive(:print) + end + + def main_object + @main_object ||= TOPLEVEL_BINDING.eval('self') + end +end diff --git a/spec/support/helpers/reactive_caching_helpers.rb b/spec/support/helpers/reactive_caching_helpers.rb new file mode 100644 index 00000000000..e22dd974c6a --- /dev/null +++ b/spec/support/helpers/reactive_caching_helpers.rb @@ -0,0 +1,48 @@ +module ReactiveCachingHelpers + def reactive_cache_key(subject, *qualifiers) + ([subject.class.reactive_cache_key.call(subject)].flatten + qualifiers).join(':') + end + + def alive_reactive_cache_key(subject, *qualifiers) + reactive_cache_key(subject, *(qualifiers + ['alive'])) + end + + def stub_reactive_cache(subject = nil, data = nil, *qualifiers) + allow(ReactiveCachingWorker).to receive(:perform_async) + allow(ReactiveCachingWorker).to receive(:perform_in) + write_reactive_cache(subject, data, *qualifiers) if data + end + + def synchronous_reactive_cache(subject) + allow(service).to receive(:with_reactive_cache) do |*args, &block| + block.call(service.calculate_reactive_cache(*args)) + end + end + + def read_reactive_cache(subject, *qualifiers) + Rails.cache.read(reactive_cache_key(subject, *qualifiers)) + end + + def write_reactive_cache(subject, data, *qualifiers) + start_reactive_cache_lifetime(subject, *qualifiers) + Rails.cache.write(reactive_cache_key(subject, *qualifiers), data) + end + + def reactive_cache_alive?(subject, *qualifiers) + Rails.cache.read(alive_reactive_cache_key(subject, *qualifiers)) + end + + def invalidate_reactive_cache(subject, *qualifiers) + Rails.cache.delete(alive_reactive_cache_key(subject, *qualifiers)) + end + + def start_reactive_cache_lifetime(subject, *qualifiers) + Rails.cache.write(alive_reactive_cache_key(subject, *qualifiers), true) + end + + def expect_reactive_cache_update_queued(subject) + expect(ReactiveCachingWorker) + .to receive(:perform_in) + .with(subject.class.reactive_cache_refresh_interval, subject.class, subject.id) + end +end diff --git a/spec/support/helpers/redis_without_keys.rb b/spec/support/helpers/redis_without_keys.rb new file mode 100644 index 00000000000..6220167dee6 --- /dev/null +++ b/spec/support/helpers/redis_without_keys.rb @@ -0,0 +1,8 @@ +class Redis + ForbiddenCommand = Class.new(StandardError) + + def keys(*args) + raise ForbiddenCommand.new("Don't use `Redis#keys` as it iterates over all "\ + "keys in redis. Use `Redis#scan_each` instead.") + end +end diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb new file mode 100644 index 00000000000..c01897ed1a1 --- /dev/null +++ b/spec/support/helpers/reference_parser_helpers.rb @@ -0,0 +1,39 @@ +module ReferenceParserHelpers + def empty_html_link + Nokogiri::HTML.fragment('').children[0] + end + + shared_examples 'no N+1 queries' do + it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do + context = Banzai::RenderContext.new(project, user) + + record_queries = lambda do |links| + ActiveRecord::QueryRecorder.new do + described_class.new(context).nodes_visible_to_user(user, links) + end + end + + control = record_queries.call(control_links) + actual = record_queries.call(actual_links) + + expect(actual.count).to be <= control.count + expect(actual.cached_count).to be <= control.cached_count + end + + it 'avoids N+1 queries in #records_for_nodes', :request_store do + context = Banzai::RenderContext.new(project, user) + + record_queries = lambda do |links| + ActiveRecord::QueryRecorder.new do + described_class.new(context).records_for_nodes(links) + end + end + + control = record_queries.call(control_links) + actual = record_queries.call(actual_links) + + expect(actual.count).to be <= control.count + expect(actual.cached_count).to be <= control.cached_count + end + end +end diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb new file mode 100644 index 00000000000..3c6956cf5e0 --- /dev/null +++ b/spec/support/helpers/repo_helpers.rb @@ -0,0 +1,118 @@ +module RepoHelpers + extend self + + # Text file in repo + # + # Ex. + # + # # Get object + # blob = RepoHelpers.text_blob + # + # blob.path # => 'files/js/commit.js.coffee' + # blob.data # => 'class Commit...' + # + def sample_blob + OpenStruct.new( + oid: '5f53439ca4b009096571d3c8bc3d09d30e7431b3', + path: "files/js/commit.js.coffee", + data: < + $('.files .diff-file').each -> + new CommitFile(this) + +@Commit = Commit +eos + ) + end + + def sample_commit + OpenStruct.new( + id: "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", + parent_id: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9', + author_full_name: "Dmitriy Zaporozhets", + author_email: "dmitriy.zaporozhets@gmail.com", + files_changed_count: 2, + line_code: '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14', + line_code_path: 'files/ruby/popen.rb', + del_line_code: '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13', + message: < +eos + ) + end + + def another_sample_commit + OpenStruct.new( + id: "e56497bb5f03a90a51293fc6d516788730953899", + parent_id: '4cd80ccab63c82b4bad16faa5193fbd2aa06df40', + author_full_name: "Sytse Sijbrandij", + author_email: "sytse@gitlab.com", + files_changed_count: 1, + message: < +eos + ) + end + + def sample_image_commit + OpenStruct.new( + id: "2f63565e7aac07bcdadb654e253078b727143ec4", + author_full_name: "Dmitriy Zaporozhets", + author_email: "dmitriy.zaporozhets@gmail.com", + old_blob_id: '33f3729a45c02fc67d00adb1b8bca394b0e761d9', + new_blob_id: '2f63565e7aac07bcdadb654e253078b727143ec4', + message: < +eos + ) + end + + def sample_compare + changes = [ + { + line_code: 'a5cc2925ca8258af241be7e5b0381edf30266302_20_20', + file_path: '.gitignore' + }, + { + line_code: '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44_4_6', + file_path: '.gitmodules' + } + ] + + commits = %w( + 5937ac0a7beb003549fc5fd26fc247adbce4a52e + 570e7b2abdd848b95f2f578043fc23bd6f6fd24d + 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + d14d6c0abdd253381df51a723d58691b2ee1ab08 + c1acaa58bbcbc3eafe538cb8274ba387047b69f8 + ).reverse # last commit is recent one + + OpenStruct.new( + source_branch: 'master', + target_branch: 'feature', + changes: changes, + commits: commits + ) + end +end diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb new file mode 100644 index 00000000000..abbbb636d66 --- /dev/null +++ b/spec/support/helpers/search_helpers.rb @@ -0,0 +1,5 @@ +module SearchHelpers + def select_filter(name) + find(:xpath, "//ul[contains(@class, 'search-filter')]//a[contains(.,'#{name}')]").click + end +end diff --git a/spec/support/helpers/seed_helper.rb b/spec/support/helpers/seed_helper.rb new file mode 100644 index 00000000000..8fd107260cc --- /dev/null +++ b/spec/support/helpers/seed_helper.rb @@ -0,0 +1,110 @@ +require_relative 'test_env' + +# This file is specific to specs in spec/lib/gitlab/git/ + +SEED_STORAGE_PATH = TestEnv.repos_path +TEST_REPO_PATH = 'gitlab-git-test.git'.freeze +TEST_NORMAL_REPO_PATH = 'not-bare-repo.git'.freeze +TEST_MUTABLE_REPO_PATH = 'mutable-repo.git'.freeze +TEST_BROKEN_REPO_PATH = 'broken-repo.git'.freeze + +module SeedHelper + GITLAB_GIT_TEST_REPO_URL = File.expand_path('../gitlab-git-test.git', __dir__).freeze + + def ensure_seeds + if File.exist?(SEED_STORAGE_PATH) + FileUtils.rm_r(SEED_STORAGE_PATH) + end + + FileUtils.mkdir_p(SEED_STORAGE_PATH) + + create_bare_seeds + create_normal_seeds + create_mutable_seeds + create_broken_seeds + create_git_attributes + create_invalid_git_attributes + end + + def create_bare_seeds + system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{GITLAB_GIT_TEST_REPO_URL}), + chdir: SEED_STORAGE_PATH, + out: '/dev/null', + err: '/dev/null') + end + + def create_normal_seeds + system(git_env, *%W(#{Gitlab.config.git.bin_path} clone #{TEST_REPO_PATH} #{TEST_NORMAL_REPO_PATH}), + chdir: SEED_STORAGE_PATH, + out: '/dev/null', + err: '/dev/null') + end + + def create_mutable_seeds + system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} #{TEST_MUTABLE_REPO_PATH}), + chdir: SEED_STORAGE_PATH, + out: '/dev/null', + err: '/dev/null') + + mutable_repo_full_path = File.join(SEED_STORAGE_PATH, TEST_MUTABLE_REPO_PATH) + system(git_env, *%W(#{Gitlab.config.git.bin_path} branch -t feature origin/feature), + chdir: mutable_repo_full_path, out: '/dev/null', err: '/dev/null') + + system(git_env, *%W(#{Gitlab.config.git.bin_path} remote add expendable #{GITLAB_GIT_TEST_REPO_URL}), + chdir: mutable_repo_full_path, out: '/dev/null', err: '/dev/null') + end + + def create_broken_seeds + system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} #{TEST_BROKEN_REPO_PATH}), + chdir: SEED_STORAGE_PATH, + out: '/dev/null', + err: '/dev/null') + + refs_path = File.join(SEED_STORAGE_PATH, TEST_BROKEN_REPO_PATH, 'refs') + + FileUtils.rm_r(refs_path) + end + + def create_git_attributes + dir = File.join(SEED_STORAGE_PATH, 'with-git-attributes.git', 'info') + + FileUtils.mkdir_p(dir) + + File.open(File.join(dir, 'attributes'), 'w') do |handle| + handle.write <<-EOF.strip +# This is a comment, it should be ignored. + +*.txt text +*.jpg -text +*.sh eol=lf gitlab-language=shell +*.haml.* gitlab-language=haml +foo/bar.* foo +*.cgi key=value?p1=v1&p2=v2 +/*.png gitlab-language=png +*.binary binary + +# This uses a tab instead of spaces to ensure the parser also supports this. +*.md\tgitlab-language=markdown +bla/bla.txt + EOF + end + end + + def create_invalid_git_attributes + dir = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git', 'info') + + FileUtils.mkdir_p(dir) + + enc = Encoding::UTF_16 + + File.open(File.join(dir, 'attributes'), 'w', encoding: enc) do |handle| + handle.write('# hello'.encode(enc)) + end + end + + # Prevent developer git configurations from being persisted to test + # repositories + def git_env + { 'GIT_TEMPLATE_DIR' => '' } + end +end diff --git a/spec/support/helpers/seed_repo.rb b/spec/support/helpers/seed_repo.rb new file mode 100644 index 00000000000..b4868e82cd7 --- /dev/null +++ b/spec/support/helpers/seed_repo.rb @@ -0,0 +1,153 @@ +# This file is generated by generate-seed-repo-rb. Do not edit this file manually. +# +# Seed repo: +# 4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6 Merge branch 'master' into 'master' +# 0e1b353b348f8477bdbec1ef47087171c5032cd9 adds an executable with different permissions +# 0e50ec4d3c7ce42ab74dda1d422cb2cbffe1e326 Merge branch 'lfs_pointers' into 'master' +# 33bcff41c232a11727ac6d660bd4b0c2ba86d63d Add valid and invalid lfs pointers +# 732401c65e924df81435deb12891ef570167d2e2 Update year in license file +# b0e52af38d7ea43cf41d8a6f2471351ac036d6c9 Empty commit +# 40f4a7a617393735a95a0bb67b08385bc1e7c66d Add ISO-8859-encoded file +# 66028349a123e695b589e09a36634d976edcc5e8 Merge branch 'add-comments-to-gitmodules' into 'master' +# de5714f34c4e34f1d50b9a61a2e6c9132fe2b5fd Add comments to the end of .gitmodules to test parsing +# fa1b1e6c004a68b7d8763b86455da9e6b23e36d6 Merge branch 'add-files' into 'master' +# eb49186cfa5c4338011f5f590fac11bd66c5c631 Add submodules nested deeper than the root +# 18d9c205d0d22fdf62bc2f899443b83aafbf941f Add executables and links files +# 5937ac0a7beb003549fc5fd26fc247adbce4a52e Add submodule from gitlab.com +# 570e7b2abdd848b95f2f578043fc23bd6f6fd24d Change some files +# 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 More submodules +# d14d6c0abdd253381df51a723d58691b2ee1ab08 Remove ds_store files +# c1acaa58bbcbc3eafe538cb8274ba387047b69f8 Ignore DS files +# ae73cb07c9eeaf35924a10f713b364d32b2dd34f Binary file added +# 874797c3a73b60d2187ed6e2fcabd289ff75171e Ruby files modified +# 2f63565e7aac07bcdadb654e253078b727143ec4 Modified image +# 33f3729a45c02fc67d00adb1b8bca394b0e761d9 Image added +# 913c66a37b4a45b9769037c55c2d238bd0942d2e Files, encoding and much more +# cfe32cf61b73a0d5e9f13e774abde7ff789b1660 Add submodule +# 6d394385cf567f80a8fd85055db1ab4c5295806f Added contributing guide +# 1a0b36b3cdad1d2ee32457c102a8c0b7056fa863 Initial commit + +module SeedRepo + module BigCommit + ID = "913c66a37b4a45b9769037c55c2d238bd0942d2e".freeze + PARENT_ID = "cfe32cf61b73a0d5e9f13e774abde7ff789b1660".freeze + MESSAGE = "Files, encoding and much more".freeze + AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze + FILES_COUNT = 2 + end + + module Commit + ID = "570e7b2abdd848b95f2f578043fc23bd6f6fd24d".freeze + PARENT_ID = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9".freeze + MESSAGE = "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \n".freeze + AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze + FILES = ["files/ruby/popen.rb", "files/ruby/regex.rb"].freeze + FILES_COUNT = 2 + C_FILE_PATH = "files/ruby".freeze + C_FILES = ["popen.rb", "regex.rb", "version_info.rb"].freeze + BLOB_FILE = %{%h3= @key.title\n%hr\n%pre= @key.key\n.actions\n = link_to 'Remove', @key, :confirm => 'Are you sure?', :method => :delete, :class => \"btn danger delete-key\"\n\n\n}.freeze + BLOB_FILE_PATH = "app/views/keys/show.html.haml".freeze + end + + module EmptyCommit + ID = "b0e52af38d7ea43cf41d8a6f2471351ac036d6c9".freeze + PARENT_ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d".freeze + MESSAGE = "Empty commit".freeze + AUTHOR_FULL_NAME = "Rémy Coutable".freeze + FILES = [].freeze + FILES_COUNT = FILES.count + end + + module EncodingCommit + ID = "40f4a7a617393735a95a0bb67b08385bc1e7c66d".freeze + PARENT_ID = "66028349a123e695b589e09a36634d976edcc5e8".freeze + MESSAGE = "Add ISO-8859-encoded file".freeze + AUTHOR_FULL_NAME = "Stan Hu".freeze + FILES = ["encoding/iso8859.txt"].freeze + FILES_COUNT = FILES.count + end + + module FirstCommit + ID = "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863".freeze + PARENT_ID = nil + MESSAGE = "Initial commit".freeze + AUTHOR_FULL_NAME = "Dmitriy Zaporozhets".freeze + FILES = ["LICENSE", ".gitignore", "README.md"].freeze + FILES_COUNT = 3 + end + + module LastCommit + ID = "4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6".freeze + PARENT_ID = "0e1b353b348f8477bdbec1ef47087171c5032cd9".freeze + MESSAGE = "Merge branch 'master' into 'master'".freeze + AUTHOR_FULL_NAME = "Stan Hu".freeze + FILES = ["bin/executable"].freeze + FILES_COUNT = FILES.count + end + + module Repo + HEAD = "master".freeze + BRANCHES = %w[ + feature + fix + fix-blob-path + fix-existing-submodule-dir + fix-mode + gitattributes + gitattributes-updated + master + merge-test + missing-gitmodules + ].freeze + TAGS = %w[ + v1.0.0 + v1.1.0 + v1.2.0 + v1.2.1 + ].freeze + end + + module RubyBlob + ID = "7e3e39ebb9b2bf433b4ad17313770fbe4051649c".freeze + NAME = "popen.rb".freeze + CONTENT = <<-eos.freeze +require 'fileutils' +require 'open3' + +module Popen + extend self + + def popen(cmd, path=nil) + unless cmd.is_a?(Array) + raise RuntimeError, "System commands must be given as an array of strings" + end + + path ||= Dir.pwd + + vars = { + "PWD" => path + } + + options = { + chdir: path + } + + unless File.directory?(path) + FileUtils.mkdir_p(path) + end + + @cmd_output = "" + @cmd_status = 0 + + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + @cmd_output << stdout.read + @cmd_output << stderr.read + @cmd_status = wait_thr.value.exitstatus + end + + return @cmd_output, @cmd_status + end +end + eos + end +end diff --git a/spec/support/helpers/select2_helper.rb b/spec/support/helpers/select2_helper.rb new file mode 100644 index 00000000000..90618ba5b19 --- /dev/null +++ b/spec/support/helpers/select2_helper.rb @@ -0,0 +1,35 @@ +# Select2 ajax programmatic helper +# It allows you to select value from select2 +# +# Params +# value - real value of selected item +# opts - options containing css selector +# +# Usage: +# +# select2(2, from: '#user_ids') +# + +module Select2Helper + def select2(value, options = {}) + raise ArgumentError, 'options must be a Hash' unless options.is_a?(Hash) + + selector = options.fetch(:from) + + first(selector, visible: false) + + if options[:multiple] + execute_script("$('#{selector}').select2('val', ['#{value}']).trigger('change');") + else + execute_script("$('#{selector}').select2('val', '#{value}').trigger('change');") + end + end + + def open_select2(selector) + execute_script("$('#{selector}').select2('open');") + end + + def scroll_select2_to_bottom(selector) + evaluate_script "$('#{selector}').scrollTop($('#{selector}')[0].scrollHeight); $('#{selector}');" + end +end diff --git a/spec/support/helpers/selection_helper.rb b/spec/support/helpers/selection_helper.rb new file mode 100644 index 00000000000..b4725b137b2 --- /dev/null +++ b/spec/support/helpers/selection_helper.rb @@ -0,0 +1,6 @@ +module SelectionHelper + def select_element(selector) + find(selector) + execute_script("let range = document.createRange(); let sel = window.getSelection(); range.selectNodeContents(document.querySelector('#{selector}')); sel.addRange(range);") + end +end diff --git a/spec/support/helpers/sorting_helper.rb b/spec/support/helpers/sorting_helper.rb new file mode 100644 index 00000000000..577518d726c --- /dev/null +++ b/spec/support/helpers/sorting_helper.rb @@ -0,0 +1,18 @@ +# Helper allows you to sort items +# +# Params +# value - value for sorting +# +# Usage: +# include SortingHelper +# +# sorting_by('Oldest updated') +# +module SortingHelper + def sorting_by(value) + find('button.dropdown-toggle').click + page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + click_link value + end + end +end diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb new file mode 100644 index 00000000000..1823099dd9c --- /dev/null +++ b/spec/support/helpers/stub_configuration.rb @@ -0,0 +1,102 @@ +require 'active_support/core_ext/hash/transform_values' +require 'active_support/hash_with_indifferent_access' + +module StubConfiguration + def stub_application_setting(messages) + add_predicates(messages) + + # Stubbing both of these because we're not yet consistent with how we access + # current application settings + allow_any_instance_of(ApplicationSetting).to receive_messages(to_settings(messages)) + allow(Gitlab::CurrentSettings.current_application_settings) + .to receive_messages(to_settings(messages)) + + # Ensure that we don't use the Markdown cache when stubbing these values + allow_any_instance_of(ApplicationSetting).to receive(:cached_html_up_to_date?).and_return(false) + end + + def stub_not_protect_default_branch + stub_application_setting( + default_branch_protection: Gitlab::Access::PROTECTION_NONE) + end + + def stub_config_setting(messages) + allow(Gitlab.config.gitlab).to receive_messages(to_settings(messages)) + end + + def stub_gravatar_setting(messages) + allow(Gitlab.config.gravatar).to receive_messages(to_settings(messages)) + end + + def stub_incoming_email_setting(messages) + allow(Gitlab.config.incoming_email).to receive_messages(to_settings(messages)) + end + + def stub_mattermost_setting(messages) + allow(Gitlab.config.mattermost).to receive_messages(to_settings(messages)) + end + + def stub_omniauth_setting(messages) + allow(Gitlab.config.omniauth).to receive_messages(to_settings(messages)) + end + + def stub_backup_setting(messages) + allow(Gitlab.config.backup).to receive_messages(to_settings(messages)) + end + + def stub_lfs_setting(messages) + allow(Gitlab.config.lfs).to receive_messages(to_settings(messages)) + end + + def stub_artifacts_setting(messages) + allow(Gitlab.config.artifacts).to receive_messages(to_settings(messages)) + end + + def stub_storage_settings(messages) + messages.deep_stringify_keys! + + # Default storage is always required + messages['default'] ||= Gitlab.config.repositories.storages.default + messages.each do |storage_name, storage_hash| + if !storage_hash.key?('path') || storage_hash['path'] == Gitlab::GitalyClient::StorageSettings::Deprecated + storage_hash['path'] = TestEnv.repos_path + end + + messages[storage_name] = Gitlab::GitalyClient::StorageSettings.new(storage_hash.to_h) + end + + allow(Gitlab.config.repositories).to receive(:storages).and_return(Settingslogic.new(messages)) + end + + private + + # Modifies stubbed messages to also stub possible predicate versions + # + # Examples: + # + # add_predicates(foo: true) + # # => {foo: true, foo?: true} + # + # add_predicates(signup_enabled?: false) + # # => {signup_enabled? false} + def add_predicates(messages) + # Only modify keys that aren't already predicates + keys = messages.keys.map(&:to_s).reject { |k| k.end_with?('?') } + + keys.each do |key| + predicate = key + '?' + messages[predicate.to_sym] = messages[key.to_sym] + end + end + + # Support nested hashes by converting all values into Settingslogic objects + def to_settings(hash) + hash.transform_values do |value| + if value.is_a? Hash + Settingslogic.new(value.deep_stringify_keys) + else + value + end + end + end +end diff --git a/spec/support/helpers/stub_env.rb b/spec/support/helpers/stub_env.rb new file mode 100644 index 00000000000..36b90fc68d6 --- /dev/null +++ b/spec/support/helpers/stub_env.rb @@ -0,0 +1,36 @@ +# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb +module StubENV + def stub_env(key_or_hash, value = nil) + init_stub unless env_stubbed? + + if key_or_hash.is_a? Hash + key_or_hash.each { |k, v| add_stubbed_value(k, v) } + else + add_stubbed_value key_or_hash, value + end + end + + private + + STUBBED_KEY = '__STUBBED__'.freeze + + def add_stubbed_value(key, value) + allow(ENV).to receive(:[]).with(key).and_return(value) + allow(ENV).to receive(:key?).with(key).and_return(true) + allow(ENV).to receive(:fetch).with(key).and_return(value) + allow(ENV).to receive(:fetch).with(key, anything()) do |_, default_val| + value || default_val + end + end + + def env_stubbed? + ENV[STUBBED_KEY] + end + + def init_stub + allow(ENV).to receive(:[]).and_call_original + allow(ENV).to receive(:key?).and_call_original + allow(ENV).to receive(:fetch).and_call_original + add_stubbed_value(STUBBED_KEY, true) + end +end diff --git a/spec/support/helpers/stub_feature_flags.rb b/spec/support/helpers/stub_feature_flags.rb new file mode 100644 index 00000000000..b96338bf548 --- /dev/null +++ b/spec/support/helpers/stub_feature_flags.rb @@ -0,0 +1,8 @@ +module StubFeatureFlags + def stub_feature_flags(features) + features.each do |feature_name, enabled| + allow(Feature).to receive(:enabled?).with(feature_name) { enabled } + allow(Feature).to receive(:enabled?).with(feature_name.to_s) { enabled } + end + end +end diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb new file mode 100644 index 00000000000..c1618f5086c --- /dev/null +++ b/spec/support/helpers/stub_gitlab_calls.rb @@ -0,0 +1,129 @@ +module StubGitlabCalls + def stub_gitlab_calls + stub_session + stub_user + stub_project_8 + stub_project_8_hooks + stub_projects + stub_projects_owned + stub_ci_enable + end + + def stub_js_gitlab_calls + allow_any_instance_of(Network).to receive(:projects) { project_hash_array } + end + + def stub_ci_pipeline_to_return_yaml_file + stub_ci_pipeline_yaml_file(gitlab_ci_yaml) + end + + def stub_ci_pipeline_yaml_file(ci_yaml) + allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file) { ci_yaml } + end + + def stub_repository_ci_yaml_file(sha:, path: '.gitlab-ci.yml') + allow_any_instance_of(Repository) + .to receive(:gitlab_ci_yml_for).with(sha, path) + .and_return(gitlab_ci_yaml) + end + + def stub_ci_builds_disabled + allow_any_instance_of(Project).to receive(:builds_enabled?).and_return(false) + end + + def stub_container_registry_config(registry_settings) + allow(Gitlab.config.registry).to receive_messages(registry_settings) + allow(Auth::ContainerRegistryAuthenticationService) + .to receive(:full_access_token).and_return('token') + end + + def stub_container_registry_tags(repository: :any, tags:) + repository = any_args if repository == :any + + allow_any_instance_of(ContainerRegistry::Client) + .to receive(:repository_tags).with(repository) + .and_return({ 'tags' => tags }) + + allow_any_instance_of(ContainerRegistry::Client) + .to receive(:repository_manifest).with(repository, anything) + .and_return(stub_container_registry_tag_manifest) + + allow_any_instance_of(ContainerRegistry::Client) + .to receive(:blob).with(repository, anything, 'application/octet-stream') + .and_return(stub_container_registry_blob) + end + + private + + def stub_container_registry_tag_manifest + fixture_path = 'spec/fixtures/container_registry/tag_manifest.json' + + JSON.parse(File.read(Rails.root + fixture_path)) + end + + def stub_container_registry_blob + fixture_path = 'spec/fixtures/container_registry/config_blob.json' + + File.read(Rails.root + fixture_path) + end + + def gitlab_url + Gitlab.config.gitlab.url + end + + def stub_session + f = File.read(Rails.root.join('spec/support/gitlab_stubs/session.json')) + + stub_request(:post, "#{gitlab_url}api/v3/session.json") + .with(body: "{\"email\":\"test@test.com\",\"password\":\"123456\"}", + headers: { 'Content-Type' => 'application/json' }) + .to_return(status: 201, body: f, headers: { 'Content-Type' => 'application/json' }) + end + + def stub_user + f = File.read(Rails.root.join('spec/support/gitlab_stubs/user.json')) + + stub_request(:get, "#{gitlab_url}api/v3/user?private_token=Wvjy2Krpb7y8xi93owUz") + .with(headers: { 'Content-Type' => 'application/json' }) + .to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' }) + + stub_request(:get, "#{gitlab_url}api/v3/user?access_token=some_token") + .with(headers: { 'Content-Type' => 'application/json' }) + .to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' }) + end + + def stub_project_8 + data = File.read(Rails.root.join('spec/support/gitlab_stubs/project_8.json')) + allow_any_instance_of(Network).to receive(:project).and_return(JSON.parse(data)) + end + + def stub_project_8_hooks + data = File.read(Rails.root.join('spec/support/gitlab_stubs/project_8_hooks.json')) + allow_any_instance_of(Network).to receive(:project_hooks).and_return(JSON.parse(data)) + end + + def stub_projects + f = File.read(Rails.root.join('spec/support/gitlab_stubs/projects.json')) + + stub_request(:get, "#{gitlab_url}api/v3/projects.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz") + .with(headers: { 'Content-Type' => 'application/json' }) + .to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' }) + end + + def stub_projects_owned + stub_request(:get, "#{gitlab_url}api/v3/projects/owned.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz") + .with(headers: { 'Content-Type' => 'application/json' }) + .to_return(status: 200, body: "", headers: {}) + end + + def stub_ci_enable + stub_request(:put, "#{gitlab_url}api/v3/projects/2/services/gitlab-ci.json?private_token=Wvjy2Krpb7y8xi93owUz") + .with(headers: { 'Content-Type' => 'application/json' }) + .to_return(status: 200, body: "", headers: {}) + end + + def project_hash_array + f = File.read(Rails.root.join('spec/support/gitlab_stubs/projects.json')) + JSON.parse f + end +end diff --git a/spec/support/helpers/stub_gitlab_data.rb b/spec/support/helpers/stub_gitlab_data.rb new file mode 100644 index 00000000000..fa402f35b95 --- /dev/null +++ b/spec/support/helpers/stub_gitlab_data.rb @@ -0,0 +1,5 @@ +module StubGitlabData + def gitlab_ci_yaml + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end +end diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb new file mode 100644 index 00000000000..19d744b959a --- /dev/null +++ b/spec/support/helpers/stub_object_storage.rb @@ -0,0 +1,48 @@ +module StubObjectStorage + def stub_object_storage_uploader( + config:, + uploader:, + remote_directory:, + enabled: true, + proxy_download: false, + background_upload: false, + direct_upload: false + ) + allow(config).to receive(:enabled) { enabled } + allow(config).to receive(:proxy_download) { proxy_download } + allow(config).to receive(:background_upload) { background_upload } + allow(config).to receive(:direct_upload) { direct_upload } + + return unless enabled + + Fog.mock! + + ::Fog::Storage.new(uploader.object_store_credentials).tap do |connection| + begin + connection.directories.create(key: remote_directory) + rescue Excon::Error::Conflict + end + end + end + + def stub_artifacts_object_storage(**params) + stub_object_storage_uploader(config: Gitlab.config.artifacts.object_store, + uploader: JobArtifactUploader, + remote_directory: 'artifacts', + **params) + end + + def stub_lfs_object_storage(**params) + stub_object_storage_uploader(config: Gitlab.config.lfs.object_store, + uploader: LfsObjectUploader, + remote_directory: 'lfs-objects', + **params) + end + + def stub_uploads_object_storage(uploader = described_class, **params) + stub_object_storage_uploader(config: Gitlab.config.uploads.object_store, + uploader: uploader, + remote_directory: 'uploads', + **params) + end +end diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb new file mode 100644 index 00000000000..d87f265cdf0 --- /dev/null +++ b/spec/support/helpers/test_env.rb @@ -0,0 +1,366 @@ +require 'rspec/mocks' +require 'toml-rb' + +module TestEnv + extend self + + ComponentFailedToInstallError = Class.new(StandardError) + + # When developing the seed repository, comment out the branch you will modify. + BRANCH_SHA = { + 'signed-commits' => '2d1096e', + 'not-merged-branch' => 'b83d6e3', + 'branch-merged' => '498214d', + 'empty-branch' => '7efb185', + 'ends-with.json' => '98b0d8b', + 'flatten-dir' => 'e56497b', + 'feature' => '0b4bc9a', + 'feature_conflict' => 'bb5206f', + 'fix' => '48f0be4', + 'improve/awesome' => '5937ac0', + 'merged-target' => '21751bf', + 'markdown' => '0ed8c6c', + 'lfs' => '55bc176', + 'master' => 'b83d6e3', + 'merge-test' => '5937ac0', + "'test'" => 'e56497b', + 'orphaned-branch' => '45127a9', + 'binary-encoding' => '7b1cf43', + 'gitattributes' => '5a62481', + 'expand-collapse-diffs' => '4842455', + 'symlink-expand-diff' => '81e6355', + 'expand-collapse-files' => '025db92', + 'expand-collapse-lines' => '238e82d', + 'video' => '8879059', + 'add-balsamiq-file' => 'b89b56d', + 'crlf-diff' => '5938907', + 'conflict-start' => '824be60', + 'conflict-resolvable' => '1450cd6', + 'conflict-binary-file' => '259a6fb', + 'conflict-contains-conflict-markers' => '78a3086', + 'conflict-missing-side' => 'eb227b3', + 'conflict-non-utf8' => 'd0a293c', + 'conflict-too-large' => '39fa04f', + 'deleted-image-test' => '6c17798', + 'wip' => 'b9238ee', + 'csv' => '3dd0896', + 'v1.1.0' => 'b83d6e3', + 'add-ipython-files' => '93ee732', + 'add-pdf-file' => 'e774ebd', + 'add-pdf-text-binary' => '79faa7b', + 'add_images_and_changes' => '010d106' + }.freeze + + # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily + # need to keep all the branches in sync. + # We currently only need a subset of the branches + FORKED_BRANCH_SHA = { + 'add-submodule-version-bump' => '3f547c0', + 'master' => '5937ac0', + 'remove-submodule' => '2a33e0c', + 'conflict-resolvable-fork' => '404fa3f' + }.freeze + + TMP_TEST_PATH = Rails.root.join('tmp', 'tests', '**') + REPOS_STORAGE = 'default'.freeze + + # Test environment + # + # See gitlab.yml.example test section for paths + # + def init(opts = {}) + unless Rails.env.test? + puts "\nTestEnv.init can only be run if `RAILS_ENV` is set to 'test' not '#{Rails.env}'!\n" + exit 1 + end + + # Disable mailer for spinach tests + disable_mailer if opts[:mailer] == false + + clean_test_path + + # Setup GitLab shell for test instance + setup_gitlab_shell + + setup_gitaly + + # Create repository for FactoryBot.create(:project) + setup_factory_repo + + # Create repository for FactoryBot.create(:forked_project_with_submodules) + setup_forked_repo + end + + def disable_mailer + allow_any_instance_of(NotificationService).to receive(:mailer) + .and_return(double.as_null_object) + end + + def enable_mailer + allow_any_instance_of(NotificationService).to receive(:mailer) + .and_call_original + end + + def disable_pre_receive + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) + end + + # Clean /tmp/tests + # + # Keeps gitlab-shell and gitlab-test + def clean_test_path + Dir[TMP_TEST_PATH].each do |entry| + unless File.basename(entry) =~ /\A(gitaly|gitlab-(shell|test|test_bare|test-fork|test-fork_bare))\z/ + FileUtils.rm_rf(entry) + end + end + + FileUtils.mkdir_p(repos_path) + FileUtils.mkdir_p(backup_path) + FileUtils.mkdir_p(pages_path) + FileUtils.mkdir_p(artifacts_path) + end + + def clean_gitlab_test_path + Dir[TMP_TEST_PATH].each do |entry| + if File.basename(entry) =~ /\A(gitlab-(test|test_bare|test-fork|test-fork_bare))\z/ + FileUtils.rm_rf(entry) + end + end + end + + def setup_gitlab_shell + component_timed_setup('GitLab Shell', + install_dir: Gitlab.config.gitlab_shell.path, + version: Gitlab::Shell.version_required, + task: 'gitlab:shell:install') + end + + def setup_gitaly + socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '') + gitaly_dir = File.dirname(socket_path) + + component_timed_setup('Gitaly', + install_dir: gitaly_dir, + version: Gitlab::GitalyClient.expected_server_version, + task: "gitlab:gitaly:install[#{gitaly_dir}]") do + + # Always re-create config, in case it's outdated. This is fast anyway. + Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, force: true) + + start_gitaly(gitaly_dir) + end + end + + def start_gitaly(gitaly_dir) + if ENV['CI'].present? + # Gitaly has been spawned outside this process already + return + end + + spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s + @gitaly_pid = Bundler.with_original_env { IO.popen([spawn_script], &:read).to_i } + Kernel.at_exit { stop_gitaly } + + wait_gitaly + end + + def wait_gitaly + sleep_time = 10 + sleep_interval = 0.1 + socket = Gitlab::GitalyClient.address('default').sub('unix:', '') + + Integer(sleep_time / sleep_interval).times do + begin + Socket.unix(socket) + return + rescue + sleep sleep_interval + end + end + + raise "could not connect to gitaly at #{socket.inspect} after #{sleep_time} seconds" + end + + def stop_gitaly + return unless @gitaly_pid + + Process.kill('KILL', @gitaly_pid) + rescue Errno::ESRCH + # The process can already be gone if the test run was INTerrupted. + end + + def setup_factory_repo + setup_repo(factory_repo_path, factory_repo_path_bare, factory_repo_name, + BRANCH_SHA) + end + + # This repo has a submodule commit that is not present in the main test + # repository. + def setup_forked_repo + setup_repo(forked_repo_path, forked_repo_path_bare, forked_repo_name, + FORKED_BRANCH_SHA) + end + + def setup_repo(repo_path, repo_path_bare, repo_name, refs) + clone_url = "https://gitlab.com/gitlab-org/#{repo_name}.git" + + unless File.directory?(repo_path) + system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path})) + end + + set_repo_refs(repo_path, refs) + + unless File.directory?(repo_path_bare) + # We must copy bare repositories because we will push to them. + system(git_env, *%W(#{Gitlab.config.git.bin_path} clone -q --bare #{repo_path} #{repo_path_bare})) + end + end + + def copy_repo(project, bare_repo:, refs:) + target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.disk_path}.git") + FileUtils.mkdir_p(target_repo_path) + FileUtils.cp_r("#{File.expand_path(bare_repo)}/.", target_repo_path) + FileUtils.chmod_R 0755, target_repo_path + set_repo_refs(target_repo_path, refs) + end + + def repos_path + Gitlab.config.repositories.storages[REPOS_STORAGE].legacy_disk_path + end + + def backup_path + Gitlab.config.backup.path + end + + def pages_path + Gitlab.config.pages.path + end + + def artifacts_path + Gitlab.config.artifacts.storage_path + end + + # When no cached assets exist, manually hit the root path to create them + # + # Otherwise they'd be created by the first test, often timing out and + # causing a transient test failure + def eager_load_driver_server + return unless defined?(Capybara) + + puts "Starting the Capybara driver server..." + Capybara.current_session.visit '/' + end + + def factory_repo_path_bare + "#{factory_repo_path}_bare" + end + + def forked_repo_path_bare + "#{forked_repo_path}_bare" + end + + def with_empty_bare_repository(name = nil) + path = Rails.root.join('tmp/tests', name || 'empty-bare-repository').to_s + + yield(Rugged::Repository.init_at(path, :bare)) + ensure + FileUtils.rm_rf(path) + end + + private + + def factory_repo_path + @factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name) + end + + def factory_repo_name + 'gitlab-test' + end + + def forked_repo_path + @forked_repo_path ||= Rails.root.join('tmp', 'tests', forked_repo_name) + end + + def forked_repo_name + 'gitlab-test-fork' + end + + # Prevent developer git configurations from being persisted to test + # repositories + def git_env + { 'GIT_TEMPLATE_DIR' => '' } + end + + def set_repo_refs(repo_path, branch_sha) + instructions = branch_sha.map { |branch, sha| "update refs/heads/#{branch}\x00#{sha}\x00" }.join("\x00") << "\x00" + update_refs = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z) + reset = proc do + Dir.chdir(repo_path) do + IO.popen(update_refs, "w") { |io| io.write(instructions) } + $?.success? + end + end + + # Try to reset without fetching to avoid using the network. + unless reset.call + raise 'Could not fetch test seed repository.' unless system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} fetch origin)) + + # Before we used Git clone's --mirror option, bare repos could end up + # with missing refs, clearing them and retrying should fix the issue. + clean_gitlab_test_path && init unless reset.call + end + end + + def component_timed_setup(component, install_dir:, version:, task:) + puts "\n==> Setting up #{component}..." + start = Time.now + + ensure_component_dir_name_is_correct!(component, install_dir) + + # On CI, once installed, components never need update + return if File.exist?(install_dir) && ENV['CI'] + + if component_needs_update?(install_dir, version) + # Cleanup the component entirely to ensure we start fresh + FileUtils.rm_rf(install_dir) + + unless system('rake', task) + raise ComponentFailedToInstallError + end + end + + yield if block_given? + + rescue ComponentFailedToInstallError + puts "\n#{component} failed to install, cleaning up #{install_dir}!\n" + FileUtils.rm_rf(install_dir) + exit 1 + ensure + puts " #{component} setup in #{Time.now - start} seconds...\n" + end + + def ensure_component_dir_name_is_correct!(component, path) + actual_component_dir_name = File.basename(path) + expected_component_dir_name = component.parameterize + + unless actual_component_dir_name == expected_component_dir_name + puts " #{component} install dir should be named '#{expected_component_dir_name}', not '#{actual_component_dir_name}' (full install path given was '#{path}')!\n" + exit 1 + end + end + + def component_needs_update?(component_folder, expected_version) + # Allow local overrides of the component for tests during development + return false if Rails.env.test? && File.symlink?(component_folder) + + version = File.read(File.join(component_folder, 'VERSION')).strip + + # Notice that this will always yield true when using branch versions + # (`=branch_name`), but that actually makes sure the server is always based + # on the latest branch revision. + version != expected_version + rescue Errno::ENOENT + true + end +end diff --git a/spec/support/helpers/upload_helpers.rb b/spec/support/helpers/upload_helpers.rb new file mode 100644 index 00000000000..5eead80c935 --- /dev/null +++ b/spec/support/helpers/upload_helpers.rb @@ -0,0 +1,16 @@ +require 'fileutils' + +module UploadHelpers + extend self + + def uploaded_image_temp_path + basename = 'banana_sample.gif' + orig_path = File.join(Rails.root, 'spec', 'fixtures', basename) + tmp_path = File.join(Rails.root, 'tmp', 'tests', basename) + # Because we use 'move_to_store' on all uploaders, we create a new + # tempfile on each call: the file we return here will be renamed in most + # cases. + FileUtils.copy(orig_path, tmp_path) + tmp_path + end +end diff --git a/spec/support/helpers/user_activities_helpers.rb b/spec/support/helpers/user_activities_helpers.rb new file mode 100644 index 00000000000..44feb104644 --- /dev/null +++ b/spec/support/helpers/user_activities_helpers.rb @@ -0,0 +1,7 @@ +module UserActivitiesHelpers + def user_activity(user) + Gitlab::UserActivities.new + .find { |k, _| k == user.id.to_s }&. + second + end +end diff --git a/spec/support/helpers/wait_for_requests.rb b/spec/support/helpers/wait_for_requests.rb new file mode 100644 index 00000000000..fda0e29f983 --- /dev/null +++ b/spec/support/helpers/wait_for_requests.rb @@ -0,0 +1,78 @@ +module WaitForRequests + extend self + + # This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests + def block_and_wait_for_requests_complete + block_requests { wait_for_all_requests } + end + + # Block all requests inside block with 503 response + def block_requests + Gitlab::Testing::RequestBlockerMiddleware.block_requests! + yield + ensure + Gitlab::Testing::RequestBlockerMiddleware.allow_requests! + end + + # Slow down requests inside block by injecting `sleep 0.2` before each response + def slow_requests + Gitlab::Testing::RequestBlockerMiddleware.slow_requests! + yield + ensure + Gitlab::Testing::RequestBlockerMiddleware.allow_requests! + end + + # Wait for client-side AJAX requests + def wait_for_requests + wait_for('JS requests complete') { finished_all_js_requests? } + end + + # Wait for active Rack requests and client-side AJAX requests + def wait_for_all_requests + wait_for('pending requests complete') do + finished_all_rack_reqiests? && + finished_all_js_requests? + end + end + + private + + def finished_all_rack_reqiests? + Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero? + end + + def finished_all_js_requests? + return true unless javascript_test? + + finished_all_ajax_requests? && + finished_all_vue_resource_requests? + end + + # Waits until the passed block returns true + def wait_for(condition_name, max_wait_time: Capybara.default_max_wait_time, polling_interval: 0.01) + wait_until = Time.now + max_wait_time.seconds + loop do + break if yield + + if Time.now > wait_until + raise "Condition not met: #{condition_name}" + else + sleep(polling_interval) + end + end + end + + def finished_all_vue_resource_requests? + Capybara.page.evaluate_script('window.activeVueResources || 0').zero? + end + + def finished_all_ajax_requests? + return true if Capybara.page.evaluate_script('typeof jQuery === "undefined"') + + Capybara.page.evaluate_script('jQuery.active').zero? + end + + def javascript_test? + Capybara.current_driver == Capybara.javascript_driver + end +end diff --git a/spec/support/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb new file mode 100644 index 00000000000..ef1f9f68671 --- /dev/null +++ b/spec/support/helpers/workhorse_helpers.rb @@ -0,0 +1,21 @@ +module WorkhorseHelpers + extend self + + def workhorse_send_data + @_workhorse_send_data ||= begin + header = response.headers[Gitlab::Workhorse::SEND_DATA_HEADER] + split_header = header.split(':') + type = split_header.shift + header = split_header.join(':') + [ + type, + JSON.parse(Base64.urlsafe_decode64(header)) + ] + end + end + + def workhorse_internal_api_request_header + jwt_token = JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') + { 'HTTP_' + Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER.upcase.tr('-', '_') => jwt_token } + end +end diff --git a/spec/support/import_spec_helper.rb b/spec/support/import_spec_helper.rb deleted file mode 100644 index d4eced724fa..00000000000 --- a/spec/support/import_spec_helper.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'ostruct' - -# Helper methods for controller specs in the Import namespace -# -# Must be included manually. -module ImportSpecHelper - # Stub `controller` to return a null object double with the provided messages - # when `client` is called - # - # Examples: - # - # stub_client(foo: %w(foo)) - # - # controller.client.foo # => ["foo"] - # controller.client.bar.baz.foo # => ["foo"] - # - # Returns the client double - def stub_client(messages = {}) - client = double('client', messages).as_null_object - allow(controller).to receive(:client).and_return(client) - - client - end - - def stub_omniauth_provider(name) - provider = OpenStruct.new( - name: name, - app_id: 'asd123', - app_secret: 'asd123' - ) - stub_omniauth_setting(providers: [provider]) - end -end diff --git a/spec/support/input_helper.rb b/spec/support/input_helper.rb deleted file mode 100644 index acbb42274ec..00000000000 --- a/spec/support/input_helper.rb +++ /dev/null @@ -1,7 +0,0 @@ -# see app/assets/javascripts/test_utils/simulate_input.js - -module InputHelper - def simulate_input(selector, input = '') - evaluate_script("window.simulateInput(#{selector.to_json}, #{input.to_json});") - end -end diff --git a/spec/support/inspect_requests.rb b/spec/support/inspect_requests.rb deleted file mode 100644 index 88ddc5c7f6c..00000000000 --- a/spec/support/inspect_requests.rb +++ /dev/null @@ -1,17 +0,0 @@ -require_relative './wait_for_requests' - -module InspectRequests - extend self - include WaitForRequests - - def inspect_requests(inject_headers: {}) - Gitlab::Testing::RequestInspectorMiddleware.log_requests!(inject_headers) - - yield - - wait_for_all_requests - Gitlab::Testing::RequestInspectorMiddleware.requests - ensure - Gitlab::Testing::RequestInspectorMiddleware.stop_logging! - end -end diff --git a/spec/support/issuable_shared_examples.rb b/spec/support/issuable_shared_examples.rb deleted file mode 100644 index 42f3b4db23c..00000000000 --- a/spec/support/issuable_shared_examples.rb +++ /dev/null @@ -1,38 +0,0 @@ -shared_examples 'cache counters invalidator' do - it 'invalidates counter cache for assignees' do - expect_any_instance_of(User).to receive(:invalidate_merge_request_cache_counts) - - described_class.new(project, user, {}).execute(merge_request) - end -end - -shared_examples 'system notes for milestones' do - def update_issuable(opts) - issuable = try(:issue) || try(:merge_request) - described_class.new(project, user, opts).execute(issuable) - end - - context 'group milestones' do - let(:group) { create(:group) } - let(:group_milestone) { create(:milestone, group: group) } - - before do - project.update(namespace: group) - create(:group_member, group: group, user: user) - end - - it 'creates a system note' do - expect do - update_issuable(milestone: group_milestone) - end.to change { Note.system.count }.by(1) - end - end - - context 'project milestones' do - it 'creates a system note' do - expect do - update_issuable(milestone: create(:milestone)) - end.to change { Note.system.count }.by(1) - end - end -end diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/issuables_list_metadata_shared_examples.rb deleted file mode 100644 index e61983c60b4..00000000000 --- a/spec/support/issuables_list_metadata_shared_examples.rb +++ /dev/null @@ -1,46 +0,0 @@ -shared_examples 'issuables list meta-data' do |issuable_type, action = nil| - before do - @issuable_ids = [] - - %w[fix improve/awesome].each do |source_branch| - issuable = - if issuable_type == :issue - create(issuable_type, project: project, author: project.creator) - else - create(issuable_type, source_project: project, source_branch: source_branch, author: project.creator) - end - - @issuable_ids << issuable.id - end - end - - it "creates indexed meta-data object for issuable notes and votes count" do - if action - get action, author_id: project.creator.id - else - get :index, namespace_id: project.namespace, project_id: project - end - - meta_data = assigns(:issuable_meta_data) - - aggregate_failures do - expect(meta_data.keys).to match_array(@issuable_ids) - expect(meta_data.values).to all(be_kind_of(Issuable::IssuableMeta)) - end - end - - describe "when given empty collection" do - let(:project2) { create(:project, :public) } - - it "doesn't execute any queries with false conditions" do - get_action = - if action - proc { get action, author_id: project.creator.id } - else - proc { get :index, namespace_id: project2.namespace, project_id: project2 } - end - - expect(&get_action).not_to make_queries_matching(/WHERE (?:1=0|0=1)/) - end - end -end diff --git a/spec/support/issue_helpers.rb b/spec/support/issue_helpers.rb deleted file mode 100644 index ffd72515f37..00000000000 --- a/spec/support/issue_helpers.rb +++ /dev/null @@ -1,13 +0,0 @@ -module IssueHelpers - def visit_issues(project, opts = {}) - visit project_issues_path project, opts - end - - def first_issue - page.all('ul.issues-list > li').first.text - end - - def last_issue - page.all('ul.issues-list > li').last.text - end -end diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb deleted file mode 100644 index a6ab03cb808..00000000000 --- a/spec/support/issue_tracker_service_shared_example.rb +++ /dev/null @@ -1,22 +0,0 @@ -RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr| - it { is_expected.to allow_value('https://example.com').for(url_attr) } - - it { is_expected.not_to allow_value('example.com').for(url_attr) } - it { is_expected.not_to allow_value('ftp://example.com').for(url_attr) } - it { is_expected.not_to allow_value('herp-and-derp').for(url_attr) } -end - -RSpec.shared_examples 'allows project key on reference pattern' do |url_attr| - it 'allows underscores in the project name' do - expect(described_class.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' - end - - it 'allows numbers in the project name' do - expect(described_class.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234' - end - - it 'requires the project name to begin with A-Z' do - expect(described_class.reference_pattern.match('3EXT_EXT-1234')).to eq nil - expect(described_class.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' - end -end diff --git a/spec/support/javascript_fixtures_helpers.rb b/spec/support/javascript_fixtures_helpers.rb deleted file mode 100644 index 2197bc9d853..00000000000 --- a/spec/support/javascript_fixtures_helpers.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'action_dispatch/testing/test_request' -require 'fileutils' - -module JavaScriptFixturesHelpers - include Gitlab::Popen - - FIXTURE_PATH = 'spec/javascripts/fixtures'.freeze - - # Public: Removes all fixture files from given directory - # - # directory_name - directory of the fixtures (relative to FIXTURE_PATH) - # - def clean_frontend_fixtures(directory_name) - directory_name = File.expand_path(directory_name, FIXTURE_PATH) - Dir[File.expand_path('*.html.raw', directory_name)].each do |file_name| - FileUtils.rm(file_name) - end - end - - # Public: Store a response object as fixture file - # - # response - string or response object to store - # fixture_file_name - file name to store the fixture in (relative to FIXTURE_PATH) - # - def store_frontend_fixture(response, fixture_file_name) - fixture_file_name = File.expand_path(fixture_file_name, FIXTURE_PATH) - fixture = response.respond_to?(:body) ? parse_response(response) : response - - FileUtils.mkdir_p(File.dirname(fixture_file_name)) - File.write(fixture_file_name, fixture) - end - - def remove_repository(project) - Gitlab::Shell.new.remove_repository(project.repository_storage_path, project.disk_path) - end - - private - - # Private: Prepare a response object for use as a frontend fixture - # - # response - response object to prepare - # - def parse_response(response) - fixture = response.body - fixture.force_encoding("utf-8") - - response_mime_type = Mime::Type.lookup(response.content_type) - if response_mime_type.html? - doc = Nokogiri::HTML::DocumentFragment.parse(fixture) - - link_tags = doc.css('link') - link_tags.remove - - scripts = doc.css("script:not([type='text/template']):not([type='text/x-template'])") - scripts.remove - - fixture = doc.to_html - - # replace relative links - test_host = ActionDispatch::TestRequest::DEFAULT_ENV['HTTP_HOST'] - fixture.gsub!(%r{="/}, "=\"http://#{test_host}/") - end - - fixture - end -end diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb deleted file mode 100644 index 88a7aeba461..00000000000 --- a/spec/support/jira_service_helper.rb +++ /dev/null @@ -1,88 +0,0 @@ -module JiraServiceHelper - JIRA_URL = "http://jira.example.net".freeze - JIRA_API = JIRA_URL + "/rest/api/2" - - def jira_service_settings - properties = { - title: "JIRA tracker", - url: JIRA_URL, - username: 'jira-user', - password: 'my-secret-password', - project_key: "JIRA", - jira_issue_transition_id: '1' - } - - jira_tracker.update_attributes(properties: properties, active: true) - end - - def jira_issue_comments - "{\"startAt\":0,\"maxResults\":11,\"total\":11, - \"comments\":[{\"self\":\"http://0.0.0.0:4567/rest/api/2/issue/10002/comment/10609\", - \"id\":\"10609\",\"author\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\", - \"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", - \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", - \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", - \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", - \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"}, - \"displayName\":\"GitLab\",\"active\":true}, - \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned JIRA-1 in Merge request of [gitlab-org/gitlab-test|http://localhost:3000/gitlab-org/gitlab-test/merge_requests/2].\", - \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", - \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", - \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", - \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", - \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true}, - \"created\":\"2015-02-12T22:47:07.826+0100\", - \"updated\":\"2015-02-12T22:47:07.826+0100\"}, - {\"self\":\"http://0.0.0.0:4567/rest/api/2/issue/10002/comment/10700\", - \"id\":\"10700\",\"author\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\", - \"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", - \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", - \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", - \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", - \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true}, - \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned this issue in [a commit of h5bp/html5-boilerplate|http://localhost:3000/h5bp/html5-boilerplate/commit/2439f77897122fbeee3bfd9bb692d3608848433e].\", - \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", - \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", - \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", - \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", - \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true}, - \"created\":\"2015-04-01T03:45:55.667+0200\", - \"updated\":\"2015-04-01T03:45:55.667+0200\" - } - ]}" - end - - def jira_project_url - JIRA_API + "/project" - end - - def jira_api_comment_url(issue_id) - JIRA_API + "/issue/#{issue_id}/comment" - end - - def jira_api_remote_link_url(issue_id) - JIRA_API + "/issue/#{issue_id}/remotelink" - end - - def jira_api_transition_url(issue_id) - JIRA_API + "/issue/#{issue_id}/transitions" - end - - def jira_api_test_url - JIRA_API + "/myself" - end - - def jira_issue_url(issue_id) - JIRA_API + "/issue/#{issue_id}" - end - - def stub_jira_urls(issue_id) - WebMock.stub_request(:get, jira_project_url) - WebMock.stub_request(:get, jira_api_comment_url(issue_id)).to_return(body: jira_issue_comments) - WebMock.stub_request(:get, jira_issue_url(issue_id)) - WebMock.stub_request(:get, jira_api_test_url) - WebMock.stub_request(:post, jira_api_comment_url(issue_id)) - WebMock.stub_request(:post, jira_api_remote_link_url(issue_id)) - WebMock.stub_request(:post, jira_api_transition_url(issue_id)) - end -end diff --git a/spec/support/json_response.rb b/spec/support/json_response.rb new file mode 100644 index 00000000000..210b0e6d867 --- /dev/null +++ b/spec/support/json_response.rb @@ -0,0 +1,5 @@ +RSpec.configure do |config| + config.include_context 'JSON response' + config.include_context 'JSON response', type: :request + config.include_context 'JSON response', :api +end diff --git a/spec/support/json_response_helpers.rb b/spec/support/json_response_helpers.rb deleted file mode 100644 index aa235529c56..00000000000 --- a/spec/support/json_response_helpers.rb +++ /dev/null @@ -1,9 +0,0 @@ -shared_context 'JSON response' do - let(:json_response) { JSON.parse(response.body) } -end - -RSpec.configure do |config| - config.include_context 'JSON response' - config.include_context 'JSON response', type: :request - config.include_context 'JSON response', :api -end diff --git a/spec/support/kubernetes_helpers.rb b/spec/support/kubernetes_helpers.rb deleted file mode 100644 index e46b61b6461..00000000000 --- a/spec/support/kubernetes_helpers.rb +++ /dev/null @@ -1,104 +0,0 @@ -module KubernetesHelpers - include Gitlab::Kubernetes - - def kube_response(body) - { body: body.to_json } - end - - def kube_pods_response - kube_response(kube_pods_body) - end - - def stub_kubeclient_discover(api_url) - WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body)) - end - - def stub_kubeclient_pods(response = nil) - stub_kubeclient_discover(service.api_url) - pods_url = service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods" - - WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response) - end - - def stub_kubeclient_get_secrets(api_url, **options) - WebMock.stub_request(:get, api_url + '/api/v1/secrets') - .to_return(kube_response(kube_v1_secrets_body(options))) - end - - def stub_kubeclient_get_secrets_error(api_url) - WebMock.stub_request(:get, api_url + '/api/v1/secrets') - .to_return(status: [404, "Internal Server Error"]) - end - - def kube_v1_secrets_body(**options) - { - "kind" => "SecretList", - "apiVersion": "v1", - "items" => [ - { - "metadata": { - "name": options[:metadata_name] || "default-token-1", - "namespace": "kube-system" - }, - "data": { - "token": options[:token] || Base64.encode64('token-sample-123') - } - } - ] - } - end - - def kube_v1_discovery_body - { - "kind" => "APIResourceList", - "resources" => [ - { "name" => "pods", "namespaced" => true, "kind" => "Pod" }, - { "name" => "secrets", "namespaced" => true, "kind" => "Secret" } - ] - } - end - - def kube_pods_body - { - "kind" => "PodList", - "items" => [kube_pod] - } - end - - # This is a partial response, it will have many more elements in reality but - # these are the ones we care about at the moment - def kube_pod(name: "kube-pod", app: "valid-pod-label") - { - "metadata" => { - "name" => name, - "creationTimestamp" => "2016-11-25T19:55:19Z", - "labels" => { "app" => app } - }, - "spec" => { - "containers" => [ - { "name" => "container-0" }, - { "name" => "container-1" } - ] - }, - "status" => { "phase" => "Running" } - } - end - - def kube_terminals(service, pod) - pod_name = pod['metadata']['name'] - containers = pod['spec']['containers'] - - containers.map do |container| - terminal = { - selectors: { pod: pod_name, container: container['name'] }, - url: container_exec_url(service.api_url, service.actual_namespace, pod_name, container['name']), - subprotocols: ['channel.k8s.io'], - headers: { 'Authorization' => ["Bearer #{service.token}"] }, - created_at: DateTime.parse(pod['metadata']['creationTimestamp']), - max_session_time: 0 - } - terminal[:ca_pem] = service.ca_pem if service.ca_pem.present? - terminal - end - end -end diff --git a/spec/support/ldap_helpers.rb b/spec/support/ldap_helpers.rb deleted file mode 100644 index 0e87b3d359d..00000000000 --- a/spec/support/ldap_helpers.rb +++ /dev/null @@ -1,49 +0,0 @@ -module LdapHelpers - def ldap_adapter(provider = 'ldapmain', ldap = double(:ldap)) - ::Gitlab::Auth::LDAP::Adapter.new(provider, ldap) - end - - def user_dn(uid) - "uid=#{uid},ou=users,dc=example,dc=com" - end - - # Accepts a hash of Gitlab::Auth::LDAP::Config keys and values. - # - # Example: - # stub_ldap_config( - # group_base: 'ou=groups,dc=example,dc=com', - # admin_group: 'my-admin-group' - # ) - def stub_ldap_config(messages) - allow_any_instance_of(::Gitlab::Auth::LDAP::Config).to receive_messages(messages) - end - - # Stub an LDAP person search and provide the return entry. Specify `nil` for - # `entry` to simulate when an LDAP person is not found - # - # Example: - # adapter = ::Gitlab::Auth::LDAP::Adapter.new('ldapmain', double(:ldap)) - # ldap_user_entry = ldap_user_entry('john_doe') - # - # stub_ldap_person_find_by_uid('john_doe', ldap_user_entry, adapter) - def stub_ldap_person_find_by_uid(uid, entry, provider = 'ldapmain') - return_value = ::Gitlab::Auth::LDAP::Person.new(entry, provider) if entry.present? - - allow(::Gitlab::Auth::LDAP::Person) - .to receive(:find_by_uid).with(uid, any_args).and_return(return_value) - end - - # Create a simple LDAP user entry. - def ldap_user_entry(uid) - entry = Net::LDAP::Entry.new - entry['dn'] = user_dn(uid) - entry['uid'] = uid - - entry - end - - def raise_ldap_connection_error - allow_any_instance_of(Gitlab::Auth::LDAP::Adapter) - .to receive(:ldap_search).and_raise(Gitlab::Auth::LDAP::LDAPConnectionError) - end -end diff --git a/spec/support/ldap_shared_examples.rb b/spec/support/ldap_shared_examples.rb deleted file mode 100644 index 52c34e78965..00000000000 --- a/spec/support/ldap_shared_examples.rb +++ /dev/null @@ -1,69 +0,0 @@ -shared_examples_for 'normalizes a DN' do - using RSpec::Parameterized::TableSyntax - - where(:test_description, :given, :expected) do - 'strips extraneous whitespace' | 'uid =John Smith , ou = People, dc= example,dc =com' | 'uid=john smith,ou=people,dc=example,dc=com' - 'strips extraneous whitespace for a DN with a single RDN' | 'uid = John Smith' | 'uid=john smith' - 'unescapes non-reserved, non-special Unicode characters' | 'uid = Sebasti\\c3\\a1n\\ C.\\20Smith, ou=People (aka. \\22humans\\") ,dc=example, dc=com' | 'uid=sebastián c. smith,ou=people (aka. \\"humans\\"),dc=example,dc=com' - 'downcases the whole string' | 'UID=John Smith,ou=People,dc=example,dc=com' | 'uid=john smith,ou=people,dc=example,dc=com' - 'for a null DN (empty string), returns empty string and does not error' | '' | '' - 'does not strip an escaped leading space in an attribute value' | 'uid=\\ John Smith,ou=People,dc=example,dc=com' | 'uid=\\ john smith,ou=people,dc=example,dc=com' - 'does not strip an escaped leading space in the last attribute value' | 'uid=\\ John Smith' | 'uid=\\ john smith' - 'does not strip an escaped trailing space in an attribute value' | 'uid=John Smith\\ ,ou=People,dc=example,dc=com' | 'uid=john smith\\ ,ou=people,dc=example,dc=com' - 'strips extraneous spaces after an escaped trailing space' | 'uid=John Smith\\ ,ou=People,dc=example,dc=com' | 'uid=john smith\\ ,ou=people,dc=example,dc=com' - 'strips extraneous spaces after an escaped trailing space at the end of the DN' | 'uid=John Smith,ou=People,dc=example,dc=com\\ ' | 'uid=john smith,ou=people,dc=example,dc=com\\ ' - 'properly preserves escaped trailing space after unescaped trailing spaces' | 'uid=John Smith \\ ,ou=People,dc=example,dc=com' | 'uid=john smith \\ ,ou=people,dc=example,dc=com' - 'preserves multiple inner spaces in an attribute value' | 'uid=John Smith,ou=People,dc=example,dc=com' | 'uid=john smith,ou=people,dc=example,dc=com' - 'preserves inner spaces after an escaped space' | 'uid=John\\ Smith,ou=People,dc=example,dc=com' | 'uid=john smith,ou=people,dc=example,dc=com' - 'hex-escapes an escaped leading newline in an attribute value' | "uid=\\\nJohn Smith,ou=People,dc=example,dc=com" | "uid=\\0ajohn smith,ou=people,dc=example,dc=com" - 'hex-escapes and does not strip an escaped trailing newline in an attribute value' | "uid=John Smith\\\n,ou=People,dc=example,dc=com" | "uid=john smith\\0a,ou=people,dc=example,dc=com" - 'hex-escapes an unescaped leading newline (actually an invalid DN?)' | "uid=\nJohn Smith,ou=People,dc=example,dc=com" | "uid=\\0ajohn smith,ou=people,dc=example,dc=com" - 'strips an unescaped trailing newline (actually an invalid DN?)' | "uid=John Smith\n,ou=People,dc=example,dc=com" | "uid=john smith,ou=people,dc=example,dc=com" - 'does not strip if no extraneous whitespace' | 'uid=John Smith,ou=People,dc=example,dc=com' | 'uid=john smith,ou=people,dc=example,dc=com' - 'does not modify an escaped equal sign in an attribute value' | 'uid= foo \\= bar' | 'uid=foo \\= bar' - 'converts an escaped hex equal sign to an escaped equal sign in an attribute value' | 'uid= foo \\3D bar' | 'uid=foo \\= bar' - 'does not modify an escaped comma in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\, CA' | 'uid=john c. smith,ou=san francisco\\, ca' - 'converts an escaped hex comma to an escaped comma in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\2C CA' | 'uid=john c. smith,ou=san francisco\\, ca' - 'does not modify an escaped hex carriage return character in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\,\\0DCA' | 'uid=john c. smith,ou=san francisco\\,\\0dca' - 'does not modify an escaped hex line feed character in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\,\\0ACA' | 'uid=john c. smith,ou=san francisco\\,\\0aca' - 'does not modify an escaped hex CRLF in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\,\\0D\\0ACA' | 'uid=john c. smith,ou=san francisco\\,\\0d\\0aca' - 'allows attribute type name OIDs' | '0.9.2342.19200300.100.1.25=Example,0.9.2342.19200300.100.1.25=Com' | '0.9.2342.19200300.100.1.25=example,0.9.2342.19200300.100.1.25=com' - 'strips extraneous whitespace from attribute type name OIDs' | '0.9.2342.19200300.100.1.25 = Example, 0.9.2342.19200300.100.1.25 = Com' | '0.9.2342.19200300.100.1.25=example,0.9.2342.19200300.100.1.25=com' - end - - with_them do - it 'normalizes the DN' do - assert_generic_test(test_description, subject, expected) - end - end -end - -shared_examples_for 'normalizes a DN attribute value' do - using RSpec::Parameterized::TableSyntax - - where(:test_description, :given, :expected) do - 'strips extraneous whitespace' | ' John Smith ' | 'john smith' - 'unescapes non-reserved, non-special Unicode characters' | 'Sebasti\\c3\\a1n\\ C.\\20Smith' | 'sebastián c. smith' - 'downcases the whole string' | 'JoHn C. Smith' | 'john c. smith' - 'does not strip an escaped leading space in an attribute value' | '\\ John Smith' | '\\ john smith' - 'does not strip an escaped trailing space in an attribute value' | 'John Smith\\ ' | 'john smith\\ ' - 'hex-escapes an escaped leading newline in an attribute value' | "\\\nJohn Smith" | "\\0ajohn smith" - 'hex-escapes and does not strip an escaped trailing newline in an attribute value' | "John Smith\\\n" | "john smith\\0a" - 'hex-escapes an unescaped leading newline (actually an invalid DN value?)' | "\nJohn Smith" | "\\0ajohn smith" - 'strips an unescaped trailing newline (actually an invalid DN value?)' | "John Smith\n" | "john smith" - 'does not strip if no extraneous whitespace' | 'John Smith' | 'john smith' - 'does not modify an escaped equal sign in an attribute value' | ' foo \\= bar' | 'foo \\= bar' - 'converts an escaped hex equal sign to an escaped equal sign in an attribute value' | ' foo \\3D bar' | 'foo \\= bar' - 'does not modify an escaped comma in an attribute value' | 'San Francisco\\, CA' | 'san francisco\\, ca' - 'converts an escaped hex comma to an escaped comma in an attribute value' | 'San Francisco\\2C CA' | 'san francisco\\, ca' - 'does not modify an escaped hex carriage return character in an attribute value' | 'San Francisco\\,\\0DCA' | 'san francisco\\,\\0dca' - 'does not modify an escaped hex line feed character in an attribute value' | 'San Francisco\\,\\0ACA' | 'san francisco\\,\\0aca' - 'does not modify an escaped hex CRLF in an attribute value' | 'San Francisco\\,\\0D\\0ACA' | 'san francisco\\,\\0d\\0aca' - end - - with_them do - it 'normalizes the DN attribute value' do - assert_generic_test(test_description, subject, expected) - end - end -end diff --git a/spec/support/legacy_path_redirect_shared_examples.rb b/spec/support/legacy_path_redirect_shared_examples.rb deleted file mode 100644 index f300bdd48b1..00000000000 --- a/spec/support/legacy_path_redirect_shared_examples.rb +++ /dev/null @@ -1,13 +0,0 @@ -shared_examples 'redirecting a legacy path' do |source, target| - include RSpec::Rails::RequestExampleGroup - - it "redirects #{source} to #{target} when the resource does not exist" do - expect(get(source)).to redirect_to(target) - end - - it "does not redirect #{source} to #{target} when the resource exists" do - resource - - expect(get(source)).not_to redirect_to(target) - end -end diff --git a/spec/support/live_debugger.rb b/spec/support/live_debugger.rb deleted file mode 100644 index 911eb48a8ca..00000000000 --- a/spec/support/live_debugger.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'io/console' - -module LiveDebugger - def live_debug - puts - puts "Current example is paused for live debugging." - puts "Opening #{current_url} in your default browser..." - puts "The current user credentials are: #{@current_user.username} / #{@current_user.password}" if @current_user - puts "Press any key to resume the execution of the example!!" - - `open #{current_url}` - - loop until $stdin.getch - - puts "Back to the example!" - end -end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb deleted file mode 100644 index db34090e971..00000000000 --- a/spec/support/login_helpers.rb +++ /dev/null @@ -1,162 +0,0 @@ -require_relative 'devise_helpers' - -module LoginHelpers - include DeviseHelpers - - # Overriding Devise::Test::IntegrationHelpers#sign_in to store @current_user - # since we may need it in LiveDebugger#live_debug. - def sign_in(resource, scope: nil) - super - - @current_user = resource - end - - # Overriding Devise::Test::IntegrationHelpers#sign_out to clear @current_user. - def sign_out(resource_or_scope) - super - - @current_user = nil - end - - # Internal: Log in as a specific user or a new user of a specific role - # - # user_or_role - User object, or a role to create (e.g., :admin, :user) - # - # Examples: - # - # # Create a user automatically - # gitlab_sign_in(:user) - # - # # Create an admin automatically - # gitlab_sign_in(:admin) - # - # # Provide an existing User record - # user = create(:user) - # gitlab_sign_in(user) - def gitlab_sign_in(user_or_role, **kwargs) - user = - if user_or_role.is_a?(User) - user_or_role - else - create(user_or_role) - end - - gitlab_sign_in_with(user, **kwargs) - - @current_user = user - end - - def gitlab_sign_in_via(provider, user, uid) - mock_auth_hash(provider, uid, user.email) - visit new_user_session_path - click_link provider - end - - # Requires Javascript driver. - def gitlab_sign_out - find(".header-user-dropdown-toggle").click - click_link "Sign out" - @current_user = nil - - expect(page).to have_button('Sign in') - end - - private - - # Private: Login as the specified user - # - # user - User instance to login with - # remember - Whether or not to check "Remember me" (default: false) - def gitlab_sign_in_with(user, remember: false) - visit new_user_session_path - - fill_in "user_login", with: user.email - fill_in "user_password", with: "12345678" - check 'user_remember_me' if remember - - click_button "Sign in" - end - - def login_via(provider, user, uid, remember_me: false) - mock_auth_hash(provider, uid, user.email) - visit new_user_session_path - expect(page).to have_content('Sign in with') - - check 'remember_me' if remember_me - - click_link "oauth-login-#{provider}" - end - - def mock_auth_hash(provider, uid, email) - # The mock_auth configuration allows you to set per-provider (or default) - # authentication hashes to return during integration testing. - OmniAuth.config.mock_auth[provider.to_sym] = OmniAuth::AuthHash.new({ - provider: provider, - uid: uid, - info: { - name: 'mockuser', - email: email, - image: 'mock_user_thumbnail_url' - }, - credentials: { - token: 'mock_token', - secret: 'mock_secret' - }, - extra: { - raw_info: { - info: { - name: 'mockuser', - email: email, - image: 'mock_user_thumbnail_url' - } - } - } - }) - Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:saml] - end - - def mock_saml_config - OpenStruct.new(name: 'saml', label: 'saml', args: { - assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback', - idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52', - idp_sso_target_url: 'https://idp.example.com/sso/saml', - issuer: 'https://localhost:3443/', - name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' - }) - end - - def stub_omniauth_provider(provider, context: Rails.application) - env = env_from_context(context) - - set_devise_mapping(context: context) - env['omniauth.auth'] = OmniAuth.config.mock_auth[provider] - end - - def stub_omniauth_saml_config(messages) - set_devise_mapping(context: Rails.application) - Rails.application.routes.disable_clear_and_finalize = true - Rails.application.routes.draw do - post '/users/auth/saml' => 'omniauth_callbacks#saml' - end - allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config) - stub_omniauth_setting(messages) - stub_saml_authorize_path_helpers - end - - def stub_saml_authorize_path_helpers - allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml') - allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml') - end - - def stub_omniauth_config(messages) - allow(Gitlab.config.omniauth).to receive_messages(messages) - end - - def stub_basic_saml_config - allow(Gitlab::Auth::Saml::Config).to receive_messages({ options: { name: 'saml', args: {} } }) - end - - def stub_saml_group_config(groups) - allow(Gitlab::Auth::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } }) - end -end diff --git a/spec/support/malicious_regexp_shared_examples.rb b/spec/support/malicious_regexp_shared_examples.rb deleted file mode 100644 index ac5d22298bb..00000000000 --- a/spec/support/malicious_regexp_shared_examples.rb +++ /dev/null @@ -1,8 +0,0 @@ -shared_examples 'malicious regexp' do - let(:malicious_text) { 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!' } - let(:malicious_regexp) { '(?i)^(([a-z])+.)+[A-Z]([a-z])+$' } - - it 'takes under a second' do - expect { Timeout.timeout(1) { subject } }.not_to raise_error - end -end diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb deleted file mode 100644 index 39e94ad53de..00000000000 --- a/spec/support/markdown_feature.rb +++ /dev/null @@ -1,128 +0,0 @@ -# This is a helper class used by the GitLab Markdown feature spec -# -# Because the feature spec only cares about the output of the Markdown, and the -# test setup and teardown and parsing is fairly expensive, we only want to do it -# once. Unfortunately RSpec will not let you access `let`s in a `before(:all)` -# block, so we fake it by encapsulating all the shared setup in this class. -# -# The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for -# reference to the factory-created objects. -class MarkdownFeature - include FactoryBot::Syntax::Methods - - def user - @user ||= create(:user) - end - - def group - @group ||= create(:group).tap do |group| - group.add_developer(user) - end - end - - # Direct references ---------------------------------------------------------- - - def project - @project ||= create(:project, :repository, group: group).tap do |project| - project.add_master(user) - end - end - - def project_wiki - @project_wiki ||= ProjectWiki.new(project, user) - end - - def project_wiki_page - @project_wiki_page ||= build(:wiki_page, wiki: project_wiki) - end - - def issue - @issue ||= create(:issue, project: project) - end - - def merge_request - @merge_request ||= create(:merge_request, :simple, source_project: project) - end - - def snippet - @snippet ||= create(:project_snippet, project: project) - end - - def commit - @commit ||= project.commit - end - - def commit_range - @commit_range ||= begin - commit2 = project.commit('HEAD~3') - CommitRange.new("#{commit.id}...#{commit2.id}", project) - end - end - - def simple_label - @simple_label ||= create(:label, name: 'gfm', project: project) - end - - def label - @label ||= create(:label, name: 'awaiting feedback', project: project) - end - - def simple_milestone - @simple_milestone ||= create(:milestone, name: 'gfm-milestone', project: project) - end - - def milestone - @milestone ||= create(:milestone, name: 'next goal', project: project) - end - - def group_milestone - @group_milestone ||= create(:milestone, name: 'group-milestone', group: group) - end - - # Cross-references ----------------------------------------------------------- - - def xproject - @xproject ||= begin - group = create(:group, :nested) - create(:project, :repository, namespace: group) do |project| - project.add_developer(user) - end - end - end - - def xissue - @xissue ||= create(:issue, project: xproject) - end - - def xmerge_request - @xmerge_request ||= create(:merge_request, :simple, source_project: xproject) - end - - def xsnippet - @xsnippet ||= create(:project_snippet, project: xproject) - end - - def xcommit - @xcommit ||= xproject.commit - end - - def xcommit_range - @xcommit_range ||= begin - xcommit2 = xproject.commit('HEAD~2') - CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject) - end - end - - def xmilestone - @xmilestone ||= create(:milestone, project: xproject) - end - - def urls - Gitlab::Routing.url_helpers - end - - def raw_markdown - markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb')) - ERB.new(markdown).result(binding) - end -end diff --git a/spec/support/matchers/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb new file mode 100644 index 00000000000..f4127efc6ae --- /dev/null +++ b/spec/support/matchers/background_migrations_matchers.rb @@ -0,0 +1,26 @@ +RSpec::Matchers.define :be_scheduled_delayed_migration do |delay, *expected| + match do |migration| + BackgroundMigrationWorker.jobs.any? do |job| + job['args'] == [migration, expected] && + job['at'].to_i == (delay.to_i + Time.now.to_i) + end + end + + failure_message do |migration| + "Migration `#{migration}` with args `#{expected.inspect}` " \ + 'not scheduled in expected time!' + end +end + +RSpec::Matchers.define :be_scheduled_migration do |*expected| + match do |migration| + BackgroundMigrationWorker.jobs.any? do |job| + args = job['args'].size == 1 ? [BackgroundMigrationWorker.jobs[0]['args'][0], []] : job['args'] + args == [migration, expected] + end + end + + failure_message do |migration| + "Migration `#{migration}` with args `#{expected.inspect}` not scheduled!" + end +end diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb new file mode 100644 index 00000000000..88d22a3ddd9 --- /dev/null +++ b/spec/support/matchers/exceed_query_limit.rb @@ -0,0 +1,64 @@ +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 + + def with_threshold(threshold) + @threshold = threshold + self + end + + def for_query(query) + @query = query + self + end + + def threshold + @threshold.to_i + end + + def expected_count + if expected.is_a?(ActiveRecord::QueryRecorder) + expected.count + else + expected + end + end + + def actual_count + @actual_count ||= if @query + recorder.log.select { |recorded| recorded =~ @query }.size + else + recorder.count + end + end + + def recorder + @recorder ||= ActiveRecord::QueryRecorder.new(&@subject_block) + end + + def count_queries(queries) + queries.each_with_object(Hash.new(0)) { |query, counts| counts[query] += 1 } + end + + def log_message + if expected.is_a?(ActiveRecord::QueryRecorder) + counts = count_queries(expected.log) + extra_queries = @recorder.log.reject { |query| counts[query] -= 1 unless counts[query].zero? } + extra_queries_display = count_queries(extra_queries).map { |query, count| "[#{count}] #{query}" } + + (['Extra queries:'] + extra_queries_display).join("\n\n") + else + @recorder.log_message + end + end +end diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb deleted file mode 100644 index 1685decbe94..00000000000 --- a/spec/support/mentionable_shared_examples.rb +++ /dev/null @@ -1,144 +0,0 @@ -# Specifications for behavior common to all Mentionable implementations. -# Requires a shared context containing: -# - subject { "the mentionable implementation" } -# - let(:backref_text) { "the way that +subject+ should refer to itself in backreferences " } -# - let(:set_mentionable_text) { lambda { |txt| "block that assigns txt to the subject's mentionable_text" } } - -shared_context 'mentionable context' do - let(:project) { subject.project } - let(:author) { subject.author } - - let(:mentioned_issue) { create(:issue, project: project) } - let!(:mentioned_mr) { create(:merge_request, source_project: project) } - let(:mentioned_commit) { project.commit("HEAD~1") } - - let(:ext_proj) { create(:project, :public, :repository) } - let(:ext_issue) { create(:issue, project: ext_proj) } - let(:ext_mr) { create(:merge_request, :simple, source_project: ext_proj) } - let(:ext_commit) { ext_proj.commit("HEAD~2") } - - # Override to add known commits to the repository stub. - let(:extra_commits) { [] } - - # A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference - # to this string and place it in their +mentionable_text+. - let(:ref_string) do - <<-MSG.strip_heredoc - These references are new: - Issue: #{mentioned_issue.to_reference} - Merge: #{mentioned_mr.to_reference} - Commit: #{mentioned_commit.to_reference} - - This reference is a repeat and should only be mentioned once: - Repeat: #{mentioned_issue.to_reference} - - These references are cross-referenced: - Issue: #{ext_issue.to_reference(project)} - Merge: #{ext_mr.to_reference(project)} - Commit: #{ext_commit.to_reference(project)} - - This is a self-reference and should not be mentioned at all: - Self: #{backref_text} - MSG - end - - before do - # Wire the project's repository to return the mentioned commit, and +nil+ - # for any unrecognized commits. - allow_any_instance_of(::Repository).to receive(:commit).and_call_original - allow_any_instance_of(::Repository).to receive(:commit).with(mentioned_commit.short_id).and_return(mentioned_commit) - extra_commits.each do |commit| - allow_any_instance_of(::Repository).to receive(:commit).with(commit.short_id).and_return(commit) - end - - set_mentionable_text.call(ref_string) - - project.add_developer(author) - end -end - -shared_examples 'a mentionable' do - include_context 'mentionable context' - - it 'generates a descriptive back-reference' do - expect(subject.gfm_reference).to eq(backref_text) - end - - it "extracts references from its reference property" do - # De-duplicate and omit itself - refs = subject.referenced_mentionables - expect(refs.size).to eq(6) - expect(refs).to include(mentioned_issue) - expect(refs).to include(mentioned_mr) - expect(refs).to include(mentioned_commit) - expect(refs).to include(ext_issue) - expect(refs).to include(ext_mr) - expect(refs).to include(ext_commit) - end - - it 'creates cross-reference notes' do - mentioned_objects = [mentioned_issue, mentioned_mr, mentioned_commit, - ext_issue, ext_mr, ext_commit] - - mentioned_objects.each do |referenced| - expect(SystemNoteService).to receive(:cross_reference) - .with(referenced, subject.local_reference, author) - end - - subject.create_cross_references! - end -end - -shared_examples 'an editable mentionable' do - include_context 'mentionable context' - - it_behaves_like 'a mentionable' - - let(:new_issues) do - [create(:issue, project: project), create(:issue, project: ext_proj)] - end - - it 'creates new cross-reference notes when the mentionable text is edited' do - subject.save - subject.create_cross_references! - - new_text = <<-MSG.strip_heredoc - These references already existed: - - Issue: #{mentioned_issue.to_reference} - - Commit: #{mentioned_commit.to_reference} - - --- - - This cross-project reference already existed: - - Issue: #{ext_issue.to_reference(project)} - - --- - - These two references are introduced in an edit: - - Issue: #{new_issues[0].to_reference} - - Cross: #{new_issues[1].to_reference(project)} - MSG - - # These three objects were already referenced, and should not receive new - # notes - [mentioned_issue, mentioned_commit, ext_issue].each do |oldref| - expect(SystemNoteService).not_to receive(:cross_reference) - .with(oldref, any_args) - end - - # These two issues are new and should receive reference notes - # In the case of MergeRequests remember that cannot mention commits included in the MergeRequest - new_issues.each do |newref| - expect(SystemNoteService).to receive(:cross_reference) - .with(newref, subject.local_reference, author) - end - - set_mentionable_text.call(new_text) - subject.create_new_cross_references!(author) - end -end diff --git a/spec/support/merge_request_helpers.rb b/spec/support/merge_request_helpers.rb deleted file mode 100644 index 772adff4626..00000000000 --- a/spec/support/merge_request_helpers.rb +++ /dev/null @@ -1,22 +0,0 @@ -module MergeRequestHelpers - def visit_merge_requests(project, opts = {}) - visit project_merge_requests_path project, opts - end - - def first_merge_request - page.all('ul.mr-list > li').first.text - end - - def last_merge_request - page.all('ul.mr-list > li').last.text - end - - def expect_mr_list_count(open_count, closed_count = 0) - all_count = open_count + closed_count - - expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count) - page.within '.mr-list' do - expect(page).to have_selector('.merge-request', count: open_count) - end - end -end diff --git a/spec/support/migrations_helpers.rb b/spec/support/migrations_helpers.rb deleted file mode 100644 index 5d6f662e8fe..00000000000 --- a/spec/support/migrations_helpers.rb +++ /dev/null @@ -1,92 +0,0 @@ -module MigrationsHelpers - def table(name) - Class.new(ActiveRecord::Base) do - self.table_name = name - self.inheritance_column = :_type_disabled - end - end - - def migrations_paths - ActiveRecord::Migrator.migrations_paths - end - - def table_exists?(name) - ActiveRecord::Base.connection.table_exists?(name) - end - - def migrations - ActiveRecord::Migrator.migrations(migrations_paths) - end - - def clear_schema_cache! - ActiveRecord::Base.connection_pool.connections.each do |conn| - conn.schema_cache.clear! - end - end - - def reset_column_in_all_models - clear_schema_cache! - - # Reset column information for the most offending classes **after** we - # migrated the schema up, otherwise, column information could be - # outdated. We have a separate method for this so we can override it in EE. - ActiveRecord::Base.descendants.each(&method(:reset_column_information)) - - # Without that, we get errors because of missing attributes, e.g. - # super: no superclass method `elasticsearch_indexing' for # - ApplicationSetting.define_attribute_methods - end - - def reset_column_information(klass) - klass.reset_column_information - end - - def previous_migration - migrations.each_cons(2) do |previous, migration| - break previous if migration.name == described_class.name - end - end - - def migration_schema_version - metadata_schema = self.class.metadata[:schema] - - if metadata_schema == :latest - migrations.last.version - else - metadata_schema || previous_migration.version - end - end - - def schema_migrate_down! - disable_migrations_output do - ActiveRecord::Migrator.migrate(migrations_paths, - migration_schema_version) - end - - reset_column_in_all_models - end - - def schema_migrate_up! - reset_column_in_all_models - - disable_migrations_output do - ActiveRecord::Migrator.migrate(migrations_paths) - end - - reset_column_in_all_models - end - - def disable_migrations_output - ActiveRecord::Migration.verbose = false - - yield - ensure - ActiveRecord::Migration.verbose = true - end - - def migrate! - ActiveRecord::Migrator.up(migrations_paths) do |migration| - migration.name == described_class.name - end - end -end diff --git a/spec/support/milestone_tabs_examples.rb b/spec/support/milestone_tabs_examples.rb deleted file mode 100644 index 70b499198bf..00000000000 --- a/spec/support/milestone_tabs_examples.rb +++ /dev/null @@ -1,84 +0,0 @@ -shared_examples 'milestone tabs' do - def go(path, extra_params = {}) - params = - case milestone - when DashboardMilestone - { id: milestone.safe_title, title: milestone.title } - when GroupMilestone - { group_id: group.to_param, id: milestone.safe_title, title: milestone.title } - else - { namespace_id: project.namespace.to_param, project_id: project, id: milestone.iid } - end - - get path, params.merge(extra_params) - end - - describe '#merge_requests' do - context 'as html' do - before do - go(:merge_requests, format: 'html') - end - - it 'redirects to milestone#show' do - expect(response).to redirect_to(milestone_path) - end - end - - context 'as json' do - before do - go(:merge_requests, format: 'json') - end - - it 'renders the merge requests tab template to a string' do - expect(response).to render_template('shared/milestones/_merge_requests_tab') - expect(json_response).to have_key('html') - end - end - end - - describe '#participants' do - context 'as html' do - before do - go(:participants, format: 'html') - end - - it 'redirects to milestone#show' do - expect(response).to redirect_to(milestone_path) - end - end - - context 'as json' do - before do - go(:participants, format: 'json') - end - - it 'renders the participants tab template to a string' do - expect(response).to render_template('shared/milestones/_participants_tab') - expect(json_response).to have_key('html') - end - end - end - - describe '#labels' do - context 'as html' do - before do - go(:labels, format: 'html') - end - - it 'redirects to milestone#show' do - expect(response).to redirect_to(milestone_path) - end - end - - context 'as json' do - before do - go(:labels, format: 'json') - end - - it 'renders the labels tab template to a string' do - expect(response).to render_template('shared/milestones/_labels_tab') - expect(json_response).to have_key('html') - end - end - end -end diff --git a/spec/support/mobile_helpers.rb b/spec/support/mobile_helpers.rb deleted file mode 100644 index 3b9eb84e824..00000000000 --- a/spec/support/mobile_helpers.rb +++ /dev/null @@ -1,17 +0,0 @@ -module MobileHelpers - def resize_screen_xs - resize_window(767, 768) - end - - def resize_screen_sm - resize_window(900, 768) - end - - def restore_window_size - resize_window(1366, 768) - end - - def resize_window(width, height) - Capybara.current_session.current_window.resize_to(width, height) - end -end diff --git a/spec/support/notify_shared_examples.rb b/spec/support/notify_shared_examples.rb deleted file mode 100644 index e2c23607406..00000000000 --- a/spec/support/notify_shared_examples.rb +++ /dev/null @@ -1,199 +0,0 @@ -shared_context 'gitlab email notification' do - set(:project) { create(:project, :repository) } - set(:recipient) { create(:user, email: 'recipient@example.com') } - - let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } - let(:gitlab_sender) { Gitlab.config.gitlab.email_from } - let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to } - let(:new_user_address) { 'newguy@example.com' } - - before do - email = recipient.emails.create(email: "notifications@example.com") - recipient.update_attribute(:notification_email, email.email) - stub_incoming_email_setting(enabled: true, address: "reply+%{key}@#{Gitlab.config.gitlab.host}") - end -end - -shared_context 'reply-by-email is enabled with incoming address without %{key}' do - before do - stub_incoming_email_setting(enabled: true, address: "reply@#{Gitlab.config.gitlab.host}") - end -end - -shared_examples 'a multiple recipients email' do - it 'is sent to the given recipient' do - is_expected.to deliver_to recipient.notification_email - end -end - -shared_examples 'an email sent from GitLab' do - it 'has the characteristics of an email sent from GitLab' do - sender = subject.header[:from].addrs[0] - reply_to = subject.header[:reply_to].addresses - - aggregate_failures do - expect(sender.display_name).to eq(gitlab_sender_display_name) - expect(sender.address).to eq(gitlab_sender) - expect(reply_to).to eq([gitlab_sender_reply_to]) - end - end -end - -shared_examples 'an email that contains a header with author username' do - it 'has X-GitLab-Author header containing author\'s username' do - is_expected.to have_header 'X-GitLab-Author', user.username - end -end - -shared_examples 'an email with X-GitLab headers containing project details' do - it 'has X-GitLab-Project headers' do - aggregate_failures do - is_expected.to have_header('X-GitLab-Project', /#{project.name}/) - is_expected.to have_header('X-GitLab-Project-Id', /#{project.id}/) - is_expected.to have_header('X-GitLab-Project-Path', /#{project.full_path}/) - end - end -end - -shared_examples 'a new thread email with reply-by-email enabled' do - it 'has the characteristics of a threaded email' do - host = Gitlab.config.gitlab.host - route_key = "#{model.class.model_name.singular_route_key}_#{model.id}" - - aggregate_failures do - is_expected.to have_header('Message-ID', "<#{route_key}@#{host}>") - is_expected.to have_header('References', /\A\Z/ ) - end - end -end - -shared_examples 'a thread answer email with reply-by-email enabled' do - include_examples 'an email with X-GitLab headers containing project details' - - it 'has the characteristics of a threaded reply' do - host = Gitlab.config.gitlab.host - route_key = "#{model.class.model_name.singular_route_key}_#{model.id}" - - aggregate_failures do - is_expected.to have_header('Message-ID', /\A<.*@#{host}>\Z/) - is_expected.to have_header('In-Reply-To', "<#{route_key}@#{host}>") - is_expected.to have_header('References', /\A<#{route_key}@#{host}> \Z/ ) - is_expected.to have_subject(/^Re: /) - end - end -end - -shared_examples 'an email starting a new thread with reply-by-email enabled' do - include_examples 'an email with X-GitLab headers containing project details' - include_examples 'a new thread email with reply-by-email enabled' - - context 'when reply-by-email is enabled with incoming address with %{key}' do - it 'has a Reply-To header' do - is_expected.to have_header 'Reply-To', /\Z/ - end - end - - context 'when reply-by-email is enabled with incoming address without %{key}' do - include_context 'reply-by-email is enabled with incoming address without %{key}' - include_examples 'a new thread email with reply-by-email enabled' - - it 'has a Reply-To header' do - is_expected.to have_header 'Reply-To', /\Z/ - end - end -end - -shared_examples 'an answer to an existing thread with reply-by-email enabled' do - include_examples 'an email with X-GitLab headers containing project details' - include_examples 'a thread answer email with reply-by-email enabled' - - context 'when reply-by-email is enabled with incoming address with %{key}' do - it 'has a Reply-To header' do - is_expected.to have_header 'Reply-To', /\Z/ - end - end - - context 'when reply-by-email is enabled with incoming address without %{key}' do - include_context 'reply-by-email is enabled with incoming address without %{key}' - include_examples 'a thread answer email with reply-by-email enabled' - - it 'has a Reply-To header' do - is_expected.to have_header 'Reply-To', /\Z/ - end - end -end - -shared_examples 'it should have Gmail Actions links' do - it do - aggregate_failures do - is_expected.to have_body_text('