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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-10-19 15:57:54 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-10-19 15:57:54 +0300
commit419c53ec62de6e97a517abd5fdd4cbde3a942a34 (patch)
tree1f43a548b46bca8a5fb8fe0c31cef1883d49c5b6 /spec/lib/gitlab
parent1da20d9135b3ad9e75e65b028bffc921aaf8deb7 (diff)
Add latest changes from gitlab-org/gitlab@16-5-stable-eev16.5.0-rc42
Diffstat (limited to 'spec/lib/gitlab')
-rw-r--r--spec/lib/gitlab/auth/auth_finders_spec.rb71
-rw-r--r--spec/lib/gitlab/auth/ldap/config_spec.rb15
-rw-r--r--spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb30
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb31
-rw-r--r--spec/lib/gitlab/auth_spec.rb103
-rw-r--r--spec/lib/gitlab/background_migration/backfill_finding_id_in_vulnerabilities_spec.rb133
-rw-r--r--spec/lib/gitlab/background_migration/backfill_has_remediations_of_vulnerability_reads_spec.rb136
-rw-r--r--spec/lib/gitlab/background_migration/delete_orphans_approval_merge_request_rules2_spec.rb121
-rw-r--r--spec/lib/gitlab/background_migration/delete_orphans_approval_project_rules2_spec.rb122
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb28
-rw-r--r--spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb103
-rw-r--r--spec/lib/gitlab/bitbucket_import/importers/issue_notes_importer_spec.rb85
-rw-r--r--spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb70
-rw-r--r--spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb53
-rw-r--r--spec/lib/gitlab/bitbucket_import/importers/lfs_object_importer_spec.rb82
-rw-r--r--spec/lib/gitlab/bitbucket_import/importers/lfs_objects_importer_spec.rb116
-rw-r--r--spec/lib/gitlab/bitbucket_import/importers/pull_request_notes_importer_spec.rb68
-rw-r--r--spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb51
-rw-r--r--spec/lib/gitlab/bitbucket_import/ref_converter_spec.rb69
-rw-r--r--spec/lib/gitlab/bitbucket_server_import/importers/pull_request_importer_spec.rb62
-rw-r--r--spec/lib/gitlab/bitbucket_server_import/importers/pull_requests_importer_spec.rb94
-rw-r--r--spec/lib/gitlab/chat_spec.rb19
-rw-r--r--spec/lib/gitlab/checks/global_file_size_check_spec.rb16
-rw-r--r--spec/lib/gitlab/checks/tag_check_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/build/context/build_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/build/duration_parser_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/components/instance_path_spec.rb45
-rw-r--r--spec/lib/gitlab/ci/config/external/file/component_spec.rb32
-rw-r--r--spec/lib/gitlab/ci/config/header/input_spec.rb19
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/context_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/functions/expand_vars_spec.rb90
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb409
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/yaml/loader_spec.rb32
-rw-r--r--spec/lib/gitlab/ci/config/yaml/result_spec.rb55
-rw-r--r--spec/lib/gitlab/ci/lint_spec.rb41
-rw-r--r--spec/lib/gitlab/ci/parsers/security/common_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/parsers/test/junit_spec.rb32
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern/regular_expression_spec.rb57
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb13
-rw-r--r--spec/lib/gitlab/ci/status/bridge/factory_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/status/canceled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/created_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/factory_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/failed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pending_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/preparing_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/running_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/scheduled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/skipped_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/success_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/success_warning_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/variables/builder/group_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb25
-rw-r--r--spec/lib/gitlab/content_security_policy/config_loader_spec.rb11
-rw-r--r--spec/lib/gitlab/database/click_house_client_spec.rb22
-rw-r--r--spec/lib/gitlab/database/gitlab_schema_spec.rb82
-rw-r--r--spec/lib/gitlab/database/load_balancing/service_discovery_spec.rb21
-rw-r--r--spec/lib/gitlab/database/migration_helpers/swapping_spec.rb172
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb59
-rw-r--r--spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb59
-rw-r--r--spec/lib/gitlab/database/migrations/milestone_mixin_spec.rb48
-rw-r--r--spec/lib/gitlab/database/migrations/observers/query_statistics_spec.rb8
-rw-r--r--spec/lib/gitlab/database/migrations/swap_columns_default_spec.rb118
-rw-r--r--spec/lib/gitlab/database/migrations/swap_columns_spec.rb64
-rw-r--r--spec/lib/gitlab/database/migrations/version_spec.rb120
-rw-r--r--spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb8
-rw-r--r--spec/lib/gitlab/database/partitioning/partition_manager_spec.rb75
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb292
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb313
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb190
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb78
-rw-r--r--spec/lib/gitlab/database_importers/work_items/related_links_restrictions_importer_spec.rb10
-rw-r--r--spec/lib/gitlab/deploy_key_access_spec.rb10
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb66
-rw-r--r--spec/lib/gitlab/doctor/reset_tokens_spec.rb133
-rw-r--r--spec/lib/gitlab/email/handler/service_desk_handler_spec.rb12
-rw-r--r--spec/lib/gitlab/email/message/build_ios_app_guide_spec.rb19
-rw-r--r--spec/lib/gitlab/email/message/in_product_marketing/helper_spec.rb75
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb20
-rw-r--r--spec/lib/gitlab/email/service_desk_receiver_spec.rb35
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb37
-rw-r--r--spec/lib/gitlab/exclusive_lease_spec.rb331
-rw-r--r--spec/lib/gitlab/experiment/rollout/feature_spec.rb94
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb122
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb63
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb117
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb28
-rw-r--r--spec/lib/gitlab/git_audit_event_spec.rb79
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb26
-rw-r--r--spec/lib/gitlab/github_gists_import/importer/gist_importer_spec.rb8
-rw-r--r--spec/lib/gitlab/github_import/bulk_importing_spec.rb18
-rw-r--r--spec/lib/gitlab/github_import/client_spec.rb6
-rw-r--r--spec/lib/gitlab/github_import/clients/proxy_spec.rb150
-rw-r--r--spec/lib/gitlab/github_import/importer/attachments/issues_importer_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/importer/attachments/merge_requests_importer_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/importer/attachments/notes_importer_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/importer/attachments/releases_importer_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb14
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_importer_spec.rb38
-rw-r--r--spec/lib/gitlab/github_import/importer/labels_importer_spec.rb5
-rw-r--r--spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb5
-rw-r--r--spec/lib/gitlab/github_import/importer/note_importer_spec.rb53
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb27
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_requests/review_requests_importer_spec.rb12
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/releases_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/settings_spec.rb11
-rw-r--r--spec/lib/gitlab/gon_helper_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/deprecations/deprecation_spec.rb17
-rw-r--r--spec/lib/gitlab/graphql/pagination/array_connection_spec.rb3
-rw-r--r--spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb3
-rw-r--r--spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb6
-rw-r--r--spec/lib/gitlab/graphql/timeout_spec.rb5
-rw-r--r--spec/lib/gitlab/group_search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/hashed_storage/migrator_spec.rb247
-rw-r--r--spec/lib/gitlab/http_spec.rb447
-rw-r--r--spec/lib/gitlab/i18n_spec.rb14
-rw-r--r--spec/lib/gitlab/import/errors_spec.rb1
-rw-r--r--spec/lib/gitlab/import/import_failure_service_spec.rb33
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml5
-rw-r--r--spec/lib/gitlab/import_export/attributes_finder_spec.rb30
-rw-r--r--spec/lib/gitlab/import_export/base/object_builder_spec.rb12
-rw-r--r--spec/lib/gitlab/import_export/base/relation_factory_spec.rb18
-rw-r--r--spec/lib/gitlab/import_export/design_repo_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb28
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/merge_request_parser_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/project/export_task_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/import_task_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/object_builder_spec.rb150
-rw-r--r--spec/lib/gitlab/import_export/project/relation_factory_spec.rb24
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb46
-rw-r--r--spec/lib/gitlab/import_export/project/tree_saver_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/shared_spec.rb12
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/internal_events/event_definitions_spec.rb10
-rw-r--r--spec/lib/gitlab/internal_events_spec.rb50
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb4
-rw-r--r--spec/lib/gitlab/legacy_http_spec.rb448
-rw-r--r--spec/lib/gitlab/memory/instrumentation_spec.rb4
-rw-r--r--spec/lib/gitlab/merge_requests/mergeability/check_result_spec.rb15
-rw-r--r--spec/lib/gitlab/metrics/web_transaction_spec.rb19
-rw-r--r--spec/lib/gitlab/middleware/handle_malformed_strings_spec.rb33
-rw-r--r--spec/lib/gitlab/middleware/path_traversal_check_spec.rb197
-rw-r--r--spec/lib/gitlab/observability_spec.rb204
-rw-r--r--spec/lib/gitlab/octokit/middleware_spec.rb4
-rw-r--r--spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb102
-rw-r--r--spec/lib/gitlab/path_traversal_spec.rb7
-rw-r--r--spec/lib/gitlab/prometheus/metric_group_spec.rb48
-rw-r--r--spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb39
-rw-r--r--spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb137
-rw-r--r--spec/lib/gitlab/prometheus/queries/validate_query_spec.rb59
-rw-r--r--spec/lib/gitlab/prometheus/query_variables_spec.rb96
-rw-r--r--spec/lib/gitlab/protocol_access_spec.rb49
-rw-r--r--spec/lib/gitlab/puma/error_handler_spec.rb85
-rw-r--r--spec/lib/gitlab/rack_attack/request_spec.rb41
-rw-r--r--spec/lib/gitlab/redis/multi_store_spec.rb49
-rw-r--r--spec/lib/gitlab/redis/queues_metadata_spec.rb35
-rw-r--r--spec/lib/gitlab/redis/workhorse_spec.rb38
-rw-r--r--spec/lib/gitlab/regex_spec.rb27
-rw-r--r--spec/lib/gitlab/saas_spec.rb5
-rw-r--r--spec/lib/gitlab/search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/shell_spec.rb81
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb65
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb10
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb135
-rw-r--r--spec/lib/gitlab/slash_commands/run_spec.rb10
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb20
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb6
-rw-r--r--spec/lib/gitlab/usage/metric_definition_spec.rb100
-rw-r--r--spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb10
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/container_registry_db_enabled_metric_spec.rb9
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_ci_internal_pipelines_metric_spec.rb2
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_csv_imports_metric_spec.rb32
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_issues_created_manually_from_alerts_metric_spec.rb2
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_jira_imports_metric_spec.rb32
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_packages_metric_spec.rb33
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_projects_metric_spec.rb33
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_cta_clicked_metric_spec.rb8
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_sent_metric_spec.rb6
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/incoming_email_encrypted_secrets_enabled_metric_spec.rb2
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/service_desk_email_encrypted_secrets_enabled_metric_spec.rb2
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/total_count_metric_spec.rb40
-rw-r--r--spec/lib/gitlab/usage/metrics/query_spec.rb6
-rw-r--r--spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb16
-rw-r--r--spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb6
-rw-r--r--spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb56
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_queries_spec.rb13
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb61
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb84
204 files changed, 5714 insertions, 4252 deletions
diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb
index b0ec46a3a0e..95199ae18de 100644
--- a/spec/lib/gitlab/auth/auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/auth_finders_spec.rb
@@ -1149,4 +1149,75 @@ RSpec.describe Gitlab::Auth::AuthFinders, feature_category: :system_access do
end
end
end
+
+ describe '#authentication_token_present?' do
+ subject { authentication_token_present? }
+
+ context 'no auth header/param/oauth' do
+ before do
+ request.headers['Random'] = 'Something'
+ set_param(:random, 'something')
+ end
+
+ it { is_expected.to be(false) }
+ end
+
+ context 'with auth header' do
+ before do
+ request.headers[header] = 'invalid'
+ end
+
+ context 'with private-token' do
+ let(:header) { 'Private-Token' }
+
+ it { is_expected.to be(true) }
+ end
+
+ context 'with job-token' do
+ let(:header) { 'Job-Token' }
+
+ it { is_expected.to be(true) }
+ end
+
+ context 'with deploy-token' do
+ let(:header) { 'Deploy-Token' }
+
+ it { is_expected.to be(true) }
+ end
+ end
+
+ context 'with authorization bearer (oauth token)' do
+ before do
+ request.headers['Authorization'] = 'Bearer invalid'
+ end
+
+ it { is_expected.to be(true) }
+ end
+
+ context 'with auth param' do
+ context 'with private_token' do
+ it 'returns true' do
+ set_param(:private_token, 'invalid')
+
+ expect(subject).to be(true)
+ end
+ end
+
+ context 'with job_token' do
+ it 'returns true' do
+ set_param(:job_token, 'invalid')
+
+ expect(subject).to be(true)
+ end
+ end
+
+ context 'with token' do
+ it 'returns true' do
+ set_param(:token, 'invalid')
+
+ expect(subject).to be(true)
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb
index 160fd78b2b9..48039b58216 100644
--- a/spec/lib/gitlab/auth/ldap/config_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Auth::Ldap::Config do
+RSpec.describe Gitlab::Auth::Ldap::Config, feature_category: :system_access do
include LdapHelpers
before do
@@ -362,6 +362,19 @@ AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK
expect(config.omniauth_options.keys).not_to include(:bind_dn, :password)
end
+ it 'defaults to plain encryption when not configured' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 386,
+ 'base' => 'ou=users,dc=example,dc=com',
+ 'uid' => 'uid'
+ }
+ )
+
+ expect(config.omniauth_options).to include(encryption: 'plain')
+ end
+
it 'includes authentication options when auth is configured' do
stub_ldap_config(
options: {
diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
index 8c50b2acac6..5d01f09df41 100644
--- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Auth::OAuth::AuthHash, feature_category: :user_management do
- let(:provider) { 'ldap' }
+ let(:provider) { 'openid_connect' }
let(:auth_hash) do
described_class.new(
OmniAuth::AuthHash.new(
@@ -19,7 +19,6 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, feature_category: :user_management
)
end
- let(:provider_config) { { 'args' => { 'gitlab_username_claim' => 'first_name' } } }
let(:uid_raw) do
+"CN=Onur K\xC3\xBC\xC3\xA7\xC3\xBCk,OU=Test,DC=example,DC=net"
end
@@ -90,6 +89,22 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, feature_category: :user_management
end
end
+ context 'when username claim is in email format' do
+ let(:info_hash) do
+ {
+ email: nil,
+ name: 'GitLab test',
+ nickname: 'GitLab@gitlabsandbox.onmicrosoft.com',
+ uid: uid_ascii
+ }
+ end
+
+ it 'creates proper email and username fields' do
+ expect(auth_hash.username).to eql 'GitLab'
+ expect(auth_hash.email).to eql 'temp-email-for-oauth-GitLab@gitlab.localhost'
+ end
+ end
+
context 'name not provided' do
before do
info_hash.delete(:name)
@@ -101,8 +116,17 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, feature_category: :user_management
end
context 'custom username field provided' do
+ let(:provider_config) do
+ GitlabSettings::Options.build(
+ {
+ name: provider,
+ args: { 'gitlab_username_claim' => 'first_name' }
+ }
+ )
+ end
+
before do
- allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for).and_return(provider_config)
+ stub_omniauth_setting(providers: [provider_config])
end
it 'uses the custom field for the username within info' do
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 78e0df91103..8a9182f6457 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -535,6 +535,37 @@ RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :system_access do
end
end
+ context "and a corresponding LDAP person with some values being nil" do
+ before do
+ allow(ldap_user).to receive(:uid) { uid }
+ allow(ldap_user).to receive(:username) { uid }
+ allow(ldap_user).to receive(:name) { nil }
+ allow(ldap_user).to receive(:email) { nil }
+ allow(ldap_user).to receive(:dn) { dn }
+
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
+
+ oauth_user.save # rubocop:disable Rails/SaveBang
+ end
+
+ it "creates the user correctly" do
+ expect(gl_user).to be_valid
+ expect(gl_user.username).to eq(uid)
+ expect(gl_user.name).to eq(info_hash[:name])
+ expect(gl_user.email).to eq(info_hash[:email])
+ end
+
+ it "does not have the attributes not provided by LDAP set as synced" do
+ expect(gl_user.user_synced_attributes_metadata.name_synced).to be_falsey
+ expect(gl_user.user_synced_attributes_metadata.email_synced).to be_falsey
+ end
+
+ it "does not have the attributes not provided by LDAP set as read-only" do
+ expect(gl_user.read_only_attribute?(:name)).to be_falsey
+ expect(gl_user.read_only_attribute?(:email)).to be_falsey
+ end
+ end
+
context 'and a corresponding LDAP person with a non-default username' do
before do
allow(ldap_user).to receive(:uid) { uid }
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 8da617175ca..f5b9555916c 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -34,7 +34,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate
end
end
- context 'available_scopes' do
+ describe 'available_scopes' do
before do
stub_container_registry_config(enabled: true)
end
@@ -43,26 +43,26 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate
expect(subject.all_available_scopes).to match_array %i[api read_user read_api read_repository write_repository read_registry write_registry sudo admin_mode read_observability write_observability create_runner k8s_proxy ai_features]
end
- it 'contains for non-admin user all non-default scopes without ADMIN access and without observability scopes' do
+ it 'contains for non-admin user all non-default scopes without ADMIN access and without observability scopes and ai_features' do
user = build_stubbed(:user, admin: false)
- expect(subject.available_scopes_for(user)).to match_array %i[api read_user read_api read_repository write_repository read_registry write_registry create_runner k8s_proxy ai_features]
+ expect(subject.available_scopes_for(user)).to match_array %i[api read_user read_api read_repository write_repository read_registry write_registry create_runner k8s_proxy]
end
- it 'contains for admin user all non-default scopes with ADMIN access and without observability scopes' do
+ it 'contains for admin user all non-default scopes with ADMIN access and without observability scopes and ai_features' do
user = build_stubbed(:user, admin: true)
- expect(subject.available_scopes_for(user)).to match_array %i[api read_user read_api read_repository write_repository read_registry write_registry sudo admin_mode create_runner k8s_proxy ai_features]
+ expect(subject.available_scopes_for(user)).to match_array %i[api read_user read_api read_repository write_repository read_registry write_registry sudo admin_mode create_runner k8s_proxy]
end
- it 'contains for project all resource bot scopes without observability scopes' do
- expect(subject.available_scopes_for(project)).to match_array %i[api read_api read_repository write_repository read_registry write_registry create_runner k8s_proxy ai_features]
+ it 'contains for project all resource bot scopes without ai_features' do
+ expect(subject.available_scopes_for(project)).to match_array %i[api read_api read_repository write_repository read_registry write_registry read_observability write_observability create_runner k8s_proxy]
end
it 'contains for group all resource bot scopes' do
- group = build_stubbed(:group)
+ group = build_stubbed(:group).tap { |g| g.namespace_settings = build_stubbed(:namespace_settings, namespace: g) }
- expect(subject.available_scopes_for(group)).to match_array %i[api read_api read_repository write_repository read_registry write_registry read_observability write_observability create_runner k8s_proxy ai_features]
+ expect(subject.available_scopes_for(group)).to match_array %i[api read_api read_repository write_repository read_registry write_registry read_observability write_observability create_runner k8s_proxy]
end
it 'contains for unsupported type no scopes' do
@@ -73,44 +73,101 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate
expect(subject.optional_scopes).to match_array %i[read_user read_api read_repository write_repository read_registry write_registry sudo admin_mode openid profile email read_observability write_observability create_runner k8s_proxy ai_features]
end
- context 'with observability_group_tab feature flag' do
+ describe 'ai_features scope' do
+ let(:resource) { nil }
+
+ subject { described_class.available_scopes_for(resource) }
+
+ context 'when resource is user', 'and user has a group with ai features' do
+ let(:resource) { build_stubbed(:user) }
+
+ it { is_expected.not_to include(:ai_features) }
+ end
+
+ context 'when resource is project' do
+ let(:resource) { build_stubbed(:project) }
+
+ it 'does not include ai_features scope' do
+ is_expected.not_to include(:ai_features)
+ end
+ end
+
+ context 'when resource is group' do
+ let(:resource) { build_stubbed(:group) }
+
+ it 'does not include ai_features scope' do
+ is_expected.not_to include(:ai_features)
+ end
+ end
+ end
+
+ context 'with observability_tracing feature flag' do
context 'when disabled' do
before do
- stub_feature_flags(observability_group_tab: false)
+ stub_feature_flags(observability_tracing: false)
end
it 'contains for group all resource bot scopes without observability scopes' do
- group = build_stubbed(:group)
+ group = build_stubbed(:group).tap do |g|
+ g.namespace_settings = build_stubbed(:namespace_settings, namespace: g)
+ end
- expect(subject.available_scopes_for(group)).to match_array %i[api read_api read_repository write_repository read_registry write_registry create_runner k8s_proxy ai_features]
+ expect(subject.available_scopes_for(group)).to match_array %i[api read_api read_repository write_repository read_registry write_registry create_runner k8s_proxy]
+ end
+
+ it 'contains for project all resource bot scopes without observability scopes' do
+ group = build_stubbed(:group).tap do |g|
+ g.namespace_settings = build_stubbed(:namespace_settings, namespace: g)
+ end
+ project = build_stubbed(:project, namespace: group)
+
+ expect(subject.available_scopes_for(project)).to match_array %i[api read_api read_repository write_repository read_registry write_registry create_runner k8s_proxy]
end
end
- context 'when enabled for specific group' do
- let(:group) { build_stubbed(:group) }
+ context 'when enabled for specific root group' do
+ let(:parent) { build_stubbed(:group) }
+ let(:group) do
+ build_stubbed(:group, parent: parent).tap { |g| g.namespace_settings = build_stubbed(:namespace_settings, namespace: g) }
+ end
+
+ let(:project) { build_stubbed(:project, namespace: group) }
before do
- stub_feature_flags(observability_group_tab: group)
+ stub_feature_flags(observability_tracing: parent)
end
- it 'contains for other group all resource bot scopes including observability scopes' do
- expect(subject.available_scopes_for(group)).to match_array %i[api read_api read_repository write_repository read_registry write_registry read_observability write_observability create_runner k8s_proxy ai_features]
+ it 'contains for group all resource bot scopes including observability scopes' do
+ expect(subject.available_scopes_for(group)).to match_array %i[api read_api read_repository write_repository read_registry write_registry read_observability write_observability create_runner k8s_proxy]
end
it 'contains for admin user all non-default scopes with ADMIN access and without observability scopes' do
user = build_stubbed(:user, admin: true)
- expect(subject.available_scopes_for(user)).to match_array %i[api read_user read_api read_repository write_repository read_registry write_registry sudo admin_mode create_runner k8s_proxy ai_features]
+ expect(subject.available_scopes_for(user)).to match_array %i[api read_user read_api read_repository write_repository read_registry write_registry sudo admin_mode create_runner k8s_proxy]
end
- it 'contains for project all resource bot scopes without observability scopes' do
- expect(subject.available_scopes_for(project)).to match_array %i[api read_api read_repository write_repository read_registry write_registry create_runner k8s_proxy ai_features]
+ it 'contains for project all resource bot scopes including observability scopes' do
+ expect(subject.available_scopes_for(project)).to match_array %i[api read_api read_repository write_repository read_registry write_registry read_observability write_observability create_runner k8s_proxy]
end
it 'contains for other group all resource bot scopes without observability scopes' do
- other_group = build_stubbed(:group)
+ other_parent = build_stubbed(:group)
+ other_group = build_stubbed(:group, parent: other_parent).tap do |g|
+ g.namespace_settings = build_stubbed(:namespace_settings, namespace: g)
+ end
+
+ expect(subject.available_scopes_for(other_group)).to match_array %i[api read_api read_repository write_repository read_registry write_registry create_runner k8s_proxy]
+ end
+
+ it 'contains for other project all resource bot scopes without observability scopes' do
+ other_parent = build_stubbed(:group)
+ other_group = build_stubbed(:group, parent: other_parent).tap do |g|
+ g.namespace_settings = build_stubbed(:namespace_settings, namespace: g)
+ end
+ other_project = build_stubbed(:project, namespace: other_group)
- expect(subject.available_scopes_for(other_group)).to match_array %i[api read_api read_repository write_repository read_registry write_registry create_runner k8s_proxy ai_features]
+ expect(subject.available_scopes_for(other_project)).to match_array %i[api read_api read_repository write_repository read_registry write_registry create_runner k8s_proxy]
end
end
end
diff --git a/spec/lib/gitlab/background_migration/backfill_finding_id_in_vulnerabilities_spec.rb b/spec/lib/gitlab/background_migration/backfill_finding_id_in_vulnerabilities_spec.rb
new file mode 100644
index 00000000000..3dbb1b34726
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_finding_id_in_vulnerabilities_spec.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+RSpec.describe Gitlab::BackgroundMigration::BackfillFindingIdInVulnerabilities, schema: 20230912105945, feature_category: :vulnerability_management do # rubocop:disable Layout/LineLength
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:users) { table(:users) }
+ let(:members) { table(:members) }
+ let(:vulnerability_identifiers) { table(:vulnerability_identifiers) }
+ let(:vulnerability_scanners) { table(:vulnerability_scanners) }
+ let(:vulnerability_findings) { table(:vulnerability_occurrences) }
+ let(:vulnerabilities) { table(:vulnerabilities) }
+ let!(:user) { create_user(email: "test1@example.com", username: "test1") }
+ let!(:namespace) { namespaces.create!(name: "test-1", path: "test-1", owner_id: user.id) }
+ let!(:project) do
+ projects.create!(
+ id: 9999, namespace_id: namespace.id,
+ project_namespace_id: namespace.id,
+ creator_id: user.id
+ )
+ end
+
+ let!(:membership) do
+ members.create!(access_level: 50, source_id: project.id, source_type: "Project", user_id: user.id, state: 0,
+ notification_level: 3, type: "ProjectMember", member_namespace_id: namespace.id)
+ end
+
+ let(:migration_attrs) do
+ {
+ start_id: vulnerabilities.first.id,
+ end_id: vulnerabilities.last.id,
+ batch_table: :vulnerabilities,
+ batch_column: :id,
+ sub_batch_size: 100,
+ pause_ms: 0,
+ connection: ApplicationRecord.connection
+ }
+ end
+
+ describe "#perform" do
+ subject(:background_migration) { described_class.new(**migration_attrs).perform }
+
+ # This scenario is what usually happens because we first create a Vulnerabilities::Finding record, then create
+ # a Vulnerability record and populate the Vulnerabilities::Finding#vulnerability_id
+ let(:vulnerabilities_finding_1) { create_finding(project, vulnerability_id: vulnerability_without_finding_id.id) }
+ let(:vulnerability_without_finding_id) { create_vulnerability }
+
+ # This scenario can occur because we have modified our Vulnerabilities ingestion pipeline to populate
+ # vulnerabilities.finding_id as soon as possible
+ let(:vulnerabilities_finding_2) { create_finding(project) }
+ let(:vulnerability_with_finding_id) { create_vulnerability(finding_id: vulnerabilities_finding_2.id) }
+
+ it 'backfills finding_id column in the vulnerabilities table' do
+ expect { background_migration }.to change { vulnerability_without_finding_id.reload.finding_id }
+ .from(nil).to(vulnerabilities_finding_1.id)
+ end
+
+ it 'does not affect rows with finding_id populated' do
+ expect { background_migration }.not_to change { vulnerability_with_finding_id.reload.finding_id }
+ end
+ end
+
+ private
+
+ def create_scanner(project, overrides = {})
+ attrs = {
+ project_id: project.id,
+ external_id: "test_vulnerability_scanner",
+ name: "Test Vulnerabilities::Scanner"
+ }.merge(overrides)
+
+ vulnerability_scanners.create!(attrs)
+ end
+
+ def create_identifier(project, overrides = {})
+ attrs = {
+ project_id: project.id,
+ external_id: "CVE-2018-1234",
+ external_type: "CVE",
+ name: "CVE-2018-1234",
+ fingerprint: SecureRandom.hex(20)
+ }.merge(overrides)
+
+ vulnerability_identifiers.create!(attrs)
+ end
+
+ def create_finding(project, overrides = {})
+ attrs = {
+ project_id: project.id,
+ scanner_id: create_scanner(project).id,
+ severity: 5, # medium
+ confidence: 2, # unknown,
+ report_type: 99, # generic
+ primary_identifier_id: create_identifier(project).id,
+ project_fingerprint: SecureRandom.hex(20),
+ location_fingerprint: SecureRandom.hex(20),
+ uuid: SecureRandom.uuid,
+ name: "CVE-2018-1234",
+ raw_metadata: "{}",
+ metadata_version: "test:1.0"
+ }.merge(overrides)
+
+ vulnerability_findings.create!(attrs)
+ end
+
+ def create_vulnerability(overrides = {})
+ attrs = {
+ project_id: project.id,
+ author_id: user.id,
+ title: 'test',
+ severity: 1,
+ confidence: 1,
+ report_type: 1,
+ state: 1,
+ detected_at: Time.zone.now
+ }.merge(overrides)
+
+ vulnerabilities.create!(attrs)
+ end
+
+ def create_user(overrides = {})
+ attrs = {
+ email: "test@example.com",
+ notification_email: "test@example.com",
+ name: "test",
+ username: "test",
+ state: "active",
+ projects_limit: 10
+ }.merge(overrides)
+
+ users.create!(attrs)
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_has_remediations_of_vulnerability_reads_spec.rb b/spec/lib/gitlab/background_migration/backfill_has_remediations_of_vulnerability_reads_spec.rb
new file mode 100644
index 00000000000..0e7a0210758
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_has_remediations_of_vulnerability_reads_spec.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillHasRemediationsOfVulnerabilityReads,
+ feature_category: :database do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:users) { table(:users) }
+ let(:scanners) { table(:vulnerability_scanners) }
+ let(:vulnerabilities) { table(:vulnerabilities) }
+ let(:vulnerability_reads) { table(:vulnerability_reads) }
+
+ let(:namespace) { namespaces.create!(name: 'user', path: 'user') }
+ let(:project) { projects.create!(namespace_id: namespace.id, project_namespace_id: namespace.id) }
+ let(:user) { users.create!(username: 'john_doe', email: 'johndoe@gitlab.com', projects_limit: 10) }
+ let(:scanner) { scanners.create!(project_id: project.id, external_id: 'external_id', name: 'Test Scanner') }
+
+ let(:vulnerability_1) { create_vulnerability(title: 'vulnerability 1') }
+ let(:vulnerability_2) { create_vulnerability(title: 'vulnerability 2') }
+
+ let!(:vulnerability_read_1) { create_vulnerability_read(vulnerability_id: vulnerability_1.id) }
+ let!(:vulnerability_read_2) { create_vulnerability_read(vulnerability_id: vulnerability_2.id) }
+
+ let(:vulnerability_findings) { table(:vulnerability_occurrences) }
+ let(:vulnerability_findings_remediations) { table(:vulnerability_findings_remediations) }
+ let(:vulnerability_remediations) { table(:vulnerability_remediations) }
+ let(:vulnerability_identifiers) { table(:vulnerability_identifiers) }
+
+ subject(:perform_migration) do
+ described_class.new(
+ start_id: vulnerability_reads.first.vulnerability_id,
+ end_id: vulnerability_reads.last.vulnerability_id,
+ batch_table: :vulnerability_reads,
+ batch_column: :vulnerability_id,
+ sub_batch_size: vulnerability_reads.count,
+ pause_ms: 0,
+ connection: ActiveRecord::Base.connection
+ ).perform
+ end
+
+ it 'updates vulnerability_reads records which has remediations' do
+ vuln_remediation = create_remediation
+ vuln_finding = create_finding(vulnerability_id: vulnerability_1.id)
+ vulnerability_findings_remediations.create!(
+ vulnerability_occurrence_id: vuln_finding.id,
+ vulnerability_remediation_id: vuln_remediation.id
+ )
+
+ expect { perform_migration }.to change { vulnerability_read_1.reload.has_remediations }.from(false).to(true)
+ .and not_change { vulnerability_read_2.reload.has_remediations }.from(false)
+ end
+
+ it 'does not modify has_remediations of vulnerabilities which do not have remediations' do
+ expect { perform_migration }.to not_change { vulnerability_read_1.reload.has_remediations }.from(false)
+ .and not_change { vulnerability_read_2.reload.has_remediations }.from(false)
+ end
+
+ private
+
+ def create_vulnerability(overrides = {})
+ attrs = {
+ project_id: project.id,
+ author_id: user.id,
+ title: 'test',
+ severity: 1,
+ confidence: 1,
+ report_type: 1
+ }.merge(overrides)
+
+ vulnerabilities.create!(attrs)
+ end
+
+ def create_vulnerability_read(overrides = {})
+ attrs = {
+ project_id: project.id,
+ vulnerability_id: 1,
+ scanner_id: scanner.id,
+ severity: 1,
+ report_type: 1,
+ state: 1,
+ uuid: SecureRandom.uuid
+ }.merge(overrides)
+
+ vulnerability_reads.create!(attrs)
+ end
+
+ def create_finding(overrides = {})
+ attrs = {
+ project_id: project.id,
+ scanner_id: scanner.id,
+ severity: 5, # medium
+ confidence: 2, # unknown,
+ report_type: 99, # generic
+ primary_identifier_id: create_identifier.id,
+ project_fingerprint: SecureRandom.hex(20),
+ location_fingerprint: SecureRandom.hex(20),
+ uuid: SecureRandom.uuid,
+ name: "CVE-2018-1234",
+ raw_metadata: "{}",
+ metadata_version: "test:1.0"
+ }.merge(overrides)
+
+ vulnerability_findings.create!(attrs)
+ end
+
+ def create_remediation(overrides = {})
+ remediation_hash = { summary: 'summary', diff: "ZGlmZiAtLWdp" }
+
+ attrs = {
+ project_id: project.id,
+ summary: remediation_hash[:summary],
+ checksum: checksum(remediation_hash[:diff]),
+ file: Tempfile.new.path
+ }.merge(overrides)
+
+ vulnerability_remediations.create!(attrs)
+ end
+
+ def create_identifier(overrides = {})
+ attrs = {
+ project_id: project.id,
+ external_id: "CVE-2018-1234",
+ external_type: "CVE",
+ name: "CVE-2018-1234",
+ fingerprint: SecureRandom.hex(20)
+ }.merge(overrides)
+
+ vulnerability_identifiers.create!(attrs)
+ end
+
+ def checksum(value)
+ sha = Digest::SHA256.hexdigest(value)
+ Gitlab::Database::ShaAttribute.new.serialize(sha)
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/delete_orphans_approval_merge_request_rules2_spec.rb b/spec/lib/gitlab/background_migration/delete_orphans_approval_merge_request_rules2_spec.rb
new file mode 100644
index 00000000000..81dd37f0fe9
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/delete_orphans_approval_merge_request_rules2_spec.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::DeleteOrphansApprovalMergeRequestRules2, feature_category: :security_policy_management do
+ describe '#perform' do
+ let(:batch_table) { :approval_merge_request_rules }
+ let(:batch_column) { :id }
+ let(:sub_batch_size) { 1 }
+ let(:pause_ms) { 0 }
+ let(:connection) { ApplicationRecord.connection }
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:approval_project_rules) { table(:approval_project_rules) }
+ let(:approval_merge_request_rules) { table(:approval_merge_request_rules) }
+ let(:approval_merge_request_rule_sources) { table(:approval_merge_request_rule_sources) }
+ let(:security_orchestration_policy_configurations) { table(:security_orchestration_policy_configurations) }
+ let(:namespace) { namespaces.create!(name: 'name', path: 'path') }
+ let(:project) do
+ projects
+ .create!(name: "project", path: "project", namespace_id: namespace.id, project_namespace_id: namespace.id)
+ end
+
+ let(:namespace_2) { namespaces.create!(name: 'name_2', path: 'path_2') }
+ let(:security_project) do
+ projects
+ .create!(name: "security_project", path: "security_project", namespace_id: namespace_2.id,
+ project_namespace_id: namespace_2.id)
+ end
+
+ let!(:security_orchestration_policy_configuration) do
+ security_orchestration_policy_configurations
+ .create!(project_id: project.id, security_policy_management_project_id: security_project.id)
+ end
+
+ let(:merge_request) do
+ table(:merge_requests).create!(target_project_id: project.id, target_branch: 'main', source_branch: 'feature')
+ end
+
+ let!(:approval_rule) do
+ approval_merge_request_rules.create!(
+ name: 'rule',
+ merge_request_id: merge_request.id,
+ report_type: 4,
+ security_orchestration_policy_configuration_id: security_orchestration_policy_configuration.id)
+ end
+
+ let!(:approval_rule_other_report_type) do
+ approval_merge_request_rules.create!(
+ name: 'rule 2',
+ merge_request_id: merge_request.id,
+ report_type: 1,
+ security_orchestration_policy_configuration_id: security_orchestration_policy_configuration.id)
+ end
+
+ let!(:approval_rule_license_scanning) do
+ approval_merge_request_rules.create!(
+ name: 'rule 4',
+ merge_request_id: merge_request.id,
+ report_type: 2,
+ security_orchestration_policy_configuration_id: security_orchestration_policy_configuration.id)
+ end
+
+ let!(:approval_rule_license_scanning_without_policy_id) do
+ approval_merge_request_rules.create!(name: 'rule 5', merge_request_id: merge_request.id, report_type: 2)
+ end
+
+ let!(:approval_rule_last) do
+ approval_merge_request_rules.create!(name: 'rule 3', merge_request_id: merge_request.id, report_type: 4)
+ end
+
+ subject do
+ described_class.new(
+ start_id: approval_rule.id,
+ end_id: approval_rule_last.id,
+ batch_table: batch_table,
+ batch_column: batch_column,
+ sub_batch_size: sub_batch_size,
+ pause_ms: pause_ms,
+ connection: connection
+ ).perform
+ end
+
+ it 'delete only approval rules without association with the security project and report_type equals to 4 or 2' do
+ expect { subject }.to change { approval_merge_request_rules.all }.to(
+ contain_exactly(approval_rule,
+ approval_rule_other_report_type,
+ approval_rule_license_scanning))
+ end
+
+ context 'with rule sources' do # rubocop: disable RSpec/MultipleMemoizedHelpers
+ let(:project_approval_rule_1) { approval_project_rules.create!(project_id: project.id, name: '1') }
+ let(:project_approval_rule_2) { approval_project_rules.create!(project_id: project.id, name: '2') }
+
+ let!(:rule_source_1) do
+ approval_merge_request_rule_sources.create!(
+ approval_merge_request_rule_id: approval_rule_license_scanning_without_policy_id.id,
+ approval_project_rule_id: project_approval_rule_1.id)
+ end
+
+ let!(:rule_source_2) do
+ approval_merge_request_rule_sources.create!(
+ approval_merge_request_rule_id: approval_rule_other_report_type.id,
+ approval_project_rule_id: project_approval_rule_2.id)
+ end
+
+ it 'deletes referenced sources' do
+ # rubocop: disable CodeReuse/ActiveRecord
+ expect { subject }.to change { approval_merge_request_rule_sources.exists?(rule_source_1.id) }.to(false)
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+
+ it 'does not delete unreferenced sources' do
+ # rubocop: disable CodeReuse/ActiveRecord
+ expect { subject }.not_to change { approval_merge_request_rule_sources.exists?(rule_source_2.id) }
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/delete_orphans_approval_project_rules2_spec.rb b/spec/lib/gitlab/background_migration/delete_orphans_approval_project_rules2_spec.rb
new file mode 100644
index 00000000000..c6563efe173
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/delete_orphans_approval_project_rules2_spec.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::DeleteOrphansApprovalProjectRules2, feature_category: :security_policy_management do
+ describe '#perform' do
+ let(:batch_table) { :approval_project_rules }
+ let(:batch_column) { :id }
+ let(:sub_batch_size) { 1 }
+ let(:pause_ms) { 0 }
+ let(:connection) { ApplicationRecord.connection }
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:approval_project_rules) { table(:approval_project_rules) }
+ let(:approval_merge_request_rules) { table(:approval_merge_request_rules) }
+ let(:approval_merge_request_rule_sources) { table(:approval_merge_request_rule_sources) }
+ let(:security_orchestration_policy_configurations) { table(:security_orchestration_policy_configurations) }
+ let(:namespace) { namespaces.create!(name: 'name', path: 'path') }
+ let(:project) do
+ projects
+ .create!(name: "project", path: "project", namespace_id: namespace.id, project_namespace_id: namespace.id)
+ end
+
+ let(:merge_request) do
+ table(:merge_requests).create!(target_project_id: project.id, target_branch: 'main', source_branch: 'feature')
+ end
+
+ let(:namespace_2) { namespaces.create!(name: 'name_2', path: 'path_2') }
+ let(:security_project) do
+ projects
+ .create!(name: "security_project", path: "security_project", namespace_id: namespace_2.id,
+ project_namespace_id: namespace_2.id)
+ end
+
+ let!(:security_orchestration_policy_configuration) do
+ security_orchestration_policy_configurations
+ .create!(project_id: project.id, security_policy_management_project_id: security_project.id)
+ end
+
+ let!(:project_rule) do
+ approval_project_rules.create!(
+ name: 'rule',
+ project_id: project.id,
+ report_type: 4,
+ security_orchestration_policy_configuration_id: security_orchestration_policy_configuration.id)
+ end
+
+ let!(:project_rule_other_report_type) do
+ approval_project_rules.create!(
+ name: 'rule 2',
+ project_id: project.id,
+ report_type: 1,
+ security_orchestration_policy_configuration_id: security_orchestration_policy_configuration.id)
+ end
+
+ let!(:project_rule_license_scanning) do
+ approval_project_rules.create!(
+ name: 'rule 4',
+ project_id: project.id,
+ report_type: 2,
+ security_orchestration_policy_configuration_id: security_orchestration_policy_configuration.id)
+ end
+
+ let!(:project_rule_license_scanning_without_policy_id) do
+ approval_project_rules.create!(name: 'rule 5', project_id: project.id, report_type: 2)
+ end
+
+ let!(:project_rule_last) do
+ approval_project_rules.create!(name: 'rule 3', project_id: project.id, report_type: 4)
+ end
+
+ subject do
+ described_class.new(
+ start_id: project_rule.id,
+ end_id: project_rule_last.id,
+ batch_table: batch_table,
+ batch_column: batch_column,
+ sub_batch_size: sub_batch_size,
+ pause_ms: pause_ms,
+ connection: connection
+ ).perform
+ end
+
+ it 'delete only approval rules without association with the security project and report_type equals to 4' do
+ expect { subject }.to change { approval_project_rules.all }.to(
+ contain_exactly(project_rule,
+ project_rule_other_report_type,
+ project_rule_license_scanning))
+ end
+
+ context 'with rule sources' do # rubocop: disable RSpec/MultipleMemoizedHelpers
+ let(:approval_merge_request_rule_1) do
+ approval_merge_request_rules.create!(merge_request_id: merge_request.id, name: '1')
+ end
+
+ let(:approval_merge_request_rule_2) do
+ approval_merge_request_rules.create!(merge_request_id: merge_request.id, name: '2')
+ end
+
+ let!(:rule_source_1) do
+ approval_merge_request_rule_sources.create!(
+ approval_merge_request_rule_id: approval_merge_request_rule_1.id,
+ approval_project_rule_id: project_rule_license_scanning_without_policy_id.id)
+ end
+
+ let!(:rule_source_2) do
+ approval_merge_request_rule_sources.create!(
+ approval_merge_request_rule_id: approval_merge_request_rule_2.id,
+ approval_project_rule_id: project_rule_other_report_type.id)
+ end
+
+ it 'deletes referenced sources' do
+ expect { subject }.to change { approval_merge_request_rule_sources.exists?(rule_source_1.id) }.to(false)
+ end
+
+ it 'does not delete unreferenced sources' do
+ expect { subject }.not_to change { approval_merge_request_rule_sources.exists?(rule_source_2.id) }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 9786e7a364e..517d557d665 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -112,6 +112,7 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea
end
let(:pull_request_author) { 'other' }
+ let(:comments) { [@inline_note, @reply] }
let(:author_line) { "*Created by: someuser*\n\n" }
@@ -145,8 +146,6 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea
has_parent?: true,
parent_id: 2)
- comments = [@inline_note, @reply]
-
allow(subject.client).to receive(:repo)
allow(subject.client).to receive(:pull_requests).and_return([pull_request])
allow(subject.client).to receive(:pull_request_comments).with(anything, pull_request.iid).and_return(comments)
@@ -202,6 +201,12 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea
end
end
+ it 'calls RefConverter to convert Bitbucket refs to Gitlab refs' do
+ expect(subject.instance_values['ref_converter']).to receive(:convert_note).twice
+
+ subject.execute
+ end
+
context 'when importing a pull request throws an exception' do
before do
allow(pull_request).to receive(:raw).and_return({ error: "broken" })
@@ -384,6 +389,12 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea
expect(label_after_import.attributes).to eq(existing_label.attributes)
end
end
+
+ it 'raises an error if a label is not valid' do
+ stub_const("#{described_class}::LABELS", [{ title: nil, color: nil }])
+
+ expect { importer.create_labels }.to raise_error(StandardError, /Failed to create label/)
+ end
end
it 'maps statuses to open or closed' do
@@ -444,26 +455,33 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea
end
context 'with issue comments' do
+ let(:note) { 'Hello world' }
let(:inline_note) do
- instance_double(Bitbucket::Representation::Comment, note: 'Hello world', author: 'someuser', created_at: Time.now, updated_at: Time.now)
+ instance_double(Bitbucket::Representation::Comment, note: note, author: 'someuser', created_at: Time.now, updated_at: Time.now)
end
before do
allow_next_instance_of(Bitbucket::Client) do |instance|
allow(instance).to receive(:issue_comments).and_return([inline_note])
end
+ allow(importer).to receive(:import_wiki)
end
it 'imports issue comments' do
- allow(importer).to receive(:import_wiki)
importer.execute
comment = project.notes.first
expect(project.notes.size).to eq(7)
- expect(comment.note).to include(inline_note.note)
+ expect(comment.note).to include(note)
expect(comment.note).to include(inline_note.author)
expect(importer.errors).to be_empty
end
+
+ it 'calls RefConverter to convert Bitbucket refs to Gitlab refs' do
+ expect(importer.instance_values['ref_converter']).to receive(:convert_note).exactly(7).times
+
+ importer.execute
+ end
end
context 'when issue was already imported' do
diff --git a/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb
new file mode 100644
index 00000000000..8f79390d2d9
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/importers/issue_importer_spec.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BitbucketImport::Importers::IssueImporter, :clean_gitlab_redis_cache, feature_category: :importers do
+ include AfterNextHelpers
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:bitbucket_user) { create(:user) }
+ let_it_be(:identity) { create(:identity, user: bitbucket_user, extern_uid: 'bitbucket_user', provider: :bitbucket) }
+ let_it_be(:default_work_item_type) { create(:work_item_type) }
+ let_it_be(:label) { create(:label, project: project) }
+
+ let(:hash) do
+ {
+ iid: 111,
+ title: 'title',
+ description: 'description',
+ state: 'closed',
+ author: 'bitbucket_user',
+ milestone: 'my milestone',
+ issue_type_id: default_work_item_type.id,
+ label_id: label.id,
+ created_at: Date.today,
+ updated_at: Date.today
+ }
+ end
+
+ subject(:importer) { described_class.new(project, hash) }
+
+ before do
+ allow(Gitlab::Git).to receive(:ref_name).and_return('refname')
+ end
+
+ describe '#execute' do
+ it 'creates an issue' do
+ expect { importer.execute }.to change { project.issues.count }.from(0).to(1)
+
+ issue = project.issues.first
+
+ expect(issue.description).to eq('description')
+ expect(issue.author).to eq(bitbucket_user)
+ expect(issue.closed?).to be_truthy
+ expect(issue.milestone).to eq(project.milestones.first)
+ expect(issue.work_item_type).to eq(default_work_item_type)
+ expect(issue.labels).to eq([label])
+ expect(issue.created_at).to eq(Date.today)
+ expect(issue.updated_at).to eq(Date.today)
+ end
+
+ context 'when the author does not have a bitbucket identity' do
+ before do
+ identity.update!(provider: :github)
+ end
+
+ it 'sets the author to the project creator and adds the author to the description' do
+ importer.execute
+
+ issue = project.issues.first
+
+ expect(issue.author).to eq(project.creator)
+ expect(issue.description).to eq("*Created by: bitbucket_user*\n\ndescription")
+ end
+ end
+
+ context 'when a milestone with the same title exists' do
+ let_it_be(:milestone) { create(:milestone, project: project, title: 'my milestone') }
+
+ it 'assigns the milestone and does not create a new milestone' do
+ expect { importer.execute }.not_to change { project.milestones.count }
+
+ expect(project.issues.first.milestone).to eq(milestone)
+ end
+ end
+
+ context 'when a milestone with the same title does not exist' do
+ it 'creates a new milestone and assigns it' do
+ expect { importer.execute }.to change { project.milestones.count }.from(0).to(1)
+
+ expect(project.issues.first.milestone).to eq(project.milestones.first)
+ end
+ end
+
+ context 'when an error is raised' do
+ it 'tracks the failure and does not fail' do
+ expect(Gitlab::Import::ImportFailureService).to receive(:track).once
+
+ described_class.new(project, hash.except(:title)).execute
+ end
+ end
+
+ it 'logs its progress' do
+ allow(Gitlab::Import::MergeRequestCreator).to receive_message_chain(:new, :execute)
+
+ expect(Gitlab::BitbucketImport::Logger)
+ .to receive(:info).with(include(message: 'starting', iid: anything)).and_call_original
+ expect(Gitlab::BitbucketImport::Logger)
+ .to receive(:info).with(include(message: 'finished', iid: anything)).and_call_original
+
+ importer.execute
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importers/issue_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issue_notes_importer_spec.rb
new file mode 100644
index 00000000000..1a2a43d6877
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/importers/issue_notes_importer_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BitbucketImport::Importers::IssueNotesImporter, :clean_gitlab_redis_cache, feature_category: :importers do
+ let_it_be(:project) do
+ create(:project, :import_started, import_source: 'namespace/repo',
+ import_data_attributes: {
+ credentials: { 'base_uri' => 'http://bitbucket.org/', 'user' => 'bitbucket', 'password' => 'password' }
+ }
+ )
+ end
+
+ let_it_be(:bitbucket_user) { create(:user) }
+ let_it_be(:identity) { create(:identity, user: bitbucket_user, extern_uid: 'bitbucket_user', provider: :bitbucket) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let(:hash) { { iid: issue.iid } }
+ let(:note_body) { 'body' }
+ let(:client) { Bitbucket::Client.new({}) }
+
+ subject(:importer) { described_class.new(project, hash) }
+
+ describe '#execute' do
+ let(:issue_comments_response) do
+ [
+ Bitbucket::Representation::Comment.new({
+ 'user' => { 'nickname' => 'bitbucket_user' },
+ 'content' => { 'raw' => note_body },
+ 'created_on' => Date.today,
+ 'updated_on' => Date.today
+ })
+ ]
+ end
+
+ before do
+ allow(Bitbucket::Client).to receive(:new).and_return(client)
+ allow(client).to receive(:issue_comments).and_return(issue_comments_response)
+ end
+
+ it 'creates a new note with the correct attributes' do
+ expect { importer.execute }.to change { issue.notes.count }.from(0).to(1)
+
+ note = issue.notes.first
+
+ expect(note.project).to eq(project)
+ expect(note.note).to eq(note_body)
+ expect(note.author).to eq(bitbucket_user)
+ expect(note.created_at).to eq(Date.today)
+ expect(note.updated_at).to eq(Date.today)
+ end
+
+ context 'when the author does not have a bitbucket identity' do
+ before do
+ identity.update!(provider: :github)
+ end
+
+ it 'sets the author to the project creator and adds the author to the note' do
+ importer.execute
+
+ note = issue.notes.first
+
+ expect(note.author).to eq(project.creator)
+ expect(note.note).to eq("*Created by: bitbucket_user*\n\nbody")
+ end
+ end
+
+ it 'calls RefConverter to convert Bitbucket refs to Gitlab refs' do
+ expect(importer.instance_values['ref_converter']).to receive(:convert_note).once
+
+ importer.execute
+ end
+
+ context 'when an error is raised' do
+ before do
+ allow(client).to receive(:issue_comments).and_raise(StandardError)
+ end
+
+ it 'tracks the failure and does not fail' do
+ expect(Gitlab::Import::ImportFailureService).to receive(:track).once
+
+ importer.execute
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb
new file mode 100644
index 00000000000..a361a9343dd
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/importers/issues_importer_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BitbucketImport::Importers::IssuesImporter, feature_category: :importers do
+ let_it_be(:project) do
+ create(:project, :import_started,
+ import_data_attributes: {
+ data: { 'project_key' => 'key', 'repo_slug' => 'slug' },
+ credentials: { 'base_uri' => 'http://bitbucket.org/', 'user' => 'bitbucket', 'password' => 'password' }
+ }
+ )
+ end
+
+ subject(:importer) { described_class.new(project) }
+
+ describe '#execute', :clean_gitlab_redis_cache do
+ before do
+ allow_next_instance_of(Bitbucket::Client) do |client|
+ allow(client).to receive(:issues).and_return(
+ [
+ Bitbucket::Representation::Issue.new({ 'id' => 1 }),
+ Bitbucket::Representation::Issue.new({ 'id' => 2 })
+ ],
+ []
+ )
+ end
+ end
+
+ it 'imports each issue in parallel', :aggregate_failures do
+ expect(Gitlab::BitbucketImport::ImportIssueWorker).to receive(:perform_in).twice
+
+ waiter = importer.execute
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(2)
+ expect(Gitlab::Cache::Import::Caching.values_from_set(importer.already_enqueued_cache_key))
+ .to match_array(%w[1 2])
+ end
+
+ context 'when the client raises an error' do
+ before do
+ allow_next_instance_of(Bitbucket::Client) do |client|
+ allow(client).to receive(:issues).and_raise(StandardError)
+ end
+ end
+
+ it 'tracks the failure and does not fail' do
+ expect(Gitlab::Import::ImportFailureService).to receive(:track).once
+
+ importer.execute
+ end
+ end
+
+ context 'when issue was already enqueued' do
+ before do
+ Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 1)
+ end
+
+ it 'does not schedule job for enqueued issues', :aggregate_failures do
+ expect(Gitlab::BitbucketImport::ImportIssueWorker).to receive(:perform_in).once
+
+ waiter = importer.execute
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb
new file mode 100644
index 00000000000..043cd7f17b9
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/importers/issues_notes_importer_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BitbucketImport::Importers::IssuesNotesImporter, feature_category: :importers do
+ let_it_be(:project) { create(:project, :import_started) }
+ # let_it_be(:merge_request_1) { create(:merge_request, source_project: project) }
+ # let_it_be(:merge_request_2) { create(:merge_request, source_project: project, source_branch: 'other-branch') }
+ let_it_be(:issue_1) { create(:issue, project: project) }
+ let_it_be(:issue_2) { create(:issue, project: project) }
+
+ subject(:importer) { described_class.new(project) }
+
+ describe '#execute', :clean_gitlab_redis_cache do
+ it 'imports the notes from each issue in parallel', :aggregate_failures do
+ expect(Gitlab::BitbucketImport::ImportIssueNotesWorker).to receive(:perform_in).twice
+
+ waiter = importer.execute
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(2)
+ expect(Gitlab::Cache::Import::Caching.values_from_set(importer.already_enqueued_cache_key))
+ .to match_array(%w[1 2])
+ end
+
+ context 'when an error is raised' do
+ before do
+ allow(importer).to receive(:mark_as_enqueued).and_raise(StandardError)
+ end
+
+ it 'tracks the failure and does not fail' do
+ expect(Gitlab::Import::ImportFailureService).to receive(:track).once
+
+ importer.execute
+ end
+ end
+
+ context 'when issue was already enqueued' do
+ before do
+ Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 2)
+ end
+
+ it 'does not schedule job for enqueued issues', :aggregate_failures do
+ expect(Gitlab::BitbucketImport::ImportIssueNotesWorker).to receive(:perform_in).once
+
+ waiter = importer.execute
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importers/lfs_object_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/lfs_object_importer_spec.rb
new file mode 100644
index 00000000000..4d56853032a
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/importers/lfs_object_importer_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BitbucketImport::Importers::LfsObjectImporter, feature_category: :importers do
+ let_it_be(:project) { create(:project) }
+ let(:oid) { 'a' * 64 }
+
+ let(:lfs_attributes) do
+ {
+ 'oid' => oid,
+ 'size' => 1,
+ 'link' => 'http://www.gitlab.com/lfs_objects/oid',
+ 'headers' => { 'X-Some-Header' => '456' }
+ }
+ end
+
+ let(:importer) { described_class.new(project, lfs_attributes) }
+
+ describe '#execute' do
+ it 'calls the LfsDownloadService with the lfs object attributes' do
+ expect_next_instance_of(
+ Projects::LfsPointers::LfsDownloadService, project, have_attributes(lfs_attributes)
+ ) do |service|
+ expect(service).to receive(:execute).and_return(ServiceResponse.success)
+ end
+
+ importer.execute
+ end
+
+ context 'when the object is not valid' do
+ let(:oid) { 'invalid' }
+
+ it 'tracks the validation errors and does not continue' do
+ expect(Gitlab::Import::ImportFailureService).to receive(:track).once
+
+ expect(Projects::LfsPointers::LfsDownloadService).not_to receive(:new)
+
+ importer.execute
+ end
+ end
+
+ context 'when an error is raised' do
+ let(:exception) { StandardError.new('messsage') }
+
+ before do
+ allow_next_instance_of(Projects::LfsPointers::LfsDownloadService) do |service|
+ allow(service).to receive(:execute).and_raise(exception)
+ end
+ end
+
+ it 'rescues and logs the exception' do
+ expect(Gitlab::Import::ImportFailureService)
+ .to receive(:track)
+ .with(hash_including(exception: exception))
+
+ importer.execute
+ end
+ end
+
+ it 'logs its progress' do
+ allow_next_instance_of(Projects::LfsPointers::LfsDownloadService) do |service|
+ allow(service).to receive(:execute).and_return(ServiceResponse.success)
+ end
+
+ common_log_message = {
+ oid: oid,
+ import_stage: 'import_lfs_object',
+ class: described_class.name,
+ project_id: project.id,
+ project_path: project.full_path
+ }
+
+ expect(Gitlab::BitbucketImport::Logger)
+ .to receive(:info).with(common_log_message.merge(message: 'starting')).and_call_original
+ expect(Gitlab::BitbucketImport::Logger)
+ .to receive(:info).with(common_log_message.merge(message: 'finished')).and_call_original
+
+ importer.execute
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importers/lfs_objects_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/lfs_objects_importer_spec.rb
new file mode 100644
index 00000000000..fbce8337264
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/importers/lfs_objects_importer_spec.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BitbucketImport::Importers::LfsObjectsImporter, feature_category: :importers do
+ let_it_be(:project) do
+ create(:project, :import_started,
+ import_data_attributes: {
+ data: { 'project_key' => 'key', 'repo_slug' => 'slug' },
+ credentials: { 'token' => 'token' }
+ }
+ )
+ end
+
+ let(:lfs_attributes) do
+ {
+ oid: 'a' * 64,
+ size: 1,
+ link: 'http://www.gitlab.com/lfs_objects/oid',
+ headers: { 'X-Some-Header' => '456' }
+ }
+ end
+
+ let(:lfs_download_object) { LfsDownloadObject.new(**lfs_attributes) }
+
+ let(:common_log_messages) do
+ {
+ import_stage: 'import_lfs_objects',
+ class: described_class.name,
+ project_id: project.id,
+ project_path: project.full_path
+ }
+ end
+
+ describe '#execute', :clean_gitlab_redis_cache do
+ context 'when lfs is enabled' do
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
+ end
+
+ it 'imports each lfs object in parallel' do
+ importer = described_class.new(project)
+
+ expect_next_instance_of(Projects::LfsPointers::LfsObjectDownloadListService) do |service|
+ expect(service).to receive(:each_list_item).and_yield(lfs_download_object)
+ end
+
+ expect(Gitlab::BitbucketImport::ImportLfsObjectWorker).to receive(:perform_in)
+ .with(1.second, project.id, lfs_attributes.stringify_keys, start_with(Gitlab::JobWaiter::KEY_PREFIX))
+
+ waiter = importer.execute
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(1)
+ end
+
+ it 'logs its progress' do
+ importer = described_class.new(project)
+
+ expect(Gitlab::BitbucketImport::Logger)
+ .to receive(:info).with(common_log_messages.merge(message: 'starting')).and_call_original
+ expect(Gitlab::BitbucketImport::Logger)
+ .to receive(:info).with(common_log_messages.merge(message: 'finished')).and_call_original
+
+ importer.execute
+ end
+
+ context 'when LFS list download fails' do
+ let(:exception) { StandardError.new('Invalid Project URL') }
+
+ before do
+ allow_next_instance_of(Projects::LfsPointers::LfsObjectDownloadListService) do |service|
+ allow(service).to receive(:each_list_item).and_raise(exception)
+ end
+ end
+
+ it 'rescues and logs the exception' do
+ importer = described_class.new(project)
+
+ expect(Gitlab::Import::ImportFailureService)
+ .to receive(:track)
+ .with(
+ project_id: project.id,
+ exception: exception,
+ error_source: described_class.name
+ ).and_call_original
+
+ expect(Gitlab::BitbucketImport::ImportLfsObjectWorker).not_to receive(:perform_in)
+
+ waiter = importer.execute
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(0)
+ end
+ end
+ end
+
+ context 'when LFS is not enabled' do
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(false)
+ end
+
+ it 'logs progress but does nothing' do
+ importer = described_class.new(project)
+
+ expect(Gitlab::BitbucketImport::Logger).to receive(:info).twice
+ expect(Gitlab::BitbucketImport::ImportLfsObjectWorker).not_to receive(:perform_in)
+
+ waiter = importer.execute
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(0)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_request_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_request_notes_importer_spec.rb
new file mode 100644
index 00000000000..4a30f225d66
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/importers/pull_request_notes_importer_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestNotesImporter, feature_category: :importers do
+ let_it_be(:project) do
+ create(:project, :import_started,
+ import_data_attributes: {
+ credentials: { 'base_uri' => 'http://bitbucket.org/', 'user' => 'bitbucket', 'password' => 'password' }
+ }
+ )
+ end
+
+ let_it_be(:merge_request) { create(:merge_request, source_project: project) }
+
+ let(:hash) { { iid: merge_request.iid } }
+ let(:importer_helper) { Gitlab::BitbucketImport::Importer.new(project) }
+
+ subject(:importer) { described_class.new(project, hash) }
+
+ before do
+ allow(Gitlab::BitbucketImport::Importer).to receive(:new).and_return(importer_helper)
+ end
+
+ describe '#execute' do
+ it 'calls Importer.import_pull_request_comments' do
+ expect(importer_helper).to receive(:import_pull_request_comments).once
+
+ importer.execute
+ end
+
+ context 'when the merge request does not exist' do
+ let(:hash) { { iid: 'nonexistent' } }
+
+ it 'does not call Importer.import_pull_request_comments' do
+ expect(importer_helper).not_to receive(:import_pull_request_comments)
+
+ importer.execute
+ end
+ end
+
+ context 'when the merge request exists but not for this project' do
+ let_it_be(:another_project) { create(:project) }
+
+ before do
+ merge_request.update!(source_project: another_project, target_project: another_project)
+ end
+
+ it 'does not call Importer.import_pull_request_comments' do
+ expect(importer_helper).not_to receive(:import_pull_request_comments)
+
+ importer.execute
+ end
+ end
+
+ context 'when an error is raised' do
+ before do
+ allow(importer_helper).to receive(:import_pull_request_comments).and_raise(StandardError)
+ end
+
+ it 'tracks the failure and does not fail' do
+ expect(Gitlab::Import::ImportFailureService).to receive(:track).once
+
+ importer.execute
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb
new file mode 100644
index 00000000000..c44fc259c3b
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/importers/pull_requests_notes_importer_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestsNotesImporter, feature_category: :importers do
+ let_it_be(:project) { create(:project, :import_started) }
+ let_it_be(:merge_request_1) { create(:merge_request, source_project: project) }
+ let_it_be(:merge_request_2) { create(:merge_request, source_project: project, source_branch: 'other-branch') }
+
+ subject(:importer) { described_class.new(project) }
+
+ describe '#execute', :clean_gitlab_redis_cache do
+ it 'imports the notes from each merge request in parallel', :aggregate_failures do
+ expect(Gitlab::BitbucketImport::ImportPullRequestNotesWorker).to receive(:perform_in).twice
+
+ waiter = importer.execute
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(2)
+ expect(Gitlab::Cache::Import::Caching.values_from_set(importer.already_enqueued_cache_key))
+ .to match_array(%w[1 2])
+ end
+
+ context 'when an error is raised' do
+ before do
+ allow(importer).to receive(:mark_as_enqueued).and_raise(StandardError)
+ end
+
+ it 'tracks the failure and does not fail' do
+ expect(Gitlab::Import::ImportFailureService).to receive(:track).once
+
+ importer.execute
+ end
+ end
+
+ context 'when merge request was already enqueued' do
+ before do
+ Gitlab::Cache::Import::Caching.set_add(importer.already_enqueued_cache_key, 2)
+ end
+
+ it 'does not schedule job for enqueued merge requests', :aggregate_failures do
+ expect(Gitlab::BitbucketImport::ImportPullRequestNotesWorker).to receive(:perform_in).once
+
+ waiter = importer.execute
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/ref_converter_spec.rb b/spec/lib/gitlab/bitbucket_import/ref_converter_spec.rb
new file mode 100644
index 00000000000..578b661d86b
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/ref_converter_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BitbucketImport::RefConverter, feature_category: :importers do
+ let_it_be(:project_identifier) { 'namespace/repo' }
+ let_it_be(:project) { create(:project, import_source: project_identifier) }
+ let(:path) { project.full_path }
+
+ let(:ref_converter) { described_class.new(project) }
+
+ shared_examples 'converts the ref correctly' do
+ it 'converts the ref to a gitlab reference' do
+ actual = ref_converter.convert_note(note)
+
+ expect(actual).to eq(expected)
+ end
+ end
+
+ context 'when the note has an issue ref' do
+ let(:note) { "[https://bitbucket.org/namespace/repo/issues/1/first-issue](https://bitbucket.org/namespace/repo/issues/1/first-issue){: data-inline-card='' } " }
+ let(:expected) { "[http://localhost/#{path}/-/issues/1/](http://localhost/#{path}/-/issues/1/)" }
+
+ it_behaves_like 'converts the ref correctly'
+ end
+
+ context 'when the note has a pull request ref' do
+ let(:note) { "[https://bitbucket.org/namespace/repo/pull-requests/7](https://bitbucket.org/namespace/repo/pull-requests/7){: data-inline-card='' } " }
+ let(:expected) { "[http://localhost/#{path}/-/merge_requests/7](http://localhost/#{path}/-/merge_requests/7)" }
+
+ it_behaves_like 'converts the ref correctly'
+ end
+
+ context 'when the note has a reference to a branch' do
+ let(:note) { "[https://bitbucket.org/namespace/repo/src/master/](https://bitbucket.org/namespace/repo/src/master/){: data-inline-card='' } " }
+ let(:expected) { "[http://localhost/#{path}/-/blob/master/](http://localhost/#{path}/-/blob/master/)" }
+
+ it_behaves_like 'converts the ref correctly'
+ end
+
+ context 'when the note has a reference to a line in a file' do
+ let(:note) do
+ "[https://bitbucket.org/namespace/repo/src/0f16a22c21671421780980c9a7433eb8c986b9af/.gitignore#lines-6] \
+ (https://bitbucket.org/namespace/repo/src/0f16a22c21671421780980c9a7433eb8c986b9af/.gitignore#lines-6) \
+ {: data-inline-card='' }"
+ end
+
+ let(:expected) do
+ "[http://localhost/#{path}/-/blob/0f16a22c21671421780980c9a7433eb8c986b9af/.gitignore#L6] \
+ (http://localhost/#{path}/-/blob/0f16a22c21671421780980c9a7433eb8c986b9af/.gitignore#L6)"
+ end
+
+ it_behaves_like 'converts the ref correctly'
+ end
+
+ context 'when the note has a reference to a file' do
+ let(:note) { "[https://bitbucket.org/namespace/repo/src/master/.gitignore](https://bitbucket.org/namespace/repo/src/master/.gitignore){: data-inline-card='' } " }
+ let(:expected) { "[http://localhost/#{path}/-/blob/master/.gitignore](http://localhost/#{path}/-/blob/master/.gitignore)" }
+
+ it_behaves_like 'converts the ref correctly'
+ end
+
+ context 'when the note does not have a ref' do
+ let(:note) { 'Hello world' }
+ let(:expected) { 'Hello world' }
+
+ it_behaves_like 'converts the ref correctly'
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_server_import/importers/pull_request_importer_spec.rb b/spec/lib/gitlab/bitbucket_server_import/importers/pull_request_importer_spec.rb
index 3c84d888c92..1ae68f9efb8 100644
--- a/spec/lib/gitlab/bitbucket_server_import/importers/pull_request_importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_server_import/importers/pull_request_importer_spec.rb
@@ -48,6 +48,68 @@ RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestImporter, fe
end
end
+ describe 'merge request diff head_commit_sha' do
+ before do
+ allow(pull_request).to receive(:source_branch_sha).and_return(source_branch_sha)
+ end
+
+ context 'when a commit with the source_branch_sha exists' do
+ let(:source_branch_sha) { project.repository.head_commit.sha }
+
+ it 'is equal to the source_branch_sha' do
+ importer.execute
+
+ merge_request = project.merge_requests.find_by_iid(pull_request.iid)
+
+ expect(merge_request.merge_request_diffs.first.head_commit_sha).to eq(source_branch_sha)
+ end
+ end
+
+ context 'when a commit with the source_branch_sha does not exist' do
+ let(:source_branch_sha) { 'x' * Commit::MIN_SHA_LENGTH }
+
+ it 'is nil' do
+ importer.execute
+
+ merge_request = project.merge_requests.find_by_iid(pull_request.iid)
+
+ expect(merge_request.merge_request_diffs.first.head_commit_sha).to be_nil
+ end
+
+ context 'when a commit containing the sha in the message exists' do
+ let(:source_branch_sha) { project.repository.head_commit.sha }
+
+ it 'is equal to the sha' do
+ message = "
+ Squashed commit of the following:
+
+ commit #{source_branch_sha}
+ Author: John Smith <john@smith.com>
+ Date: Mon Sep 18 15:58:38 2023 +0200
+
+ My commit message
+ "
+
+ Files::CreateService.new(
+ project,
+ project.creator,
+ start_branch: 'master',
+ branch_name: 'master',
+ commit_message: message,
+ file_path: 'files/lfs/ruby.rb',
+ file_content: 'testing'
+ ).execute
+
+ importer.execute
+
+ merge_request = project.merge_requests.find_by_iid(pull_request.iid)
+
+ expect(merge_request.merge_request_diffs.first.head_commit_sha).to eq(source_branch_sha)
+ end
+ end
+ end
+ end
+
it 'logs its progress' do
expect(Gitlab::BitbucketServerImport::Logger)
.to receive(:info).with(include(message: 'starting', iid: pull_request.iid)).and_call_original
diff --git a/spec/lib/gitlab/bitbucket_server_import/importers/pull_requests_importer_spec.rb b/spec/lib/gitlab/bitbucket_server_import/importers/pull_requests_importer_spec.rb
index b9a9c8dac29..af8a0202083 100644
--- a/spec/lib/gitlab/bitbucket_server_import/importers/pull_requests_importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_server_import/importers/pull_requests_importer_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestsImporter, feature_category: :importers do
let_it_be(:project) do
- create(:project, :import_started,
+ create(:project, :with_import_url, :import_started, :empty_repo,
import_data_attributes: {
data: { 'project_key' => 'key', 'repo_slug' => 'slug' },
credentials: { 'base_uri' => 'http://bitbucket.org/', 'user' => 'bitbucket', 'password' => 'password' }
@@ -19,8 +19,30 @@ RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestsImporter, f
allow_next_instance_of(BitbucketServer::Client) do |client|
allow(client).to receive(:pull_requests).and_return(
[
- BitbucketServer::Representation::PullRequest.new({ 'id' => 1 }),
- BitbucketServer::Representation::PullRequest.new({ 'id' => 2 })
+ BitbucketServer::Representation::PullRequest.new(
+ {
+ 'id' => 1,
+ 'state' => 'MERGED',
+ 'fromRef' => { 'latestCommit' => 'aaaa1' },
+ 'toRef' => { 'latestCommit' => 'aaaa2' }
+ }
+ ),
+ BitbucketServer::Representation::PullRequest.new(
+ {
+ 'id' => 2,
+ 'state' => 'DECLINED',
+ 'fromRef' => { 'latestCommit' => 'bbbb1' },
+ 'toRef' => { 'latestCommit' => 'bbbb2' }
+ }
+ ),
+ BitbucketServer::Representation::PullRequest.new(
+ {
+ 'id' => 3,
+ 'state' => 'OPEN',
+ 'fromRef' => { 'latestCommit' => 'cccc1' },
+ 'toRef' => { 'latestCommit' => 'cccc2' }
+ }
+ )
],
[]
)
@@ -28,14 +50,14 @@ RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestsImporter, f
end
it 'imports each pull request in parallel', :aggregate_failures do
- expect(Gitlab::BitbucketServerImport::ImportPullRequestWorker).to receive(:perform_in).twice
+ expect(Gitlab::BitbucketServerImport::ImportPullRequestWorker).to receive(:perform_in).thrice
waiter = importer.execute
expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
- expect(waiter.jobs_remaining).to eq(2)
+ expect(waiter.jobs_remaining).to eq(3)
expect(Gitlab::Cache::Import::Caching.values_from_set(importer.already_processed_cache_key))
- .to match_array(%w[1 2])
+ .to match_array(%w[1 2 3])
end
context 'when pull request was already processed' do
@@ -44,12 +66,68 @@ RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestsImporter, f
end
it 'does not schedule job for processed pull requests', :aggregate_failures do
- expect(Gitlab::BitbucketServerImport::ImportPullRequestWorker).to receive(:perform_in).once
+ expect(Gitlab::BitbucketServerImport::ImportPullRequestWorker).to receive(:perform_in).twice
waiter = importer.execute
expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
- expect(waiter.jobs_remaining).to eq(2)
+ expect(waiter.jobs_remaining).to eq(3)
+ end
+ end
+
+ context 'when pull requests are in merged or declined status' do
+ it 'fetches latest commits from the remote repository' do
+ expect(project.repository).to receive(:fetch_remote).with(
+ project.import_url,
+ refmap: %w[aaaa1 aaaa2 bbbb1 bbbb2],
+ prune: false
+ )
+
+ importer.execute
+ end
+
+ context 'when feature flag "fetch_commits_for_bitbucket_server" is disabled' do
+ before do
+ stub_feature_flags(fetch_commits_for_bitbucket_server: false)
+ end
+
+ it 'does not fetch anything' do
+ expect(project.repository).not_to receive(:fetch_remote)
+ importer.execute
+ end
+ end
+
+ context 'when there are no commits to process' do
+ before do
+ Gitlab::Cache::Import::Caching.set_add(importer.already_processed_cache_key, 1)
+ Gitlab::Cache::Import::Caching.set_add(importer.already_processed_cache_key, 2)
+ end
+
+ it 'does not fetch anything' do
+ expect(project.repository).not_to receive(:fetch_remote)
+
+ importer.execute
+ end
+ end
+
+ context 'when fetch process is failed' do
+ let(:exception) { ArgumentError.new('blank or empty URL') }
+
+ before do
+ allow(project.repository).to receive(:fetch_remote).and_raise(exception)
+ end
+
+ it 'rescues and logs the exception' do
+ expect(Gitlab::Import::ImportFailureService)
+ .to receive(:track)
+ .with(
+ project_id: project.id,
+ exception: exception,
+ error_source: described_class.name
+ ).and_call_original
+
+ importer.execute
+ end
end
end
end
diff --git a/spec/lib/gitlab/chat_spec.rb b/spec/lib/gitlab/chat_spec.rb
deleted file mode 100644
index a9df35ace98..00000000000
--- a/spec/lib/gitlab/chat_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Chat, :use_clean_rails_memory_store_caching do
- describe '.available?' do
- it 'returns true when the chatops feature is available' do
- stub_feature_flags(chatops: true)
-
- expect(described_class).to be_available
- end
-
- it 'returns false when the chatops feature is not available' do
- stub_feature_flags(chatops: false)
-
- expect(described_class).not_to be_available
- end
- end
-end
diff --git a/spec/lib/gitlab/checks/global_file_size_check_spec.rb b/spec/lib/gitlab/checks/global_file_size_check_spec.rb
index a2b3ee0f761..db615053356 100644
--- a/spec/lib/gitlab/checks/global_file_size_check_spec.rb
+++ b/spec/lib/gitlab/checks/global_file_size_check_spec.rb
@@ -34,7 +34,10 @@ RSpec.describe Gitlab::Checks::GlobalFileSizeCheck, feature_category: :source_co
end
context 'when there are oversized blobs' do
- let(:blob_double) { instance_double(Gitlab::Git::Blob, size: 10) }
+ let(:mock_blob_id) { "88acbfafb1b8fdb7c51db870babce21bd861ac4f" }
+ let(:mock_blob_size) { 300 * 1024 * 1024 } # 300 MiB
+ let(:size_msg) { "300.0" } # it is (mock_blob_size / 1024.0 / 1024.0).round(2).to_s
+ let(:blob_double) { instance_double(Gitlab::Git::Blob, size: mock_blob_size, id: mock_blob_id) }
before do
allow_next_instance_of(Gitlab::Checks::FileSizeCheck::HookEnvironmentAwareAnyOversizedBlobs,
@@ -48,8 +51,15 @@ RSpec.describe Gitlab::Checks::GlobalFileSizeCheck, feature_category: :source_co
it 'logs a message with blob size and raises an exception' do
expect(Gitlab::AppJsonLogger).to receive(:info).with('Checking for blobs over the file size limit')
- expect(Gitlab::AppJsonLogger).to receive(:info).with(message: 'Found blob over global limit', blob_sizes: [10])
- expect { subject.validate! }.to raise_exception(Gitlab::GitAccess::ForbiddenError)
+ expect(Gitlab::AppJsonLogger).to receive(:info).with(
+ message: 'Found blob over global limit',
+ blob_sizes: [mock_blob_size],
+ blob_details: { mock_blob_id => { "size" => mock_blob_size } }
+ )
+ expect do
+ subject.validate!
+ end.to raise_exception(Gitlab::GitAccess::ForbiddenError,
+ /- #{mock_blob_id} \(#{size_msg} MiB\)/)
end
context 'when the enforce_global_file_size_limit feature flag is disabled' do
diff --git a/spec/lib/gitlab/checks/tag_check_spec.rb b/spec/lib/gitlab/checks/tag_check_spec.rb
index 60d3eb4bfb3..b5aafde006f 100644
--- a/spec/lib/gitlab/checks/tag_check_spec.rb
+++ b/spec/lib/gitlab/checks/tag_check_spec.rb
@@ -41,6 +41,36 @@ RSpec.describe Gitlab::Checks::TagCheck, feature_category: :source_code_manageme
expect { subject.validate! }.not_to raise_error
end
end
+
+ it "prohibits tag names that include characters incompatible with UTF-8" do
+ allow(subject).to receive(:tag_name).and_return("v6.0.0-\xCE.BETA")
+
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "Tag names must be valid when converted to UTF-8 encoding")
+ end
+
+ it "doesn't prohibit UTF-8 compatible characters" do
+ allow(subject).to receive(:tag_name).and_return("v6.0.0-Ü.BETA")
+
+ expect { subject.validate! }.not_to raise_error
+ end
+
+ context "when prohibited_tag_name_encoding_check feature flag is disabled" do
+ before do
+ stub_feature_flags(prohibited_tag_name_encoding_check: false)
+ end
+
+ it "doesn't prohibit tag names that include characters incompatible with UTF-8" do
+ allow(subject).to receive(:tag_name).and_return("v6.0.0-\xCE.BETA")
+
+ expect { subject.validate! }.not_to raise_error
+ end
+
+ it "doesn't prohibit UTF-8 compatible characters" do
+ allow(subject).to receive(:tag_name).and_return("v6.0.0-Ü.BETA")
+
+ expect { subject.validate! }.not_to raise_error
+ end
+ end
end
context 'with protected tag' do
diff --git a/spec/lib/gitlab/ci/build/context/build_spec.rb b/spec/lib/gitlab/ci/build/context/build_spec.rb
index 6047eb1b1e0..fae02e140f2 100644
--- a/spec/lib/gitlab/ci/build/context/build_spec.rb
+++ b/spec/lib/gitlab/ci/build/context/build_spec.rb
@@ -4,7 +4,13 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Build::Context::Build, feature_category: :pipeline_composition do
let(:pipeline) { create(:ci_pipeline) }
- let(:seed_attributes) { { 'name' => 'some-job' } }
+ let(:seed_attributes) do
+ {
+ name: 'some-job',
+ tag_list: %w[ruby docker postgresql],
+ needs_attributes: [{ name: 'setup-test-env', artifacts: true, optional: false }]
+ }
+ end
subject(:context) { described_class.new(pipeline, seed_attributes) }
@@ -23,7 +29,7 @@ RSpec.describe Gitlab::Ci::Build::Context::Build, feature_category: :pipeline_co
end
context 'when environment:name is provided' do
- let(:seed_attributes) { { 'name' => 'some-job', 'environment' => 'test' } }
+ let(:seed_attributes) { { name: 'some-job', environment: 'test' } }
it { is_expected.to include('CI_ENVIRONMENT_NAME' => 'test') }
end
@@ -35,6 +41,16 @@ RSpec.describe Gitlab::Ci::Build::Context::Build, feature_category: :pipeline_co
it { expect(context.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) }
it_behaves_like 'variables collection'
+
+ context 'with FF disabled' do
+ before do
+ stub_feature_flags(reduced_build_attributes_list_for_rules: false)
+ end
+
+ it { expect(context.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) }
+
+ it_behaves_like 'variables collection'
+ end
end
describe '#variables_hash' do
@@ -43,5 +59,15 @@ RSpec.describe Gitlab::Ci::Build::Context::Build, feature_category: :pipeline_co
it { expect(context.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) }
it_behaves_like 'variables collection'
+
+ context 'with FF disabled' do
+ before do
+ stub_feature_flags(reduced_build_attributes_list_for_rules: false)
+ end
+
+ it { expect(context.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) }
+
+ it_behaves_like 'variables collection'
+ end
end
end
diff --git a/spec/lib/gitlab/ci/build/duration_parser_spec.rb b/spec/lib/gitlab/ci/build/duration_parser_spec.rb
index bc905aa0a35..7f5ff1eb0ee 100644
--- a/spec/lib/gitlab/ci/build/duration_parser_spec.rb
+++ b/spec/lib/gitlab/ci/build/duration_parser_spec.rb
@@ -25,8 +25,8 @@ RSpec.describe Gitlab::Ci::Build::DurationParser do
it { is_expected.to be_truthy }
it 'caches data' do
- expect(ChronicDuration).to receive(:parse).with(value, use_complete_matcher: true).once.and_call_original
- expect(ChronicDuration).to receive(:parse).with(other_value, use_complete_matcher: true).once.and_call_original
+ expect(ChronicDuration).to receive(:parse).with(value).once.and_call_original
+ expect(ChronicDuration).to receive(:parse).with(other_value).once.and_call_original
2.times do
expect(described_class.validate_duration(value)).to eq(86400)
@@ -41,7 +41,7 @@ RSpec.describe Gitlab::Ci::Build::DurationParser do
it { is_expected.to be_falsy }
it 'caches data' do
- expect(ChronicDuration).to receive(:parse).with(value, use_complete_matcher: true).once.and_call_original
+ expect(ChronicDuration).to receive(:parse).with(value).once.and_call_original
2.times do
expect(described_class.validate_duration(value)).to be_falsey
diff --git a/spec/lib/gitlab/ci/components/instance_path_spec.rb b/spec/lib/gitlab/ci/components/instance_path_spec.rb
index 97843781891..0bdcfcfd546 100644
--- a/spec/lib/gitlab/ci/components/instance_path_spec.rb
+++ b/spec/lib/gitlab/ci/components/instance_path_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline_composition do
let_it_be(:user) { create(:user) }
- let(:path) { described_class.new(address: address, content_filename: 'template.yml') }
+ let(:path) { described_class.new(address: address) }
let(:settings) { GitlabSettings::Options.build({ 'component_fqdn' => current_host }) }
let(:current_host) { 'acme.com/' }
@@ -44,9 +44,10 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
context 'when the component is simple (single file template)' do
it 'fetches the component content', :aggregate_failures do
- expect(path.fetch_content!(current_user: user)).to eq('image: alpine_1')
+ result = path.fetch_content!(current_user: user)
+ expect(result.content).to eq('image: alpine_1')
+ expect(result.path).to eq('templates/secret-detection.yml')
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('templates/secret-detection.yml')
expect(path.project).to eq(project)
expect(path.sha).to eq(project.commit('master').id)
end
@@ -56,9 +57,10 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
let(:address) { "acme.com/#{project_path}/dast@#{version}" }
it 'fetches the component content', :aggregate_failures do
- expect(path.fetch_content!(current_user: user)).to eq('image: alpine_2')
+ result = path.fetch_content!(current_user: user)
+ expect(result.content).to eq('image: alpine_2')
+ expect(result.path).to eq('templates/dast/template.yml')
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('templates/dast/template.yml')
expect(path.project).to eq(project)
expect(path.sha).to eq(project.commit('master').id)
end
@@ -67,7 +69,8 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
let(:address) { "acme.com/#{project_path}/dast/another-folder@#{version}" }
it 'returns nil' do
- expect(path.fetch_content!(current_user: user)).to be_nil
+ result = path.fetch_content!(current_user: user)
+ expect(result.content).to be_nil
end
end
@@ -75,7 +78,8 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
let(:address) { "acme.com/#{project_path}/dast/another-template@#{version}" }
it 'returns nil' do
- expect(path.fetch_content!(current_user: user)).to be_nil
+ result = path.fetch_content!(current_user: user)
+ expect(result.content).to be_nil
end
end
end
@@ -110,9 +114,10 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
end
it 'fetches the component content', :aggregate_failures do
- expect(path.fetch_content!(current_user: user)).to eq('image: alpine_2')
+ result = path.fetch_content!(current_user: user)
+ expect(result.content).to eq('image: alpine_2')
+ expect(result.path).to eq('templates/secret-detection.yml')
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('templates/secret-detection.yml')
expect(path.project).to eq(project)
expect(path.sha).to eq(latest_sha)
end
@@ -124,7 +129,6 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
it 'returns nil', :aggregate_failures do
expect(path.fetch_content!(current_user: user)).to be_nil
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to be_nil
expect(path.project).to eq(project)
expect(path.sha).to be_nil
end
@@ -135,9 +139,10 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
let(:current_host) { 'acme.com/gitlab/' }
it 'fetches the component content', :aggregate_failures do
- expect(path.fetch_content!(current_user: user)).to eq('image: alpine_1')
+ result = path.fetch_content!(current_user: user)
+ expect(result.content).to eq('image: alpine_1')
+ expect(result.path).to eq('templates/secret-detection.yml')
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('templates/secret-detection.yml')
expect(path.project).to eq(project)
expect(path.sha).to eq(project.commit('master').id)
end
@@ -164,9 +169,10 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
end
it 'fetches the component content', :aggregate_failures do
- expect(path.fetch_content!(current_user: user)).to eq('image: alpine')
+ result = path.fetch_content!(current_user: user)
+ expect(result.content).to eq('image: alpine')
+ expect(result.path).to eq('component/template.yml')
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('component/template.yml')
expect(path.project).to eq(project)
expect(path.sha).to eq(project.commit('master').id)
end
@@ -184,9 +190,10 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
end
it 'fetches the component content', :aggregate_failures do
- expect(path.fetch_content!(current_user: user)).to eq('image: alpine')
+ result = path.fetch_content!(current_user: user)
+ expect(result.content).to eq('image: alpine')
+ expect(result.path).to eq('component/template.yml')
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('component/template.yml')
expect(path.project).to eq(project)
expect(path.sha).to eq(project.commit('master').id)
end
@@ -197,9 +204,10 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
let(:current_host) { 'acme.com/gitlab/' }
it 'fetches the component content', :aggregate_failures do
- expect(path.fetch_content!(current_user: user)).to eq('image: alpine')
+ result = path.fetch_content!(current_user: user)
+ expect(result.content).to eq('image: alpine')
+ expect(result.path).to eq('component/template.yml')
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('component/template.yml')
expect(path.project).to eq(project)
expect(path.sha).to eq(project.commit('master').id)
end
@@ -211,7 +219,6 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
it 'returns nil', :aggregate_failures do
expect(path.fetch_content!(current_user: user)).to be_nil
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to be_nil
expect(path.project).to eq(project)
expect(path.sha).to be_nil
end
diff --git a/spec/lib/gitlab/ci/config/external/file/component_spec.rb b/spec/lib/gitlab/ci/config/external/file/component_spec.rb
index 0f7b811b5df..88e272ac3fd 100644
--- a/spec/lib/gitlab/ci/config/external/file/component_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/component_spec.rb
@@ -99,7 +99,9 @@ RSpec.describe Gitlab::Ci::Config::External::File::Component, feature_category:
let(:response) do
ServiceResponse.success(payload: {
content: content,
- path: instance_double(::Gitlab::Ci::Components::InstancePath, project: project, sha: '12345')
+ path: 'templates/component.yml',
+ project: project,
+ sha: '12345'
})
end
@@ -132,7 +134,9 @@ RSpec.describe Gitlab::Ci::Config::External::File::Component, feature_category:
let(:response) do
ServiceResponse.success(payload: {
content: content,
- path: instance_double(::Gitlab::Ci::Components::InstancePath, project: project, sha: '12345')
+ path: 'templates/component.yml',
+ project: project,
+ sha: '12345'
})
end
@@ -158,15 +162,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Component, feature_category:
describe '#metadata' do
subject(:metadata) { external_resource.metadata }
- let(:component_path) do
- instance_double(::Gitlab::Ci::Components::InstancePath,
- project: project,
- sha: '12345',
- project_file_path: 'my-component/template.yml')
- end
-
let(:response) do
- ServiceResponse.success(payload: { path: component_path })
+ ServiceResponse.success(payload: { path: 'my-component/template.yml', project: project, sha: '12345' })
end
it 'returns the metadata' do
@@ -183,14 +180,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Component, feature_category:
end
describe '#expand_context' do
- let(:component_path) do
- instance_double(::Gitlab::Ci::Components::InstancePath,
- project: project,
- sha: '12345')
- end
-
let(:response) do
- ServiceResponse.success(payload: { path: component_path })
+ ServiceResponse.success(payload: { path: 'templates/component.yml', project: project, sha: '12345' })
end
subject { external_resource.send(:expand_context_attrs) }
@@ -207,11 +198,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Component, feature_category:
describe '#to_hash' do
context 'when interpolation is being used' do
let(:response) do
- ServiceResponse.success(payload: { content: content, path: path })
- end
-
- let(:path) do
- instance_double(::Gitlab::Ci::Components::InstancePath, project: project, sha: '12345')
+ ServiceResponse.success(payload: { content: content, path: 'templates/component.yml', project: project,
+ sha: '12345' })
end
let(:content) do
diff --git a/spec/lib/gitlab/ci/config/header/input_spec.rb b/spec/lib/gitlab/ci/config/header/input_spec.rb
index b5155dff6e8..5d1fa4a8e6e 100644
--- a/spec/lib/gitlab/ci/config/header/input_spec.rb
+++ b/spec/lib/gitlab/ci/config/header/input_spec.rb
@@ -46,6 +46,12 @@ RSpec.describe Gitlab::Ci::Config::Header::Input, feature_category: :pipeline_co
it_behaves_like 'a valid input'
end
+ context 'when has a description value' do
+ let(:input_hash) { { description: 'bar' } }
+
+ it_behaves_like 'a valid input'
+ end
+
context 'when is a required input' do
let(:input_hash) { nil }
@@ -62,6 +68,12 @@ RSpec.describe Gitlab::Ci::Config::Header::Input, feature_category: :pipeline_co
end
end
+ context 'when the input has RegEx validation' do
+ let(:input_hash) { { regex: '\w+' } }
+
+ it_behaves_like 'a valid input'
+ end
+
context 'when given an invalid type' do
let(:input_hash) { { type: 'datetime' } }
let(:expected_errors) { ['foo input type unknown value: datetime'] }
@@ -84,4 +96,11 @@ RSpec.describe Gitlab::Ci::Config::Header::Input, feature_category: :pipeline_co
it_behaves_like 'an invalid input'
end
+
+ context 'when RegEx validation value is not a string' do
+ let(:input_hash) { { regex: [] } }
+ let(:expected_errors) { ['foo input regex should be a string'] }
+
+ it_behaves_like 'an invalid input'
+ end
end
diff --git a/spec/lib/gitlab/ci/config/interpolation/context_spec.rb b/spec/lib/gitlab/ci/config/interpolation/context_spec.rb
index c90866c986a..56a572312eb 100644
--- a/spec/lib/gitlab/ci/config/interpolation/context_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/context_spec.rb
@@ -17,6 +17,12 @@ RSpec.describe Gitlab::Ci::Config::Interpolation::Context, feature_category: :pi
end
end
+ describe '.new' do
+ it 'returns variables as a Variables::Collection object' do
+ expect(subject.variables.class).to eq(Gitlab::Ci::Variables::Collection)
+ end
+ end
+
describe '#to_h' do
it 'returns the context hash' do
expect(subject.to_h).to eq(ctx)
diff --git a/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb
index c193e88dbe2..a2b575afb6f 100644
--- a/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb
@@ -18,6 +18,6 @@ RSpec.describe Gitlab::Ci::Config::Interpolation::Functions::Base, feature_categ
it 'defines an expected interface for child classes' do
expect { described_class.function_expression_pattern }.to raise_error(NotImplementedError)
expect { described_class.name }.to raise_error(NotImplementedError)
- expect { custom_function_klass.new('test').execute('input') }.to raise_error(NotImplementedError)
+ expect { custom_function_klass.new('test', nil).execute('input') }.to raise_error(NotImplementedError)
end
end
diff --git a/spec/lib/gitlab/ci/config/interpolation/functions/expand_vars_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions/expand_vars_spec.rb
new file mode 100644
index 00000000000..2a627b435d3
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/functions/expand_vars_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Functions::ExpandVars, feature_category: :pipeline_composition do
+ let(:variables) do
+ Gitlab::Ci::Variables::Collection.new([
+ { key: 'VAR1', value: 'value1', masked: false },
+ { key: 'VAR2', value: 'value2', masked: false },
+ { key: 'NESTED_VAR', value: '$MY_VAR', masked: false },
+ { key: 'MASKED_VAR', value: 'masked', masked: true }
+ ])
+ end
+
+ let(:function_expression) { 'expand_vars' }
+ let(:ctx) { Gitlab::Ci::Config::Interpolation::Context.new({}, variables: variables) }
+
+ subject(:function) { described_class.new(function_expression, ctx) }
+
+ describe '#execute' do
+ let(:input_value) { '$VAR1' }
+
+ subject(:execute) { function.execute(input_value) }
+
+ it 'expands the variable' do
+ expect(execute).to eq('value1')
+ expect(function).to be_valid
+ end
+
+ context 'when the variable contains another variable' do
+ let(:input_value) { '$NESTED_VAR' }
+
+ it 'does not expand the inner variable' do
+ expect(execute).to eq('$MY_VAR')
+ expect(function).to be_valid
+ end
+ end
+
+ context 'when the variable is masked' do
+ let(:input_value) { '$MASKED_VAR' }
+
+ it 'returns an error' do
+ expect(execute).to be_nil
+ expect(function).not_to be_valid
+ expect(function.errors).to contain_exactly(
+ 'error in `expand_vars` function: variable expansion error: masked variables cannot be expanded'
+ )
+ end
+ end
+
+ context 'when the variable is unknown' do
+ let(:input_value) { '$UNKNOWN_VAR' }
+
+ it 'does not expand the variable' do
+ expect(execute).to eq('$UNKNOWN_VAR')
+ expect(function).to be_valid
+ end
+ end
+
+ context 'when there are multiple variables' do
+ let(:input_value) { '${VAR1} $VAR2 %VAR1%' }
+
+ it 'expands the variables' do
+ expect(execute).to eq('value1 value2 value1')
+ expect(function).to be_valid
+ end
+ end
+
+ context 'when the input is not a string' do
+ let(:input_value) { 100 }
+
+ it 'returns an error' do
+ expect(execute).to be_nil
+ expect(function).not_to be_valid
+ expect(function.errors).to contain_exactly(
+ 'error in `expand_vars` function: invalid input type: expand_vars can only be used with string inputs'
+ )
+ end
+ end
+ end
+
+ describe '.matches?' do
+ it 'matches exactly the expand_vars function with no arguments' do
+ expect(described_class.matches?('expand_vars')).to be_truthy
+ expect(described_class.matches?('expand_vars()')).to be_falsey
+ expect(described_class.matches?('expand_vars(1)')).to be_falsey
+ expect(described_class.matches?('unknown')).to be_falsey
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb
index c521eff9811..93e5d4ef48c 100644
--- a/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Gitlab::Ci::Config::Interpolation::Functions::Truncate, feature_c
end
it 'truncates the given input' do
- function = described_class.new('truncate(1,2)')
+ function = described_class.new('truncate(1,2)', nil)
output = function.execute('test')
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::Ci::Config::Interpolation::Functions::Truncate, feature_c
context 'when given a non-string input' do
it 'returns an error' do
- function = described_class.new('truncate(1,2)')
+ function = described_class.new('truncate(1,2)', nil)
function.execute(100)
diff --git a/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb
index 881f092c440..9ac0ef05c61 100644
--- a/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Config::Interpolation::FunctionsStack, feature_category: :pipeline_composition do
let(:functions) { ['truncate(0,4)', 'truncate(1,2)'] }
let(:input_value) { 'test_input_value' }
- subject { described_class.new(functions).evaluate(input_value) }
+ subject { described_class.new(functions, nil).evaluate(input_value) }
it 'modifies the given input value according to the function expressions' do
expect(subject).to be_success
diff --git a/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb b/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb
index ea06f181fa4..b0618081207 100644
--- a/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb
@@ -7,130 +7,303 @@ RSpec.describe Gitlab::Ci::Config::Interpolation::Inputs, feature_category: :pip
let(:specs) { { foo: { default: 'bar' } } }
let(:args) { {} }
- context 'when inputs are valid' do
- where(:specs, :args, :merged) do
- [
- [
- { foo: { default: 'bar' } }, {},
- { foo: 'bar' }
- ],
- [
- { foo: { default: 'bar' } }, { foo: 'test' },
- { foo: 'test' }
- ],
- [
- { foo: nil }, { foo: 'bar' },
- { foo: 'bar' }
- ],
- [
- { foo: { type: 'string' } }, { foo: 'bar' },
- { foo: 'bar' }
- ],
- [
- { foo: { type: 'string', default: 'bar' } }, { foo: 'test' },
- { foo: 'test' }
- ],
- [
- { foo: { type: 'string', default: 'bar' } }, {},
- { foo: 'bar' }
- ],
- [
- { foo: { default: 'bar' }, baz: nil }, { baz: 'test' },
- { foo: 'bar', baz: 'test' }
- ],
- [
- { number_input: { type: 'number' } },
- { number_input: 8 },
- { number_input: 8 }
- ],
- [
- { default_number_input: { default: 9, type: 'number' } },
- {},
- { default_number_input: 9 }
- ],
- [
- { true_input: { type: 'boolean' }, false_input: { type: 'boolean' } },
- { true_input: true, false_input: false },
- { true_input: true, false_input: false }
- ],
- [
- { default_boolean_input: { default: true, type: 'boolean' } },
- {},
- { default_boolean_input: true }
- ]
- ]
- end
-
- with_them do
- it 'contains the merged inputs' do
+ context 'when given unrecognized inputs' do
+ let(:specs) { { foo: nil } }
+ let(:args) { { foo: 'bar', test: 'bar' } }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly('unknown input arguments: test')
+ end
+ end
+
+ context 'when given unrecognized configuration keywords' do
+ let(:specs) { { foo: 123 } }
+ let(:args) { {} }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly(
+ 'unknown input specification for `foo` (valid types: boolean, number, string)'
+ )
+ end
+ end
+
+ context 'when the inputs have multiple errors' do
+ let(:specs) { { foo: nil } }
+ let(:args) { { test: 'bar', gitlab: '1' } }
+
+ it 'reports all of them' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly(
+ 'unknown input arguments: test, gitlab',
+ '`foo` input: required value has not been provided'
+ )
+ end
+ end
+
+ describe 'required inputs' do
+ let(:specs) { { foo: nil } }
+
+ context 'when a value is given' do
+ let(:args) { { foo: 'bar' } }
+
+ it 'is valid' do
+ expect(inputs).to be_valid
+ expect(inputs.to_hash).to eq(foo: 'bar')
+ end
+ end
+
+ context 'when no value is given' do
+ let(:args) { {} }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly('`foo` input: required value has not been provided')
+ end
+ end
+ end
+
+ describe 'inputs with a default value' do
+ let(:specs) { { foo: { default: 'bar' } } }
+
+ context 'when a value is given' do
+ let(:args) { { foo: 'test' } }
+
+ it 'uses the given value' do
+ expect(inputs).to be_valid
+ expect(inputs.to_hash).to eq(foo: 'test')
+ end
+ end
+
+ context 'when no value is given' do
+ let(:args) { {} }
+
+ it 'uses the default value' do
expect(inputs).to be_valid
- expect(inputs.to_hash).to eq(merged)
+ expect(inputs.to_hash).to eq(foo: 'bar')
end
end
end
- context 'when inputs are invalid' do
- where(:specs, :args, :errors) do
- [
- [
- { foo: nil }, { foo: 'bar', test: 'bar' },
- ['unknown input arguments: test']
- ],
- [
- { foo: nil }, { test: 'bar', gitlab: '1' },
- ['unknown input arguments: test, gitlab', '`foo` input: required value has not been provided']
- ],
- [
- { foo: 123 }, {},
- ['unknown input specification for `foo` (valid types: boolean, number, string)']
- ],
- [
- { a: nil, foo: 123 }, { a: '123' },
- ['unknown input specification for `foo` (valid types: boolean, number, string)']
- ],
- [
- { foo: nil }, {},
- ['`foo` input: required value has not been provided']
- ],
- [
- { foo: { default: 123 } }, { foo: 'test' },
- ['`foo` input: default value is not a string']
- ],
- [
- { foo: { default: 'test' } }, { foo: 123 },
- ['`foo` input: provided value is not a string']
- ],
- [
- { foo: nil }, { foo: 123 },
- ['`foo` input: provided value is not a string']
- ],
- [
- { number_input: { type: 'number' } },
- { number_input: 'NaN' },
- ['`number_input` input: provided value is not a number']
- ],
- [
- { default_number_input: { default: 'NaN', type: 'number' } },
- {},
- ['`default_number_input` input: default value is not a number']
- ],
- [
- { boolean_input: { type: 'boolean' } },
- { boolean_input: 'string' },
- ['`boolean_input` input: provided value is not a boolean']
- ],
- [
- { default_boolean_input: { default: 'string', type: 'boolean' } },
- {},
- ['`default_boolean_input` input: default value is not a boolean']
- ]
- ]
- end
-
- with_them do
- it 'contains the merged inputs', :aggregate_failures do
+ describe 'inputs with type validation' do
+ describe 'string validation' do
+ let(:specs) { { a_input: nil, b_input: { default: 'test' }, c_input: { default: 123 } } }
+ let(:args) { { a_input: 123, b_input: 123, c_input: 'test' } }
+
+ it 'is the default type' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly(
+ '`a_input` input: provided value is not a string',
+ '`b_input` input: provided value is not a string',
+ '`c_input` input: default value is not a string'
+ )
+ end
+
+ context 'when the value is a string' do
+ let(:specs) { { foo: { type: 'string' } } }
+ let(:args) { { foo: 'bar' } }
+
+ it 'is valid' do
+ expect(inputs).to be_valid
+ expect(inputs.to_hash).to eq(foo: 'bar')
+ end
+ end
+
+ context 'when the default is a string' do
+ let(:specs) { { foo: { type: 'string', default: 'bar' } } }
+ let(:args) { {} }
+
+ it 'is valid' do
+ expect(inputs).to be_valid
+ expect(inputs.to_hash).to eq(foo: 'bar')
+ end
+ end
+
+ context 'when the value is not a string' do
+ let(:specs) { { foo: { type: 'string' } } }
+ let(:args) { { foo: 123 } }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly('`foo` input: provided value is not a string')
+ end
+ end
+
+ context 'when the default is not a string' do
+ let(:specs) { { foo: { default: 123, type: 'string' } } }
+ let(:args) { {} }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly('`foo` input: default value is not a string')
+ end
+ end
+ end
+
+ describe 'number validation' do
+ let(:specs) { { integer: { type: 'number' }, float: { type: 'number' } } }
+
+ context 'when the value is a float or integer' do
+ let(:args) { { integer: 6, float: 6.6 } }
+
+ it 'is valid' do
+ expect(inputs).to be_valid
+ expect(inputs.to_hash).to eq(integer: 6, float: 6.6)
+ end
+ end
+
+ context 'when the default is a float or integer' do
+ let(:specs) { { integer: { default: 6, type: 'number' }, float: { default: 6.6, type: 'number' } } }
+
+ it 'is valid' do
+ expect(inputs).to be_valid
+ expect(inputs.to_hash).to eq(integer: 6, float: 6.6)
+ end
+ end
+
+ context 'when the value is not a number' do
+ let(:specs) { { number_input: { type: 'number' } } }
+ let(:args) { { number_input: 'NaN' } }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly('`number_input` input: provided value is not a number')
+ end
+ end
+
+ context 'when the default is not a number' do
+ let(:specs) { { number_input: { default: 'NaN', type: 'number' } } }
+ let(:args) { {} }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly('`number_input` input: default value is not a number')
+ end
+ end
+ end
+
+ describe 'boolean validation' do
+ context 'when the value is true or false' do
+ let(:specs) { { truthy: { type: 'boolean' }, falsey: { type: 'boolean' } } }
+ let(:args) { { truthy: true, falsey: false } }
+
+ it 'is valid' do
+ expect(inputs).to be_valid
+ expect(inputs.to_hash).to eq(truthy: true, falsey: false)
+ end
+ end
+
+ context 'when the default is true or false' do
+ let(:specs) { { truthy: { default: true, type: 'boolean' }, falsey: { default: false, type: 'boolean' } } }
+ let(:args) { {} }
+
+ it 'is valid' do
+ expect(inputs).to be_valid
+ expect(inputs.to_hash).to eq(truthy: true, falsey: false)
+ end
+ end
+
+ context 'when the value is not a boolean' do
+ let(:specs) { { boolean_input: { type: 'boolean' } } }
+ let(:args) { { boolean_input: 'string' } }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly('`boolean_input` input: provided value is not a boolean')
+ end
+ end
+
+ context 'when the default is not a boolean' do
+ let(:specs) { { boolean_input: { default: 'string', type: 'boolean' } } }
+ let(:args) { {} }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly('`boolean_input` input: default value is not a boolean')
+ end
+ end
+ end
+
+ context 'when given an unknown type' do
+ let(:specs) { { unknown: { type: 'datetime' } } }
+ let(:args) { { unknown: '2023-10-31' } }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly(
+ 'unknown input specification for `unknown` (valid types: boolean, number, string)'
+ )
+ end
+ end
+ end
+
+ describe 'inputs with RegEx validation' do
+ context 'when given a value that matches the pattern' do
+ let(:specs) { { test_input: { regex: '^input_value$' } } }
+ let(:args) { { test_input: 'input_value' } }
+
+ it 'is valid' do
+ expect(inputs).to be_valid
+ expect(inputs.to_hash).to eq(test_input: 'input_value')
+ end
+ end
+
+ context 'when given a default that matches the pattern' do
+ let(:specs) { { test_input: { default: 'input_value', regex: '^input_value$' } } }
+ let(:args) { {} }
+
+ it 'is valid' do
+ expect(inputs).to be_valid
+ expect(inputs.to_hash).to eq(test_input: 'input_value')
+ end
+ end
+
+ context 'when given a value that does not match the pattern' do
+ let(:specs) { { test_input: { regex: '^input_value$' } } }
+ let(:args) { { test_input: 'input' } }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly(
+ '`test_input` input: provided value does not match required RegEx pattern'
+ )
+ end
+ end
+
+ context 'when given a default that does not match the pattern' do
+ let(:specs) { { test_input: { default: 'input', regex: '^input_value$' } } }
+ let(:args) { {} }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly(
+ '`test_input` input: default value does not match required RegEx pattern'
+ )
+ end
+ end
+
+ context 'when used with any type other than `string`' do
+ let(:specs) { { test_input: { regex: '^input_value$', type: 'number' } } }
+ let(:args) { { test_input: 999 } }
+
+ it 'is invalid' do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly(
+ '`test_input` input: RegEx validation can only be used with string inputs'
+ )
+ end
+ end
+
+ context 'when the pattern is unsafe' do
+ let(:specs) { { test_input: { regex: 'a++' } } }
+ let(:args) { { test_input: 'aaaaaaaaaaaaaaaaaaaaa' } }
+
+ it 'is invalid' do
expect(inputs).not_to be_valid
- expect(inputs.errors).to contain_exactly(*errors)
+ expect(inputs.errors).to contain_exactly(
+ '`test_input` input: invalid regular expression'
+ )
end
end
end
diff --git a/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb b/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb
index 804164c933a..c924323837b 100644
--- a/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::Ci::Config::Interpolation::Interpolator, feature_category
let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(config: [header, content]) }
- subject { described_class.new(result, arguments) }
+ subject { described_class.new(result, arguments, []) }
context 'when input data is valid' do
let(:header) do
diff --git a/spec/lib/gitlab/ci/config/yaml/loader_spec.rb b/spec/lib/gitlab/ci/config/yaml/loader_spec.rb
index 57a9a47d699..684da1df43b 100644
--- a/spec/lib/gitlab/ci/config/yaml/loader_spec.rb
+++ b/spec/lib/gitlab/ci/config/yaml/loader_spec.rb
@@ -58,4 +58,36 @@ RSpec.describe ::Gitlab::Ci::Config::Yaml::Loader, feature_category: :pipeline_c
end
end
end
+
+ describe '#load_uninterpolated_yaml' do
+ let(:yaml) do
+ <<~YAML
+ ---
+ spec:
+ inputs:
+ test_input:
+ ---
+ test_job:
+ script:
+ - echo "$[[ inputs.test_input ]]"
+ YAML
+ end
+
+ subject(:result) { described_class.new(yaml).load_uninterpolated_yaml }
+
+ it 'returns the config' do
+ expected_content = { test_job: { script: ["echo \"$[[ inputs.test_input ]]\""] } }
+ expect(result).to be_valid
+ expect(result.content).to eq(expected_content)
+ end
+
+ context 'when there is a format error in the yaml' do
+ let(:yaml) { 'invalid: yaml: all the time' }
+
+ it 'returns an error' do
+ expect(result).not_to be_valid
+ expect(result.error).to include('mapping values are not allowed in this context')
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/yaml/result_spec.rb b/spec/lib/gitlab/ci/config/yaml/result_spec.rb
index a66c630dfc9..5e9dee02190 100644
--- a/spec/lib/gitlab/ci/config/yaml/result_spec.rb
+++ b/spec/lib/gitlab/ci/config/yaml/result_spec.rb
@@ -3,12 +3,44 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Yaml::Result, feature_category: :pipeline_composition do
+ it 'raises an error when reading a header when there is none' do
+ result = described_class.new(config: { b: 2 })
+
+ expect { result.header }.to raise_error(ArgumentError)
+ end
+
+ it 'stores an error / exception when initialized with it' do
+ result = described_class.new(error: ArgumentError.new('abc'))
+
+ expect(result).not_to be_valid
+ expect(result.error).to be_a ArgumentError
+ end
+
it 'does not have a header when config is a single hash' do
result = described_class.new(config: { a: 1, b: 2 })
expect(result).not_to have_header
end
+ describe '#inputs' do
+ it 'returns the value of the spec inputs' do
+ result = described_class.new(config: [{ spec: { inputs: { website: nil } } }, { b: 2 }])
+
+ expect(result).to have_header
+ expect(result.inputs).to eq({ website: nil })
+ end
+ end
+
+ describe '#interpolated?' do
+ it 'defaults to false' do
+ expect(described_class.new).not_to be_interpolated
+ end
+
+ it 'returns the value passed to the initializer' do
+ expect(described_class.new(interpolated: true)).to be_interpolated
+ end
+ end
+
context 'when config is an array of hashes' do
context 'when first document matches the header schema' do
it 'has a header' do
@@ -38,27 +70,4 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Result, feature_category: :pipeline_com
expect(result.content).to be_empty
end
end
-
- it 'raises an error when reading a header when there is none' do
- result = described_class.new(config: { b: 2 })
-
- expect { result.header }.to raise_error(ArgumentError)
- end
-
- it 'stores an error / exception when initialized with it' do
- result = described_class.new(error: ArgumentError.new('abc'))
-
- expect(result).not_to be_valid
- expect(result.error).to be_a ArgumentError
- end
-
- describe '#interpolated?' do
- it 'defaults to false' do
- expect(described_class.new).not_to be_interpolated
- end
-
- it 'returns the value passed to the initializer' do
- expect(described_class.new(interpolated: true)).to be_interpolated
- end
- end
end
diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb
index 4196aad2db4..1637d084c42 100644
--- a/spec/lib/gitlab/ci/lint_spec.rb
+++ b/spec/lib/gitlab/ci/lint_spec.rb
@@ -7,8 +7,18 @@ RSpec.describe Gitlab::Ci::Lint, feature_category: :pipeline_composition do
let_it_be(:user) { create(:user) }
let(:sha) { nil }
+ let(:verify_project_sha) { nil }
let(:ref) { project.default_branch }
- let(:lint) { described_class.new(project: project, current_user: user, sha: sha) }
+ let(:kwargs) do
+ {
+ project: project,
+ current_user: user,
+ sha: sha,
+ verify_project_sha: verify_project_sha
+ }.compact
+ end
+
+ let(:lint) { described_class.new(**kwargs) }
describe '#validate' do
subject { lint.validate(content, dry_run: dry_run, ref: ref) }
@@ -252,6 +262,19 @@ RSpec.describe Gitlab::Ci::Lint, feature_category: :pipeline_composition do
subject
end
+ shared_examples 'when sha is not provided' do
+ it 'runs YamlProcessor with verify_project_sha: false' do
+ expect(Gitlab::Ci::YamlProcessor)
+ .to receive(:new)
+ .with(content, a_hash_including(verify_project_sha: false))
+ .and_call_original
+
+ subject
+ end
+ end
+
+ it_behaves_like 'when sha is not provided'
+
context 'when sha is provided' do
let(:sha) { project.commit.sha }
@@ -288,20 +311,16 @@ RSpec.describe Gitlab::Ci::Lint, feature_category: :pipeline_composition do
context 'when a project ref does not contain the sha' do
it 'returns an error' do
expect(subject).not_to be_valid
- expect(subject.errors).to include(/Could not validate configuration/)
+ expect(subject.errors).to include(
+ /configuration originates from an external project or a commit not associated with a Git reference/)
end
end
end
- end
- context 'when sha is not provided' do
- it 'runs YamlProcessor with verify_project_sha: false' do
- expect(Gitlab::Ci::YamlProcessor)
- .to receive(:new)
- .with(content, a_hash_including(verify_project_sha: false))
- .and_call_original
+ context 'when verify_project_sha is false' do
+ let(:verify_project_sha) { false }
- subject
+ it_behaves_like 'when sha is not provided'
end
end
end
@@ -468,7 +487,7 @@ RSpec.describe Gitlab::Ci::Lint, feature_category: :pipeline_composition do
end
context 'when project is not provided' do
- let(:project) { nil }
+ let(:lint) { described_class.new(project: nil, **kwargs) }
let(:project_nil_loggable_data) do
expected_data.except('project_id')
diff --git a/spec/lib/gitlab/ci/parsers/security/common_spec.rb b/spec/lib/gitlab/ci/parsers/security/common_spec.rb
index 9470d59f502..648b8ac2db9 100644
--- a/spec/lib/gitlab/ci/parsers/security/common_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/security/common_spec.rb
@@ -370,6 +370,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common, feature_category: :vulnera
end
end
+ describe 'setting CVSS' do
+ let(:cvss_vectors) { report.findings.filter_map(&:cvss).reject(&:empty?) }
+
+ it 'ingests the provided CVSS vectors' do
+ expect(cvss_vectors.count).to eq(1)
+ end
+ end
+
describe 'setting the uuid' do
let(:finding_uuids) { report.findings.map(&:uuid) }
let(:uuid_1) do
diff --git a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
index 821a5057d2e..1bab27c877d 100644
--- a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
@@ -111,7 +111,21 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do
it_behaves_like '<testcase> XML parser',
::Gitlab::Ci::Reports::TestCase::STATUS_FAILED,
- 'Some failure'
+ "System Err:\n\nSome failure"
+ end
+
+ context 'and has failure with message, system-out and system-err' do
+ let(:testcase_content) do
+ <<-EOF.strip_heredoc
+ <failure>Some failure</failure>
+ <system-out>This is the system output</system-out>
+ <system-err>This is the system err</system-err>
+ EOF
+ end
+
+ it_behaves_like '<testcase> XML parser',
+ ::Gitlab::Ci::Reports::TestCase::STATUS_FAILED,
+ "Some failure\n\nSystem Out:\n\nThis is the system output\n\nSystem Err:\n\nThis is the system err"
end
context 'and has error' do
@@ -132,7 +146,21 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do
it_behaves_like '<testcase> XML parser',
::Gitlab::Ci::Reports::TestCase::STATUS_ERROR,
- 'Some error'
+ "System Err:\n\nSome error"
+ end
+
+ context 'and has error with message, system-out and system-err' do
+ let(:testcase_content) do
+ <<-EOF.strip_heredoc
+ <error>Some error</error>
+ <system-out>This is the system output</system-out>
+ <system-err>This is the system err</system-err>
+ EOF
+ end
+
+ it_behaves_like '<testcase> XML parser',
+ ::Gitlab::Ci::Reports::TestCase::STATUS_ERROR,
+ "Some error\n\nSystem Out:\n\nThis is the system output\n\nSystem Err:\n\nThis is the system err"
end
context 'and has skipped' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
index c3516c467d4..2a26747f65a 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities, feature_categor
it 'adds an error about imports' do
expect(pipeline.errors.to_a)
- .to include /Import in progress/
+ .to include /before project import is complete/
end
it 'breaks the pipeline builder chain' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern/regular_expression_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern/regular_expression_spec.rb
new file mode 100644
index 00000000000..145777a9476
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern/regular_expression_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern::RegularExpression, feature_category: :continuous_integration do
+ describe '#initialize' do
+ it 'initializes the pattern' do
+ pattern = described_class.new('/foo/')
+
+ expect(pattern.value).to eq('/foo/')
+ end
+ end
+
+ describe '#valid?' do
+ subject { described_class.new(pattern).valid? }
+
+ context 'with valid expressions' do
+ let(:pattern) { '/foo\\/bar/' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when the value is not a valid regular expression' do
+ let(:pattern) { 'foo' }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#expression' do
+ subject { described_class.new(pattern).expression }
+
+ context 'with valid expressions' do
+ let(:pattern) { '/bar/' }
+
+ it { is_expected.to eq Gitlab::UntrustedRegexp.new('bar') }
+ end
+
+ context 'when the value is not a valid regular expression' do
+ let(:pattern) { 'foo' }
+
+ it { expect { subject }.to raise_error(RegexpError) }
+ end
+
+ context 'when the request store is activated', :request_store do
+ let(:pattern) { '/foo\\/bar/' }
+
+ it 'fabricates once' do
+ expect(Gitlab::UntrustedRegexp::RubySyntax).to receive(:fabricate!).once.and_call_original
+
+ 2.times do
+ expect(described_class.new(pattern).expression).to be_a(Gitlab::UntrustedRegexp)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
index be205395b69..09899cb9fc4 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern, feature_category: :continuous_integration do
describe '#initialize' do
context 'when the value is a valid regular expression' do
it 'initializes the pattern' do
@@ -164,14 +164,5 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
expect(regexp.evaluate).to eq Gitlab::UntrustedRegexp.new('abc')
end
-
- it 'raises error if evaluated regexp is not valid' do
- allow(Gitlab::UntrustedRegexp::RubySyntax).to receive(:valid?).and_return(true)
-
- regexp = described_class.new('/invalid ( .*/')
-
- expect { regexp.evaluate }
- .to raise_error(Gitlab::Ci::Pipeline::Expression::RuntimeError)
- end
end
end
diff --git a/spec/lib/gitlab/ci/status/bridge/factory_spec.rb b/spec/lib/gitlab/ci/status/bridge/factory_spec.rb
index 040c3ec7f6e..ca1b00e2f5b 100644
--- a/spec/lib/gitlab/ci/status/bridge/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/bridge/factory_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory, feature_category: :continuou
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|created')
+ expect(status.text).to eq s_('CiStatusText|Created')
expect(status.icon).to eq 'status_created'
expect(status.favicon).to eq 'favicon_status_created'
expect(status.label).to eq 'created'
@@ -49,11 +49,11 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory, feature_category: :continuou
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|failed')
+ expect(status.text).to eq s_('CiStatusText|Failed')
expect(status.icon).to eq 'status_failed'
expect(status.favicon).to eq 'favicon_status_failed'
expect(status.label).to eq 'failed'
- expect(status.status_tooltip).to eq "#{s_('CiStatusText|failed')} - (unknown failure)"
+ expect(status.status_tooltip).to eq "#{s_('CiStatusLabel|failed')} - (unknown failure)"
expect(status).not_to have_details
expect(status).to have_action
end
@@ -67,7 +67,7 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory, feature_category: :continuou
it 'fabricates correct status_tooltip' do
expect(status.status_tooltip).to eq(
- "#{s_('CiStatusText|failed')} - (downstream pipeline can not be created, Pipeline will not run for the selected trigger. " \
+ "#{s_('CiStatusLabel|failed')} - (downstream pipeline can not be created, Pipeline will not run for the selected trigger. " \
"The rules configuration prevented any jobs from being added to the pipeline., other error)"
)
end
@@ -93,7 +93,7 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory, feature_category: :continuou
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|manual')
+ expect(status.text).to eq s_('CiStatusText|Manual')
expect(status.group).to eq 'manual'
expect(status.icon).to eq 'status_manual'
expect(status.favicon).to eq 'favicon_status_manual'
@@ -128,7 +128,7 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory, feature_category: :continuou
end
it 'fabricates status with correct details' do
- expect(status.text).to eq 'waiting'
+ expect(status.text).to eq 'Waiting'
expect(status.group).to eq 'waiting-for-resource'
expect(status.icon).to eq 'status_pending'
expect(status.favicon).to eq 'favicon_status_pending'
@@ -154,7 +154,7 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory, feature_category: :continuou
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|passed')
+ expect(status.text).to eq s_('CiStatusText|Passed')
expect(status.icon).to eq 'status_success'
expect(status.favicon).to eq 'favicon_status_success'
expect(status).to have_action
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index f71f3d47452..1d043966321 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|passed')
+ expect(status.text).to eq s_('CiStatusText|Passed')
expect(status.icon).to eq 'status_success'
expect(status.favicon).to eq 'favicon_status_success'
expect(status.label).to eq s_('CiStatusLabel|passed')
@@ -58,7 +58,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|passed')
+ expect(status.text).to eq s_('CiStatusText|Passed')
expect(status.icon).to eq 'status_success'
expect(status.favicon).to eq 'favicon_status_success'
expect(status.label).to eq s_('CiStatusLabel|passed')
@@ -86,11 +86,11 @@ RSpec.describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|failed')
+ expect(status.text).to eq s_('CiStatusText|Failed')
expect(status.icon).to eq 'status_failed'
expect(status.favicon).to eq 'favicon_status_failed'
expect(status.label).to eq s_('CiStatusLabel|failed')
- expect(status.status_tooltip).to eq "#{s_('CiStatusText|failed')} - (unknown failure)"
+ expect(status.status_tooltip).to eq "#{s_('CiStatusLabel|failed')} - (unknown failure)"
expect(status).to have_details
expect(status).to have_action
end
@@ -115,7 +115,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|failed')
+ expect(status.text).to eq s_('CiStatusText|Failed')
expect(status.icon).to eq 'status_warning'
expect(status.favicon).to eq 'favicon_status_failed'
expect(status.label).to eq 'failed (allowed to fail)'
@@ -144,7 +144,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|failed')
+ expect(status.text).to eq s_('CiStatusText|Failed')
expect(status.icon).to eq 'status_failed'
expect(status.favicon).to eq 'favicon_status_failed'
expect(status.label).to eq s_('CiStatusLabel|failed')
@@ -173,7 +173,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|canceled')
+ expect(status.text).to eq s_('CiStatusText|Canceled')
expect(status.icon).to eq 'status_canceled'
expect(status.favicon).to eq 'favicon_status_canceled'
expect(status.illustration).to include(:image, :size, :title)
@@ -200,10 +200,10 @@ RSpec.describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatus|running')
+ expect(status.text).to eq s_('CiStatusText|Running')
expect(status.icon).to eq 'status_running'
expect(status.favicon).to eq 'favicon_status_running'
- expect(status.label).to eq s_('CiStatus|running')
+ expect(status.label).to eq s_('CiStatusLabel|running')
expect(status).to have_details
expect(status).to have_action
end
@@ -226,7 +226,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|pending')
+ expect(status.text).to eq s_('CiStatusText|Pending')
expect(status.icon).to eq 'status_pending'
expect(status.favicon).to eq 'favicon_status_pending'
expect(status.illustration).to include(:image, :size, :title, :content)
@@ -252,7 +252,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|skipped')
+ expect(status.text).to eq s_('CiStatusText|Skipped')
expect(status.icon).to eq 'status_skipped'
expect(status.favicon).to eq 'favicon_status_skipped'
expect(status.illustration).to include(:image, :size, :title)
@@ -282,7 +282,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|manual')
+ expect(status.text).to eq s_('CiStatusText|Manual')
expect(status.group).to eq 'manual'
expect(status.icon).to eq 'status_manual'
expect(status.favicon).to eq 'favicon_status_manual'
@@ -339,7 +339,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|manual')
+ expect(status.text).to eq s_('CiStatusText|Manual')
expect(status.group).to eq 'manual'
expect(status.icon).to eq 'status_manual'
expect(status.favicon).to eq 'favicon_status_manual'
@@ -370,7 +370,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Factory do
end
it 'fabricates status with correct details' do
- expect(status.text).to eq s_('CiStatusText|scheduled')
+ expect(status.text).to eq s_('CiStatusText|Scheduled')
expect(status.group).to eq 'scheduled'
expect(status.icon).to eq 'status_scheduled'
expect(status.favicon).to eq 'favicon_status_scheduled'
diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb
index 7fae76f61ea..ddb8b7ecff9 100644
--- a/spec/lib/gitlab/ci/status/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/canceled_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Status::Canceled do
end
describe '#text' do
- it { expect(subject.text).to eq 'canceled' }
+ it { expect(subject.text).to eq 'Canceled' }
end
describe '#label' do
diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb
index 1e54d1ed8c5..19fecbb33b9 100644
--- a/spec/lib/gitlab/ci/status/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/created_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Status::Created do
end
describe '#text' do
- it { expect(subject.text).to eq 'created' }
+ it { expect(subject.text).to eq 'Created' }
end
describe '#label' do
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
index 94a6255f1e2..277b440a21d 100644
--- a/spec/lib/gitlab/ci/status/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -74,7 +74,7 @@ RSpec.describe Gitlab::Ci::Status::Factory do
end
it 'delegates to core status' do
- expect(fabricated_status.text).to eq 'passed'
+ expect(fabricated_status.text).to eq 'Passed'
end
it 'latest matches status becomes a status name' do
@@ -104,7 +104,7 @@ RSpec.describe Gitlab::Ci::Status::Factory do
end
it 'delegates to core status' do
- expect(fabricated_status.text).to eq 'passed'
+ expect(fabricated_status.text).to eq 'Passed'
end
it 'matches correct core status' do
diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb
index f3f3304b04d..48df3e99855 100644
--- a/spec/lib/gitlab/ci/status/failed_spec.rb
+++ b/spec/lib/gitlab/ci/status/failed_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Status::Failed do
end
describe '#text' do
- it { expect(subject.text).to eq 'failed' }
+ it { expect(subject.text).to eq 'Failed' }
end
describe '#label' do
diff --git a/spec/lib/gitlab/ci/status/manual_spec.rb b/spec/lib/gitlab/ci/status/manual_spec.rb
index a9203438898..6e02772f670 100644
--- a/spec/lib/gitlab/ci/status/manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/manual_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Status::Manual do
end
describe '#text' do
- it { expect(subject.text).to eq 'manual' }
+ it { expect(subject.text).to eq 'Manual' }
end
describe '#label' do
diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb
index 1c062a0133d..82ea987e4c9 100644
--- a/spec/lib/gitlab/ci/status/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/pending_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Status::Pending do
end
describe '#text' do
- it { expect(subject.text).to eq 'pending' }
+ it { expect(subject.text).to eq 'Pending' }
end
describe '#label' do
diff --git a/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb b/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
index 8fd974972e4..8948d83f9cb 100644
--- a/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Gitlab::Ci::Status::Pipeline::Blocked do
describe '#text' do
it 'overrides status text' do
- expect(subject.text).to eq 'blocked'
+ expect(subject.text).to eq 'Blocked'
end
end
diff --git a/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
index 1302c2069ff..072ea642e70 100644
--- a/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Gitlab::Ci::Status::Pipeline::Delayed do
describe '#text' do
it 'overrides status text' do
- expect(subject.text).to eq 'delayed'
+ expect(subject.text).to eq 'Delayed'
end
end
diff --git a/spec/lib/gitlab/ci/status/preparing_spec.rb b/spec/lib/gitlab/ci/status/preparing_spec.rb
index ec1850c1959..f9033bce5f2 100644
--- a/spec/lib/gitlab/ci/status/preparing_spec.rb
+++ b/spec/lib/gitlab/ci/status/preparing_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Status::Preparing do
end
describe '#text' do
- it { expect(subject.text).to eq 'preparing' }
+ it { expect(subject.text).to eq 'Preparing' }
end
describe '#label' do
diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb
index e40d696ee4d..aefc7e90e85 100644
--- a/spec/lib/gitlab/ci/status/running_spec.rb
+++ b/spec/lib/gitlab/ci/status/running_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Status::Running do
end
describe '#text' do
- it { expect(subject.text).to eq 'running' }
+ it { expect(subject.text).to eq 'Running' }
end
describe '#label' do
diff --git a/spec/lib/gitlab/ci/status/scheduled_spec.rb b/spec/lib/gitlab/ci/status/scheduled_spec.rb
index df72455d3c1..1a8e48052ec 100644
--- a/spec/lib/gitlab/ci/status/scheduled_spec.rb
+++ b/spec/lib/gitlab/ci/status/scheduled_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Status::Scheduled, feature_category: :continuous_inte
end
describe '#text' do
- it { expect(subject.text).to eq 'scheduled' }
+ it { expect(subject.text).to eq 'Scheduled' }
end
describe '#label' do
diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb
index ac3c2f253f7..da674df2090 100644
--- a/spec/lib/gitlab/ci/status/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/skipped_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Status::Skipped do
end
describe '#text' do
- it { expect(subject.text).to eq 'skipped' }
+ it { expect(subject.text).to eq 'Skipped' }
end
describe '#label' do
diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb
index f2069334abd..c6567684ac0 100644
--- a/spec/lib/gitlab/ci/status/success_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Status::Success do
end
describe '#text' do
- it { expect(subject.text).to eq 'passed' }
+ it { expect(subject.text).to eq 'Passed' }
end
describe '#label' do
diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb
index 1725f90a0cf..4a669da358e 100644
--- a/spec/lib/gitlab/ci/status/success_warning_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb
@@ -9,8 +9,8 @@ RSpec.describe Gitlab::Ci::Status::SuccessWarning, feature_category: :continuous
described_class.new(status)
end
- describe '#test' do
- it { expect(subject.text).to eq 'warning' }
+ describe '#text' do
+ it { expect(subject.text).to eq 'Warning' }
end
describe '#label' do
@@ -25,6 +25,10 @@ RSpec.describe Gitlab::Ci::Status::SuccessWarning, feature_category: :continuous
it { expect(subject.group).to eq 'success-with-warnings' }
end
+ describe '#name' do
+ it { expect(subject.name).to eq 'SUCCESS_WITH_WARNINGS' }
+ end
+
describe '.matches?' do
let(:matchable) { double('matchable') }
diff --git a/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb b/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
index 6f5ab77a358..bd9663fb80f 100644
--- a/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
+++ b/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Status::WaitingForResource do
end
describe '#text' do
- it { expect(subject.text).to eq 'waiting' }
+ it { expect(subject.text).to eq 'Waiting' }
end
describe '#label' do
@@ -27,6 +27,10 @@ RSpec.describe Gitlab::Ci::Status::WaitingForResource do
it { expect(subject.group).to eq 'waiting-for-resource' }
end
+ describe '#name' do
+ it { expect(subject.name).to eq 'WAITING_FOR_RESOURCE' }
+ end
+
describe '#details_path' do
it { expect(subject.details_path).to be_nil }
end
diff --git a/spec/lib/gitlab/ci/variables/builder/group_spec.rb b/spec/lib/gitlab/ci/variables/builder/group_spec.rb
index c3743ebd2d7..004e63f424f 100644
--- a/spec/lib/gitlab/ci/variables/builder/group_spec.rb
+++ b/spec/lib/gitlab/ci/variables/builder/group_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Variables::Builder::Group do
+RSpec.describe Gitlab::Ci::Variables::Builder::Group, feature_category: :secrets_management do
let_it_be(:group) { create(:group) }
let(:builder) { described_class.new(group) }
@@ -185,21 +185,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Group do
end
end
- context 'recursive' do
- before do
- stub_feature_flags(use_traversal_ids: false)
- end
-
- include_examples 'correct ancestor order'
- end
-
- context 'linear' do
- before do
- stub_feature_flags(use_traversal_ids: true)
- end
-
- include_examples 'correct ancestor order'
- end
+ include_examples 'correct ancestor order'
end
end
end
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index f7c6f7f51df..d96c8f1bd0c 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Variables::Collection::Item do
+RSpec.describe Gitlab::Ci::Variables::Collection::Item, feature_category: :secrets_management do
let(:variable_key) { 'VAR' }
let(:variable_value) { 'something' }
let(:expected_value) { variable_value }
@@ -217,6 +217,25 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do
end
end
+ describe '#masked?' do
+ let(:variable_hash) { { key: variable_key, value: variable_value } }
+ let(:item) { described_class.new(**variable_hash) }
+
+ context 'when :masked is not specified' do
+ it 'returns false' do
+ expect(item.masked?).to eq(false)
+ end
+ end
+
+ context 'when :masked is specified as true' do
+ let(:variable_hash) { { key: variable_key, value: variable_value, masked: true } }
+
+ it 'returns true' do
+ expect(item.masked?).to eq(true)
+ end
+ end
+ end
+
describe '#to_runner_variable' do
context 'when variable is not a file-related' do
it 'returns a runner-compatible hash representation' do
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 5cfd8d9b9fb..81bc8c7ab9a 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -794,28 +794,6 @@ module Gitlab
it_behaves_like 'returns errors', 'test_job_1 has the following needs duplicated: test_job_2.'
end
-
- context 'when needed job name is too long' do
- let(:job_name) { 'a' * (::Ci::BuildNeed::MAX_JOB_NAME_LENGTH + 1) }
-
- let(:config) do
- <<-EOYML
- lint_job:
- script: 'echo lint_job'
- rules:
- - if: $var == null
- needs: [#{job_name}]
- #{job_name}:
- script: 'echo job'
- EOYML
- end
-
- it 'returns an error' do
- expect(subject.errors).to include(
- "lint_job job: need `#{job_name}` name is too long (maximum is #{::Ci::BuildNeed::MAX_JOB_NAME_LENGTH} characters)"
- )
- end
- end
end
context 'rule needs as hash' do
@@ -3659,7 +3637,8 @@ module Gitlab
context 'when a project ref does not contain the forked commit sha' do
it 'returns an error' do
is_expected.not_to be_valid
- expect(subject.errors).to include(/Could not validate configuration/)
+ expect(subject.errors).to include(
+ /configuration originates from an external project or a commit not associated with a Git reference/)
end
it_behaves_like 'when the processor is executed twice consecutively'
diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
index 3682a654181..9e2f3bda14c 100644
--- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
+++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
@@ -577,17 +577,6 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader, feature_category: :s
end
end
- context 'when browsersdk_tracking is disabled' do
- before do
- stub_feature_flags(browsersdk_tracking: false)
- stub_env('GITLAB_ANALYTICS_URL', analytics_url)
- end
-
- it 'does not add GITLAB_ANALYTICS_URL to connect-src' do
- expect(connect_src).not_to include(analytics_url)
- end
- end
-
context 'when GITLAB_ANALYTICS_URL is not set' do
before do
stub_env('GITLAB_ANALYTICS_URL', nil)
diff --git a/spec/lib/gitlab/database/click_house_client_spec.rb b/spec/lib/gitlab/database/click_house_client_spec.rb
index 6e63ae56557..271500ed3f6 100644
--- a/spec/lib/gitlab/database/click_house_client_spec.rb
+++ b/spec/lib/gitlab/database/click_house_client_spec.rb
@@ -112,6 +112,28 @@ RSpec.describe 'ClickHouse::Client', :click_house, feature_category: :database d
results = ClickHouse::Client.select(select_query, :main)
expect(results).to be_empty
+
+ # Async, lazy deletion
+ # Set the `deleted` field to 1 and update the `updated_at` timestamp.
+ # Based on the highest version of the given row (updated_at), CH will eventually remove the row.
+ # See: https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replacingmergetree#is_deleted
+ soft_delete_query = ClickHouse::Client::Query.new(
+ raw_query: %{
+ INSERT INTO events (id, deleted, updated_at)
+ VALUES ({id:UInt64}, 1, {updated_at:DateTime64(6, 'UTC')})
+ },
+ placeholders: { id: event2.id, updated_at: (event2.updated_at + 2.hours).utc.to_f }
+ )
+
+ ClickHouse::Client.execute(soft_delete_query, :main)
+
+ select_query = ClickHouse::Client::Query.new(
+ raw_query: 'SELECT * FROM events FINAL WHERE id = {id:UInt64}',
+ placeholders: { id: event2.id }
+ )
+
+ results = ClickHouse::Client.select(select_query, :main)
+ expect(results).to be_empty
end
end
end
diff --git a/spec/lib/gitlab/database/gitlab_schema_spec.rb b/spec/lib/gitlab/database/gitlab_schema_spec.rb
index e402014df90..a6de695c345 100644
--- a/spec/lib/gitlab/database/gitlab_schema_spec.rb
+++ b/spec/lib/gitlab/database/gitlab_schema_spec.rb
@@ -226,57 +226,83 @@ RSpec.describe Gitlab::Database::GitlabSchema, feature_category: :database do
allow_cross_joins: %i[gitlab_shared],
allow_cross_transactions: %i[gitlab_internal gitlab_shared],
allow_cross_foreign_keys: %i[]
+ ),
+ Gitlab::Database::GitlabSchemaInfo.new(
+ name: "gitlab_main_cell",
+ allow_cross_joins: [
+ :gitlab_shared,
+ :gitlab_main,
+ { gitlab_main_clusterwide: { specific_tables: %w[plans] } }
+ ],
+ allow_cross_transactions: [
+ :gitlab_internal,
+ :gitlab_shared,
+ :gitlab_main,
+ { gitlab_main_clusterwide: { specific_tables: %w[plans] } }
+ ],
+ allow_cross_foreign_keys: [
+ { gitlab_main_clusterwide: { specific_tables: %w[plans] } }
+ ]
)
].index_by(&:name)
)
end
describe '.cross_joins_allowed?' do
- where(:schemas, :result) do
- %i[] | true
- %i[gitlab_main_clusterwide gitlab_main] | true
- %i[gitlab_main_clusterwide gitlab_ci] | false
- %i[gitlab_main_clusterwide gitlab_main gitlab_ci] | false
- %i[gitlab_main_clusterwide gitlab_internal] | false
- %i[gitlab_main gitlab_ci] | false
- %i[gitlab_main_clusterwide gitlab_main gitlab_shared] | true
- %i[gitlab_main_clusterwide gitlab_shared] | true
+ where(:schemas, :tables, :result) do
+ %i[] | %i[] | true
+ %i[gitlab_main] | %i[] | true
+ %i[gitlab_main_clusterwide gitlab_main] | %i[] | true
+ %i[gitlab_main_clusterwide gitlab_ci] | %i[] | false
+ %i[gitlab_main_clusterwide gitlab_main gitlab_ci] | %i[] | false
+ %i[gitlab_main_clusterwide gitlab_internal] | %i[] | false
+ %i[gitlab_main gitlab_ci] | %i[] | false
+ %i[gitlab_main_clusterwide gitlab_main gitlab_shared] | %i[] | true
+ %i[gitlab_main_clusterwide gitlab_shared] | %i[] | true
+ %i[gitlab_main_clusterwide gitlab_main_cell] | %w[users namespaces] | false
+ %i[gitlab_main_clusterwide gitlab_main_cell] | %w[plans namespaces] | true
end
with_them do
- it { expect(described_class.cross_joins_allowed?(schemas)).to eq(result) }
+ it { expect(described_class.cross_joins_allowed?(schemas, tables)).to eq(result) }
end
end
describe '.cross_transactions_allowed?' do
- where(:schemas, :result) do
- %i[] | true
- %i[gitlab_main_clusterwide gitlab_main] | true
- %i[gitlab_main_clusterwide gitlab_ci] | false
- %i[gitlab_main_clusterwide gitlab_main gitlab_ci] | false
- %i[gitlab_main_clusterwide gitlab_internal] | true
- %i[gitlab_main gitlab_ci] | false
- %i[gitlab_main_clusterwide gitlab_main gitlab_shared] | true
- %i[gitlab_main_clusterwide gitlab_shared] | true
+ where(:schemas, :tables, :result) do
+ %i[] | %i[] | true
+ %i[gitlab_main] | %i[] | true
+ %i[gitlab_main_clusterwide gitlab_main] | %i[] | true
+ %i[gitlab_main_clusterwide gitlab_ci] | %i[] | false
+ %i[gitlab_main_clusterwide gitlab_main gitlab_ci] | %i[] | false
+ %i[gitlab_main_clusterwide gitlab_internal] | %i[] | true
+ %i[gitlab_main gitlab_ci] | %i[] | false
+ %i[gitlab_main_clusterwide gitlab_main gitlab_shared] | %i[] | true
+ %i[gitlab_main_clusterwide gitlab_shared] | %i[] | true
+ %i[gitlab_main_clusterwide gitlab_main_cell] | %w[users namespaces] | false
+ %i[gitlab_main_clusterwide gitlab_main_cell] | %w[plans namespaces] | true
end
with_them do
- it { expect(described_class.cross_transactions_allowed?(schemas)).to eq(result) }
+ it { expect(described_class.cross_transactions_allowed?(schemas, tables)).to eq(result) }
end
end
describe '.cross_foreign_key_allowed?' do
- where(:schemas, :result) do
- %i[] | false
- %i[gitlab_main_clusterwide gitlab_main] | true
- %i[gitlab_main_clusterwide gitlab_ci] | false
- %i[gitlab_main_clusterwide gitlab_internal] | false
- %i[gitlab_main gitlab_ci] | false
- %i[gitlab_main_clusterwide gitlab_shared] | false
+ where(:schemas, :tables, :result) do
+ %i[] | %i[] | false
+ %i[gitlab_main] | %i[] | true
+ %i[gitlab_main_clusterwide gitlab_main] | %i[] | true
+ %i[gitlab_main_clusterwide gitlab_ci] | %i[] | false
+ %i[gitlab_main_clusterwide gitlab_internal] | %i[] | false
+ %i[gitlab_main gitlab_ci] | %i[] | false
+ %i[gitlab_main_clusterwide gitlab_shared] | %i[] | false
+ %i[gitlab_main_clusterwide gitlab_main_cell] | %w[users namespaces] | false
+ %i[gitlab_main_clusterwide gitlab_main_cell] | %w[plans namespaces] | true
end
with_them do
- it { expect(described_class.cross_foreign_key_allowed?(schemas)).to eq(result) }
+ it { expect(described_class.cross_foreign_key_allowed?(schemas, tables)).to eq(result) }
end
end
end
diff --git a/spec/lib/gitlab/database/load_balancing/service_discovery_spec.rb b/spec/lib/gitlab/database/load_balancing/service_discovery_spec.rb
index 7197b99fe33..442fa678d4e 100644
--- a/spec/lib/gitlab/database/load_balancing/service_discovery_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/service_discovery_spec.rb
@@ -194,7 +194,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::ServiceDiscovery, feature_catego
describe '#replace_hosts' do
before do
- stub_env('LOAD_BALANCER_PARALLEL_DISCONNECT', 'true')
allow(service)
.to receive(:load_balancer)
.and_return(load_balancer)
@@ -257,26 +256,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::ServiceDiscovery, feature_catego
service.replace_hosts([address_foo, address_bar])
end
end
-
- context 'when LOAD_BALANCER_PARALLEL_DISCONNECT is false' do
- before do
- stub_env('LOAD_BALANCER_PARALLEL_DISCONNECT', 'false')
- end
-
- it 'disconnects them sequentially' do
- host = load_balancer.host_list.hosts.first
-
- allow(service)
- .to receive(:disconnect_timeout)
- .and_return(2)
-
- expect(host)
- .to receive(:disconnect!)
- .with(timeout: 2)
-
- service.replace_hosts([address_bar])
- end
- end
end
describe '#addresses_from_dns' do
diff --git a/spec/lib/gitlab/database/migration_helpers/swapping_spec.rb b/spec/lib/gitlab/database/migration_helpers/swapping_spec.rb
new file mode 100644
index 00000000000..0940c6f4c30
--- /dev/null
+++ b/spec/lib/gitlab/database/migration_helpers/swapping_spec.rb
@@ -0,0 +1,172 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::MigrationHelpers::Swapping, feature_category: :database do
+ let(:connection) { ApplicationRecord.connection }
+ let(:migration_context) do
+ ActiveRecord::Migration
+ .new
+ .extend(described_class)
+ .extend(Gitlab::Database::MigrationHelpers)
+ end
+
+ let(:service_instance) { instance_double('Gitlab::Database::Migrations::SwapColumns', execute: nil) }
+
+ describe '#reset_trigger_function' do
+ let(:trigger_function_name) { 'existing_trigger_function' }
+
+ before do
+ connection.execute(<<~SQL)
+ CREATE FUNCTION #{trigger_function_name}() RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+ BEGIN
+ NEW."bigint_column" := NEW."integer_column";
+ RETURN NEW;
+ END;
+ $$;
+ SQL
+ end
+
+ it 'resets' do
+ recorder = ActiveRecord::QueryRecorder.new do
+ migration_context.reset_trigger_function(trigger_function_name)
+ end
+ expect(recorder.log).to include(/ALTER FUNCTION "existing_trigger_function" RESET ALL/)
+ end
+ end
+
+ describe '#swap_columns' do
+ let(:table) { :ci_pipeline_variables }
+ let(:column1) { :pipeline_id }
+ let(:column2) { :pipeline_id_convert_to_bigint }
+
+ it 'calls service' do
+ expect(::Gitlab::Database::Migrations::SwapColumns).to receive(:new).with(
+ migration_context: migration_context,
+ table: table,
+ column1: column1,
+ column2: column2
+ ).and_return(service_instance)
+
+ migration_context.swap_columns(table, column1, column2)
+ end
+ end
+
+ describe '#swap_columns_default' do
+ let(:table) { :_test_table }
+ let(:column1) { :pipeline_id }
+ let(:column2) { :pipeline_id_convert_to_bigint }
+
+ it 'calls service' do
+ expect(::Gitlab::Database::Migrations::SwapColumnsDefault).to receive(:new).with(
+ migration_context: migration_context,
+ table: table,
+ column1: column1,
+ column2: column2
+ ).and_return(service_instance)
+
+ migration_context.swap_columns_default(table, column1, column2)
+ end
+ end
+
+ describe '#swap_foreign_keys' do
+ let(:table) { :_test_swap_foreign_keys }
+ let(:referenced_table) { "#{table}_referenced" }
+ let(:foreign_key1) { :fkey_on_integer_column }
+ let(:foreign_key2) { :fkey_on_bigint_column }
+
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE #{table} (
+ integer_column integer NOT NULL,
+ bigint_column bigint DEFAULT 0 NOT NULL
+ );
+ CREATE TABLE #{referenced_table} (
+ id bigint NOT NULL
+ );
+
+ ALTER TABLE ONLY #{referenced_table}
+ ADD CONSTRAINT pk PRIMARY KEY (id);
+
+ ALTER TABLE ONLY #{table}
+ ADD CONSTRAINT #{foreign_key1}
+ FOREIGN KEY (integer_column) REFERENCES #{referenced_table}(id) ON DELETE SET NULL;
+
+ ALTER TABLE ONLY #{table}
+ ADD CONSTRAINT #{foreign_key2}
+ FOREIGN KEY (bigint_column) REFERENCES #{referenced_table}(id) ON DELETE SET NULL;
+ SQL
+ end
+
+ shared_examples_for 'swapping foreign keys correctly' do
+ specify do
+ expect { migration_context.swap_foreign_keys(table, foreign_key1, foreign_key2) }
+ .to change {
+ find_foreign_key_by(foreign_key1).options[:column]
+ }.from('integer_column').to('bigint_column')
+ .and change {
+ find_foreign_key_by(foreign_key2).options[:column]
+ }.from('bigint_column').to('integer_column')
+ end
+ end
+
+ it_behaves_like 'swapping foreign keys correctly'
+
+ context 'when foreign key names are 63 bytes' do
+ let(:foreign_key1) { :f1_012345678901234567890123456789012345678901234567890123456789 }
+ let(:foreign_key2) { :f2_012345678901234567890123456789012345678901234567890123456789 }
+
+ it_behaves_like 'swapping foreign keys correctly'
+ end
+
+ private
+
+ def find_foreign_key_by(name)
+ connection.foreign_keys(table).find { |k| k.options[:name].to_s == name.to_s }
+ end
+ end
+
+ describe '#swap_indexes' do
+ let(:table) { :_test_swap_indexes }
+ let(:index1) { :index_on_integer }
+ let(:index2) { :index_on_bigint }
+
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE #{table} (
+ integer_column integer NOT NULL,
+ bigint_column bigint DEFAULT 0 NOT NULL
+ );
+
+ CREATE INDEX #{index1} ON #{table} USING btree (integer_column);
+
+ CREATE INDEX #{index2} ON #{table} USING btree (bigint_column);
+ SQL
+ end
+
+ shared_examples_for 'swapping indexes correctly' do
+ specify do
+ expect { migration_context.swap_indexes(table, index1, index2) }
+ .to change { find_index_by(index1).columns }.from(['integer_column']).to(['bigint_column'])
+ .and change { find_index_by(index2).columns }.from(['bigint_column']).to(['integer_column'])
+ end
+ end
+
+ it_behaves_like 'swapping indexes correctly'
+
+ context 'when index names are 63 bytes' do
+ let(:index1) { :i1_012345678901234567890123456789012345678901234567890123456789 }
+ let(:index2) { :i2_012345678901234567890123456789012345678901234567890123456789 }
+
+ it_behaves_like 'swapping indexes correctly'
+ end
+
+ private
+
+ def find_index_by(name)
+ connection.indexes(table).find { |c| c.name == name.to_s }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index f3c181db3aa..dd51cca688c 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -1774,6 +1774,35 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
end
describe '#copy_indexes' do
+ context 'when index name is too long' do
+ it 'does not fail' do
+ index = double(:index,
+ columns: %w(uuid),
+ name: 'index_vuln_findings_on_uuid_including_vuln_id_1',
+ using: nil,
+ where: nil,
+ opclasses: {},
+ unique: true,
+ lengths: [],
+ orders: [])
+
+ allow(model).to receive(:indexes_for).with(:vulnerability_occurrences, 'uuid')
+ .and_return([index])
+
+ expect(model).to receive(:add_concurrent_index)
+ .with(:vulnerability_occurrences,
+ %w(tmp_undo_cleanup_column_8cbf300838),
+ {
+ unique: true,
+ name: 'idx_copy_191a1af1a0',
+ length: [],
+ order: []
+ })
+
+ model.copy_indexes(:vulnerability_occurrences, :uuid, :tmp_undo_cleanup_column_8cbf300838)
+ end
+ end
+
context 'using a regular index using a single column' do
it 'copies the index' do
index = double(:index,
@@ -2326,6 +2355,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
end
describe '#revert_initialize_conversion_of_integer_to_bigint' do
+ let(:setup_table) { true }
let(:table) { :_test_table }
before do
@@ -2334,7 +2364,18 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
t.integer :other_id
end
- model.initialize_conversion_of_integer_to_bigint(table, columns)
+ model.initialize_conversion_of_integer_to_bigint(table, columns) if setup_table
+ end
+
+ context 'when column and trigger do not exist' do
+ let(:setup_table) { false }
+ let(:columns) { :id }
+
+ it 'does not raise an error' do
+ expect do
+ model.revert_initialize_conversion_of_integer_to_bigint(table, columns)
+ end.not_to raise_error
+ end
end
context 'when single column is given' do
@@ -2906,4 +2947,20 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
it { expect(recorder.log).to be_empty }
end
end
+
+ describe '#lock_tables' do
+ let(:lock_statement) do
+ /LOCK TABLE ci_builds, ci_pipelines IN ACCESS EXCLUSIVE MODE/
+ end
+
+ subject(:recorder) do
+ ActiveRecord::QueryRecorder.new do
+ model.lock_tables(:ci_builds, :ci_pipelines)
+ end
+ end
+
+ it 'locks the tables' do
+ expect(recorder.log).to include(lock_statement)
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
index 158497b1fef..f1271f2434c 100644
--- a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers do
+RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers, feature_category: :database do
let(:migration_class) do
Class.new(ActiveRecord::Migration[6.1])
.include(described_class)
@@ -70,39 +70,54 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
end
end
- it 'creates the database record for the migration' do
- expect(Gitlab::Database::PgClass).to receive(:for_table).with(:projects).and_return(pgclass_info)
+ context "when the migration doesn't exist already" do
+ before do
+ allow(Gitlab::Database::PgClass).to receive(:for_table).with(:projects).and_return(pgclass_info)
+ end
- expect do
+ subject(:enqueue_batched_background_migration) do
migration.queue_batched_background_migration(
job_class.name,
:projects,
:id,
job_interval: 5.minutes,
+ queued_migration_version: format("%.14d", 123),
batch_min_value: 5,
batch_max_value: 1000,
batch_class_name: 'MyBatchClass',
batch_size: 100,
max_batch_size: 10000,
sub_batch_size: 10,
- gitlab_schema: :gitlab_ci)
- end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1)
-
- expect(Gitlab::Database::BackgroundMigration::BatchedMigration.last).to have_attributes(
- job_class_name: 'MyJobClass',
- table_name: 'projects',
- column_name: 'id',
- interval: 300,
- min_value: 5,
- max_value: 1000,
- batch_class_name: 'MyBatchClass',
- batch_size: 100,
- max_batch_size: 10000,
- sub_batch_size: 10,
- job_arguments: %w[],
- status_name: :active,
- total_tuple_count: pgclass_info.cardinality_estimate,
- gitlab_schema: 'gitlab_ci')
+ gitlab_schema: :gitlab_ci
+ )
+ end
+
+ it 'enqueues exactly one batched migration' do
+ expect { enqueue_batched_background_migration }
+ .to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1)
+ end
+
+ it 'creates the database record for the migration' do
+ batched_background_migration = enqueue_batched_background_migration
+
+ expect(batched_background_migration.reload).to have_attributes(
+ job_class_name: 'MyJobClass',
+ table_name: 'projects',
+ column_name: 'id',
+ interval: 300,
+ min_value: 5,
+ max_value: 1000,
+ batch_class_name: 'MyBatchClass',
+ batch_size: 100,
+ max_batch_size: 10000,
+ sub_batch_size: 10,
+ job_arguments: %w[],
+ status_name: :active,
+ total_tuple_count: pgclass_info.cardinality_estimate,
+ gitlab_schema: 'gitlab_ci',
+ queued_migration_version: format("%.14d", 123)
+ )
+ end
end
context 'when the job interval is lower than the minimum' do
diff --git a/spec/lib/gitlab/database/migrations/milestone_mixin_spec.rb b/spec/lib/gitlab/database/migrations/milestone_mixin_spec.rb
new file mode 100644
index 00000000000..e375af494a2
--- /dev/null
+++ b/spec/lib/gitlab/database/migrations/milestone_mixin_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Migrations::MilestoneMixin, feature_category: :database do
+ let(:migration_no_mixin) do
+ Class.new(Gitlab::Database::Migration[2.1]) do
+ def change
+ # no-op here to make rubocop happy
+ end
+ end
+ end
+
+ let(:migration_mixin) do
+ Class.new(Gitlab::Database::Migration[2.1]) do
+ include Gitlab::Database::Migrations::MilestoneMixin
+ end
+ end
+
+ let(:migration_mixin_version) do
+ Class.new(Gitlab::Database::Migration[2.1]) do
+ include Gitlab::Database::Migrations::MilestoneMixin
+ milestone '16.4'
+ end
+ end
+
+ context 'when the mixin is not included' do
+ it 'does not raise an error' do
+ expect { migration_no_mixin.new(4, 4) }.not_to raise_error
+ end
+ end
+
+ context 'when the mixin is included' do
+ context 'when a milestone is not specified' do
+ it "raises MilestoneNotSetError" do
+ expect { migration_mixin.new(4, 4, :regular) }.to raise_error(
+ "#{described_class}::MilestoneNotSetError".constantize
+ )
+ end
+ end
+
+ context 'when a milestone is specified' do
+ it "does not raise an error" do
+ expect { migration_mixin_version.new(4, 4, :regular) }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/migrations/observers/query_statistics_spec.rb b/spec/lib/gitlab/database/migrations/observers/query_statistics_spec.rb
index 66de25d65bb..330c9d18fb2 100644
--- a/spec/lib/gitlab/database/migrations/observers/query_statistics_spec.rb
+++ b/spec/lib/gitlab/database/migrations/observers/query_statistics_spec.rb
@@ -41,7 +41,13 @@ RSpec.describe Gitlab::Database::Migrations::Observers::QueryStatistics do
let(:result) { double }
let(:pgss_query) do
<<~SQL
- SELECT query, calls, total_time, max_time, mean_time, rows
+ SELECT
+ query,
+ calls,
+ total_exec_time + total_plan_time AS total_time,
+ max_exec_time + max_plan_time AS max_time,
+ mean_exec_time + mean_plan_time AS mean_time,
+ "rows"
FROM pg_stat_statements
WHERE pg_get_userbyid(userid) = current_user
ORDER BY total_time DESC
diff --git a/spec/lib/gitlab/database/migrations/swap_columns_default_spec.rb b/spec/lib/gitlab/database/migrations/swap_columns_default_spec.rb
new file mode 100644
index 00000000000..e53480d453e
--- /dev/null
+++ b/spec/lib/gitlab/database/migrations/swap_columns_default_spec.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Migrations::SwapColumnsDefault, feature_category: :database do
+ describe '#execute' do
+ let(:connection) { ApplicationRecord.connection }
+ let(:migration_context) do
+ Gitlab::Database::Migration[2.1]
+ .new('name', 'version')
+ .extend(Gitlab::Database::MigrationHelpers::Swapping)
+ end
+
+ let(:table) { :_test_swap_columns_and_defaults }
+ let(:column1) { :integer_column }
+ let(:column2) { :bigint_column }
+
+ subject(:execute_service) do
+ described_class.new(
+ migration_context: migration_context,
+ table: table,
+ column1: column1,
+ column2: column2
+ ).execute
+ end
+
+ before do
+ connection.execute(sql)
+ end
+
+ context 'when defaults are static values' do
+ let(:sql) do
+ <<~SQL
+ CREATE TABLE #{table} (
+ id integer NOT NULL,
+ #{column1} integer DEFAULT 8 NOT NULL,
+ #{column2} bigint DEFAULT 100 NOT NULL
+ );
+ SQL
+ end
+
+ it 'swaps the default correctly' do
+ expect { execute_service }
+ .to change { find_column_by(column1).default }.to('100')
+ .and change { find_column_by(column2).default }.to('8')
+ .and not_change { find_column_by(column1).default_function }.from(nil)
+ .and not_change { find_column_by(column2).default_function }.from(nil)
+ end
+ end
+
+ context 'when default is sequence' do
+ let(:sql) do
+ <<~SQL
+ CREATE TABLE #{table} (
+ id integer NOT NULL,
+ #{column1} integer NOT NULL,
+ #{column2} bigint DEFAULT 100 NOT NULL
+ );
+
+ CREATE SEQUENCE #{table}_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ ALTER SEQUENCE #{table}_seq OWNED BY #{table}.#{column1};
+ ALTER TABLE ONLY #{table} ALTER COLUMN #{column1} SET DEFAULT nextval('#{table}_seq'::regclass);
+ SQL
+ end
+
+ it 'swaps the default correctly' do
+ recorder = nil
+ expect { recorder = ActiveRecord::QueryRecorder.new { execute_service } }
+ .to change { find_column_by(column1).default }.to('100')
+ .and change { find_column_by(column1).default_function }.to(nil)
+ .and change { find_column_by(column2).default }.to(nil)
+ .and change {
+ find_column_by(column2).default_function
+ }.to("nextval('_test_swap_columns_and_defaults_seq'::regclass)")
+ expect(recorder.log).to include(
+ /SEQUENCE "_test_swap_columns_and_defaults_seq" OWNED BY "_test_swap_columns_and_defaults"."bigint_column"/
+ )
+ expect(recorder.log).to include(
+ /COLUMN "bigint_column" SET DEFAULT nextval\('_test_swap_columns_and_defaults_seq'::regclass\)/
+ )
+ end
+ end
+
+ context 'when defaults are the same' do
+ let(:sql) do
+ <<~SQL
+ CREATE TABLE #{table} (
+ id integer NOT NULL,
+ #{column1} integer DEFAULT 100 NOT NULL,
+ #{column2} bigint DEFAULT 100 NOT NULL
+ );
+ SQL
+ end
+
+ it 'does nothing' do
+ recorder = nil
+ expect { recorder = ActiveRecord::QueryRecorder.new { execute_service } }
+ .to not_change { find_column_by(column1).default }
+ .and not_change { find_column_by(column1).default_function }
+ .and not_change { find_column_by(column2).default }
+ .and not_change { find_column_by(column2).default_function }
+ expect(recorder.log).not_to include(/ALTER TABLE/)
+ end
+ end
+
+ private
+
+ def find_column_by(name)
+ connection.columns(table).find { |c| c.name == name.to_s }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/migrations/swap_columns_spec.rb b/spec/lib/gitlab/database/migrations/swap_columns_spec.rb
new file mode 100644
index 00000000000..a119b23dda4
--- /dev/null
+++ b/spec/lib/gitlab/database/migrations/swap_columns_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Migrations::SwapColumns, feature_category: :database do
+ describe '#execute' do
+ let(:connection) { ApplicationRecord.connection }
+ let(:sql) do
+ <<~SQL
+ CREATE TABLE #{table} (
+ id integer NOT NULL,
+ #{column1} integer DEFAULT 8 NOT NULL,
+ #{column2} bigint DEFAULT 100 NOT NULL
+ );
+ SQL
+ end
+
+ let(:migration_context) do
+ Gitlab::Database::Migration[2.1]
+ .new('name', 'version')
+ .extend(Gitlab::Database::MigrationHelpers::Swapping)
+ end
+
+ let(:table) { :_test_swap_columns_and_defaults }
+ let(:column1) { :integer_column }
+ let(:column2) { :bigint_column }
+
+ subject(:execute_service) do
+ described_class.new(
+ migration_context: migration_context,
+ table: table,
+ column1: column1,
+ column2: column2
+ ).execute
+ end
+
+ before do
+ connection.execute(sql)
+ end
+
+ shared_examples_for 'swapping columns correctly' do
+ specify do
+ expect { execute_service }
+ .to change { find_column_by(column1).sql_type }.from('integer').to('bigint')
+ .and change { find_column_by(column2).sql_type }.from('bigint').to('integer')
+ end
+ end
+
+ it_behaves_like 'swapping columns correctly'
+
+ context 'when column names are 63 bytes' do
+ let(:column1) { :int012345678901234567890123456789012345678901234567890123456789 }
+ let(:column2) { :big012345678901234567890123456789012345678901234567890123456789 }
+
+ it_behaves_like 'swapping columns correctly'
+ end
+
+ private
+
+ def find_column_by(name)
+ connection.columns(table).find { |c| c.name == name.to_s }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/migrations/version_spec.rb b/spec/lib/gitlab/database/migrations/version_spec.rb
new file mode 100644
index 00000000000..821a2156539
--- /dev/null
+++ b/spec/lib/gitlab/database/migrations/version_spec.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Migrations::Version, feature_category: :database do
+ let(:test_versions) do
+ [
+ 4,
+ 5,
+ described_class.new(6, Gitlab::VersionInfo.parse_from_milestone('10.3'), :regular),
+ 7,
+ described_class.new(8, Gitlab::VersionInfo.parse_from_milestone('10.3'), :regular),
+ described_class.new(9, Gitlab::VersionInfo.parse_from_milestone('10.4'), :regular),
+ described_class.new(10, Gitlab::VersionInfo.parse_from_milestone('10.3'), :post),
+ described_class.new(11, Gitlab::VersionInfo.parse_from_milestone('10.3'), :regular)
+ ]
+ end
+
+ describe "#<=>" do
+ it 'sorts by existence of milestone, then by milestone, then by type, then by timestamp when sorted by version' do
+ expect(test_versions.sort.map(&:to_i)).to eq [4, 5, 7, 6, 8, 11, 10, 9]
+ end
+ end
+
+ describe 'initialize' do
+ context 'when the type is :post or :regular' do
+ it 'does not raise an error' do
+ expect { described_class.new(4, 4, :regular) }.not_to raise_error
+ expect { described_class.new(4, 4, :post) }.not_to raise_error
+ end
+ end
+
+ context 'when the type is anything else' do
+ it 'does not raise an error' do
+ expect { described_class.new(4, 4, 'foo') }.to raise_error("#{described_class}::InvalidTypeError".constantize)
+ end
+ end
+ end
+
+ describe 'eql?' do
+ where(:version1, :version2, :expected_equality) do
+ [
+ [
+ described_class.new(4, Gitlab::VersionInfo.parse_from_milestone('10.3'), :regular),
+ described_class.new(4, Gitlab::VersionInfo.parse_from_milestone('10.3'), :regular),
+ true
+ ],
+ [
+ described_class.new(4, Gitlab::VersionInfo.parse_from_milestone('10.3'), :regular),
+ described_class.new(4, Gitlab::VersionInfo.parse_from_milestone('10.4'), :regular),
+ false
+ ],
+ [
+ described_class.new(4, Gitlab::VersionInfo.parse_from_milestone('10.3'), :regular),
+ described_class.new(4, Gitlab::VersionInfo.parse_from_milestone('10.3'), :post),
+ false
+ ],
+ [
+ described_class.new(4, Gitlab::VersionInfo.parse_from_milestone('10.3'), :regular),
+ described_class.new(5, Gitlab::VersionInfo.parse_from_milestone('10.3'), :regular),
+ false
+ ]
+ ]
+ end
+
+ with_them do
+ it 'correctly evaluates deep equality' do
+ expect(version1.eql?(version2)).to eq(expected_equality)
+ end
+
+ it 'correctly evaluates deep equality using ==' do
+ expect(version1 == version2).to eq(expected_equality)
+ end
+ end
+ end
+
+ describe 'type' do
+ subject { described_class.new(4, Gitlab::VersionInfo.parse_from_milestone('10.3'), migration_type) }
+
+ context 'when the migration is regular' do
+ let(:migration_type) { :regular }
+
+ it 'correctly identifies the migration type' do
+ expect(subject.type).to eq(:regular)
+ expect(subject.regular?).to eq(true)
+ expect(subject.post_deployment?).to eq(false)
+ end
+ end
+
+ context 'when the migration is post_deployment' do
+ let(:migration_type) { :post }
+
+ it 'correctly identifies the migration type' do
+ expect(subject.type).to eq(:post)
+ expect(subject.regular?).to eq(false)
+ expect(subject.post_deployment?).to eq(true)
+ end
+ end
+ end
+
+ describe 'to_s' do
+ subject { described_class.new(4, Gitlab::VersionInfo.parse_from_milestone('10.3'), :regular) }
+
+ it 'returns the given timestamp value as a string' do
+ expect(subject.to_s).to eql('4')
+ end
+ end
+
+ describe 'hash' do
+ subject { described_class.new(4, Gitlab::VersionInfo.parse_from_milestone('10.3'), :regular) }
+
+ let(:expected_hash) { subject.hash }
+
+ it 'deterministically returns a hash of the timestamp, milestone, and type value' do
+ 3.times do
+ expect(subject.hash).to eq(expected_hash)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
index 2fa4c9e562f..c6cd5e55754 100644
--- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
@@ -23,8 +23,6 @@ RSpec.describe 'cross-database foreign keys' do
'merge_requests.merge_user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080
'merge_requests.author_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080
'project_authorizations.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422044
- 'projects.creator_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/421844
- 'projects.marked_for_deletion_by_user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/421844
'user_group_callouts.user_id' # https://gitlab.com/gitlab-org/gitlab/-/issues/421287
]
end
@@ -34,9 +32,11 @@ RSpec.describe 'cross-database foreign keys' do
end
def is_cross_db?(fk_record)
- table_schemas = Gitlab::Database::GitlabSchema.table_schemas!([fk_record.from_table, fk_record.to_table])
+ tables = [fk_record.from_table, fk_record.to_table]
- !Gitlab::Database::GitlabSchema.cross_foreign_key_allowed?(table_schemas)
+ table_schemas = Gitlab::Database::GitlabSchema.table_schemas!(tables)
+
+ !Gitlab::Database::GitlabSchema.cross_foreign_key_allowed?(table_schemas, tables)
end
it 'onlies have allowed list of cross-database foreign keys', :aggregate_failures do
diff --git a/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb b/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb
index c41228777ca..80ffa708d8a 100644
--- a/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb
+++ b/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb
@@ -322,74 +322,33 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager, feature_categor
allow(connection).to receive(:select_value).and_return(nil, Time.current, Time.current)
end
- context 'when feature flag database_analyze_on_partitioned_tables is enabled' do
- before do
- stub_feature_flags(database_analyze_on_partitioned_tables: true)
- end
-
- it_behaves_like 'run only once analyze within interval'
+ it_behaves_like 'run only once analyze within interval'
- context 'when analyze is false' do
- let(:analyze) { false }
+ context 'when analyze is false' do
+ let(:analyze) { false }
- it_behaves_like 'not to run the analyze at all'
- end
+ it_behaves_like 'not to run the analyze at all'
+ end
- context 'when model does not set analyze_interval' do
- let(:my_model) do
- Class.new(ApplicationRecord) do
- include PartitionedTable
+ context 'when model does not set analyze_interval' do
+ let(:my_model) do
+ Class.new(ApplicationRecord) do
+ include PartitionedTable
- partitioned_by :partition_id,
- strategy: :ci_sliding_list,
- next_partition_if: proc { false },
- detach_partition_if: proc { false }
- end
+ partitioned_by :partition_id,
+ strategy: :ci_sliding_list,
+ next_partition_if: proc { false },
+ detach_partition_if: proc { false }
end
-
- it_behaves_like 'not to run the analyze at all'
- end
-
- context 'when no partition is created' do
- let(:create_partition) { false }
-
- it_behaves_like 'run only once analyze within interval'
- end
- end
-
- context 'when feature flag database_analyze_on_partitioned_tables is disabled' do
- before do
- stub_feature_flags(database_analyze_on_partitioned_tables: false)
end
it_behaves_like 'not to run the analyze at all'
+ end
- context 'when analyze is false' do
- let(:analyze) { false }
-
- it_behaves_like 'not to run the analyze at all'
- end
-
- context 'when model does not set analyze_interval' do
- let(:my_model) do
- Class.new(ApplicationRecord) do
- include PartitionedTable
-
- partitioned_by :partition_id,
- strategy: :ci_sliding_list,
- next_partition_if: proc { false },
- detach_partition_if: proc { false }
- end
- end
-
- it_behaves_like 'not to run the analyze at all'
- end
-
- context 'when no partition is created' do
- let(:create_partition) { false }
+ context 'when no partition is created' do
+ let(:create_partition) { false }
- it_behaves_like 'not to run the analyze at all'
- end
+ it_behaves_like 'run only once analyze within interval'
end
end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
deleted file mode 100644
index 370d03b495c..00000000000
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ /dev/null
@@ -1,292 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete, feature_category: :groups_and_projects do
- let(:migration) { FakeRenameReservedPathMigrationV1.new }
- let(:subject) { described_class.new(['the-path'], migration) }
-
- before do
- allow(migration).to receive(:say)
- TestEnv.clean_test_path
- end
-
- def migration_namespace(namespace)
- Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::
- Namespace.find(namespace.id)
- end
-
- def migration_project(project)
- Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::
- Project.find(project.id)
- end
-
- describe "#remove_last_occurrence" do
- it "removes only the last occurrence of a string" do
- input = "this/is/a-word-to-replace/namespace/with/a-word-to-replace"
-
- expect(subject.remove_last_occurrence(input, "a-word-to-replace"))
- .to eq("this/is/a-word-to-replace/namespace/with/")
- end
- end
-
- describe '#remove_cached_html_for_projects' do
- let(:project) { create(:project, description_html: 'Project description') }
-
- it 'removes description_html from projects' do
- subject.remove_cached_html_for_projects([project.id])
-
- expect(project.reload.description_html).to be_nil
- end
-
- it 'removes issue descriptions' do
- issue = create(:issue, project: project, description_html: 'Issue description')
-
- subject.remove_cached_html_for_projects([project.id])
-
- expect(issue.reload.description_html).to be_nil
- end
-
- it 'removes merge request descriptions' do
- merge_request = create(:merge_request,
- source_project: project,
- target_project: project,
- description_html: 'MergeRequest description')
-
- subject.remove_cached_html_for_projects([project.id])
-
- expect(merge_request.reload.description_html).to be_nil
- end
-
- it 'removes note html' do
- note = create(:note,
- project: project,
- noteable: create(:issue, project: project),
- note_html: 'note description')
-
- subject.remove_cached_html_for_projects([project.id])
-
- expect(note.reload.note_html).to be_nil
- end
-
- it 'removes milestone description' do
- milestone = create(:milestone,
- project: project,
- description_html: 'milestone description')
-
- subject.remove_cached_html_for_projects([project.id])
-
- expect(milestone.reload.description_html).to be_nil
- end
- end
-
- describe '#rename_path_for_routable' do
- context 'for personal namespaces' do
- let(:namespace) { create(:namespace, path: 'the-path') }
-
- it "renames namespaces called the-path" do
- subject.rename_path_for_routable(migration_namespace(namespace))
-
- expect(namespace.reload.path).to eq("the-path0")
- end
-
- it "renames the route to the namespace" do
- subject.rename_path_for_routable(migration_namespace(namespace))
-
- expect(Namespace.find(namespace.id).full_path).to eq("the-path0")
- end
-
- it "renames the route for projects of the namespace" do
- project = create(:project, :repository, path: "project-path", namespace: namespace)
-
- subject.rename_path_for_routable(migration_namespace(namespace))
-
- expect(project.route.reload.path).to eq("the-path0/project-path")
- end
-
- it 'returns the old & the new path' do
- old_path, new_path = subject.rename_path_for_routable(migration_namespace(namespace))
-
- expect(old_path).to eq('the-path')
- expect(new_path).to eq('the-path0')
- end
-
- it "doesn't rename routes that start with a similar name" do
- other_namespace = create(:namespace, path: 'the-path-but-not-really')
- project = create(:project, path: 'the-project', namespace: other_namespace)
-
- subject.rename_path_for_routable(migration_namespace(namespace))
-
- expect(project.route.reload.path).to eq('the-path-but-not-really/the-project')
- end
- end
-
- context 'for groups' do
- context "the-path group -> subgroup -> the-path0 project" do
- it "updates the route of the project correctly" do
- group = create(:group, path: 'the-path')
- subgroup = create(:group, path: "subgroup", parent: group)
- project = create(:project, :repository, path: "the-path0", namespace: subgroup)
-
- subject.rename_path_for_routable(migration_namespace(group))
-
- expect(project.route.reload.path).to eq("the-path0/subgroup/the-path0")
- end
- end
- end
-
- context 'for projects' do
- let(:parent) { create(:namespace, path: 'the-parent') }
- let(:project) { create(:project, path: 'the-path', namespace: parent) }
-
- it 'renames the project called `the-path`' do
- subject.rename_path_for_routable(migration_project(project))
-
- expect(project.reload.path).to eq('the-path0')
- end
-
- it 'renames the route for the project' do
- subject.rename_path_for_routable(project)
-
- expect(project.reload.route.path).to eq('the-parent/the-path0')
- end
-
- it 'returns the old & new path' do
- old_path, new_path = subject.rename_path_for_routable(migration_project(project))
-
- expect(old_path).to eq('the-parent/the-path')
- expect(new_path).to eq('the-parent/the-path0')
- end
- end
- end
-
- describe '#perform_rename' do
- context 'for personal namespaces' do
- it 'renames the path' do
- namespace = create(:namespace, path: 'the-path')
-
- subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed')
-
- expect(namespace.reload.path).to eq('renamed')
- expect(namespace.reload.route.path).to eq('renamed')
- end
- end
-
- context 'for groups' do
- it 'renames all the routes for the group' do
- group = create(:group, path: 'the-path')
- child = create(:group, path: 'child', parent: group)
- project = create(:project, :repository, namespace: child, path: 'the-project')
- other_one = create(:group, path: 'the-path-is-similar')
-
- subject.perform_rename(migration_namespace(group), 'the-path', 'renamed')
-
- expect(group.reload.route.path).to eq('renamed')
- expect(child.reload.route.path).to eq('renamed/child')
- expect(project.reload.route.path).to eq('renamed/child/the-project')
- expect(other_one.reload.route.path).to eq('the-path-is-similar')
- end
- end
- end
-
- describe '#move_pages' do
- it 'moves the pages directory' do
- expect(subject).to receive(:move_folders)
- .with(TestEnv.pages_path, 'old-path', 'new-path')
-
- subject.move_pages('old-path', 'new-path')
- end
- end
-
- describe "#move_uploads" do
- let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'rename_reserved_paths') }
- let(:uploads_dir) { File.join(test_dir, 'public', 'uploads') }
-
- it 'moves subdirectories in the uploads folder' do
- expect(subject).to receive(:uploads_dir).and_return(uploads_dir)
- expect(subject).to receive(:move_folders).with(uploads_dir, 'old_path', 'new_path')
-
- subject.move_uploads('old_path', 'new_path')
- end
-
- it "doesn't move uploads when they are stored in object storage" do
- expect(subject).to receive(:file_storage?).and_return(false)
- expect(subject).not_to receive(:move_folders)
-
- subject.move_uploads('old_path', 'new_path')
- end
- end
-
- describe '#move_folders' do
- let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'rename_reserved_paths') }
- let(:uploads_dir) { File.join(test_dir, 'public', 'uploads') }
-
- before do
- FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
- FileUtils.mkdir_p(uploads_dir)
- allow(subject).to receive(:uploads_dir).and_return(uploads_dir)
- end
-
- it 'moves a folder with files' do
- source = File.join(uploads_dir, 'parent-group', 'sub-group')
- FileUtils.mkdir_p(source)
- destination = File.join(uploads_dir, 'parent-group', 'moved-group')
- FileUtils.touch(File.join(source, 'test.txt'))
- expected_file = File.join(destination, 'test.txt')
-
- subject.move_folders(uploads_dir, File.join('parent-group', 'sub-group'), File.join('parent-group', 'moved-group'))
-
- expect(File.exist?(expected_file)).to be(true)
- end
- end
-
- describe '#track_rename', :redis do
- it 'tracks a rename in redis' do
- key = 'rename:FakeRenameReservedPathMigrationV1:namespace'
-
- subject.track_rename('namespace', 'path/to/namespace', 'path/to/renamed')
-
- old_path = nil
- new_path = nil
- Gitlab::Redis::SharedState.with do |redis|
- rename_info = redis.lpop(key)
- old_path, new_path = Gitlab::Json.parse(rename_info)
- end
-
- expect(old_path).to eq('path/to/namespace')
- expect(new_path).to eq('path/to/renamed')
- end
- end
-
- describe '#reverts_for_type', :redis do
- it 'yields for each tracked rename' do
- subject.track_rename('project', 'old_path', 'new_path')
- subject.track_rename('project', 'old_path2', 'new_path2')
- subject.track_rename('namespace', 'namespace_path', 'new_namespace_path')
-
- expect { |b| subject.reverts_for_type('project', &b) }
- .to yield_successive_args(%w(old_path2 new_path2), %w(old_path new_path))
- expect { |b| subject.reverts_for_type('namespace', &b) }
- .to yield_with_args('namespace_path', 'new_namespace_path')
- end
-
- it 'keeps the revert in redis if it failed' do
- subject.track_rename('project', 'old_path', 'new_path')
-
- subject.reverts_for_type('project') do
- raise 'whatever happens, keep going!'
- end
-
- key = 'rename:FakeRenameReservedPathMigrationV1:project'
- stored_renames = nil
- rename_count = 0
- Gitlab::Redis::SharedState.with do |redis|
- stored_renames = redis.lrange(key, 0, 1)
- rename_count = redis.llen(key)
- end
-
- expect(rename_count).to eq(1)
- expect(Gitlab::Json.parse(stored_renames.first)).to eq(%w(old_path new_path))
- end
- end
-end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
deleted file mode 100644
index b00a1d4a9e1..00000000000
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
+++ /dev/null
@@ -1,313 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :delete,
-feature_category: :groups_and_projects do
- let(:migration) { FakeRenameReservedPathMigrationV1.new }
- let(:subject) { described_class.new(['the-path'], migration) }
- let(:namespace) { create(:group, name: 'the-path') }
-
- before do
- allow(migration).to receive(:say)
- TestEnv.clean_test_path
- end
-
- def migration_namespace(namespace)
- Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::
- Namespace.find(namespace.id)
- end
-
- describe '#namespaces_for_paths' do
- context 'nested namespaces' do
- let(:subject) { described_class.new(['parent/the-Path'], migration) }
-
- it 'includes the namespace' do
- parent = create(:group, path: 'parent')
- child = create(:group, path: 'the-path', parent: parent)
-
- found_ids = subject.namespaces_for_paths(type: :child)
- .map(&:id)
-
- expect(found_ids).to contain_exactly(child.id)
- end
- end
-
- context 'for child namespaces' do
- it 'only returns child namespaces with the correct path' do
- _root_namespace = create(:group, path: 'THE-path')
- _other_path = create(:group,
- path: 'other',
- parent: create(:group))
- namespace = create(:group,
- path: 'the-path',
- parent: create(:group))
-
- found_ids = subject.namespaces_for_paths(type: :child)
- .map(&:id)
-
- expect(found_ids).to contain_exactly(namespace.id)
- end
-
- it 'has no namespaces that look the same' do
- _root_namespace = create(:group, path: 'THE-path')
- _similar_path = create(:group,
- path: 'not-really-the-path',
- parent: create(:group))
- namespace = create(:group,
- path: 'the-path',
- parent: create(:group))
-
- found_ids = subject.namespaces_for_paths(type: :child)
- .map(&:id)
-
- expect(found_ids).to contain_exactly(namespace.id)
- end
- end
-
- context 'for top levelnamespaces' do
- it 'only returns child namespaces with the correct path' do
- root_namespace = create(:group, path: 'the-path')
- _other_path = create(:group, path: 'other')
- _child_namespace = create(:group,
- path: 'the-path',
- parent: create(:group))
-
- found_ids = subject.namespaces_for_paths(type: :top_level)
- .map(&:id)
-
- expect(found_ids).to contain_exactly(root_namespace.id)
- end
-
- it 'has no namespaces that just look the same' do
- root_namespace = create(:group, path: 'the-path')
- _similar_path = create(:group, path: 'not-really-the-path')
- _child_namespace = create(:group,
- path: 'the-path',
- parent: create(:group))
-
- found_ids = subject.namespaces_for_paths(type: :top_level)
- .map(&:id)
-
- expect(found_ids).to contain_exactly(root_namespace.id)
- end
- end
- end
-
- describe '#move_repositories' do
- let(:namespace) { create(:group, name: 'hello-group') }
-
- it 'moves a project for a namespace' do
- project = create(:project, :repository, :legacy_storage, namespace: namespace, path: 'hello-project')
- expected_repository = Gitlab::Git::Repository.new(
- project.repository_storage,
- 'bye-group/hello-project.git',
- nil,
- nil
- )
-
- subject.move_repositories(namespace, 'hello-group', 'bye-group')
-
- expect(expected_repository).to exist
- end
-
- it 'moves a namespace in a subdirectory correctly' do
- child_namespace = create(:group, name: 'sub-group', parent: namespace)
- project = create(:project, :repository, :legacy_storage, namespace: child_namespace, path: 'hello-project')
-
- expected_repository = Gitlab::Git::Repository.new(
- project.repository_storage,
- 'hello-group/renamed-sub-group/hello-project.git',
- nil,
- nil
- )
-
- subject.move_repositories(child_namespace, 'hello-group/sub-group', 'hello-group/renamed-sub-group')
-
- expect(expected_repository).to exist
- end
-
- it 'moves a parent namespace with subdirectories' do
- child_namespace = create(:group, name: 'sub-group', parent: namespace)
- project = create(:project, :repository, :legacy_storage, namespace: child_namespace, path: 'hello-project')
- expected_repository = Gitlab::Git::Repository.new(
- project.repository_storage,
- 'renamed-group/sub-group/hello-project.git',
- nil,
- nil
- )
-
- subject.move_repositories(child_namespace, 'hello-group', 'renamed-group')
-
- expect(expected_repository).to exist
- end
- end
-
- describe "#child_ids_for_parent" do
- it "collects child ids for all levels" do
- parent = create(:group)
- first_child = create(:group, parent: parent)
- second_child = create(:group, parent: parent)
- third_child = create(:group, parent: second_child)
- all_ids = [parent.id, first_child.id, second_child.id, third_child.id]
-
- collected_ids = subject.child_ids_for_parent(parent, ids: [parent.id])
-
- expect(collected_ids).to contain_exactly(*all_ids)
- end
- end
-
- describe "#rename_namespace" do
- it 'renames paths & routes for the namespace' do
- expect(subject).to receive(:rename_path_for_routable)
- .with(namespace)
- .and_call_original
-
- subject.rename_namespace(namespace)
-
- expect(namespace.reload.path).to eq('the-path0')
- end
-
- it 'tracks the rename' do
- expect(subject).to receive(:track_rename)
- .with('namespace', 'the-path', 'the-path0')
-
- subject.rename_namespace(namespace)
- end
-
- it 'renames things related to the namespace' do
- expect(subject).to receive(:rename_namespace_dependencies)
- .with(namespace, 'the-path', 'the-path0')
-
- subject.rename_namespace(namespace)
- end
- end
-
- describe '#rename_namespace_dependencies' do
- it "moves the repository for a project in the namespace" do
- project = create(:project, :repository, :legacy_storage, namespace: namespace, path: "the-path-project")
- expected_repository = Gitlab::Git::Repository.new(
- project.repository_storage,
- "the-path0/the-path-project.git",
- nil,
- nil
- )
-
- subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
-
- expect(expected_repository).to exist
- end
-
- it "moves the uploads for the namespace" do
- expect(subject).to receive(:move_uploads).with("the-path", "the-path0")
-
- subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
- end
-
- it "moves the pages for the namespace" do
- expect(subject).to receive(:move_pages).with("the-path", "the-path0")
-
- subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
- end
-
- it 'invalidates the markdown cache of related projects' do
- project = create(:project, :legacy_storage, namespace: namespace, path: "the-path-project")
-
- expect(subject).to receive(:remove_cached_html_for_projects).with([project.id])
-
- subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
- end
-
- it "doesn't rename users for other namespaces" do
- expect(subject).not_to receive(:rename_user)
-
- subject.rename_namespace_dependencies(namespace, 'the-path', 'the-path0')
- end
-
- it 'renames the username of a namespace for a user' do
- user = create(:user, username: 'the-path')
-
- expect(subject).to receive(:rename_user).with('the-path', 'the-path0')
-
- subject.rename_namespace_dependencies(user.namespace, 'the-path', 'the-path0')
- end
- end
-
- describe '#rename_user' do
- it 'renames a username' do
- subject = described_class.new([], migration)
- user = create(:user, username: 'broken')
-
- subject.rename_user('broken', 'broken0')
-
- expect(user.reload.username).to eq('broken0')
- end
- end
-
- describe '#rename_namespaces' do
- let!(:top_level_namespace) { create(:group, path: 'the-path') }
- let!(:child_namespace) do
- create(:group, path: 'the-path', parent: create(:group))
- end
-
- it 'renames top level namespaces the namespace' do
- expect(subject).to receive(:rename_namespace)
- .with(migration_namespace(top_level_namespace))
-
- subject.rename_namespaces(type: :top_level)
- end
-
- it 'renames child namespaces' do
- expect(subject).to receive(:rename_namespace)
- .with(migration_namespace(child_namespace))
-
- subject.rename_namespaces(type: :child)
- end
- end
-
- describe '#revert_renames', :redis do
- it 'renames the routes back to the previous values' do
- project = create(:project, :legacy_storage, :repository, path: 'a-project', namespace: namespace)
- subject.rename_namespace(namespace)
-
- expect(subject).to receive(:perform_rename)
- .with(
- kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Namespace),
- 'the-path0',
- 'the-path'
- ).and_call_original
-
- subject.revert_renames
-
- expect(namespace.reload.path).to eq('the-path')
- expect(namespace.reload.route.path).to eq('the-path')
- expect(project.reload.route.path).to eq('the-path/a-project')
- end
-
- it 'moves the repositories back to their original place' do
- project = create(:project, :repository, :legacy_storage, path: 'a-project', namespace: namespace)
- project.create_repository
- subject.rename_namespace(namespace)
-
- expected_repository = Gitlab::Git::Repository.new(project.repository_storage, 'the-path/a-project.git', nil, nil)
-
- expect(subject).to receive(:rename_namespace_dependencies)
- .with(
- kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Namespace),
- 'the-path0',
- 'the-path'
- ).and_call_original
-
- subject.revert_renames
-
- expect(expected_repository).to exist
- end
-
- it "doesn't break when the namespace was renamed" do
- subject.rename_namespace(namespace)
- namespace.update!(path: 'renamed-afterwards')
-
- expect { subject.revert_renames }.not_to raise_error
- end
- end
-end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
deleted file mode 100644
index d2665664fb0..00000000000
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
+++ /dev/null
@@ -1,190 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :delete,
-feature_category: :groups_and_projects do
- let(:migration) { FakeRenameReservedPathMigrationV1.new }
- let(:subject) { described_class.new(['the-path'], migration) }
- let(:project) do
- create(:project,
- :legacy_storage,
- path: 'the-path',
- namespace: create(:namespace, path: 'known-parent' ))
- end
-
- before do
- allow(migration).to receive(:say)
- TestEnv.clean_test_path
- end
-
- describe '#projects_for_paths' do
- it 'searches using nested paths' do
- namespace = create(:namespace, path: 'hello')
- project = create(:project, :legacy_storage, path: 'THE-path', namespace: namespace)
-
- result_ids = described_class.new(['Hello/the-path'], migration)
- .projects_for_paths.map(&:id)
-
- expect(result_ids).to contain_exactly(project.id)
- end
-
- it 'includes the correct projects' do
- project = create(:project, :legacy_storage, path: 'THE-path')
- _other_project = create(:project, :legacy_storage)
-
- result_ids = subject.projects_for_paths.map(&:id)
-
- expect(result_ids).to contain_exactly(project.id)
- end
- end
-
- describe '#rename_projects' do
- let!(:projects) { create_list(:project, 2, :legacy_storage, path: 'the-path') }
-
- it 'renames each project' do
- expect(subject).to receive(:rename_project).twice
-
- subject.rename_projects
- end
-
- it 'invalidates the markdown cache of related projects' do
- expect(subject).to receive(:remove_cached_html_for_projects)
- .with(a_collection_containing_exactly(*projects.map(&:id)))
-
- subject.rename_projects
- end
- end
-
- describe '#rename_project' do
- it 'renames path & route for the project' do
- expect(subject).to receive(:rename_path_for_routable)
- .with(project)
- .and_call_original
-
- subject.rename_project(project)
-
- expect(project.reload.path).to eq('the-path0')
- end
-
- it 'tracks the rename' do
- expect(subject).to receive(:track_rename)
- .with('project', 'known-parent/the-path', 'known-parent/the-path0')
-
- subject.rename_project(project)
- end
-
- it 'renames the folders for the project' do
- expect(subject).to receive(:move_project_folders).with(project, 'known-parent/the-path', 'known-parent/the-path0')
-
- subject.rename_project(project)
- end
- end
-
- describe '#move_project_folders' do
- it 'moves the wiki & the repo' do
- expect(subject).to receive(:move_repository)
- .with(project, 'known-parent/the-path.wiki', 'known-parent/the-path0.wiki')
- expect(subject).to receive(:move_repository)
- .with(project, 'known-parent/the-path', 'known-parent/the-path0')
-
- subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
- end
-
- it 'does not move the repositories when hashed storage is enabled' do
- project.update!(storage_version: Project::HASHED_STORAGE_FEATURES[:repository])
-
- expect(subject).not_to receive(:move_repository)
-
- subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
- end
-
- it 'moves uploads' do
- expect(subject).to receive(:move_uploads)
- .with('known-parent/the-path', 'known-parent/the-path0')
-
- subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
- end
-
- it 'does not move uploads when hashed storage is enabled for attachments' do
- project.update!(storage_version: Project::HASHED_STORAGE_FEATURES[:attachments])
-
- expect(subject).not_to receive(:move_uploads)
-
- subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
- end
-
- it 'moves pages' do
- expect(subject).to receive(:move_pages)
- .with('known-parent/the-path', 'known-parent/the-path0')
-
- subject.move_project_folders(project, 'known-parent/the-path', 'known-parent/the-path0')
- end
- end
-
- describe '#move_repository' do
- let(:known_parent) { create(:namespace, path: 'known-parent') }
- let(:project) { create(:project, :repository, :legacy_storage, path: 'the-path', namespace: known_parent) }
-
- it 'moves the repository for a project' do
- expected_repository = Gitlab::Git::Repository.new(
- project.repository_storage,
- 'known-parent/new-repo.git',
- nil,
- nil
- )
-
- subject.move_repository(project, 'known-parent/the-path', 'known-parent/new-repo')
-
- expect(expected_repository).to exist
- end
- end
-
- describe '#revert_renames', :redis do
- it 'renames the routes back to the previous values' do
- subject.rename_project(project)
-
- expect(subject).to receive(:perform_rename)
- .with(
- kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Project),
- 'known-parent/the-path0',
- 'known-parent/the-path'
- ).and_call_original
-
- subject.revert_renames
-
- expect(project.reload.path).to eq('the-path')
- expect(project.route.path).to eq('known-parent/the-path')
- end
-
- it 'moves the repositories back to their original place' do
- project.create_repository
- subject.rename_project(project)
-
- expected_repository = Gitlab::Git::Repository.new(
- project.repository_storage,
- 'known-parent/the-path.git',
- nil,
- nil
- )
-
- expect(subject).to receive(:move_project_folders)
- .with(
- kind_of(Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Project),
- 'known-parent/the-path0',
- 'known-parent/the-path'
- ).and_call_original
-
- subject.revert_renames
-
- expect(expected_repository).to exist
- end
-
- it "doesn't break when the project was renamed" do
- subject.rename_project(project)
- project.update!(path: 'renamed-afterwards')
-
- expect { subject.revert_renames }.not_to raise_error
- end
- end
-end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
deleted file mode 100644
index 3b2d3ab1354..00000000000
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.shared_examples 'renames child namespaces' do |type|
- it 'renames namespaces' do
- rename_namespaces = double
- expect(described_class::RenameNamespaces)
- .to receive(:new).with(%w[first-path second-path], subject)
- .and_return(rename_namespaces)
- expect(rename_namespaces).to receive(:rename_namespaces)
- .with(type: :child)
-
- subject.rename_wildcard_paths(%w[first-path second-path])
- end
-end
-
-RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do
- let(:subject) { FakeRenameReservedPathMigrationV1.new }
-
- before do
- allow(subject).to receive(:say)
- end
-
- describe '#rename_child_paths' do
- it_behaves_like 'renames child namespaces'
- end
-
- describe '#rename_wildcard_paths' do
- it_behaves_like 'renames child namespaces'
-
- it 'renames projects' do
- rename_projects = double
- expect(described_class::RenameProjects)
- .to receive(:new).with(['the-path'], subject)
- .and_return(rename_projects)
-
- expect(rename_projects).to receive(:rename_projects)
-
- subject.rename_wildcard_paths(['the-path'])
- end
- end
-
- describe '#rename_root_paths' do
- it 'renames namespaces' do
- rename_namespaces = double
- expect(described_class::RenameNamespaces)
- .to receive(:new).with(['the-path'], subject)
- .and_return(rename_namespaces)
- expect(rename_namespaces).to receive(:rename_namespaces)
- .with(type: :top_level)
-
- subject.rename_root_paths('the-path')
- end
- end
-
- describe '#revert_renames' do
- it 'renames namespaces' do
- rename_namespaces = double
- expect(described_class::RenameNamespaces)
- .to receive(:new).with([], subject)
- .and_return(rename_namespaces)
- expect(rename_namespaces).to receive(:revert_renames)
-
- subject.revert_renames
- end
-
- it 'renames projects' do
- rename_projects = double
- expect(described_class::RenameProjects)
- .to receive(:new).with([], subject)
- .and_return(rename_projects)
- expect(rename_projects).to receive(:revert_renames)
-
- subject.revert_renames
- end
- end
-end
diff --git a/spec/lib/gitlab/database_importers/work_items/related_links_restrictions_importer_spec.rb b/spec/lib/gitlab/database_importers/work_items/related_links_restrictions_importer_spec.rb
new file mode 100644
index 00000000000..39d02922acc
--- /dev/null
+++ b/spec/lib/gitlab/database_importers/work_items/related_links_restrictions_importer_spec.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::DatabaseImporters::WorkItems::RelatedLinksRestrictionsImporter,
+ feature_category: :portfolio_management do
+ subject { described_class.upsert_restrictions }
+
+ it_behaves_like 'work item related links restrictions importer'
+end
diff --git a/spec/lib/gitlab/deploy_key_access_spec.rb b/spec/lib/gitlab/deploy_key_access_spec.rb
index e32858cc13f..0a85fc5d967 100644
--- a/spec/lib/gitlab/deploy_key_access_spec.rb
+++ b/spec/lib/gitlab/deploy_key_access_spec.rb
@@ -23,16 +23,6 @@ RSpec.describe Gitlab::DeployKeyAccess, feature_category: :source_code_managemen
it 'returns false' do
expect(access.can_create_tag?('v0.1.2')).to be_falsey
end
-
- context 'when deploy_key_for_protected_tags FF is disabled' do
- before do
- stub_feature_flags(deploy_key_for_protected_tags: false)
- end
-
- it 'allows to push the tag' do
- expect(access.can_create_tag?('v0.1.2')).to be_truthy
- end
- end
end
context 'push tag that matches a protected tag pattern via a deploy key' do
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index 4aa4f160fc9..059058c5499 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -116,5 +116,71 @@ RSpec.describe Gitlab::Diff::PositionTracer do
expect(diff_refs.head_sha).to eq(new_diff_refs.head_sha)
end
end
+
+ describe 'when requesting diffs' do
+ shared_examples 'it does not call diff stats' do
+ it 'does not call diff stats' do
+ expect_next_instance_of(Gitlab::GitalyClient::CommitService) do |instance|
+ expect(instance).not_to receive(:diff_stats)
+ end
+
+ diff_files
+ end
+ end
+
+ shared_examples 'it calls diff stats' do
+ it 'calls diff stats' do
+ expect_next_instance_of(Gitlab::GitalyClient::CommitService) do |instance|
+ expect(instance).to receive(:diff_stats).and_call_original
+ end
+
+ diff_files
+ end
+ end
+
+ context 'when remove_request_stats_for_tracing is true' do
+ context 'ac diffs' do
+ let(:diff_files) { subject.ac_diffs.diff_files }
+
+ it_behaves_like 'it does not call diff stats'
+ end
+
+ context 'bd diffs' do
+ let(:diff_files) { subject.bd_diffs.diff_files }
+
+ it_behaves_like 'it does not call diff stats'
+ end
+
+ context 'cd diffs' do
+ let(:diff_files) { subject.cd_diffs.diff_files }
+
+ it_behaves_like 'it does not call diff stats'
+ end
+ end
+
+ context 'when remove_request_stats_for_tracing is false' do
+ before do
+ stub_feature_flags(remove_request_stats_for_tracing: false)
+ end
+
+ context 'ac diffs' do
+ let(:diff_files) { subject.ac_diffs.diff_files }
+
+ it_behaves_like 'it calls diff stats'
+ end
+
+ context 'bd diffs' do
+ let(:diff_files) { subject.bd_diffs.diff_files }
+
+ it_behaves_like 'it calls diff stats'
+ end
+
+ context 'cd diffs' do
+ let(:diff_files) { subject.cd_diffs.diff_files }
+
+ it_behaves_like 'it calls diff stats'
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/doctor/reset_tokens_spec.rb b/spec/lib/gitlab/doctor/reset_tokens_spec.rb
new file mode 100644
index 00000000000..0cc947efdb4
--- /dev/null
+++ b/spec/lib/gitlab/doctor/reset_tokens_spec.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Doctor::ResetTokens, feature_category: :runner_fleet do
+ let(:logger) { instance_double('Logger') }
+ let(:model_names) { %w[Project Group] }
+ let(:token_names) { %w[runners_token] }
+ let(:dry_run) { false }
+ let(:doctor) { described_class.new(logger, model_names: model_names, token_names: token_names, dry_run: dry_run) }
+
+ let_it_be(:functional_project) { create(:project).tap(&:runners_token) }
+ let_it_be(:functional_group) { create(:group).tap(&:runners_token) }
+
+ let(:broken_project) { create(:project).tap { |project| project.update_columns(runners_token_encrypted: 'aaa') } }
+ let(:project_with_cipher_error) do
+ create(:project).tap do |project|
+ project.update_columns(
+ runners_token_encrypted: '|rXs75DSHXPE9MGAIgyxcut8pZc72gaa/2ojU0GS1+R+cXNqkbUB13Vb5BaMwf47d98980fc1')
+ end
+ end
+
+ let(:broken_group) { create(:group, runners_token_encrypted: 'aaa') }
+
+ subject(:run!) do
+ expect(logger).to receive(:info).with(
+ "Resetting #{token_names.join(', ')} on #{model_names.join(', ')} if they can not be read"
+ )
+ expect(logger).to receive(:info).with('Done!')
+ doctor.run!
+ end
+
+ before do
+ allow(logger).to receive(:info).with(%r{Checked \d/\d Projects})
+ allow(logger).to receive(:info).with(%r{Checked \d Projects})
+ allow(logger).to receive(:info).with(%r{Checked \d/\d Groups})
+ allow(logger).to receive(:info).with(%r{Checked \d Groups})
+ end
+
+ it 'fixes broken project and not the functional project' do
+ expect(logger).to receive(:debug).with("> Fix Project[#{broken_project.id}].runners_token")
+
+ expect { run! }.to change { broken_project.reload.runners_token_encrypted }.from('aaa')
+ .and not_change { functional_project.reload.runners_token_encrypted }
+ expect { broken_project.runners_token }.not_to raise_error
+ end
+
+ it 'fixes project with cipher error' do
+ expect { project_with_cipher_error.runners_token }.to raise_error(OpenSSL::Cipher::CipherError)
+ expect(logger).to receive(:debug).with("> Fix Project[#{project_with_cipher_error.id}].runners_token")
+
+ expect { run! }.to change { project_with_cipher_error.reload.runners_token_encrypted }
+ expect { project_with_cipher_error.runners_token }.not_to raise_error
+ end
+
+ it 'fixes broken group and not the functional group' do
+ expect(logger).to receive(:debug).with("> Fix Group[#{broken_group.id}].runners_token")
+
+ expect { run! }.to change { broken_group.reload.runners_token_encrypted }.from('aaa')
+ .and not_change { functional_group.reload.runners_token_encrypted }
+
+ expect { broken_group.runners_token }.not_to raise_error
+ end
+
+ context 'when one model specified' do
+ let(:model_names) { %w[Project] }
+
+ it 'fixes broken project' do
+ expect(logger).to receive(:debug).with("> Fix Project[#{broken_project.id}].runners_token")
+
+ expect { run! }.to change { broken_project.reload.runners_token_encrypted }.from('aaa')
+ expect { broken_project.runners_token }.not_to raise_error
+ end
+
+ it 'does not fix other models' do
+ expect { run! }.not_to change { broken_group.reload.runners_token_encrypted }.from('aaa')
+ end
+ end
+
+ context 'when non-existing token field is given' do
+ let(:token_names) { %w[nonexisting_token] }
+
+ it 'does not fix anything' do
+ expect { run! }.not_to change { broken_project.reload.runners_token_encrypted }.from('aaa')
+ end
+ end
+
+ context 'when executing in a dry-run mode' do
+ let(:dry_run) { true }
+
+ it 'prints info about fixed project, but does not actually do anything' do
+ expect(logger).to receive(:info).with('Executing in DRY RUN mode, no records will actually be updated')
+ expect(logger).to receive(:debug).with("> Fix Project[#{broken_project.id}].runners_token")
+
+ expect { run! }.not_to change { broken_project.reload.runners_token_encrypted }.from('aaa')
+ expect { broken_project.runners_token }.to raise_error(TypeError)
+ end
+ end
+
+ it 'prints progress along the way' do
+ stub_const('Gitlab::Doctor::ResetTokens::PRINT_PROGRESS_EVERY', 1)
+
+ broken_project
+ project_with_cipher_error
+
+ expect(logger).to receive(:info).with(
+ "Resetting #{token_names.join(', ')} on #{model_names.join(', ')} if they can not be read"
+ )
+ expect(logger).to receive(:info).with('Checked 1/3 Projects')
+ expect(logger).to receive(:debug).with("> Fix Project[#{broken_project.id}].runners_token")
+ expect(logger).to receive(:info).with('Checked 2/3 Projects')
+ expect(logger).to receive(:debug).with("> Fix Project[#{project_with_cipher_error.id}].runners_token")
+ expect(logger).to receive(:info).with('Checked 3/3 Projects')
+ expect(logger).to receive(:info).with('Done!')
+
+ doctor.run!
+ end
+
+ it "prints 'Something went wrong' error when encounters unexpected exception, but continues" do
+ broken_project
+ project_with_cipher_error
+
+ expect(logger).to receive(:debug).with(
+ "> Something went wrong for Project[#{broken_project.id}].runners_token: Error message")
+ expect(logger).to receive(:debug).with("> Fix Project[#{project_with_cipher_error.id}].runners_token")
+
+ expect(broken_project).to receive(:runners_token).and_raise("Error message")
+ expect(Project).to receive(:find_each).and_return([broken_project, project_with_cipher_error].each)
+
+ expect { run! }.to not_change { broken_project.reload.runners_token_encrypted }.from('aaa')
+ .and change { project_with_cipher_error.reload.runners_token_encrypted }
+ end
+end
diff --git a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
index 6941ebd2e11..e6fff939632 100644
--- a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
@@ -321,7 +321,7 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se
end
end
- context 'when using custom service desk address' do
+ context 'when using additional service desk alias address' do
let(:receiver) { Gitlab::Email::ServiceDeskReceiver.new(email_raw) }
before do
@@ -587,6 +587,16 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler, feature_category: :se
end
end
+ context 'when there is no to address' do
+ before do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:to_address).and_return(nil)
+ end
+ end
+
+ it_behaves_like 'a new issue request'
+ end
+
context 'when there is no from address' do
before do
allow_next_instance_of(described_class) do |instance|
diff --git a/spec/lib/gitlab/email/message/build_ios_app_guide_spec.rb b/spec/lib/gitlab/email/message/build_ios_app_guide_spec.rb
deleted file mode 100644
index 4b77b2f7192..00000000000
--- a/spec/lib/gitlab/email/message/build_ios_app_guide_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Email::Message::BuildIosAppGuide, :saas do
- subject(:message) { described_class.new }
-
- it 'contains the correct message', :aggregate_failures do
- expect(message.subject_line).to eq 'Get set up to build for iOS'
- expect(message.title).to eq "Building for iOS? We've got you covered."
- expect(message.body_line1).to eq "Want to get your iOS app up and running, including " \
- "publishing all the way to TestFlight? Follow our guide to set up GitLab and fastlane to publish iOS apps to " \
- "the App Store."
- expect(message.cta_text).to eq 'Learn how to build for iOS'
- expect(message.cta2_text).to eq 'Watch iOS building in action.'
- expect(message.logo_path).to eq 'mailers/in_product_marketing/create-0.png'
- expect(message.unsubscribe).to include('%tag_unsubscribe_url%')
- end
-end
diff --git a/spec/lib/gitlab/email/message/in_product_marketing/helper_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/helper_spec.rb
deleted file mode 100644
index a3c2d1b428e..00000000000
--- a/spec/lib/gitlab/email/message/in_product_marketing/helper_spec.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Email::Message::InProductMarketing::Helper do
- describe 'unsubscribe_message' do
- include Gitlab::Routing
-
- let(:dummy_class_with_helper) do
- Class.new do
- include Gitlab::Email::Message::InProductMarketing::Helper
- include Gitlab::Routing
-
- def initialize(format = :html)
- @format = format
- end
-
- def default_url_options
- {}
- end
-
- attr_accessor :format
- end
- end
-
- let(:format) { :html }
-
- subject(:class_with_helper) { dummy_class_with_helper.new(format) }
-
- context 'for SaaS', :saas do
- context 'format is HTML' do
- it 'returns the correct HTML' do
- message = "If you no longer wish to receive marketing emails from us, " \
- "you may <a href=\"%tag_unsubscribe_url%\">unsubscribe</a> at any time."
- expect(class_with_helper.unsubscribe_message).to match message
- end
- end
-
- context 'format is text' do
- let(:format) { :text }
-
- it 'returns the correct string' do
- message = "If you no longer wish to receive marketing emails from us, " \
- "you may unsubscribe (%tag_unsubscribe_url%) at any time."
- expect(class_with_helper.unsubscribe_message.squish).to match message
- end
- end
- end
-
- context 'self-managed' do
- context 'format is HTML' do
- it 'returns the correct HTML' do
- preferences_link = "http://example.com/preferences"
- message = "To opt out of these onboarding emails, " \
- "<a href=\"#{profile_notifications_url}\">unsubscribe</a>. " \
- "If you don't want to receive marketing emails directly from GitLab, #{preferences_link}."
- expect(class_with_helper.unsubscribe_message(preferences_link))
- .to match message
- end
- end
-
- context 'format is text' do
- let(:format) { :text }
-
- it 'returns the correct string' do
- preferences_link = "http://example.com/preferences"
- message = "To opt out of these onboarding emails, " \
- "unsubscribe (#{profile_notifications_url}). " \
- "If you don't want to receive marketing emails directly from GitLab, #{preferences_link}."
- expect(class_with_helper.unsubscribe_message(preferences_link).squish).to match message
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index ee836fc2129..f8084d24850 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe Gitlab::Email::Receiver do
metadata = receiver.mail_metadata
- expect(metadata.keys).to match_array(%i(mail_uid from_address to_address mail_key references delivered_to envelope_to x_envelope_to meta received_recipients))
+ expect(metadata.keys).to match_array(%i(mail_uid from_address to_address mail_key references delivered_to envelope_to x_envelope_to meta received_recipients cc_address))
expect(metadata[:meta]).to include(client_id: client_id, project: project.full_path)
expect(metadata[meta_key]).to eq(meta_value)
end
@@ -112,6 +112,24 @@ RSpec.describe Gitlab::Email::Receiver do
it_behaves_like 'successful receive'
end
end
+
+ context 'when in a Cc header' do
+ let(:email_raw) do
+ <<~EMAIL
+ From: jake@example.com
+ To: to@example.com
+ Cc: incoming+gitlabhq/gitlabhq+auth_token@appmail.example.com
+ Subject: Issue titile
+
+ Issue description
+ EMAIL
+ end
+
+ let(:meta_key) { :cc_address }
+ let(:meta_value) { ["incoming+gitlabhq/gitlabhq+auth_token@appmail.example.com"] }
+
+ it_behaves_like 'successful receive'
+ end
end
context 'when we cannot find a capable handler' do
diff --git a/spec/lib/gitlab/email/service_desk_receiver_spec.rb b/spec/lib/gitlab/email/service_desk_receiver_spec.rb
index c249a5422ff..4b67020471a 100644
--- a/spec/lib/gitlab/email/service_desk_receiver_spec.rb
+++ b/spec/lib/gitlab/email/service_desk_receiver_spec.rb
@@ -7,6 +7,12 @@ RSpec.describe Gitlab::Email::ServiceDeskReceiver do
let(:receiver) { described_class.new(email) }
context 'when the email contains a valid email address' do
+ shared_examples 'received successfully' do
+ it 'finds the service desk key' do
+ expect { receiver.execute }.not_to raise_error
+ end
+ end
+
before do
stub_service_desk_email_setting(enabled: true, address: 'support+%{key}@example.com')
@@ -21,34 +27,41 @@ RSpec.describe Gitlab::Email::ServiceDeskReceiver do
end
context 'when in a To header' do
- it 'finds the service desk key' do
- receiver.execute
- end
+ it_behaves_like 'received successfully'
end
context 'when the email contains a valid email address in a header' do
context 'when in a Delivered-To header' do
let(:email) { fixture_file('emails/service_desk_custom_address_reply.eml') }
- it 'finds the service desk key' do
- receiver.execute
- end
+ it_behaves_like 'received successfully'
end
context 'when in a Envelope-To header' do
let(:email) { fixture_file('emails/service_desk_custom_address_envelope_to.eml') }
- it 'finds the service desk key' do
- receiver.execute
- end
+ it_behaves_like 'received successfully'
end
context 'when in a X-Envelope-To header' do
let(:email) { fixture_file('emails/service_desk_custom_address_x_envelope_to.eml') }
- it 'finds the service desk key' do
- receiver.execute
+ it_behaves_like 'received successfully'
+ end
+
+ context 'when in a Cc header' do
+ let(:email) do
+ <<~EMAIL
+ From: from@example.com
+ To: to@example.com
+ Cc: support+project_slug-project_key@example.com
+ Subject: Issue titile
+
+ Issue description
+ EMAIL
end
+
+ it_behaves_like 'received successfully'
end
end
end
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index bc72d1a67d6..1b7c11dfef6 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-RSpec.describe Gitlab::EncodingHelper do
+RSpec.describe Gitlab::EncodingHelper, feature_category: :shared do
using RSpec::Parameterized::TableSyntax
let(:ext_class) { Class.new { extend Gitlab::EncodingHelper } }
@@ -291,4 +291,39 @@ RSpec.describe Gitlab::EncodingHelper do
expect(described_class.strip_bom("BOM at the end\xEF\xBB\xBF")).to eq("BOM at the end\xEF\xBB\xBF")
end
end
+
+ # This cop's alternative to .dup doesn't work in this context for some reason.
+ # rubocop: disable Performance/UnfreezeString
+ describe "#force_encode_utf8" do
+ let(:stringish) do
+ Class.new(String) do
+ undef :force_encoding
+ end
+ end
+
+ it "raises an ArgumentError if the argument can't force encoding" do
+ expect { described_class.force_encode_utf8(stringish.new("foo")) }.to raise_error(ArgumentError)
+ end
+
+ it "returns the message if already UTF-8 and valid encoding" do
+ string = "føø".dup
+
+ expect(string).not_to receive(:force_encoding).and_call_original
+ expect(described_class.force_encode_utf8(string)).to eq("føø")
+ end
+
+ it "forcibly encodes a string to UTF-8" do
+ string = "føø".dup.force_encoding("ISO-8859-1")
+
+ expect(string).to receive(:force_encoding).with("UTF-8").and_call_original
+ expect(described_class.force_encode_utf8(string)).to eq("føø")
+ end
+
+ it "forcibly encodes a frozen string to UTF-8" do
+ string = "bår".dup.force_encoding("ISO-8859-1").freeze
+
+ expect(described_class.force_encode_utf8(string)).to eq("bår")
+ end
+ end
+ # rubocop: enable Performance/UnfreezeString
end
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index c8325c5b359..80154c729e3 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::ExclusiveLease, :request_store, :clean_gitlab_redis_shared_state,
+RSpec.describe Gitlab::ExclusiveLease, :request_store,
:clean_gitlab_redis_cluster_shared_state, feature_category: :shared do
let(:unique_key) { SecureRandom.hex(10) }
@@ -20,67 +20,6 @@ RSpec.describe Gitlab::ExclusiveLease, :request_store, :clean_gitlab_redis_share
sleep(2 * timeout) # lease should have expired now
expect(lease.try_obtain).to be_present
end
-
- context 'when migrating across stores' do
- let(:lease) { described_class.new(unique_key, timeout: 3600) }
-
- before do
- stub_feature_flags(use_cluster_shared_state_for_exclusive_lease: false)
- allow(lease).to receive(:same_store).and_return(false)
- end
-
- it 'acquires 2 locks' do
- # stub first SETNX
- Gitlab::Redis::SharedState.with { |r| expect(r).to receive(:set).and_return(true) }
- Gitlab::Redis::ClusterSharedState.with { |r| expect(r).to receive(:set).and_call_original }
-
- expect(lease.try_obtain).to be_truthy
- end
-
- it 'rollback first lock if second lock is not acquired' do
- Gitlab::Redis::ClusterSharedState.with do |r|
- expect(r).to receive(:set).and_return(false)
- expect(r).to receive(:eval).and_call_original
- end
-
- Gitlab::Redis::SharedState.with do |r|
- expect(r).to receive(:set).and_call_original
- expect(r).to receive(:eval).and_call_original
- end
-
- expect(lease.try_obtain).to be_falsey
- end
- end
-
- context 'when cutting over to ClusterSharedState' do
- context 'when lock is not acquired' do
- it 'waits for existing holder to yield the lock' do
- Gitlab::Redis::ClusterSharedState.with { |r| expect(r).to receive(:set).and_call_original }
- Gitlab::Redis::SharedState.with { |r| expect(r).not_to receive(:set) }
-
- lease = described_class.new(unique_key, timeout: 3600)
- expect(lease.try_obtain).to be_truthy
- end
- end
-
- context 'when lock is still acquired' do
- let(:lease) { described_class.new(unique_key, timeout: 3600) }
-
- before do
- # simulates cutover where some application's feature-flag has not updated
- stub_feature_flags(use_cluster_shared_state_for_exclusive_lease: false)
- lease.try_obtain
- stub_feature_flags(use_cluster_shared_state_for_exclusive_lease: true)
- end
-
- it 'waits for existing holder to yield the lock' do
- Gitlab::Redis::ClusterSharedState.with { |r| expect(r).not_to receive(:set) }
- Gitlab::Redis::SharedState.with { |r| expect(r).not_to receive(:set) }
-
- expect(lease.try_obtain).to be_falsey
- end
- end
- end
end
describe '.redis_shared_state_key' do
@@ -104,159 +43,131 @@ RSpec.describe Gitlab::ExclusiveLease, :request_store, :clean_gitlab_redis_share
end
end
- shared_examples 'write operations' do
- describe '#renew' do
- it 'returns true when we have the existing lease' do
- lease = described_class.new(unique_key, timeout: 3600)
- expect(lease.try_obtain).to be_present
- expect(lease.renew).to be_truthy
- end
+ describe '#renew' do
+ it 'returns true when we have the existing lease' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ expect(lease.try_obtain).to be_present
+ expect(lease.renew).to be_truthy
+ end
- it 'returns false when we dont have a lease' do
- lease = described_class.new(unique_key, timeout: 3600)
- expect(lease.renew).to be_falsey
- end
+ it 'returns false when we dont have a lease' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ expect(lease.renew).to be_falsey
end
+ end
- describe 'cancellation' do
- def new_lease(key)
- described_class.new(key, timeout: 3600)
- end
+ describe 'cancellation' do
+ def new_lease(key)
+ described_class.new(key, timeout: 3600)
+ end
- shared_examples 'cancelling a lease' do
- let(:lease) { new_lease(unique_key) }
+ shared_examples 'cancelling a lease' do
+ let(:lease) { new_lease(unique_key) }
- it 'releases the held lease' do
- uuid = lease.try_obtain
- expect(uuid).to be_present
- expect(new_lease(unique_key).try_obtain).to eq(false)
+ it 'releases the held lease' do
+ uuid = lease.try_obtain
+ expect(uuid).to be_present
+ expect(new_lease(unique_key).try_obtain).to eq(false)
- cancel_lease(uuid)
+ cancel_lease(uuid)
- expect(new_lease(unique_key).try_obtain).to be_present
- end
+ expect(new_lease(unique_key).try_obtain).to be_present
end
+ end
- describe '.cancel' do
- def cancel_lease(uuid)
- described_class.cancel(release_key, uuid)
- end
+ describe '.cancel' do
+ def cancel_lease(uuid)
+ described_class.cancel(release_key, uuid)
+ end
- context 'when called with the unprefixed key' do
- it_behaves_like 'cancelling a lease' do
- let(:release_key) { unique_key }
- end
+ context 'when called with the unprefixed key' do
+ it_behaves_like 'cancelling a lease' do
+ let(:release_key) { unique_key }
end
+ end
- context 'when called with the prefixed key' do
- it_behaves_like 'cancelling a lease' do
- let(:release_key) { described_class.redis_shared_state_key(unique_key) }
- end
+ context 'when called with the prefixed key' do
+ it_behaves_like 'cancelling a lease' do
+ let(:release_key) { described_class.redis_shared_state_key(unique_key) }
end
+ end
- it 'does not raise errors when given a nil key' do
- expect { described_class.cancel(nil, nil) }.not_to raise_error
- end
+ it 'does not raise errors when given a nil key' do
+ expect { described_class.cancel(nil, nil) }.not_to raise_error
end
+ end
- describe '#cancel' do
- def cancel_lease(_uuid)
- lease.cancel
- end
+ describe '#cancel' do
+ def cancel_lease(_uuid)
+ lease.cancel
+ end
- it_behaves_like 'cancelling a lease'
+ it_behaves_like 'cancelling a lease'
- it 'is safe to call even if the lease was never obtained' do
- lease = new_lease(unique_key)
+ it 'is safe to call even if the lease was never obtained' do
+ lease = new_lease(unique_key)
- lease.cancel
+ lease.cancel
- expect(new_lease(unique_key).try_obtain).to be_present
- end
+ expect(new_lease(unique_key).try_obtain).to be_present
end
end
+ end
- describe '.reset_all!' do
- it 'removes all existing lease keys from redis' do
- uuid = described_class.new(unique_key, timeout: 3600).try_obtain
+ describe '.reset_all!' do
+ it 'removes all existing lease keys from redis' do
+ uuid = described_class.new(unique_key, timeout: 3600).try_obtain
- expect(described_class.get_uuid(unique_key)).to eq(uuid)
+ expect(described_class.get_uuid(unique_key)).to eq(uuid)
- described_class.reset_all!
+ described_class.reset_all!
- expect(described_class.get_uuid(unique_key)).to be_falsey
- end
+ expect(described_class.get_uuid(unique_key)).to be_falsey
end
end
- shared_examples 'read operations' do
- describe '#exists?' do
- it 'returns true for an existing lease' do
- lease = described_class.new(unique_key, timeout: 3600)
- lease.try_obtain
-
- expect(lease.exists?).to eq(true)
- end
-
- it 'returns false for a lease that does not exist' do
- lease = described_class.new(unique_key, timeout: 3600)
+ describe '#exists?' do
+ it 'returns true for an existing lease' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ lease.try_obtain
- expect(lease.exists?).to eq(false)
- end
+ expect(lease.exists?).to eq(true)
end
- describe '.get_uuid' do
- it 'gets the uuid if lease with the key associated exists' do
- uuid = described_class.new(unique_key, timeout: 3600).try_obtain
-
- expect(described_class.get_uuid(unique_key)).to eq(uuid)
- end
+ it 'returns false for a lease that does not exist' do
+ lease = described_class.new(unique_key, timeout: 3600)
- it 'returns false if the lease does not exist' do
- expect(described_class.get_uuid(unique_key)).to be false
- end
+ expect(lease.exists?).to eq(false)
end
+ end
- describe '#ttl' do
- it 'returns the TTL of the Redis key' do
- lease = described_class.new('kittens', timeout: 100)
- lease.try_obtain
-
- expect(lease.ttl <= 100).to eq(true)
- end
+ describe '.get_uuid' do
+ it 'gets the uuid if lease with the key associated exists' do
+ uuid = described_class.new(unique_key, timeout: 3600).try_obtain
- it 'returns nil when the lease does not exist' do
- lease = described_class.new('kittens', timeout: 10)
+ expect(described_class.get_uuid(unique_key)).to eq(uuid)
+ end
- expect(lease.ttl).to be_nil
- end
+ it 'returns false if the lease does not exist' do
+ expect(described_class.get_uuid(unique_key)).to be false
end
end
- context 'when migrating across stores' do
- before do
- stub_feature_flags(use_cluster_shared_state_for_exclusive_lease: false)
+ describe '#ttl' do
+ it 'returns the TTL of the Redis key' do
+ lease = described_class.new('kittens', timeout: 100)
+ lease.try_obtain
+
+ expect(lease.ttl <= 100).to eq(true)
end
- it_behaves_like 'read operations'
- it_behaves_like 'write operations'
- end
+ it 'returns nil when the lease does not exist' do
+ lease = described_class.new('kittens', timeout: 10)
- context 'when feature flags are all disabled' do
- before do
- stub_feature_flags(
- use_cluster_shared_state_for_exclusive_lease: false,
- enable_exclusive_lease_double_lock_rw: false
- )
+ expect(lease.ttl).to be_nil
end
-
- it_behaves_like 'read operations'
- it_behaves_like 'write operations'
end
- it_behaves_like 'read operations'
- it_behaves_like 'write operations'
-
describe '.throttle' do
it 'prevents repeated execution of the block' do
number = 0
@@ -310,8 +221,8 @@ RSpec.describe Gitlab::ExclusiveLease, :request_store, :clean_gitlab_redis_share
it 'allows count to be specified' do
expect(described_class)
.to receive(:new)
- .with(anything, hash_including(timeout: 15.minutes.to_i))
- .and_call_original
+ .with(anything, hash_including(timeout: 15.minutes.to_i))
+ .and_call_original
described_class.throttle(1, count: 4) {}
end
@@ -319,8 +230,8 @@ RSpec.describe Gitlab::ExclusiveLease, :request_store, :clean_gitlab_redis_share
it 'allows period to be specified' do
expect(described_class)
.to receive(:new)
- .with(anything, hash_including(timeout: 1.day.to_i))
- .and_call_original
+ .with(anything, hash_including(timeout: 1.day.to_i))
+ .and_call_original
described_class.throttle(1, period: 1.day) {}
end
@@ -328,80 +239,10 @@ RSpec.describe Gitlab::ExclusiveLease, :request_store, :clean_gitlab_redis_share
it 'allows period and count to be specified' do
expect(described_class)
.to receive(:new)
- .with(anything, hash_including(timeout: 30.minutes.to_i))
- .and_call_original
+ .with(anything, hash_including(timeout: 30.minutes.to_i))
+ .and_call_original
described_class.throttle(1, count: 48, period: 1.day) {}
end
end
-
- describe 'transitions between feature-flag toggles' do
- shared_examples 'retains behaviours across transitions' do |flag|
- it 'retains read behaviour' do
- lease = described_class.new(unique_key, timeout: 3600)
- uuid = lease.try_obtain
-
- expect(lease.ttl).not_to eq(nil)
- expect(lease.exists?).to be_truthy
- expect(described_class.get_uuid(unique_key)).to eq(uuid)
-
- # simulates transition
- stub_feature_flags({ flag => true })
- Gitlab::SafeRequestStore.clear!
-
- expect(lease.ttl).not_to eq(nil)
- expect(lease.exists?).to be_truthy
- expect(described_class.get_uuid(unique_key)).to eq(uuid)
- end
-
- it 'retains renew behaviour' do
- lease = described_class.new(unique_key, timeout: 3600)
- lease.try_obtain
-
- expect(lease.renew).to be_truthy
-
- # simulates transition
- stub_feature_flags({ flag => true })
- Gitlab::SafeRequestStore.clear!
-
- expect(lease.renew).to be_truthy
- end
-
- it 'retains renew behaviour' do
- lease = described_class.new(unique_key, timeout: 3600)
- uuid = lease.try_obtain
- lease.cancel
-
- # proves successful cancellation
- expect(lease.try_obtain).to eq(uuid)
-
- # simulates transition
- stub_feature_flags({ flag => true })
- Gitlab::SafeRequestStore.clear!
-
- expect(lease.try_obtain).to be_falsey
- lease.cancel
- expect(lease.try_obtain).to eq(uuid)
- end
- end
-
- context 'when enabling enable_exclusive_lease_double_lock_rw' do
- before do
- stub_feature_flags(
- enable_exclusive_lease_double_lock_rw: false,
- use_cluster_shared_state_for_exclusive_lease: false
- )
- end
-
- it_behaves_like 'retains behaviours across transitions', :enable_exclusive_lease_double_lock_rw
- end
-
- context 'when enabling use_cluster_shared_state_for_exclusive_lease' do
- before do
- stub_feature_flags(use_cluster_shared_state_for_exclusive_lease: false)
- end
-
- it_behaves_like 'retains behaviours across transitions', :use_cluster_shared_state_for_exclusive_lease
- end
- end
end
diff --git a/spec/lib/gitlab/experiment/rollout/feature_spec.rb b/spec/lib/gitlab/experiment/rollout/feature_spec.rb
index cd46e7b3386..6d01b7a175f 100644
--- a/spec/lib/gitlab/experiment/rollout/feature_spec.rb
+++ b/spec/lib/gitlab/experiment/rollout/feature_spec.rb
@@ -2,16 +2,15 @@
require 'spec_helper'
-RSpec.describe Gitlab::Experiment::Rollout::Feature, :experiment do
+RSpec.describe Gitlab::Experiment::Rollout::Feature, :experiment, feature_category: :acquisition do
subject { described_class.new(subject_experiment) }
let(:subject_experiment) { experiment('namespaced/stub') }
- describe "#enabled?" do
+ describe "#enabled?", :saas do
before do
stub_feature_flags(gitlab_experiment: true)
allow(subject).to receive(:feature_flag_defined?).and_return(true)
- allow(Gitlab).to receive(:com?).and_return(true)
allow(subject).to receive(:feature_flag_instance).and_return(double(state: :on))
end
@@ -45,6 +44,18 @@ RSpec.describe Gitlab::Experiment::Rollout::Feature, :experiment do
end
describe "#execute_assignment" do
+ let(:variants) do
+ ->(e) do
+ # rubocop:disable Lint/EmptyBlock
+ e.control {}
+ e.variant(:red) {}
+ e.variant(:blue) {}
+ # rubocop:enable Lint/EmptyBlock
+ end
+ end
+
+ let(:subject_experiment) { experiment('namespaced/stub', &variants) }
+
before do
allow(Feature).to receive(:enabled?).with('namespaced_stub', any_args).and_return(true)
end
@@ -60,9 +71,82 @@ RSpec.describe Gitlab::Experiment::Rollout::Feature, :experiment do
end
it "returns an assigned name" do
- allow(subject).to receive(:behavior_names).and_return([:variant1, :variant2])
+ expect(subject.execute_assignment).to eq(:blue)
+ end
+
+ context "when there are no behaviors" do
+ let(:variants) { ->(e) { e.control {} } } # rubocop:disable Lint/EmptyBlock
+
+ it "does not raise an error" do
+ expect { subject.execute_assignment }.not_to raise_error
+ end
+ end
+
+ context "for even rollout to non-control", :saas do
+ let(:counts) { Hash.new(0) }
+ let(:subject_experiment) { experiment('namespaced/stub') }
+
+ before do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:enabled?).and_return(true)
+ end
+
+ subject_experiment.variant(:variant1) {} # rubocop:disable Lint/EmptyBlock
+ subject_experiment.variant(:variant2) {} # rubocop:disable Lint/EmptyBlock
+ end
+
+ it "rolls out relatively evenly to 2 behaviors" do
+ 100.times { |i| run_cycle(subject_experiment, value: i) }
+
+ expect(counts).to eq(variant1: 54, variant2: 46)
+ end
+
+ it "rolls out relatively evenly to 3 behaviors" do
+ subject_experiment.variant(:variant3) {} # rubocop:disable Lint/EmptyBlock
+
+ 100.times { |i| run_cycle(subject_experiment, value: i) }
+
+ expect(counts).to eq(variant1: 31, variant2: 29, variant3: 40)
+ end
+
+ context "when distribution is specified as an array" do
+ before do
+ subject_experiment.rollout(described_class, distribution: [32, 25, 43])
+ end
+
+ it "rolls out with the expected distribution" do
+ subject_experiment.variant(:variant3) {} # rubocop:disable Lint/EmptyBlock
+
+ 100.times { |i| run_cycle(subject_experiment, value: i) }
+
+ expect(counts).to eq(variant1: 39, variant2: 24, variant3: 37)
+ end
+ end
+
+ context "when distribution is specified as a hash" do
+ before do
+ subject_experiment.rollout(described_class, distribution: { variant1: 90, variant2: 10 })
+ end
+
+ it "rolls out with the expected distribution" do
+ 100.times { |i| run_cycle(subject_experiment, value: i) }
+
+ expect(counts).to eq(variant1: 95, variant2: 5)
+ end
+ end
+
+ def run_cycle(experiment, **context)
+ experiment.instance_variable_set(:@_assigned_variant_name, nil)
+ experiment.context(context) if context
+
+ begin
+ experiment.cache.delete
+ rescue StandardError
+ nil
+ end
- expect(subject.execute_assignment).to eq(:variant2)
+ counts[experiment.assigned.name] += 1
+ end
end
end
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index 4d78e194da8..6b3630d7a1f 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-RSpec.describe Gitlab::Git::Diff do
+RSpec.describe Gitlab::Git::Diff, feature_category: :source_code_management do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:repository) { project.repository }
@@ -336,6 +336,121 @@ EOT
end
end
+ describe '#unidiff' do
+ let_it_be(:project) { create(:project, :empty_repo) }
+ let_it_be(:repository) { project.repository }
+ let_it_be(:user) { project.first_owner }
+
+ let(:commits) { repository.commits('master', limit: 10) }
+ let(:diffs) { commits.map(&:diffs).map(&:diffs).flat_map(&:to_a).reverse }
+
+ before_all do
+ create_commit(
+ project,
+ user,
+ commit_message: "Create file",
+ actions: [{ action: 'create', content: 'foo', file_path: 'a.txt' }]
+ )
+
+ create_commit(
+ project,
+ user,
+ commit_message: "Update file",
+ actions: [{ action: 'update', content: 'foo2', file_path: 'a.txt' }]
+ )
+
+ create_commit(
+ project,
+ user,
+ commit_message: "Rename file without change",
+ actions: [{ action: 'move', previous_path: 'a.txt', file_path: 'b.txt' }]
+ )
+
+ create_commit(
+ project,
+ user,
+ commit_message: "Rename file with change",
+ actions: [{ action: 'move', content: 'foo3', previous_path: 'b.txt', file_path: 'c.txt' }]
+ )
+
+ create_commit(
+ project,
+ user,
+ commit_message: "Delete file",
+ actions: [{ action: 'delete', file_path: 'c.txt' }]
+ )
+
+ create_commit(
+ project,
+ user,
+ commit_message: "Create empty file",
+ actions: [{ action: 'create', file_path: 'empty.txt' }]
+ )
+
+ create_commit(
+ project,
+ user,
+ commit_message: "Create binary file",
+ actions: [{ action: 'create', content: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=', file_path: 'test%2Ebin', encoding: 'base64' }]
+ )
+ end
+
+ context 'when file was created' do
+ it 'returns a correct header' do
+ diff = diffs[0]
+
+ expect(diff.unidiff).to start_with("--- /dev/null\n+++ b/a.txt\n")
+ end
+ end
+
+ context 'when file was changed' do
+ it 'returns a correct header' do
+ diff = diffs[1]
+
+ expect(diff.unidiff).to start_with("--- a/a.txt\n+++ b/a.txt\n")
+ end
+ end
+
+ context 'when file was moved without content change' do
+ it 'returns an empty header' do
+ diff = diffs[2]
+
+ expect(diff.unidiff).to eq('')
+ end
+ end
+
+ context 'when file was moved with content change' do
+ it 'returns a correct header' do
+ expect(diffs[3].unidiff).to start_with("--- /dev/null\n+++ b/c.txt\n")
+ expect(diffs[4].unidiff).to start_with("--- a/b.txt\n+++ /dev/null\n")
+ end
+ end
+
+ context 'when file was deleted' do
+ it 'returns a correct header' do
+ diff = diffs[5]
+
+ expect(diff.unidiff).to start_with("--- a/c.txt\n+++ /dev/null\n")
+ end
+ end
+
+ context 'when empty file was created' do
+ it 'returns an empty header' do
+ diff = diffs[6]
+
+ expect(diff.unidiff).to eq('')
+ end
+ end
+
+ context 'when file is binary' do
+ it 'returns a binary files message' do
+ diff = diffs[7]
+
+ expect(diff.unidiff).to eq("Binary files /dev/null and b/test%2Ebin differ\n")
+ end
+ end
+ end
+
describe '#submodule?' do
let(:gitaly_submodule_diff) do
Gitlab::GitalyClient::Diff.new(
@@ -445,4 +560,9 @@ EOT
# rugged will not detect this as binary, but we can fake it
described_class.between(project.repository, 'add-pdf-text-binary', 'add-pdf-text-binary^').first
end
+
+ def create_commit(project, user, params)
+ params = { start_branch: 'master', branch_name: 'master' }.merge(params)
+ Files::MultiService.new(project, user, params).execute.fetch(:result)
+ end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 18a090a00be..47b5986cfd8 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -203,25 +203,6 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
expect(metadata['CommitId']).to eq(expected_commit_id)
end
end
-
- context 'when resolve_ambiguous_archives is disabled' do
- before do
- stub_feature_flags(resolve_ambiguous_archives: false)
- end
-
- where(:ref, :expected_commit_id, :desc) do
- 'refs/heads/branch-merged' | ref(:branch_merged_commit_id) | 'when tag looks like a branch (difference!)'
- 'branch-merged' | ref(:branch_master_commit_id) | 'when tag has the same name as a branch'
- ref(:branch_merged_commit_id) | ref(:branch_merged_commit_id) | 'when tag looks like a commit id'
- 'v0.0.0' | ref(:branch_master_commit_id) | 'when tag looks like a normal tag'
- end
-
- with_them do
- it 'selects the correct commit' do
- expect(metadata['CommitId']).to eq(expected_commit_id)
- end
- end
- end
end
context 'when branch is ambiguous' do
@@ -241,25 +222,6 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
expect(metadata['CommitId']).to eq(expected_commit_id)
end
end
-
- context 'when resolve_ambiguous_archives is disabled' do
- before do
- stub_feature_flags(resolve_ambiguous_archives: false)
- end
-
- where(:ref, :expected_commit_id, :desc) do
- 'refs/tags/v1.0.0' | ref(:tag_1_0_0_commit_id) | 'when branch looks like a tag (difference!)'
- 'v1.0.0' | ref(:tag_1_0_0_commit_id) | 'when branch has the same name as a tag'
- ref(:branch_merged_commit_id) | ref(:branch_merged_commit_id) | 'when branch looks like a commit id'
- 'just-a-normal-branch' | ref(:branch_master_commit_id) | 'when branch looks like a normal branch'
- end
-
- with_them do
- it 'selects the correct commit' do
- expect(metadata['CommitId']).to eq(expected_commit_id)
- end
- end
- end
end
context 'when ref is HEAD' do
@@ -2650,21 +2612,6 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
end
end
- describe '#rename' do
- let(:repository) { mutable_repository }
-
- it 'moves the repository' do
- checksum = repository.checksum
- new_relative_path = "rename_test/relative/path"
- renamed_repository = Gitlab::Git::Repository.new(repository.storage, new_relative_path, nil, nil)
-
- repository.rename(new_relative_path)
-
- expect(renamed_repository.checksum).to eq(checksum)
- expect(repository.exists?).to be false
- end
- end
-
describe '#remove' do
let(:repository) { mutable_repository }
@@ -2833,4 +2780,14 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
end
end
end
+
+ describe '#get_file_attributes' do
+ let(:rev) { 'master' }
+ let(:paths) { ['file.txt'] }
+ let(:attrs) { ['text'] }
+
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RepositoryService, :get_file_attributes do
+ subject { repository.get_file_attributes(rev, paths, attrs) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
index d320b9c4091..d5a0ab3d5e0 100644
--- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
+++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
@@ -1,18 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
-require 'json'
-require 'tempfile'
RSpec.describe Gitlab::Git::RuggedImpl::UseRugged, feature_category: :gitaly do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:feature_flag_name) { wrapper.rugged_feature_keys.first }
- let(:temp_gitaly_metadata_file) { create_temporary_gitaly_metadata_file }
-
- before_all do
- create_gitaly_metadata_file
- end
subject(:wrapper) do
klazz = Class.new do
@@ -24,11 +17,6 @@ RSpec.describe Gitlab::Git::RuggedImpl::UseRugged, feature_category: :gitaly do
klazz.new
end
- before do
- allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_call_original
- Gitlab::GitalyClient.instance_variable_set(:@can_use_disk, {})
- end
-
describe '#execute_rugged_call', :request_store do
let(:args) { ['refs/heads/master', 1] }
@@ -46,83 +34,9 @@ RSpec.describe Gitlab::Git::RuggedImpl::UseRugged, feature_category: :gitaly do
end
end
- context 'when feature flag is not persisted', stub_feature_flags: false do
- context 'when running puma with multiple threads' do
- before do
- allow(subject).to receive(:running_puma_with_multiple_threads?).and_return(true)
- end
-
- it 'returns false' do
- expect(subject.use_rugged?(repository, feature_flag_name)).to be false
- end
- end
-
- context 'when skip_rugged_auto_detect feature flag is enabled' do
- context 'when not running puma with multiple threads' do
- before do
- allow(subject).to receive(:running_puma_with_multiple_threads?).and_return(false)
- stub_feature_flags(feature_flag_name => nil)
- stub_feature_flags(skip_rugged_auto_detect: true)
- end
-
- it 'returns false' do
- expect(subject.use_rugged?(repository, feature_flag_name)).to be false
- end
- end
- end
-
- context 'when skip_rugged_auto_detect feature flag is disabled' do
- before do
- stub_feature_flags(skip_rugged_auto_detect: false)
- end
-
- context 'when not running puma with multiple threads' do
- before do
- allow(subject).to receive(:running_puma_with_multiple_threads?).and_return(false)
- end
-
- it 'returns true when gitaly matches disk' do
- expect(subject.use_rugged?(repository, feature_flag_name)).to be true
- end
-
- it 'returns false when disk access fails' do
- allow(Gitlab::GitalyClient).to receive(:storage_metadata_file_path).and_return("/fake/path/doesnt/exist")
-
- expect(subject.use_rugged?(repository, feature_flag_name)).to be false
- end
-
- it "returns false when gitaly doesn't match disk" do
- allow(Gitlab::GitalyClient).to receive(:storage_metadata_file_path).and_return(temp_gitaly_metadata_file)
-
- expect(subject.use_rugged?(repository, feature_flag_name)).to be_falsey
-
- File.delete(temp_gitaly_metadata_file)
- end
-
- it "doesn't lead to a second rpc call because gitaly client should use the cached value" do
- expect(subject.use_rugged?(repository, feature_flag_name)).to be true
-
- expect(Gitlab::GitalyClient).not_to receive(:filesystem_id)
-
- subject.use_rugged?(repository, feature_flag_name)
- end
- end
- end
- end
-
- context 'when feature flag is persisted' do
- it 'returns false when the feature flag is off' do
- Feature.disable(feature_flag_name)
-
- expect(subject.use_rugged?(repository, feature_flag_name)).to be_falsey
- end
-
- it "returns true when feature flag is on" do
- Feature.enable(feature_flag_name)
-
- allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(false)
-
- expect(subject.use_rugged?(repository, feature_flag_name)).to be true
+ describe '#use_rugged?' do
+ it 'returns false' do
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be false
end
end
@@ -184,7 +98,7 @@ RSpec.describe Gitlab::Git::RuggedImpl::UseRugged, feature_category: :gitaly do
context 'all features are enabled' do
let(:feature_keys) { [:feature_key_1, :feature_key_2] }
- it { is_expected.to be_truthy }
+ it { is_expected.to be_falsey }
end
context 'all features are not enabled' do
@@ -196,28 +110,7 @@ RSpec.describe Gitlab::Git::RuggedImpl::UseRugged, feature_category: :gitaly do
context 'some feature is enabled' do
let(:feature_keys) { [:feature_key_4, :feature_key_2] }
- it { is_expected.to be_truthy }
- end
- end
-
- def create_temporary_gitaly_metadata_file
- tmp = Tempfile.new('.gitaly-metadata')
- gitaly_metadata = {
- "gitaly_filesystem_id" => "some-value"
- }
- tmp.write(gitaly_metadata.to_json)
- tmp.flush
- tmp.close
- tmp.path
- end
-
- def create_gitaly_metadata_file
- metadata_filename = File.join(TestEnv.repos_path, '.gitaly-metadata')
- File.open(metadata_filename, 'w+') do |f|
- gitaly_metadata = {
- "gitaly_filesystem_id" => SecureRandom.uuid
- }
- f.write(gitaly_metadata.to_json)
+ it { is_expected.to be_falsey }
end
end
end
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index 84ab8376fe1..9675e48a77f 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -2,11 +2,11 @@
require "spec_helper"
-RSpec.describe Gitlab::Git::Tree do
+RSpec.describe Gitlab::Git::Tree, feature_category: :source_code_management do
let_it_be(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:repository) { project.repository.raw }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:repository) { project.repository.raw }
shared_examples 'repo' do
subject(:tree) { Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, pagination_params) }
@@ -95,6 +95,8 @@ RSpec.describe Gitlab::Git::Tree do
end
context :flat_path do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository.raw }
let(:filename) { 'files/flat/path/correct/content.txt' }
let(:path) { 'files/flat' }
# rubocop: disable Rails/FindBy
@@ -192,9 +194,9 @@ RSpec.describe Gitlab::Git::Tree do
end
describe '.where with Rugged enabled', :enable_rugged do
- it 'calls out to the Rugged implementation' do
+ it 'does not call to the Rugged implementation' do
allow_next_instance_of(Rugged) do |instance|
- allow(instance).to receive(:lookup).with(SeedRepo::Commit::ID)
+ allow(instance).not_to receive(:lookup)
end
described_class.where(repository, SeedRepo::Commit::ID, 'files', false, false)
@@ -214,10 +216,10 @@ RSpec.describe Gitlab::Git::Tree do
context 'when limit is equal to number of entries' do
let(:entries_count) { entries.count }
- it 'returns all entries without a cursor' do
+ it 'returns all entries with a cursor' do
result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: entries_count, page_token: nil })
- expect(cursor).to be_nil
+ expect(cursor).to eq(Gitaly::PaginationCursor.new)
expect(result.entries.count).to eq(entries_count)
end
end
@@ -234,9 +236,9 @@ RSpec.describe Gitlab::Git::Tree do
context 'when limit is missing' do
let(:pagination_params) { { limit: nil, page_token: nil } }
- it 'returns empty result' do
- expect(entries).to eq([])
- expect(cursor).to be_nil
+ it 'returns all entries' do
+ expect(entries.count).to be < 20
+ expect(cursor).to eq(Gitaly::PaginationCursor.new)
end
end
@@ -247,7 +249,7 @@ RSpec.describe Gitlab::Git::Tree do
result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: -1, page_token: nil })
expect(result.count).to eq(entries_count)
- expect(cursor).to be_nil
+ expect(cursor).to eq(Gitaly::PaginationCursor.new)
end
context 'when token is provided' do
@@ -258,7 +260,7 @@ RSpec.describe Gitlab::Git::Tree do
result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: -1, page_token: token })
expect(result.count).to eq(entries.count - 2)
- expect(cursor).to be_nil
+ expect(cursor).to eq(Gitaly::PaginationCursor.new)
end
end
end
@@ -276,7 +278,7 @@ RSpec.describe Gitlab::Git::Tree do
it 'returns only available entries' do
expect(entries.count).to be < 20
- expect(cursor).to be_nil
+ expect(cursor).to eq(Gitaly::PaginationCursor.new)
end
end
diff --git a/spec/lib/gitlab/git_audit_event_spec.rb b/spec/lib/gitlab/git_audit_event_spec.rb
new file mode 100644
index 00000000000..c533b39f550
--- /dev/null
+++ b/spec/lib/gitlab/git_audit_event_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GitAuditEvent, feature_category: :source_code_management do
+ let_it_be(:player) { create(:user) }
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:project) { create(:project) }
+
+ subject { described_class.new(player, project) }
+
+ describe '#send_audit_event' do
+ let(:msg) { 'valid_msg' }
+
+ context 'with successfully sending' do
+ let_it_be(:project) { create(:project, namespace: group) }
+
+ before do
+ allow(::Gitlab::Audit::Auditor).to receive(:audit)
+ end
+
+ context 'when player is a regular user' do
+ it 'sends git audit event' do
+ expect(::Gitlab::Audit::Auditor).to receive(:audit).with(a_hash_including(
+ name: 'repository_git_operation',
+ stream_only: true,
+ author: player,
+ scope: project,
+ target: project,
+ message: msg
+ )).once
+
+ subject.send_audit_event(msg)
+ end
+ end
+
+ context 'when player is ::API::Support::GitAccessActor' do
+ let_it_be(:user) { player }
+ let_it_be(:key) { create(:key, user: user) }
+ let_it_be(:git_access_actor) { ::API::Support::GitAccessActor.new(user: user, key: key) }
+
+ subject { described_class.new(git_access_actor, project) }
+
+ it 'sends git audit event' do
+ expect(::Gitlab::Audit::Auditor).to receive(:audit).with(a_hash_including(
+ name: 'repository_git_operation',
+ stream_only: true,
+ author: git_access_actor.deploy_key_or_user,
+ scope: project,
+ target: project,
+ message: msg
+ )).once
+
+ subject.send_audit_event(msg)
+ end
+ end
+ end
+
+ context 'when user is blank' do
+ let_it_be(:player) { nil }
+
+ it 'does not send git audit event' do
+ expect(::Gitlab::Audit::Auditor).not_to receive(:audit)
+
+ subject.send_audit_event(msg)
+ end
+ end
+
+ context 'when project is blank' do
+ let_it_be(:project) { nil }
+
+ it 'does not send git audit event' do
+ expect(::Gitlab::Audit::Auditor).not_to receive(:audit)
+
+ subject.send_audit_event(msg)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index 8e0e4525729..283a9cb45dc 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -369,17 +369,6 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService, feature_category: :gital
end
end
- describe '#rename' do
- it 'sends a rename_repository message' do
- expect_any_instance_of(Gitaly::RepositoryService::Stub)
- .to receive(:rename_repository)
- .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
- .and_return(double(value: true))
-
- client.rename('some/new/path')
- end
- end
-
describe '#remove' do
it 'sends a remove_repository message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
@@ -451,4 +440,19 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService, feature_category: :gital
client.object_pool
end
end
+
+ describe '#get_file_attributes' do
+ let(:rev) { 'master' }
+ let(:paths) { ['file.txt'] }
+ let(:attrs) { ['text'] }
+
+ it 'sends a get_file_attributes message' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:get_file_attributes)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_call_original
+
+ expect(client.get_file_attributes(rev, paths, attrs)).to be_a Gitaly::GetFileAttributesResponse
+ end
+ end
end
diff --git a/spec/lib/gitlab/github_gists_import/importer/gist_importer_spec.rb b/spec/lib/gitlab/github_gists_import/importer/gist_importer_spec.rb
index cbcd9b83c15..b098a151660 100644
--- a/spec/lib/gitlab/github_gists_import/importer/gist_importer_spec.rb
+++ b/spec/lib/gitlab/github_gists_import/importer/gist_importer_spec.rb
@@ -174,9 +174,9 @@ RSpec.describe Gitlab::GithubGistsImport::Importer::GistImporter, feature_catego
.to receive(:validate!)
.with(url, ports: [80, 443], schemes: %w[http https git],
allow_localhost: true, allow_local_network: true)
- .and_raise(Gitlab::UrlBlocker::BlockedUrlError)
+ .and_raise(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
- expect { subject.execute }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
+ expect { subject.execute }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
@@ -191,9 +191,9 @@ RSpec.describe Gitlab::GithubGistsImport::Importer::GistImporter, feature_catego
.to receive(:validate!)
.with(url, ports: [80, 443], schemes: %w[http https git],
allow_localhost: false, allow_local_network: false)
- .and_raise(Gitlab::UrlBlocker::BlockedUrlError)
+ .and_raise(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
- expect { subject.execute }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
+ expect { subject.execute }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
end
diff --git a/spec/lib/gitlab/github_import/bulk_importing_spec.rb b/spec/lib/gitlab/github_import/bulk_importing_spec.rb
index 28fbd4d883f..6b4984ceaf2 100644
--- a/spec/lib/gitlab/github_import/bulk_importing_spec.rb
+++ b/spec/lib/gitlab/github_import/bulk_importing_spec.rb
@@ -47,10 +47,9 @@ RSpec.describe Gitlab::GithubImport::BulkImporting, feature_category: :importers
.with(object)
.and_return(false)
- expect(Gitlab::Import::Logger)
+ expect(Gitlab::GithubImport::Logger)
.to receive(:info)
.with(
- import_type: :github,
project_id: 1,
importer: 'MyImporter',
message: '1 object_types fetched'
@@ -82,10 +81,9 @@ RSpec.describe Gitlab::GithubImport::BulkImporting, feature_category: :importers
.with(object)
.and_return(true)
- expect(Gitlab::Import::Logger)
+ expect(Gitlab::GithubImport::Logger)
.to receive(:info)
.with(
- import_type: :github,
project_id: 1,
importer: 'MyImporter',
message: '0 object_types fetched'
@@ -145,14 +143,13 @@ RSpec.describe Gitlab::GithubImport::BulkImporting, feature_category: :importers
}
)
- expect(Gitlab::Import::Logger)
+ expect(Gitlab::GithubImport::Logger)
.to receive(:error)
.with(
- import_type: :github,
project_id: 1,
importer: 'MyImporter',
message: ['Title is invalid'],
- github_identifiers: { id: 12345, title: 'bug,bug', object_type: :object_type }
+ external_identifiers: { id: 12345, title: 'bug,bug', object_type: :object_type }
)
expect(Gitlab::GithubImport::ObjectCounter)
@@ -172,7 +169,7 @@ RSpec.describe Gitlab::GithubImport::BulkImporting, feature_category: :importers
expect(errors).not_to be_empty
expect(errors[0][:validation_errors].full_messages).to match_array(['Title is invalid'])
- expect(errors[0][:github_identifiers]).to eq({ id: 12345, title: 'bug,bug', object_type: :object_type })
+ expect(errors[0][:external_identifiers]).to eq({ id: 12345, title: 'bug,bug', object_type: :object_type })
end
end
end
@@ -182,11 +179,10 @@ RSpec.describe Gitlab::GithubImport::BulkImporting, feature_category: :importers
it 'bulk inserts rows into the database' do
rows = [{ title: 'Foo' }] * 10
- expect(Gitlab::Import::Logger)
+ expect(Gitlab::GithubImport::Logger)
.to receive(:info)
.twice
.with(
- import_type: :github,
project_id: 1,
importer: 'MyImporter',
message: '5 object_types imported'
@@ -243,7 +239,7 @@ RSpec.describe Gitlab::GithubImport::BulkImporting, feature_category: :importers
importer.bulk_insert_failures([{
validation_errors: error,
- github_identifiers: { id: 123456 }
+ external_identifiers: { id: 123456 }
}])
end
end
diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb
index 4b0d61e3188..5f321a15de9 100644
--- a/spec/lib/gitlab/github_import/client_spec.rb
+++ b/spec/lib/gitlab/github_import/client_spec.rb
@@ -316,7 +316,7 @@ RSpec.describe Gitlab::GithubImport::Client, feature_category: :importers do
allow_retry
expect(client).to receive(:requests_remaining?).twice.and_return(true)
- expect(Gitlab::Import::Logger).to receive(:info).with(hash_including(info_params)).once
+ expect(Gitlab::GithubImport::Logger).to receive(:info).with(hash_including(info_params)).once
expect(client.with_rate_limit(&block_to_rate_limit)).to eq({})
end
@@ -337,7 +337,7 @@ RSpec.describe Gitlab::GithubImport::Client, feature_category: :importers do
it 'retries on error and succeeds' do
allow_retry
- expect(Gitlab::Import::Logger).to receive(:info).with(hash_including(info_params)).once
+ expect(Gitlab::GithubImport::Logger).to receive(:info).with(hash_including(info_params)).once
expect(client.with_rate_limit(&block_to_rate_limit)).to eq({})
end
@@ -723,7 +723,7 @@ RSpec.describe Gitlab::GithubImport::Client, feature_category: :importers do
it 'retries on error and succeeds' do
allow_retry(:post)
- expect(Gitlab::Import::Logger).to receive(:info).with(hash_including(info_params)).once
+ expect(Gitlab::GithubImport::Logger).to receive(:info).with(hash_including(info_params)).once
expect(client.search_repos_by_name_graphql('test')).to eq({})
end
diff --git a/spec/lib/gitlab/github_import/clients/proxy_spec.rb b/spec/lib/gitlab/github_import/clients/proxy_spec.rb
index 7b2a8fa9d74..99fd98d2ed4 100644
--- a/spec/lib/gitlab/github_import/clients/proxy_spec.rb
+++ b/spec/lib/gitlab/github_import/clients/proxy_spec.rb
@@ -3,10 +3,9 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Clients::Proxy, :manage, feature_category: :importers do
- subject(:client) { described_class.new(access_token, client_options) }
+ subject(:client) { described_class.new(access_token) }
let(:access_token) { 'test_token' }
- let(:client_options) { { foo: :bar } }
it { expect(client).to delegate_method(:each_object).to(:client) }
it { expect(client).to delegate_method(:user).to(:client) }
@@ -15,124 +14,67 @@ RSpec.describe Gitlab::GithubImport::Clients::Proxy, :manage, feature_category:
describe '#repos' do
let(:search_text) { 'search text' }
let(:pagination_options) { { limit: 10 } }
-
- context 'when remove_legacy_github_client FF is enabled' do
- let(:client_stub) { instance_double(Gitlab::GithubImport::Client) }
-
- let(:client_response) do
- {
- data: {
- search: {
- nodes: [{ name: 'foo' }, { name: 'bar' }],
- pageInfo: { startCursor: 'foo', endCursor: 'bar' },
- repositoryCount: 2
- }
+ let(:client_stub) { instance_double(Gitlab::GithubImport::Client) }
+ let(:client_response) do
+ {
+ data: {
+ search: {
+ nodes: [{ name: 'foo' }, { name: 'bar' }],
+ pageInfo: { startCursor: 'foo', endCursor: 'bar' },
+ repositoryCount: 2
}
}
- end
-
- it 'fetches repos with Gitlab::GithubImport::Client (GraphQL API)' do
- expect(Gitlab::GithubImport::Client)
- .to receive(:new).with(access_token).and_return(client_stub)
- expect(client_stub)
- .to receive(:search_repos_by_name_graphql)
- .with(search_text, pagination_options).and_return(client_response)
-
- expect(client.repos(search_text, pagination_options)).to eq(
- {
- repos: [{ name: 'foo' }, { name: 'bar' }],
- page_info: { startCursor: 'foo', endCursor: 'bar' },
- count: 2
- }
- )
- end
+ }
end
- context 'when remove_legacy_github_client FF is disabled' do
- let(:client_stub) { instance_double(Gitlab::LegacyGithubImport::Client) }
- let(:search_text) { nil }
-
- before do
- stub_feature_flags(remove_legacy_github_client: false)
- end
-
- it 'fetches repos with Gitlab::LegacyGithubImport::Client' do
- expect(Gitlab::LegacyGithubImport::Client)
- .to receive(:new).with(access_token, client_options).and_return(client_stub)
- expect(client_stub).to receive(:repos)
- .and_return([{ name: 'foo' }, { name: 'bar' }])
-
- expect(client.repos(search_text, pagination_options))
- .to eq({ repos: [{ name: 'foo' }, { name: 'bar' }] })
- end
-
- context 'with filter params' do
- let(:search_text) { 'fo' }
+ it 'fetches repos with Gitlab::GithubImport::Client (GraphQL API)' do
+ expect(Gitlab::GithubImport::Client)
+ .to receive(:new).with(access_token).and_return(client_stub)
+ expect(client_stub)
+ .to receive(:search_repos_by_name_graphql)
+ .with(search_text, pagination_options).and_return(client_response)
- it 'fetches repos with Gitlab::LegacyGithubImport::Client' do
- expect(Gitlab::LegacyGithubImport::Client)
- .to receive(:new).with(access_token, client_options).and_return(client_stub)
- expect(client_stub).to receive(:repos)
- .and_return([{ name: 'FOO' }, { name: 'bAr' }])
-
- expect(client.repos(search_text, pagination_options))
- .to eq({ repos: [{ name: 'FOO' }] })
- end
- end
+ expect(client.repos(search_text, pagination_options)).to eq(
+ {
+ repos: [{ name: 'foo' }, { name: 'bar' }],
+ page_info: { startCursor: 'foo', endCursor: 'bar' },
+ count: 2
+ }
+ )
end
end
describe '#count_by', :clean_gitlab_redis_cache do
- context 'when remove_legacy_github_client FF is enabled' do
- let(:client_stub) { instance_double(Gitlab::GithubImport::Client) }
- let(:client_response) { { data: { search: { repositoryCount: 1 } } } }
+ let(:client_stub) { instance_double(Gitlab::GithubImport::Client) }
+ let(:client_response) { { data: { search: { repositoryCount: 1 } } } }
+ context 'when value is cached' do
before do
- stub_feature_flags(remove_legacy_github_client: true)
+ Gitlab::Cache::Import::Caching.write('github-importer/provider-repo-count/owned/user_id', 3)
end
- context 'when value is cached' do
- before do
- Gitlab::Cache::Import::Caching.write('github-importer/provider-repo-count/owned/user_id', 3)
- end
-
- it 'returns repository count from cache' do
- expect(Gitlab::GithubImport::Client)
- .to receive(:new).with(access_token).and_return(client_stub)
- expect(client_stub)
- .not_to receive(:count_repos_by_relation_type_graphql)
- .with({ relation_type: 'owned' })
- expect(client.count_repos_by('owned', 'user_id')).to eq(3)
- end
- end
-
- context 'when value is not cached' do
- it 'returns repository count' do
- expect(Gitlab::GithubImport::Client)
- .to receive(:new).with(access_token).and_return(client_stub)
- expect(client_stub)
- .to receive(:count_repos_by_relation_type_graphql)
- .with({ relation_type: 'owned' }).and_return(client_response)
- expect(Gitlab::Cache::Import::Caching)
- .to receive(:write)
- .with('github-importer/provider-repo-count/owned/user_id', 1, timeout: 5.minutes)
- .and_call_original
- expect(client.count_repos_by('owned', 'user_id')).to eq(1)
- end
+ it 'returns repository count from cache' do
+ expect(Gitlab::GithubImport::Client)
+ .to receive(:new).with(access_token).and_return(client_stub)
+ expect(client_stub)
+ .not_to receive(:count_repos_by_relation_type_graphql)
+ .with({ relation_type: 'owned' })
+ expect(client.count_repos_by('owned', 'user_id')).to eq(3)
end
end
- context 'when remove_legacy_github_client FF is disabled' do
- let(:client_stub) { instance_double(Gitlab::LegacyGithubImport::Client) }
-
- before do
- stub_feature_flags(remove_legacy_github_client: false)
- end
-
- it 'returns nil' do
- expect(Gitlab::LegacyGithubImport::Client)
- .to receive(:new).with(access_token, client_options).and_return(client_stub)
- expect(client.count_repos_by('owned', 'user_id')).to be_nil
+ context 'when value is not cached' do
+ it 'returns repository count' do
+ expect(Gitlab::GithubImport::Client)
+ .to receive(:new).with(access_token).and_return(client_stub)
+ expect(client_stub)
+ .to receive(:count_repos_by_relation_type_graphql)
+ .with({ relation_type: 'owned' }).and_return(client_response)
+ expect(Gitlab::Cache::Import::Caching)
+ .to receive(:write)
+ .with('github-importer/provider-repo-count/owned/user_id', 1, timeout: 5.minutes)
+ .and_call_original
+ expect(client.count_repos_by('owned', 'user_id')).to eq(1)
end
end
end
diff --git a/spec/lib/gitlab/github_import/importer/attachments/issues_importer_spec.rb b/spec/lib/gitlab/github_import/importer/attachments/issues_importer_spec.rb
index 7890561bf2d..b44f1ec85f3 100644
--- a/spec/lib/gitlab/github_import/importer/attachments/issues_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/attachments/issues_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GithubImport::Importer::Attachments::IssuesImporter do
+RSpec.describe Gitlab::GithubImport::Importer::Attachments::IssuesImporter, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project) }
@@ -17,6 +17,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Attachments::IssuesImporter do
let(:importer_attrs) { [instance_of(Gitlab::GithubImport::Representation::NoteText), project, client] }
it 'imports each project issue attachments' do
+ expect(project.issues).to receive(:id_not_in).with([]).and_return(project.issues)
expect(project.issues).to receive(:select).with(:id, :description, :iid).and_call_original
expect_next_instances_of(
@@ -32,6 +33,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Attachments::IssuesImporter do
it "doesn't import this issue attachments" do
importer.mark_as_imported(issue_1)
+ expect(project.issues).to receive(:id_not_in).with([issue_1.id.to_s]).and_call_original
expect_next_instance_of(
Gitlab::GithubImport::Importer::NoteAttachmentsImporter, *importer_attrs
) do |note_attachments_importer|
diff --git a/spec/lib/gitlab/github_import/importer/attachments/merge_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/attachments/merge_requests_importer_spec.rb
index e5aa17dd81e..381cb17bb52 100644
--- a/spec/lib/gitlab/github_import/importer/attachments/merge_requests_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/attachments/merge_requests_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GithubImport::Importer::Attachments::MergeRequestsImporter do
+RSpec.describe Gitlab::GithubImport::Importer::Attachments::MergeRequestsImporter, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project) }
@@ -17,6 +17,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Attachments::MergeRequestsImporte
let(:importer_attrs) { [instance_of(Gitlab::GithubImport::Representation::NoteText), project, client] }
it 'imports each project merge request attachments' do
+ expect(project.merge_requests).to receive(:id_not_in).with([]).and_return(project.merge_requests)
expect(project.merge_requests).to receive(:select).with(:id, :description, :iid).and_call_original
expect_next_instances_of(
@@ -32,6 +33,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Attachments::MergeRequestsImporte
it "doesn't import this merge request attachments" do
importer.mark_as_imported(merge_request_1)
+ expect(project.merge_requests).to receive(:id_not_in).with([merge_request_1.id.to_s]).and_call_original
expect_next_instance_of(
Gitlab::GithubImport::Importer::NoteAttachmentsImporter, *importer_attrs
) do |note_attachments_importer|
diff --git a/spec/lib/gitlab/github_import/importer/attachments/notes_importer_spec.rb b/spec/lib/gitlab/github_import/importer/attachments/notes_importer_spec.rb
index 7ed353e1b71..5b3ad032702 100644
--- a/spec/lib/gitlab/github_import/importer/attachments/notes_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/attachments/notes_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GithubImport::Importer::Attachments::NotesImporter do
+RSpec.describe Gitlab::GithubImport::Importer::Attachments::NotesImporter, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project) }
@@ -18,6 +18,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Attachments::NotesImporter do
let(:importer_attrs) { [instance_of(Gitlab::GithubImport::Representation::NoteText), project, client] }
it 'imports each project user note' do
+ expect(project.notes).to receive(:id_not_in).with([]).and_call_original
expect(Gitlab::GithubImport::Importer::NoteAttachmentsImporter).to receive(:new)
.with(*importer_attrs).twice.and_return(importer_stub)
expect(importer_stub).to receive(:execute).twice
@@ -29,6 +30,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Attachments::NotesImporter do
it "doesn't import this note" do
importer.mark_as_imported(note_1)
+ expect(project.notes).to receive(:id_not_in).with([note_1.id.to_s]).and_call_original
expect(Gitlab::GithubImport::Importer::NoteAttachmentsImporter).to receive(:new)
.with(*importer_attrs).once.and_return(importer_stub)
expect(importer_stub).to receive(:execute).once
diff --git a/spec/lib/gitlab/github_import/importer/attachments/releases_importer_spec.rb b/spec/lib/gitlab/github_import/importer/attachments/releases_importer_spec.rb
index e1b009c3eeb..c1c19c40afb 100644
--- a/spec/lib/gitlab/github_import/importer/attachments/releases_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/attachments/releases_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GithubImport::Importer::Attachments::ReleasesImporter do
+RSpec.describe Gitlab::GithubImport::Importer::Attachments::ReleasesImporter, feature_category: :importers do
subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project) }
@@ -17,6 +17,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Attachments::ReleasesImporter do
let(:importer_attrs) { [instance_of(Gitlab::GithubImport::Representation::NoteText), project, client] }
it 'imports each project release' do
+ expect(project.releases).to receive(:id_not_in).with([]).and_return(project.releases)
expect(project.releases).to receive(:select).with(:id, :description, :tag).and_call_original
expect(Gitlab::GithubImport::Importer::NoteAttachmentsImporter).to receive(:new)
@@ -30,6 +31,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Attachments::ReleasesImporter do
it "doesn't import this release" do
importer.mark_as_imported(release_1)
+ expect(project.releases).to receive(:id_not_in).with([release_1.id.to_s]).and_call_original
expect(Gitlab::GithubImport::Importer::NoteAttachmentsImporter).to receive(:new)
.with(*importer_attrs).once.and_return(importer_stub)
expect(importer_stub).to receive(:execute).once
diff --git a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb
index 0f35c7ee0dc..7668451ad4e 100644
--- a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_failures do
+RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_failures, feature_category: :importers do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
@@ -80,17 +80,6 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_fail
expect(note.author_id).to eq(project.creator_id)
expect(note.note).to eq("*Created by: #{user.username}*\n\nHello")
end
-
- it 'does not import the note when a foreign key error is raised' do
- stub_user_finder(project.creator_id, false)
-
- expect(ApplicationRecord)
- .to receive(:legacy_bulk_insert)
- .and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key')
-
- expect { subject.execute }
- .not_to change(LegacyDiffNote, :count)
- end
end
describe '#execute' do
@@ -143,6 +132,7 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_fail
expect(note.noteable_type).to eq('MergeRequest')
expect(note.noteable_id).to eq(merge_request.id)
expect(note.project_id).to eq(project.id)
+ expect(note.namespace_id).to eq(project.project_namespace_id)
expect(note.author_id).to eq(user.id)
expect(note.system).to eq(false)
expect(note.discussion_id).to eq(discussion_id)
diff --git a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
index bf2ffda3bf1..1c453436f9f 100644
--- a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cache, feature_category: :importers do
let_it_be(:work_item_type_id) { ::WorkItems::Type.default_issue_type.id }
let(:project) { create(:project) }
@@ -77,6 +77,27 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
importer.execute
end
+
+ it 'caches the created issue ID even if importer later fails' do
+ error = StandardError.new('mocked error')
+
+ allow_next_instance_of(described_class) do |importer|
+ allow(importer)
+ .to receive(:create_issue)
+ .and_return(10)
+ allow(importer)
+ .to receive(:create_assignees)
+ .and_raise(error)
+ end
+
+ expect_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ expect(finder)
+ .to receive(:cache_database_id)
+ .with(10)
+ end
+
+ expect { importer.execute }.to raise_error(error)
+ end
end
describe '#create_issue' do
@@ -162,21 +183,6 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
end
end
- context 'when the import fails due to a foreign key error' do
- it 'does not raise any errors' do
- allow(importer.user_finder)
- .to receive(:author_id_for)
- .with(issue)
- .and_return([user.id, true])
-
- expect(importer)
- .to receive(:insert_and_return_id)
- .and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key')
-
- expect { importer.create_issue }.not_to raise_error
- end
- end
-
it 'produces a valid Issue' do
allow(importer.user_finder)
.to receive(:author_id_for)
diff --git a/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb b/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb
index fc8d9cee066..0328a36b646 100644
--- a/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/labels_importer_spec.rb
@@ -50,13 +50,12 @@ feature_category: :importers do
label = { id: 1, name: 'bug,bug', color: 'ffffff' }
expect(importer).to receive(:each_label).and_return([label])
- expect(Gitlab::Import::Logger).to receive(:error)
+ expect(Gitlab::GithubImport::Logger).to receive(:error)
.with(
- import_type: :github,
project_id: project.id,
importer: described_class.name,
message: ['Title is invalid'],
- github_identifiers: { title: 'bug,bug', object_type: :label }
+ external_identifiers: { title: 'bug,bug', object_type: :label }
)
rows, errors = importer.build_labels
diff --git a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb
index cf44d510c80..fa7283d210b 100644
--- a/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/milestones_importer_spec.rb
@@ -80,13 +80,12 @@ RSpec.describe Gitlab::GithubImport::Importer::MilestonesImporter, :clean_gitlab
.to receive(:each_milestone)
.and_return([milestone])
- expect(Gitlab::Import::Logger).to receive(:error)
+ expect(Gitlab::GithubImport::Logger).to receive(:error)
.with(
- import_type: :github,
project_id: project.id,
importer: described_class.name,
message: ["Title can't be blank"],
- github_identifiers: { iid: 2, object_type: :milestone, title: nil }
+ external_identifiers: { iid: 2, object_type: :milestone, title: nil }
)
rows, errors = importer.build_milestones
diff --git a/spec/lib/gitlab/github_import/importer/note_importer_spec.rb b/spec/lib/gitlab/github_import/importer/note_importer_spec.rb
index 5ac50578b6a..91311a8e90f 100644
--- a/spec/lib/gitlab/github_import/importer/note_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/note_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
+RSpec.describe Gitlab::GithubImport::Importer::NoteImporter, feature_category: :importers do
let(:client) { double(:client) }
let(:project) { create(:project) }
let(:user) { create(:user) }
@@ -12,13 +12,13 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
let(:github_note) do
Gitlab::GithubImport::Representation::Note.new(
+ note_id: 100,
noteable_id: 1,
noteable_type: 'Issue',
author: Gitlab::GithubImport::Representation::User.new(id: 4, login: 'alice'),
note: note_body,
created_at: created_at,
- updated_at: updated_at,
- github_id: 1
+ updated_at: updated_at
)
end
@@ -50,6 +50,7 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
noteable_type: 'Issue',
noteable_id: issue_row.id,
project_id: project.id,
+ namespace_id: project.project_namespace_id,
author_id: user.id,
note: 'This is my note',
discussion_id: match(/\A[0-9a-f]{40}\z/),
@@ -81,6 +82,7 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
noteable_type: 'Issue',
noteable_id: issue_row.id,
project_id: project.id,
+ namespace_id: project.project_namespace_id,
author_id: project.creator_id,
note: "*Created by: alice*\n\nThis is my note",
discussion_id: match(/\A[0-9a-f]{40}\z/),
@@ -126,34 +128,20 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
expect { importer.execute }.to raise_error(ActiveRecord::RecordInvalid)
end
end
- end
-
- context 'when the noteable does not exist' do
- it 'does not import the note' do
- expect(ApplicationRecord).not_to receive(:legacy_bulk_insert)
-
- importer.execute
- end
- end
-
- context 'when the import fails due to a foreign key error' do
- it 'does not raise any errors' do
- issue_row = create(:issue, project: project, iid: 1)
-
- allow(importer)
- .to receive(:find_noteable_id)
- .and_return(issue_row.id)
- allow(importer.user_finder)
- .to receive(:author_id_for)
- .with(github_note)
- .and_return([user.id, true])
-
- expect(ApplicationRecord)
- .to receive(:legacy_bulk_insert)
- .and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key')
+ context 'when noteble_id can not be found' do
+ before do
+ allow(importer)
+ .to receive(:find_noteable_id)
+ .and_return(nil)
+ end
- expect { importer.execute }.not_to raise_error
+ it 'raises NoteableNotFound' do
+ expect { importer.execute }.to raise_error(
+ ::Gitlab::GithubImport::Exceptions::NoteableNotFound,
+ 'Error to find noteable_id for note'
+ )
+ end
end
end
@@ -173,13 +161,6 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
expect(project.notes.take).to be_valid
end
-
- # rubocop:disable RSpec/AnyInstanceOf
- it 'skips markdown field cache callback' do
- expect_any_instance_of(Note).not_to receive(:refresh_markdown_cache)
- importer.execute
- end
- # rubocop:enable RSpec/AnyInstanceOf
end
describe '#find_noteable_id' do
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
index dd73b6879e0..52c91d91eff 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redis_cache, feature_category: :importers do
let(:project) { create(:project, :repository) }
let(:client) { double(:client) }
let(:user) { create(:user) }
@@ -42,9 +42,9 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitla
let(:importer) { described_class.new(pull_request, project, client) }
describe '#execute' do
- it 'imports the pull request' do
- mr = double(:merge_request, id: 10, merged?: false)
+ let(:mr) { double(:merge_request, id: 10, merged?: false) }
+ it 'imports the pull request' do
expect(importer)
.to receive(:create_merge_request)
.and_return([mr, false])
@@ -63,6 +63,27 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitla
importer.execute
end
+
+ it 'caches the created MR ID even if importer later fails' do
+ error = StandardError.new('mocked error')
+
+ allow_next_instance_of(described_class) do |importer|
+ allow(importer)
+ .to receive(:create_merge_request)
+ .and_return([mr, false])
+ allow(importer)
+ .to receive(:set_merge_request_assignees)
+ .and_raise(error)
+ end
+
+ expect_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ expect(finder)
+ .to receive(:cache_database_id)
+ .with(mr.id)
+ end
+
+ expect { importer.execute }.to raise_error(error)
+ end
end
describe '#create_merge_request' do
diff --git a/spec/lib/gitlab/github_import/importer/pull_requests/review_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests/review_requests_importer_spec.rb
index 9e9d6c6e9cd..d0145ba1120 100644
--- a/spec/lib/gitlab/github_import/importer/pull_requests/review_requests_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_requests/review_requests_importer_spec.rb
@@ -54,6 +54,9 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequests::ReviewRequestsImpor
expect(note_attachments_importer).to receive(:execute)
end
+ expect(Gitlab::GithubImport::ObjectCounter)
+ .to receive(:increment).twice.with(project, :pull_request_review_request, :fetched)
+
importer.sequential_import
end
@@ -72,6 +75,9 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequests::ReviewRequestsImpor
expect(note_attachments_importer).to receive(:execute)
end
+ expect(Gitlab::GithubImport::ObjectCounter)
+ .to receive(:increment).once.with(project, :pull_request_review_request, :fetched)
+
importer.sequential_import
end
end
@@ -115,6 +121,9 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequests::ReviewRequestsImpor
expect(Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker)
.to receive(:perform_in).with(1.second, *expected_worker_payload.second)
+ expect(Gitlab::GithubImport::ObjectCounter)
+ .to receive(:increment).twice.with(project, :pull_request_review_request, :fetched)
+
importer.parallel_import
end
@@ -130,6 +139,9 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequests::ReviewRequestsImpor
expect(Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker)
.to receive(:perform_in).with(1.second, *expected_worker_payload.second)
+ expect(Gitlab::GithubImport::ObjectCounter)
+ .to receive(:increment).once.with(project, :pull_request_review_request, :fetched)
+
importer.parallel_import
end
end
diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
index eddde272d2c..cfd75fba849 100644
--- a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
@@ -149,7 +149,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsImporter, feature_cat
it 'updates the repository' do
importer = described_class.new(project, client)
- expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect_next_instance_of(Gitlab::GithubImport::Logger) do |logger|
expect(logger)
.to receive(:info)
.with(an_instance_of(Hash))
diff --git a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
index a3d20af22c7..1cfbe8e20ae 100644
--- a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
@@ -148,7 +148,7 @@ RSpec.describe Gitlab::GithubImport::Importer::ReleasesImporter, feature_categor
expect(errors[0][:validation_errors].full_messages).to match_array(
['Description is too long (maximum is 1000000 characters)']
)
- expect(errors[0][:github_identifiers]).to eq({ tag: '1.0', object_type: :release })
+ expect(errors[0][:external_identifiers]).to eq({ tag: '1.0', object_type: :release })
end
end
diff --git a/spec/lib/gitlab/github_import/settings_spec.rb b/spec/lib/gitlab/github_import/settings_spec.rb
index d670aaea482..de497bc6689 100644
--- a/spec/lib/gitlab/github_import/settings_spec.rb
+++ b/spec/lib/gitlab/github_import/settings_spec.rb
@@ -62,17 +62,20 @@ RSpec.describe Gitlab::GithubImport::Settings, feature_category: :importers do
collaborators_import: false,
foo: :bar
},
+ timeout_strategy: "optimistic",
additional_access_tokens: %w[foo bar]
}.stringify_keys
end
- it 'puts optional steps & access tokens into projects import_data' do
- project.create_or_update_import_data(credentials: { user: 'token' })
+ it 'puts optional steps, timeout strategy & access tokens into projects import_data' do
+ project.build_or_assign_import_data(credentials: { user: 'token' })
settings.write(data_input)
expect(project.import_data.data['optional_stages'])
.to eq optional_stages.stringify_keys
+ expect(project.import_data.data['timeout_strategy'])
+ .to eq("optimistic")
expect(project.import_data.credentials.fetch(:additional_access_tokens))
.to eq(data_input['additional_access_tokens'])
end
@@ -80,7 +83,7 @@ RSpec.describe Gitlab::GithubImport::Settings, feature_category: :importers do
describe '#enabled?' do
it 'returns is enabled or not specific optional stage' do
- project.create_or_update_import_data(data: { optional_stages: optional_stages })
+ project.build_or_assign_import_data(data: { optional_stages: optional_stages })
expect(settings.enabled?(:single_endpoint_issue_events_import)).to eq true
expect(settings.enabled?(:single_endpoint_notes_import)).to eq false
@@ -91,7 +94,7 @@ RSpec.describe Gitlab::GithubImport::Settings, feature_category: :importers do
describe '#disabled?' do
it 'returns is disabled or not specific optional stage' do
- project.create_or_update_import_data(data: { optional_stages: optional_stages })
+ project.build_or_assign_import_data(data: { optional_stages: optional_stages })
expect(settings.disabled?(:single_endpoint_issue_events_import)).to eq false
expect(settings.disabled?(:single_endpoint_notes_import)).to eq true
diff --git a/spec/lib/gitlab/gon_helper_spec.rb b/spec/lib/gitlab/gon_helper_spec.rb
index fc722402917..e4684597ddf 100644
--- a/spec/lib/gitlab/gon_helper_spec.rb
+++ b/spec/lib/gitlab/gon_helper_spec.rb
@@ -206,6 +206,7 @@ RSpec.describe Gitlab::GonHelper do
context 'when feature flag is false' do
before do
stub_feature_flags(browsersdk_tracking: false)
+ stub_feature_flags(gl_analytics_tracking: false)
end
it "doesn't set the analytics_url and analytics_id" do
diff --git a/spec/lib/gitlab/graphql/deprecations/deprecation_spec.rb b/spec/lib/gitlab/graphql/deprecations/deprecation_spec.rb
index 55650b0480e..4db9c1da418 100644
--- a/spec/lib/gitlab/graphql/deprecations/deprecation_spec.rb
+++ b/spec/lib/gitlab/graphql/deprecations/deprecation_spec.rb
@@ -175,6 +175,23 @@ RSpec.describe ::Gitlab::Graphql::Deprecations::Deprecation, feature_category: :
expect(desc).to be_nil
end
+
+ it 'strips any leading or trailing spaces' do
+ desc = deprecation.edit_description(" Some description. \n")
+
+ expect(desc).to eq('Some description. Deprecated in 10.10: This was renamed.')
+ end
+
+ it 'strips any leading or trailing spaces in heredoc string literals' do
+ description = <<~DESC
+ Lorem ipsum
+ dolor sit amet.
+ DESC
+
+ desc = deprecation.edit_description(description)
+
+ expect(desc).to eq("Lorem ipsum\ndolor sit amet. Deprecated in 10.10: This was renamed.")
+ end
end
describe '#original_description' do
diff --git a/spec/lib/gitlab/graphql/pagination/array_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/array_connection_spec.rb
index 03cf53bb990..28885d0379b 100644
--- a/spec/lib/gitlab/graphql/pagination/array_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/array_connection_spec.rb
@@ -3,9 +3,10 @@
require 'spec_helper'
RSpec.describe ::Gitlab::Graphql::Pagination::ArrayConnection do
+ let(:context) { instance_double(GraphQL::Query::Context, schema: GitlabSchema) }
let(:nodes) { (1..10) }
- subject(:connection) { described_class.new(nodes, max_page_size: 100) }
+ subject(:connection) { described_class.new(nodes, context: context, max_page_size: 100) }
it_behaves_like 'a connection with collection methods'
diff --git a/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
index d2475d1edb9..e3ae6732ebb 100644
--- a/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
@@ -3,6 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection do
+ let(:context) { instance_double(GraphQL::Query::Context, schema: GitlabSchema) }
let(:prev_cursor) { 1 }
let(:next_cursor) { 6 }
let(:values) { [2, 3, 4, 5] }
@@ -10,7 +11,7 @@ RSpec.describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection d
let(:arguments) { {} }
subject(:connection) do
- described_class.new(all_nodes, **{ max_page_size: values.size }.merge(arguments))
+ described_class.new(all_nodes, **{ context: context, max_page_size: values.size }.merge(arguments))
end
it_behaves_like 'a connection with collection methods'
diff --git a/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
index 1ca7c1c3c69..a8babaf8d3b 100644
--- a/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
@@ -3,18 +3,20 @@
require 'spec_helper'
RSpec.describe Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection do
+ let(:context) { instance_double(GraphQL::Query::Context, schema: GitlabSchema) }
+
it 'subclasses from GraphQL::Relay::RelationConnection' do
expect(described_class.superclass).to eq GraphQL::Pagination::ActiveRecordRelationConnection
end
it_behaves_like 'a connection with collection methods' do
- let(:connection) { described_class.new(Project.all) }
+ let(:connection) { described_class.new(Project.all, context: context) }
end
it_behaves_like 'a redactable connection' do
let_it_be(:users) { create_list(:user, 2) }
- let(:connection) { described_class.new(User.all, max_page_size: 10) }
+ let(:connection) { described_class.new(User.all, context: context, max_page_size: 10) }
let(:unwanted) { users.second }
end
end
diff --git a/spec/lib/gitlab/graphql/timeout_spec.rb b/spec/lib/gitlab/graphql/timeout_spec.rb
index 999840019d2..fd27def6973 100644
--- a/spec/lib/gitlab/graphql/timeout_spec.rb
+++ b/spec/lib/gitlab/graphql/timeout_spec.rb
@@ -8,10 +8,9 @@ RSpec.describe Gitlab::Graphql::Timeout do
end
it 'sends the error to our GraphQL logger' do
- parent_type = double(graphql_name: 'parent_type')
- field = double(graphql_name: 'field')
+ field = double(path: 'parent_type.field')
query = double(query_string: 'query_string', provided_variables: 'provided_variables')
- error = GraphQL::Schema::Timeout::TimeoutError.new(parent_type, field)
+ error = GraphQL::Schema::Timeout::TimeoutError.new(field)
expect(Gitlab::GraphqlLogger)
.to receive(:error)
diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb
index 314759fb8a4..84a2a0549d5 100644
--- a/spec/lib/gitlab/group_search_results_spec.rb
+++ b/spec/lib/gitlab/group_search_results_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe Gitlab::GroupSearchResults, feature_category: :global_search do
let(:query) { 'foo' }
let(:scope) { 'milestones' }
- include_examples 'search results filtered by archived', 'search_milestones_hide_archived_projects'
+ include_examples 'search results filtered by archived'
end
describe '#projects' do
diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
deleted file mode 100644
index f4f15cab05a..00000000000
--- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb
+++ /dev/null
@@ -1,247 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::HashedStorage::Migrator, :redis do
- describe '#bulk_schedule_migration' do
- it 'schedules job to HashedStorage::MigratorWorker' do
- Sidekiq::Testing.fake! do
- expect { subject.bulk_schedule_migration(start: 1, finish: 5) }.to change(HashedStorage::MigratorWorker.jobs, :size).by(1)
- end
- end
- end
-
- describe '#bulk_schedule_rollback' do
- it 'schedules job to HashedStorage::RollbackerWorker' do
- Sidekiq::Testing.fake! do
- expect { subject.bulk_schedule_rollback(start: 1, finish: 5) }.to change(HashedStorage::RollbackerWorker.jobs, :size).by(1)
- end
- end
- end
-
- describe '#bulk_migrate' do
- let(:projects) { create_list(:project, 2, :legacy_storage, :empty_repo) }
- let(:ids) { projects.map(&:id) }
-
- it 'enqueue jobs to HashedStorage::ProjectMigrateWorker' do
- Sidekiq::Testing.fake! do
- expect { subject.bulk_migrate(start: ids.min, finish: ids.max) }.to change(HashedStorage::ProjectMigrateWorker.jobs, :size).by(2)
- end
- end
-
- it 'rescues and log exceptions' do
- allow_any_instance_of(Project).to receive(:migrate_to_hashed_storage!).and_raise(StandardError)
- expect { subject.bulk_migrate(start: ids.min, finish: ids.max) }.not_to raise_error
- end
-
- it 'delegates each project in specified range to #migrate' do
- projects.each do |project|
- expect(subject).to receive(:migrate).with(project)
- end
-
- subject.bulk_migrate(start: ids.min, finish: ids.max)
- end
-
- it 'has all projects migrated and set as writable', :sidekiq_might_not_need_inline do
- perform_enqueued_jobs do
- subject.bulk_migrate(start: ids.min, finish: ids.max)
- end
-
- projects.each do |project|
- project.reload
-
- expect(project.hashed_storage?(:repository)).to be_truthy
- expect(project.repository_read_only?).to be_falsey
- end
- end
- end
-
- describe '#bulk_rollback' do
- let(:projects) { create_list(:project, 2, :empty_repo) }
- let(:ids) { projects.map(&:id) }
-
- it 'enqueue jobs to HashedStorage::ProjectRollbackWorker' do
- Sidekiq::Testing.fake! do
- expect { subject.bulk_rollback(start: ids.min, finish: ids.max) }.to change(HashedStorage::ProjectRollbackWorker.jobs, :size).by(2)
- end
- end
-
- it 'rescues and log exceptions' do
- allow_any_instance_of(Project).to receive(:rollback_to_legacy_storage!).and_raise(StandardError)
- expect { subject.bulk_rollback(start: ids.min, finish: ids.max) }.not_to raise_error
- end
-
- it 'delegates each project in specified range to #rollback' do
- projects.each do |project|
- expect(subject).to receive(:rollback).with(project)
- end
-
- subject.bulk_rollback(start: ids.min, finish: ids.max)
- end
-
- it 'has all projects rolledback and set as writable', :sidekiq_might_not_need_inline do
- perform_enqueued_jobs do
- subject.bulk_rollback(start: ids.min, finish: ids.max)
- end
-
- projects.each do |project|
- project.reload
-
- expect(project.legacy_storage?).to be_truthy
- expect(project.repository_read_only?).to be_falsey
- end
- end
- end
-
- describe '#migrate' do
- let(:project) { create(:project, :legacy_storage, :empty_repo) }
-
- it 'enqueues project migration job' do
- Sidekiq::Testing.fake! do
- expect { subject.migrate(project) }.to change(HashedStorage::ProjectMigrateWorker.jobs, :size).by(1)
- end
- end
-
- it 'rescues and log exceptions' do
- allow(project).to receive(:migrate_to_hashed_storage!).and_raise(StandardError)
-
- expect { subject.migrate(project) }.not_to raise_error
- end
-
- it 'migrates project storage', :sidekiq_might_not_need_inline do
- perform_enqueued_jobs do
- subject.migrate(project)
- end
-
- expect(project.reload.hashed_storage?(:attachments)).to be_truthy
- end
-
- it 'has migrated project set as writable' do
- perform_enqueued_jobs do
- subject.migrate(project)
- end
-
- expect(project.reload.repository_read_only?).to be_falsey
- end
-
- context 'when project is already on hashed storage' do
- let(:project) { create(:project, :empty_repo) }
-
- it 'doesnt enqueue any migration job' do
- Sidekiq::Testing.fake! do
- expect { subject.migrate(project) }.not_to change(HashedStorage::ProjectMigrateWorker.jobs, :size)
- end
- end
-
- it 'returns false' do
- expect(subject.migrate(project)).to be_falsey
- end
- end
- end
-
- describe '#rollback' do
- let(:project) { create(:project, :empty_repo) }
-
- it 'enqueues project rollback job' do
- Sidekiq::Testing.fake! do
- expect { subject.rollback(project) }.to change(HashedStorage::ProjectRollbackWorker.jobs, :size).by(1)
- end
- end
-
- it 'rescues and log exceptions' do
- allow(project).to receive(:rollback_to_hashed_storage!).and_raise(StandardError)
-
- expect { subject.rollback(project) }.not_to raise_error
- end
-
- it 'rolls-back project storage', :sidekiq_might_not_need_inline do
- perform_enqueued_jobs do
- subject.rollback(project)
- end
-
- expect(project.reload.legacy_storage?).to be_truthy
- end
-
- it 'has rolled-back project set as writable' do
- perform_enqueued_jobs do
- subject.rollback(project)
- end
-
- expect(project.reload.repository_read_only?).to be_falsey
- end
-
- context 'when project is already on legacy storage' do
- let(:project) { create(:project, :legacy_storage, :empty_repo) }
-
- it 'doesnt enqueue any rollback job' do
- Sidekiq::Testing.fake! do
- expect { subject.rollback(project) }.not_to change(HashedStorage::ProjectRollbackWorker.jobs, :size)
- end
- end
-
- it 'returns false' do
- expect(subject.rollback(project)).to be_falsey
- end
- end
- end
-
- describe 'migration_pending?' do
- let_it_be(:project) { create(:project, :empty_repo) }
-
- it 'returns true when there are MigratorWorker jobs scheduled' do
- Sidekiq::Testing.disable! do
- ::HashedStorage::MigratorWorker.perform_async(1, 5)
-
- expect(subject.migration_pending?).to be_truthy
- end
- end
-
- it 'returns true when there are ProjectMigrateWorker jobs scheduled' do
- Sidekiq::Testing.disable! do
- ::HashedStorage::ProjectMigrateWorker.perform_async(1)
-
- expect(subject.migration_pending?).to be_truthy
- end
- end
-
- it 'returns false when queues are empty' do
- expect(subject.migration_pending?).to be_falsey
- end
- end
-
- describe 'rollback_pending?' do
- let_it_be(:project) { create(:project, :empty_repo) }
-
- it 'returns true when there are RollbackerWorker jobs scheduled' do
- Sidekiq::Testing.disable! do
- ::HashedStorage::RollbackerWorker.perform_async(1, 5)
-
- expect(subject.rollback_pending?).to be_truthy
- end
- end
-
- it 'returns true when there are jobs scheduled' do
- Sidekiq::Testing.disable! do
- ::HashedStorage::ProjectRollbackWorker.perform_async(1)
-
- expect(subject.rollback_pending?).to be_truthy
- end
- end
-
- it 'returns false when queues are empty' do
- expect(subject.rollback_pending?).to be_falsey
- end
- end
-
- describe 'abort_rollback!' do
- let_it_be(:project) { create(:project, :empty_repo) }
-
- it 'removes any rollback related scheduled job' do
- Sidekiq::Testing.disable! do
- ::HashedStorage::RollbackerWorker.perform_async(1, 5)
-
- expect { subject.abort_rollback! }.to change { subject.rollback_pending? }.from(true).to(false)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb
index 9d89167bf81..a9e0c6a3b92 100644
--- a/spec/lib/gitlab/http_spec.rb
+++ b/spec/lib/gitlab/http_spec.rb
@@ -2,441 +2,102 @@
require 'spec_helper'
-RSpec.describe Gitlab::HTTP do
- include StubRequests
-
- let(:default_options) { described_class::DEFAULT_TIMEOUT_OPTIONS }
-
- context 'when allow_local_requests' do
- it 'sends the request to the correct URI' do
- stub_full_request('https://example.org:8080', ip_address: '8.8.8.8').to_return(status: 200)
-
- described_class.get('https://example.org:8080', allow_local_requests: false)
-
- expect(WebMock).to have_requested(:get, 'https://8.8.8.8:8080').once
- end
+RSpec.describe Gitlab::HTTP, feature_category: :shared do
+ let(:default_options) do
+ {
+ allow_local_requests: false,
+ deny_all_requests_except_allowed: false,
+ dns_rebinding_protection_enabled: true,
+ outbound_local_requests_allowlist: [],
+ silent_mode_enabled: false
+ }
end
- context 'when not allow_local_requests' do
- it 'sends the request to the correct URI' do
- stub_full_request('https://example.org:8080')
-
- described_class.get('https://example.org:8080', allow_local_requests: true)
-
- expect(WebMock).to have_requested(:get, 'https://8.8.8.9:8080').once
- end
- end
-
- context 'when reading the response is too slow' do
- before_all do
- # Override Net::HTTP to add a delay between sending each response chunk
- mocked_http = Class.new(Net::HTTP) do
- def request(*)
- super do |response|
- response.instance_eval do
- def read_body(*)
- mock_stream = @body.split(' ')
- mock_stream.each do |fragment|
- sleep 0.002.seconds
-
- yield fragment if block_given?
- end
-
- @body
- end
- end
-
- yield response if block_given?
-
- response
- end
- end
- end
-
- @original_net_http = Net.send(:remove_const, :HTTP)
- @webmock_net_http = WebMock::HttpLibAdapters::NetHttpAdapter.instance_variable_get(:@webMockNetHTTP)
-
- Net.send(:const_set, :HTTP, mocked_http)
- WebMock::HttpLibAdapters::NetHttpAdapter.instance_variable_set(:@webMockNetHTTP, mocked_http)
+ describe '.get' do
+ it 'calls Gitlab::HTTP_V2.get with default options' do
+ expect(Gitlab::HTTP_V2).to receive(:get).with('/path', default_options)
- # Reload Gitlab::NetHttpAdapter
- Gitlab.send(:remove_const, :NetHttpAdapter)
- load "#{Rails.root}/lib/gitlab/net_http_adapter.rb"
+ described_class.get('/path')
end
- before do
- stub_const("#{described_class}::DEFAULT_READ_TOTAL_TIMEOUT", 0.001.seconds)
-
- WebMock.stub_request(:post, /.*/).to_return do
- { body: "chunk-1 chunk-2", status: 200 }
- end
- end
-
- after(:all) do
- Net.send(:remove_const, :HTTP)
- Net.send(:const_set, :HTTP, @original_net_http)
- WebMock::HttpLibAdapters::NetHttpAdapter.instance_variable_set(:@webMockNetHTTP, @webmock_net_http)
-
- # Reload Gitlab::NetHttpAdapter
- Gitlab.send(:remove_const, :NetHttpAdapter)
- load "#{Rails.root}/lib/gitlab/net_http_adapter.rb"
- end
-
- let(:options) { {} }
-
- subject(:request_slow_responder) { described_class.post('http://example.org', **options) }
-
- it 'raises an error' do
- expect { request_slow_responder }.to raise_error(Gitlab::HTTP::ReadTotalTimeout, /Request timed out after ?([0-9]*[.])?[0-9]+ seconds/)
- end
-
- context 'and timeout option is greater than DEFAULT_READ_TOTAL_TIMEOUT' do
- let(:options) { { timeout: 10.seconds } }
-
- it 'does not raise an error' do
- expect { request_slow_responder }.not_to raise_error
- end
- end
-
- context 'and stream_body option is truthy' do
- let(:options) { { stream_body: true } }
-
- it 'does not raise an error' do
- expect { request_slow_responder }.not_to raise_error
- end
- end
- end
-
- it 'calls a block' do
- WebMock.stub_request(:post, /.*/)
-
- expect { |b| described_class.post('http://example.org', &b) }.to yield_with_args
- end
-
- describe 'allow_local_requests_from_web_hooks_and_services is' do
- before do
- WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success')
- end
-
- context 'disabled' do
+ context 'when passing allow_object_storage:true' do
before do
- allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(false)
- end
-
- it 'deny requests to localhost' do
- expect { described_class.get('http://localhost:3003') }.to raise_error(Gitlab::HTTP::BlockedUrlError)
- end
-
- it 'deny requests to private network' do
- expect { described_class.get('http://192.168.1.2:3003') }.to raise_error(Gitlab::HTTP::BlockedUrlError)
+ allow(ObjectStoreSettings).to receive(:enabled_endpoint_uris).and_return([URI('http://example.com')])
end
- context 'if allow_local_requests set to true' do
- it 'override the global value and allow requests to localhost or private network' do
- stub_full_request('http://localhost:3003')
+ it 'calls Gitlab::HTTP_V2.get with default options and extra_allowed_uris' do
+ expect(Gitlab::HTTP_V2).to receive(:get)
+ .with('/path', default_options.merge(extra_allowed_uris: [URI('http://example.com')]))
- expect { described_class.get('http://localhost:3003', allow_local_requests: true) }.not_to raise_error
- end
+ described_class.get('/path', allow_object_storage: true)
end
end
-
- context 'enabled' do
- before do
- allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(true)
- end
-
- it 'allow requests to localhost' do
- stub_full_request('http://localhost:3003')
-
- expect { described_class.get('http://localhost:3003') }.not_to raise_error
- end
-
- it 'allow requests to private network' do
- expect { described_class.get('http://192.168.1.2:3003') }.not_to raise_error
- end
-
- context 'if allow_local_requests set to false' do
- it 'override the global value and ban requests to localhost or private network' do
- expect { described_class.get('http://localhost:3003', allow_local_requests: false) }.to raise_error(Gitlab::HTTP::BlockedUrlError)
- end
- end
- end
- end
-
- describe 'handle redirect loops' do
- before do
- stub_full_request("http://example.org", method: :any).to_raise(HTTParty::RedirectionTooDeep.new("Redirection Too Deep"))
- end
-
- it 'handles GET requests' do
- expect { described_class.get('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
- end
-
- it 'handles POST requests' do
- expect { described_class.post('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
- end
-
- it 'handles PUT requests' do
- expect { described_class.put('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
- end
-
- it 'handles DELETE requests' do
- expect { described_class.delete('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
- end
-
- it 'handles HEAD requests' do
- expect { described_class.head('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
- end
end
- describe 'setting default timeouts' do
- before do
- stub_full_request('http://example.org', method: :any)
- end
-
- context 'when no timeouts are set' do
- it 'sets default open and read and write timeouts' do
- expect(described_class).to receive(:httparty_perform_request).with(
- Net::HTTP::Get, 'http://example.org', default_options
- ).and_call_original
-
- described_class.get('http://example.org')
- end
- end
-
- context 'when :timeout is set' do
- it 'does not set any default timeouts' do
- expect(described_class).to receive(:httparty_perform_request).with(
- Net::HTTP::Get, 'http://example.org', { timeout: 1 }
- ).and_call_original
-
- described_class.get('http://example.org', { timeout: 1 })
- end
- end
-
- context 'when :open_timeout is set' do
- it 'only sets default read and write timeout' do
- expect(described_class).to receive(:httparty_perform_request).with(
- Net::HTTP::Get, 'http://example.org', default_options.merge(open_timeout: 1)
- ).and_call_original
+ describe '.try_get' do
+ it 'calls .get' do
+ expect(described_class).to receive(:get).with('/path', {})
- described_class.get('http://example.org', open_timeout: 1)
- end
+ described_class.try_get('/path')
end
- context 'when :read_timeout is set' do
- it 'only sets default open and write timeout' do
- expect(described_class).to receive(:httparty_perform_request).with(
- Net::HTTP::Get, 'http://example.org', default_options.merge(read_timeout: 1)
- ).and_call_original
+ it 'returns nil when .get raises an error' do
+ expect(described_class).to receive(:get).and_raise(SocketError)
- described_class.get('http://example.org', read_timeout: 1)
- end
- end
-
- context 'when :write_timeout is set' do
- it 'only sets default open and read timeout' do
- expect(described_class).to receive(:httparty_perform_request).with(
- Net::HTTP::Put, 'http://example.org', default_options.merge(write_timeout: 1)
- ).and_call_original
-
- described_class.put('http://example.org', write_timeout: 1)
- end
+ expect(described_class.try_get('/path')).to be_nil
end
end
- describe '.try_get' do
- let(:path) { 'http://example.org' }
+ describe '.perform_request' do
+ context 'when sending a GET request' do
+ it 'calls Gitlab::HTTP_V2.get with default options' do
+ expect(Gitlab::HTTP_V2).to receive(:get).with('/path', default_options)
- let(:extra_log_info_proc) do
- proc do |error, url, options|
- { klass: error.class, url: url, options: options }
+ described_class.perform_request(Net::HTTP::Get, '/path', {})
end
end
- let(:request_options) do
- default_options.merge({
- verify: false,
- basic_auth: { username: 'user', password: 'pass' }
- })
- end
-
- described_class::HTTP_ERRORS.each do |exception_class|
- context "with #{exception_class}" do
- let(:klass) { exception_class }
-
- context 'with path' do
- before do
- expect(described_class).to receive(:httparty_perform_request)
- .with(Net::HTTP::Get, path, default_options)
- .and_raise(klass)
- end
-
- it 'handles requests without extra_log_info' do
- expect(Gitlab::ErrorTracking)
- .to receive(:log_exception)
- .with(instance_of(klass), {})
-
- expect(described_class.try_get(path)).to be_nil
- end
-
- it 'handles requests with extra_log_info as hash' do
- expect(Gitlab::ErrorTracking)
- .to receive(:log_exception)
- .with(instance_of(klass), { a: :b })
-
- expect(described_class.try_get(path, extra_log_info: { a: :b })).to be_nil
- end
-
- it 'handles requests with extra_log_info as proc' do
- expect(Gitlab::ErrorTracking)
- .to receive(:log_exception)
- .with(instance_of(klass), { url: path, klass: klass, options: {} })
-
- expect(described_class.try_get(path, extra_log_info: extra_log_info_proc)).to be_nil
- end
- end
-
- context 'with path and options' do
- before do
- expect(described_class).to receive(:httparty_perform_request)
- .with(Net::HTTP::Get, path, request_options)
- .and_raise(klass)
- end
-
- it 'handles requests without extra_log_info' do
- expect(Gitlab::ErrorTracking)
- .to receive(:log_exception)
- .with(instance_of(klass), {})
-
- expect(described_class.try_get(path, request_options)).to be_nil
- end
-
- it 'handles requests with extra_log_info as hash' do
- expect(Gitlab::ErrorTracking)
- .to receive(:log_exception)
- .with(instance_of(klass), { a: :b })
-
- expect(described_class.try_get(path, **request_options, extra_log_info: { a: :b })).to be_nil
- end
-
- it 'handles requests with extra_log_info as proc' do
- expect(Gitlab::ErrorTracking)
- .to receive(:log_exception)
- .with(instance_of(klass), { klass: klass, url: path, options: request_options })
-
- expect(described_class.try_get(path, **request_options, extra_log_info: extra_log_info_proc)).to be_nil
- end
- end
-
- context 'with path, options, and block' do
- let(:block) do
- proc {}
- end
-
- before do
- expect(described_class).to receive(:httparty_perform_request)
- .with(Net::HTTP::Get, path, request_options, &block)
- .and_raise(klass)
- end
-
- it 'handles requests without extra_log_info' do
- expect(Gitlab::ErrorTracking)
- .to receive(:log_exception)
- .with(instance_of(klass), {})
-
- expect(described_class.try_get(path, request_options, &block)).to be_nil
- end
-
- it 'handles requests with extra_log_info as hash' do
- expect(Gitlab::ErrorTracking)
- .to receive(:log_exception)
- .with(instance_of(klass), { a: :b })
-
- expect(described_class.try_get(path, **request_options, extra_log_info: { a: :b }, &block)).to be_nil
- end
-
- it 'handles requests with extra_log_info as proc' do
- expect(Gitlab::ErrorTracking)
- .to receive(:log_exception)
- .with(instance_of(klass), { klass: klass, url: path, options: request_options })
-
- expect(described_class.try_get(path, **request_options, extra_log_info: extra_log_info_proc, &block)).to be_nil
- end
- end
+ context 'when sending a LOCK request' do
+ it 'raises ArgumentError' do
+ expect do
+ described_class.perform_request(Net::HTTP::Lock, '/path', {})
+ end.to raise_error(ArgumentError, "Unsupported HTTP method: 'lock'.")
end
end
end
- describe 'silent mode', feature_category: :geo_replication do
+ context 'when the FF use_gitlab_http_v2 is disabled' do
before do
- stub_full_request("http://example.org", method: :any)
- stub_application_setting(silent_mode_enabled: silent_mode)
+ stub_feature_flags(use_gitlab_http_v2: false)
end
- context 'when silent mode is enabled' do
- let(:silent_mode) { true }
-
- it 'allows GET requests' do
- expect { described_class.get('http://example.org') }.not_to raise_error
- end
+ describe '.get' do
+ it 'calls Gitlab::LegacyHTTP.get with default options' do
+ expect(Gitlab::LegacyHTTP).to receive(:get).with('/path', {})
- it 'allows HEAD requests' do
- expect { described_class.head('http://example.org') }.not_to raise_error
- end
-
- it 'allows OPTIONS requests' do
- expect { described_class.options('http://example.org') }.not_to raise_error
- end
-
- it 'blocks POST requests' do
- expect { described_class.post('http://example.org') }.to raise_error(Gitlab::HTTP::SilentModeBlockedError)
- end
-
- it 'blocks PUT requests' do
- expect { described_class.put('http://example.org') }.to raise_error(Gitlab::HTTP::SilentModeBlockedError)
- end
-
- it 'blocks DELETE requests' do
- expect { described_class.delete('http://example.org') }.to raise_error(Gitlab::HTTP::SilentModeBlockedError)
- end
-
- it 'logs blocked requests' do
- expect(::Gitlab::AppJsonLogger).to receive(:info).with(
- message: "Outbound HTTP request blocked",
- outbound_http_request_method: 'Net::HTTP::Post',
- silent_mode_enabled: true
- )
-
- expect { described_class.post('http://example.org') }.to raise_error(Gitlab::HTTP::SilentModeBlockedError)
+ described_class.get('/path')
end
end
- context 'when silent mode is disabled' do
- let(:silent_mode) { false }
-
- it 'allows GET requests' do
- expect { described_class.get('http://example.org') }.not_to raise_error
- end
+ describe '.try_get' do
+ it 'calls .get' do
+ expect(described_class).to receive(:get).with('/path', {})
- it 'allows HEAD requests' do
- expect { described_class.head('http://example.org') }.not_to raise_error
+ described_class.try_get('/path')
end
- it 'allows OPTIONS requests' do
- expect { described_class.options('http://example.org') }.not_to raise_error
- end
+ it 'returns nil when .get raises an error' do
+ expect(described_class).to receive(:get).and_raise(SocketError)
- it 'blocks POST requests' do
- expect { described_class.post('http://example.org') }.not_to raise_error
+ expect(described_class.try_get('/path')).to be_nil
end
+ end
- it 'blocks PUT requests' do
- expect { described_class.put('http://example.org') }.not_to raise_error
- end
+ describe '.perform_request' do
+ it 'calls Gitlab::LegacyHTTP.perform_request with default options' do
+ expect(Gitlab::LegacyHTTP).to receive(:perform_request).with(Net::HTTP::Get, '/path', {})
- it 'blocks DELETE requests' do
- expect { described_class.delete('http://example.org') }.not_to raise_error
+ described_class.perform_request(Net::HTTP::Get, '/path', {})
end
end
end
diff --git a/spec/lib/gitlab/i18n_spec.rb b/spec/lib/gitlab/i18n_spec.rb
index ee92831922d..fdd868acbb1 100644
--- a/spec/lib/gitlab/i18n_spec.rb
+++ b/spec/lib/gitlab/i18n_spec.rb
@@ -62,4 +62,18 @@ RSpec.describe Gitlab::I18n, feature_category: :internationalization do
end
end
end
+
+ describe '.trimmed_language_name' do
+ it 'trims the language name', :aggregate_failures do
+ expect(described_class.trimmed_language_name('en')).to eq('English')
+ expect(described_class.trimmed_language_name('bg')).to eq('Bulgarian')
+ expect(described_class.trimmed_language_name('id_ID')).to eq('Indonesian')
+ expect(described_class.trimmed_language_name('nb_NO')).to eq('Norwegian (Bokmål)')
+ expect(described_class.trimmed_language_name('zh_HK')).to eq('Chinese, Traditional (Hong Kong)')
+ end
+
+ it 'return nil for unknown language code' do
+ expect(described_class.trimmed_language_name('_invalid_code_')).to be_nil
+ end
+ end
end
diff --git a/spec/lib/gitlab/import/errors_spec.rb b/spec/lib/gitlab/import/errors_spec.rb
index 21d96601609..3b45af0618b 100644
--- a/spec/lib/gitlab/import/errors_spec.rb
+++ b/spec/lib/gitlab/import/errors_spec.rb
@@ -39,7 +39,6 @@ RSpec.describe Gitlab::Import::Errors, feature_category: :importers do
"Noteable can't be blank",
"Author can't be blank",
"Project does not match noteable project",
- "Namespace can't be blank",
"User can't be blank",
"Name is not a valid emoji name"
)
diff --git a/spec/lib/gitlab/import/import_failure_service_spec.rb b/spec/lib/gitlab/import/import_failure_service_spec.rb
index eb71b307b8d..a4682a9495e 100644
--- a/spec/lib/gitlab/import/import_failure_service_spec.rb
+++ b/spec/lib/gitlab/import/import_failure_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures do
+RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures, feature_category: :importers do
let_it_be(:import_type) { 'import_type' }
let_it_be(:project) { create(:project, :import_started, import_type: import_type) }
@@ -10,15 +10,18 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures do
let(:import_state) { nil }
let(:fail_import) { false }
let(:metrics) { false }
+ let(:external_identifiers) { {} }
+ let(:project_id) { project.id }
let(:arguments) do
{
- project_id: project.id,
+ project_id: project_id,
error_source: 'SomeImporter',
exception: exception,
fail_import: fail_import,
metrics: metrics,
- import_state: import_state
+ import_state: import_state,
+ external_identifiers: external_identifiers
}
end
@@ -33,7 +36,8 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures do
project_id: '_project_id_',
error_source: '_error_source_',
fail_import: '_fail_import_',
- metrics: '_metrics_'
+ metrics: '_metrics_',
+ external_identifiers: { id: 1 }
}
end
@@ -59,7 +63,7 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures do
subject(:service) { described_class.new(**arguments) }
shared_examples 'logs the exception and fails the import' do
- it 'when the failure does not abort the import' do
+ specify do
expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
.with(
@@ -67,7 +71,8 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures do
{
project_id: project.id,
import_type: import_type,
- source: 'SomeImporter'
+ source: 'SomeImporter',
+ external_identifiers: external_identifiers
}
)
@@ -76,10 +81,11 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures do
.with(
{
message: 'importer failed',
- 'error.message': 'some error',
+ 'exception.message': 'some error',
project_id: project.id,
import_type: import_type,
- source: 'SomeImporter'
+ source: 'SomeImporter',
+ external_identifiers: external_identifiers
}
)
@@ -95,7 +101,7 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures do
end
shared_examples 'logs the exception and does not fail the import' do
- it 'when the failure does not abort the import' do
+ specify do
expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
.with(
@@ -103,7 +109,8 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures do
{
project_id: project.id,
import_type: import_type,
- source: 'SomeImporter'
+ source: 'SomeImporter',
+ external_identifiers: external_identifiers
}
)
@@ -112,10 +119,11 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures do
.with(
{
message: 'importer failed',
- 'error.message': 'some error',
+ 'exception.message': 'some error',
project_id: project.id,
import_type: import_type,
- source: 'SomeImporter'
+ source: 'SomeImporter',
+ external_identifiers: external_identifiers
}
)
@@ -159,6 +167,7 @@ RSpec.describe Gitlab::Import::ImportFailureService, :aggregate_failures do
end
context 'when using the import_state as reference' do
+ let(:project_id) { nil }
let(:import_state) { project.import_state }
context 'when it fails the import' do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index d337a37c69f..cd899a79451 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -172,7 +172,6 @@ project_members:
- user
- source
- project
-- member_task
- member_namespace
- member_role
member_roles:
@@ -250,6 +249,7 @@ merge_requests:
- created_environments
- predictions
- user_agent_detail
+- scan_result_policy_violations
external_pull_requests:
- project
merge_request_diff:
@@ -668,6 +668,7 @@ project:
- statistics
- container_repositories
- container_registry_data_repair_detail
+- container_registry_protection_rules
- uploads
- file_uploads
- import_state
@@ -823,10 +824,12 @@ project:
- design_management_repository_state
- compliance_standards_adherence
- scan_result_policy_reads
+- scan_result_policy_violations
- project_state
- security_policy_bots
- target_branch_rules
- organization
+- dora_performance_scores
award_emoji:
- awardable
- user
diff --git a/spec/lib/gitlab/import_export/attributes_finder_spec.rb b/spec/lib/gitlab/import_export/attributes_finder_spec.rb
index f12cbe4f82f..fd9d609992d 100644
--- a/spec/lib/gitlab/import_export/attributes_finder_spec.rb
+++ b/spec/lib/gitlab/import_export/attributes_finder_spec.rb
@@ -131,19 +131,19 @@ RSpec.describe Gitlab::ImportExport::AttributesFinder, feature_category: :import
end
it 'generates the correct hash for a relation with included attributes' do
- setup_yaml(tree: { project: [:issues] },
- included_attributes: { issues: [:name, :description] })
+ setup_yaml(
+ tree: { project: [:issues] },
+ included_attributes: { issues: [:name, :description] }
+ )
is_expected.to match(
- include: [{ issues: { include: [],
- only: [:name, :description] } }],
+ include: [{ issues: { include: [], only: [:name, :description] } }],
preload: { issues: nil }
)
end
it 'generates the correct hash for a relation with excluded attributes' do
- setup_yaml(tree: { project: [:issues] },
- excluded_attributes: { issues: [:name] })
+ setup_yaml(tree: { project: [:issues] }, excluded_attributes: { issues: [:name] })
is_expected.to match(
include: [{ issues: { except: [:name],
@@ -153,25 +153,23 @@ RSpec.describe Gitlab::ImportExport::AttributesFinder, feature_category: :import
end
it 'generates the correct hash for a relation with both excluded and included attributes' do
- setup_yaml(tree: { project: [:issues] },
- excluded_attributes: { issues: [:name] },
- included_attributes: { issues: [:description] })
+ setup_yaml(
+ tree: { project: [:issues] },
+ excluded_attributes: { issues: [:name] },
+ included_attributes: { issues: [:description] }
+ )
is_expected.to match(
- include: [{ issues: { except: [:name],
- include: [],
- only: [:description] } }],
+ include: [{ issues: { except: [:name], include: [], only: [:description] } }],
preload: { issues: nil }
)
end
it 'generates the correct hash for a relation with custom methods' do
- setup_yaml(tree: { project: [:issues] },
- methods: { issues: [:name] })
+ setup_yaml(tree: { project: [:issues] }, methods: { issues: [:name] })
is_expected.to match(
- include: [{ issues: { include: [],
- methods: [:name] } }],
+ include: [{ issues: { include: [], methods: [:name] } }],
preload: { issues: nil }
)
end
diff --git a/spec/lib/gitlab/import_export/base/object_builder_spec.rb b/spec/lib/gitlab/import_export/base/object_builder_spec.rb
index 38c3b23db36..3c69a6a7746 100644
--- a/spec/lib/gitlab/import_export/base/object_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/base/object_builder_spec.rb
@@ -4,11 +4,13 @@ require 'spec_helper'
RSpec.describe Gitlab::ImportExport::Base::ObjectBuilder do
let(:project) do
- create(:project, :repository,
- :builds_disabled,
- :issues_disabled,
- name: 'project',
- path: 'project')
+ create(
+ :project, :repository,
+ :builds_disabled,
+ :issues_disabled,
+ name: 'project',
+ path: 'project'
+ )
end
let(:klass) { Milestone }
diff --git a/spec/lib/gitlab/import_export/base/relation_factory_spec.rb b/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
index 4ef8f4b5d76..5e63804c51c 100644
--- a/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
@@ -11,14 +11,16 @@ RSpec.describe Gitlab::ImportExport::Base::RelationFactory do
let(:excluded_keys) { [] }
subject do
- described_class.create(relation_sym: relation_sym, # rubocop:disable Rails/SaveBang
- relation_hash: relation_hash,
- relation_index: 1,
- object_builder: Gitlab::ImportExport::Project::ObjectBuilder,
- members_mapper: members_mapper,
- user: user,
- importable: project,
- excluded_keys: excluded_keys)
+ described_class.create( # rubocop:disable Rails/SaveBang
+ relation_sym: relation_sym,
+ relation_hash: relation_hash,
+ relation_index: 1,
+ object_builder: Gitlab::ImportExport::Project::ObjectBuilder,
+ members_mapper: members_mapper,
+ user: user,
+ importable: project,
+ excluded_keys: excluded_keys
+ )
end
describe '#create' do
diff --git a/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
index 5ef9eb78d3b..144617055ab 100644
--- a/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/design_repo_restorer_spec.rb
@@ -12,9 +12,7 @@ RSpec.describe Gitlab::ImportExport::DesignRepoRestorer do
let(:bundler) { Gitlab::ImportExport::DesignRepoSaver.new(exportable: project_with_design_repo, shared: shared) }
let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.design_repo_bundle_filename) }
let(:restorer) do
- described_class.new(path_to_bundle: bundle_path,
- shared: shared,
- importable: project)
+ described_class.new(path_to_bundle: bundle_path, shared: shared, importable: project)
end
before do
diff --git a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
index 02419267f0e..dfc7202194d 100644
--- a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
@@ -217,17 +217,18 @@ RSpec.describe Gitlab::ImportExport::FastHashSerializer, :with_license, feature_
release = create(:release)
group = create(:group)
- project = create(:project,
- :public,
- :repository,
- :issues_disabled,
- :wiki_enabled,
- :builds_private,
- description: 'description',
- releases: [release],
- group: group,
- approvals_before_merge: 1
- )
+ project = create(
+ :project,
+ :public,
+ :repository,
+ :issues_disabled,
+ :wiki_enabled,
+ :builds_private,
+ description: 'description',
+ releases: [release],
+ group: group,
+ approvals_before_merge: 1
+ )
issue = create(:issue, assignees: [user], project: project)
snippet = create(:project_snippet, project: project)
@@ -249,10 +250,7 @@ RSpec.describe Gitlab::ImportExport::FastHashSerializer, :with_license, feature_
create(:discussion_note, noteable: issue, project: project)
create(:note, noteable: merge_request, project: project)
create(:note, noteable: snippet, project: project)
- create(:note_on_commit,
- author: user,
- project: project,
- commit_id: ci_build.pipeline.sha)
+ create(:note_on_commit, author: user, project: project, commit_id: ci_build.pipeline.sha)
create(:resource_label_event, label: project_label, issue: issue)
create(:resource_label_event, label: group_label, merge_request: merge_request)
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index 53d205850c8..a98080b682b 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -80,7 +80,7 @@ RSpec.describe Gitlab::ImportExport::Importer do
context 'with sample_data_template' do
it 'initializes the Sample::TreeRestorer' do
- project.create_or_update_import_data(data: { sample_data: true })
+ project.build_or_assign_import_data(data: { sample_data: true })
expect(Gitlab::ImportExport::Project::Sample::TreeRestorer).to receive(:new).and_call_original
@@ -112,7 +112,7 @@ RSpec.describe Gitlab::ImportExport::Importer do
end
it 'sets the correct visibility_level when visibility level is a string' do
- project.create_or_update_import_data(
+ project.build_or_assign_import_data(
data: { override_params: { visibility_level: Gitlab::VisibilityLevel::PRIVATE.to_s } }
)
diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
index 3ca9f727033..17d416b0f0a 100644
--- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
+++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
@@ -16,10 +16,12 @@ RSpec.describe Gitlab::ImportExport::MergeRequestParser do
let(:diff_head_sha) { SecureRandom.hex(20) }
let(:parsed_merge_request) do
- described_class.new(project,
- diff_head_sha,
- merge_request,
- merge_request.as_json).parse!
+ described_class.new(
+ project,
+ diff_head_sha,
+ merge_request,
+ merge_request.as_json
+ ).parse!
end
after do
diff --git a/spec/lib/gitlab/import_export/project/export_task_spec.rb b/spec/lib/gitlab/import_export/project/export_task_spec.rb
index 0837874526a..8eb3c76302a 100644
--- a/spec/lib/gitlab/import_export/project/export_task_spec.rb
+++ b/spec/lib/gitlab/import_export/project/export_task_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rake_helper'
+require 'spec_helper'
RSpec.describe Gitlab::ImportExport::Project::ExportTask, :silence_stdout, feature_category: :importers do
let_it_be(:username) { 'root' }
diff --git a/spec/lib/gitlab/import_export/project/import_task_spec.rb b/spec/lib/gitlab/import_export/project/import_task_spec.rb
index 693f1984ce8..d38905992d9 100644
--- a/spec/lib/gitlab/import_export/project/import_task_spec.rb
+++ b/spec/lib/gitlab/import_export/project/import_task_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rake_helper'
+require 'spec_helper'
RSpec.describe Gitlab::ImportExport::Project::ImportTask, :request_store, :silence_stdout, feature_category: :importers do
let(:username) { 'root' }
diff --git a/spec/lib/gitlab/import_export/project/object_builder_spec.rb b/spec/lib/gitlab/import_export/project/object_builder_spec.rb
index 43794ce01a3..20e176bf6fd 100644
--- a/spec/lib/gitlab/import_export/project/object_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/project/object_builder_spec.rb
@@ -6,12 +6,15 @@ RSpec.describe Gitlab::ImportExport::Project::ObjectBuilder do
let!(:group) { create(:group, :private) }
let!(:subgroup) { create(:group, :private, parent: group) }
let!(:project) do
- create(:project, :repository,
- :builds_disabled,
- :issues_disabled,
- name: 'project',
- path: 'project',
- group: subgroup)
+ create(
+ :project,
+ :repository,
+ :builds_disabled,
+ :issues_disabled,
+ name: 'project',
+ path: 'project',
+ group: subgroup
+ )
end
let(:lru_cache) { subject.send(:lru_cache) }
@@ -19,10 +22,7 @@ RSpec.describe Gitlab::ImportExport::Project::ObjectBuilder do
context 'request store is not active' do
subject do
- described_class.new(Label,
- 'title' => 'group label',
- 'project' => project,
- 'group' => project.group)
+ described_class.new(Label, 'title' => 'group label', 'project' => project, 'group' => project.group)
end
it 'ignore cache initialize' do
@@ -33,10 +33,7 @@ RSpec.describe Gitlab::ImportExport::Project::ObjectBuilder do
context 'request store is active', :request_store do
subject do
- described_class.new(Label,
- 'title' => 'group label',
- 'project' => project,
- 'group' => project.group)
+ described_class.new(Label, 'title' => 'group label', 'project' => project, 'group' => project.group)
end
it 'initialize cache in memory' do
@@ -71,27 +68,33 @@ RSpec.describe Gitlab::ImportExport::Project::ObjectBuilder do
it 'finds the existing group label' do
group_label = create(:group_label, name: 'group label', group: project.group)
- expect(described_class.build(Label,
- 'title' => 'group label',
- 'project' => project,
- 'group' => project.group)).to eq(group_label)
+ expect(described_class.build(
+ Label,
+ 'title' => 'group label',
+ 'project' => project,
+ 'group' => project.group
+ )).to eq(group_label)
end
it 'finds the existing group label in root ancestor' do
group_label = create(:group_label, name: 'group label', group: group)
- expect(described_class.build(Label,
- 'title' => 'group label',
- 'project' => project,
- 'group' => group)).to eq(group_label)
+ expect(described_class.build(
+ Label,
+ 'title' => 'group label',
+ 'project' => project,
+ 'group' => group
+ )).to eq(group_label)
end
it 'creates a new project label' do
- label = described_class.build(Label,
- 'title' => 'group label',
- 'project' => project,
- 'group' => project.group,
- 'group_id' => project.group.id)
+ label = described_class.build(
+ Label,
+ 'title' => 'group label',
+ 'project' => project,
+ 'group' => project.group,
+ 'group_id' => project.group.id
+ )
expect(label.persisted?).to be true
expect(label).to be_an_instance_of(ProjectLabel)
@@ -103,26 +106,32 @@ RSpec.describe Gitlab::ImportExport::Project::ObjectBuilder do
it 'finds the existing group milestone' do
milestone = create(:milestone, name: 'group milestone', group: project.group)
- expect(described_class.build(Milestone,
- 'title' => 'group milestone',
- 'project' => project,
- 'group' => project.group)).to eq(milestone)
+ expect(described_class.build(
+ Milestone,
+ 'title' => 'group milestone',
+ 'project' => project,
+ 'group' => project.group
+ )).to eq(milestone)
end
it 'finds the existing group milestone in root ancestor' do
milestone = create(:milestone, name: 'group milestone', group: group)
- expect(described_class.build(Milestone,
- 'title' => 'group milestone',
- 'project' => project,
- 'group' => group)).to eq(milestone)
+ expect(described_class.build(
+ Milestone,
+ 'title' => 'group milestone',
+ 'project' => project,
+ 'group' => group
+ )).to eq(milestone)
end
it 'creates a new milestone' do
- milestone = described_class.build(Milestone,
- 'title' => 'group milestone',
- 'project' => project,
- 'group' => project.group)
+ milestone = described_class.build(
+ Milestone,
+ 'title' => 'group milestone',
+ 'project' => project,
+ 'group' => project.group
+ )
expect(milestone.persisted?).to be true
end
@@ -132,12 +141,14 @@ RSpec.describe Gitlab::ImportExport::Project::ObjectBuilder do
clashing_iid = 1
create(:milestone, iid: clashing_iid, project: project)
- milestone = described_class.build(Milestone,
- 'iid' => clashing_iid,
- 'title' => 'milestone',
- 'project' => project,
- 'group' => nil,
- 'group_id' => nil)
+ milestone = described_class.build(
+ Milestone,
+ 'iid' => clashing_iid,
+ 'title' => 'milestone',
+ 'project' => project,
+ 'group' => nil,
+ 'group_id' => nil
+ )
expect(milestone.persisted?).to be true
expect(Milestone.count).to eq(2)
@@ -173,34 +184,45 @@ RSpec.describe Gitlab::ImportExport::Project::ObjectBuilder do
context 'merge_request' do
it 'finds the existing merge_request' do
- merge_request = create(:merge_request, title: 'MergeRequest', iid: 7, target_project: project, source_project: project)
- expect(described_class.build(MergeRequest,
- 'title' => 'MergeRequest',
- 'source_project_id' => project.id,
- 'target_project_id' => project.id,
- 'source_branch' => 'SourceBranch',
- 'iid' => 7,
- 'target_branch' => 'TargetBranch',
- 'author_id' => project.creator.id)).to eq(merge_request)
+ merge_request = create(
+ :merge_request,
+ title: 'MergeRequest',
+ iid: 7,
+ target_project: project,
+ source_project: project
+ )
+
+ expect(described_class.build(
+ MergeRequest,
+ 'title' => 'MergeRequest',
+ 'source_project_id' => project.id,
+ 'target_project_id' => project.id,
+ 'source_branch' => 'SourceBranch',
+ 'iid' => 7,
+ 'target_branch' => 'TargetBranch',
+ 'author_id' => project.creator.id
+ )).to eq(merge_request)
end
it 'creates a new merge_request' do
- merge_request = described_class.build(MergeRequest,
- 'title' => 'MergeRequest',
- 'iid' => 8,
- 'source_project_id' => project.id,
- 'target_project_id' => project.id,
- 'source_branch' => 'SourceBranch',
- 'target_branch' => 'TargetBranch',
- 'author_id' => project.creator.id)
+ merge_request = described_class.build(
+ MergeRequest,
+ 'title' => 'MergeRequest',
+ 'iid' => 8,
+ 'source_project_id' => project.id,
+ 'target_project_id' => project.id,
+ 'source_branch' => 'SourceBranch',
+ 'target_branch' => 'TargetBranch',
+ 'author_id' => project.creator.id
+ )
+
expect(merge_request.persisted?).to be true
end
end
context 'merge request diff commit users' do
it 'finds the existing user' do
- user = MergeRequest::DiffCommitUser
- .find_or_create('Alice', 'alice@example.com')
+ user = MergeRequest::DiffCommitUser.find_or_create('Alice', 'alice@example.com')
found = described_class.build(
MergeRequest::DiffCommitUser,
diff --git a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
index 5e9fed32c4e..99959daa1fa 100644
--- a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
@@ -335,17 +335,19 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory, :use_clean_rails_
context 'pipeline_schedule' do
let(:relation_sym) { :pipeline_schedules }
+ let(:value) { true }
let(:relation_hash) do
{
- "id": 3,
- "created_at": "2016-07-22T08:55:44.161Z",
- "updated_at": "2016-07-22T08:55:44.161Z",
- "description": "pipeline schedule",
- "ref": "main",
- "cron": "0 4 * * 0",
- "cron_timezone": "UTC",
- "active": value,
- "project_id": project.id
+ 'id' => 3,
+ 'created_at' => '2016-07-22T08:55:44.161Z',
+ 'updated_at' => '2016-07-22T08:55:44.161Z',
+ 'description' => 'pipeline schedule',
+ 'ref' => 'main',
+ 'cron' => '0 4 * * 0',
+ 'cron_timezone' => 'UTC',
+ 'active' => value,
+ 'project_id' => project.id,
+ 'owner_id' => non_existing_record_id
}
end
@@ -360,6 +362,10 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory, :use_clean_rails_
end
end
end
+
+ it 'sets importer user as owner' do
+ expect(created_object.owner_id).to eq(importer_user.id)
+ end
end
# `project_id`, `described_class.USER_REFERENCES`, noteable_id, target_id, and some project IDs are already
diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
index b0bc31e366e..14af3028a6e 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -449,6 +449,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer, feature_category: :i
expect(pipeline_schedule.cron).to eq('0 4 * * 0')
expect(pipeline_schedule.cron_timezone).to eq('UTC')
expect(pipeline_schedule.active).to eq(false)
+ expect(pipeline_schedule.owner_id).to eq(@user.id)
end
end
@@ -853,12 +854,14 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer, feature_category: :i
end
let!(:project) do
- create(:project,
- :builds_disabled,
- :issues_disabled,
- name: 'project',
- path: 'project',
- group: group)
+ create(
+ :project,
+ :builds_disabled,
+ :issues_disabled,
+ name: 'project',
+ path: 'project',
+ group: group
+ )
end
before do
@@ -889,12 +892,14 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer, feature_category: :i
context 'with existing group models' do
let(:group) { create(:group).tap { |g| g.add_maintainer(user) } }
let!(:project) do
- create(:project,
- :builds_disabled,
- :issues_disabled,
- name: 'project',
- path: 'project',
- group: group)
+ create(
+ :project,
+ :builds_disabled,
+ :issues_disabled,
+ name: 'project',
+ path: 'project',
+ group: group
+ )
end
before do
@@ -925,12 +930,14 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer, feature_category: :i
context 'with clashing milestones on IID' do
let(:group) { create(:group).tap { |g| g.add_maintainer(user) } }
let!(:project) do
- create(:project,
- :builds_disabled,
- :issues_disabled,
- name: 'project',
- path: 'project',
- group: group)
+ create(
+ :project,
+ :builds_disabled,
+ :issues_disabled,
+ name: 'project',
+ path: 'project',
+ group: group
+ )
end
before do
@@ -1142,8 +1149,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer, feature_category: :i
let_it_be(:user) { create(:admin, email: 'user_1@gitlabexample.com') }
let_it_be(:second_user) { create(:user, email: 'user_2@gitlabexample.com') }
let_it_be(:project) do
- create(:project, :builds_disabled, :issues_disabled,
- { name: 'project', path: 'project' })
+ create(:project, :builds_disabled, :issues_disabled, { name: 'project', path: 'project' })
end
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
index abb781b277b..1bf1e5b47e1 100644
--- a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
@@ -309,8 +309,8 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver, :with_license, feature_
context 'with pipeline schedules' do
let(:relation_name) { :pipeline_schedules }
- it 'has no owner_id' do
- expect(subject.first['owner_id']).to be_nil
+ it 'has owner_id' do
+ expect(subject.first['owner_id']).to be_present
end
end
end
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 3da7af7509e..3c540eb45c9 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe Gitlab::ImportExport::RepoRestorer do
subject { described_class.new(path_to_bundle: bundle_path, shared: shared, importable: project) }
after do
- Gitlab::Shell.new.remove_repository(project.repository_storage, project.disk_path)
+ project.repository.remove
end
it 'restores the repo successfully', :aggregate_failures do
@@ -66,7 +66,7 @@ RSpec.describe Gitlab::ImportExport::RepoRestorer do
subject { described_class.new(path_to_bundle: bundle_path, shared: shared, importable: ProjectWiki.new(project)) }
after do
- Gitlab::Shell.new.remove_repository(project.wiki.repository_storage, project.wiki.disk_path)
+ project.wiki.repository.remove
end
it 'restores the wiki repo successfully', :aggregate_failures do
diff --git a/spec/lib/gitlab/import_export/shared_spec.rb b/spec/lib/gitlab/import_export/shared_spec.rb
index 408ed3a2176..37a59a68188 100644
--- a/spec/lib/gitlab/import_export/shared_spec.rb
+++ b/spec/lib/gitlab/import_export/shared_spec.rb
@@ -74,12 +74,12 @@ RSpec.describe Gitlab::ImportExport::Shared do
expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
.with(error, hash_including(
- importer: 'Import/Export',
- project_id: project.id,
- project_name: project.name,
- project_path: project.full_path,
- import_jid: import_state.jid
- ))
+ importer: 'Import/Export',
+ project_id: project.id,
+ project_name: project.name,
+ project_path: project.full_path,
+ import_jid: import_state.jid
+ ))
subject.error(error)
end
diff --git a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
index 2f39cb560d0..d7b1b180e2e 100644
--- a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
@@ -10,10 +10,12 @@ RSpec.describe Gitlab::ImportExport::SnippetRepoRestorer do
let(:shared) { project.import_export_shared }
let(:exporter) { Gitlab::ImportExport::SnippetsRepoSaver.new(project: project, shared: shared, current_user: user) }
let(:restorer) do
- described_class.new(user: user,
- shared: shared,
- snippet: snippet,
- path_to_bundle: snippet_bundle_path)
+ described_class.new(
+ user: user,
+ shared: shared,
+ snippet: snippet,
+ path_to_bundle: snippet_bundle_path
+ )
end
after do
diff --git a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
index e348e8f7991..4a9a01475cb 100644
--- a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
@@ -14,9 +14,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoRestorer, :clean_gitlab_redis_r
let(:bundle_dir) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) }
let(:service) { instance_double(Gitlab::ImportExport::SnippetRepoRestorer) }
let(:restorer) do
- described_class.new(user: user,
- shared: shared,
- project: project)
+ described_class.new(user: user, shared: shared, project: project)
end
after do
diff --git a/spec/lib/gitlab/internal_events/event_definitions_spec.rb b/spec/lib/gitlab/internal_events/event_definitions_spec.rb
index 924845504ca..a00d1ab5ecb 100644
--- a/spec/lib/gitlab/internal_events/event_definitions_spec.rb
+++ b/spec/lib/gitlab/internal_events/event_definitions_spec.rb
@@ -3,7 +3,9 @@
require "spec_helper"
RSpec.describe Gitlab::InternalEvents::EventDefinitions, feature_category: :product_analytics_data_management do
- after(:all) do
+ around do |example|
+ described_class.instance_variable_set(:@events, nil)
+ example.run
described_class.instance_variable_set(:@events, nil)
end
@@ -20,7 +22,6 @@ RSpec.describe Gitlab::InternalEvents::EventDefinitions, feature_category: :prod
let(:events2) { { 'event2' => nil } }
before do
- allow(Gitlab::Usage::MetricDefinition).to receive(:metric_definitions_changed?).and_return(true)
allow(Gitlab::Usage::MetricDefinition).to receive(:all).and_return([definition1, definition2])
allow(definition1).to receive(:available?).and_return(true)
allow(definition2).to receive(:available?).and_return(true)
@@ -58,9 +59,8 @@ RSpec.describe Gitlab::InternalEvents::EventDefinitions, feature_category: :prod
end
context 'when event does not have unique property' do
- it 'unique fails' do
- expect { described_class.unique_property('event1') }
- .to raise_error(described_class::InvalidMetricConfiguration, /Unique property not defined for/)
+ it 'returns nil' do
+ expect(described_class.unique_property('event1')).to be_nil
end
end
end
diff --git a/spec/lib/gitlab/internal_events_spec.rb b/spec/lib/gitlab/internal_events_spec.rb
index c2615e0f22c..20625add292 100644
--- a/spec/lib/gitlab/internal_events_spec.rb
+++ b/spec/lib/gitlab/internal_events_spec.rb
@@ -9,6 +9,8 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
before do
allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
+ allow(redis).to receive(:incr)
+ allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
allow(Gitlab::Tracking).to receive(:tracker).and_return(fake_snowplow)
allow(Gitlab::InternalEvents::EventDefinitions).to receive(:unique_property).and_return(:user)
allow(fake_snowplow).to receive(:event)
@@ -19,6 +21,12 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
.with(event_name, values: unique_value)
end
+ def expect_redis_tracking(event_name)
+ expect(redis).to have_received(:incr) do |redis_key|
+ expect(redis_key).to end_with(event_name)
+ end
+ end
+
def expect_snowplow_tracking(event_name)
service_ping_context = Gitlab::Tracking::ServicePingContext
.new(data_source: :redis_hll, event: event_name)
@@ -39,14 +47,16 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
let_it_be(:project) { build(:project, id: 2) }
let_it_be(:namespace) { project.namespace }
+ let(:redis) { instance_double('Redis') }
let(:fake_snowplow) { instance_double(Gitlab::Tracking::Destinations::Snowplow) }
let(:event_name) { 'g_edit_by_web_ide' }
let(:unique_value) { user.id }
- it 'updates both RedisHLL and Snowplow', :aggregate_failures do
+ it 'updates Redis, RedisHLL and Snowplow', :aggregate_failures do
params = { user: user, project: project, namespace: namespace }
described_class.track_event(event_name, **params)
+ expect_redis_tracking(event_name)
expect_redis_hll_tracking(event_name)
expect_snowplow_tracking(event_name) # Add test for arguments
end
@@ -73,9 +83,10 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
expect { described_class.track_event('unknown_event') }.not_to raise_error
end
- it 'logs error on missing property' do
+ it 'logs error on missing property', :aggregate_failures do
expect { described_class.track_event(event_name, merge_request_id: 1) }.not_to raise_error
+ expect_redis_tracking(event_name)
expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception)
.with(described_class::InvalidPropertyError, event_name: event_name, kwargs: { merge_request_id: 1 })
end
@@ -86,9 +97,10 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
.and_raise(Gitlab::InternalEvents::EventDefinitions::InvalidMetricConfiguration)
end
- it 'fails on missing unique property' do
+ it 'logs error on missing unique property', :aggregate_failures do
expect { described_class.track_event(event_name, merge_request_id: 1) }.not_to raise_error
+ expect_redis_tracking(event_name)
expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception)
end
end
@@ -107,6 +119,7 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
it 'is used when logging to RedisHLL', :aggregate_failures do
described_class.track_event(event_name, user: user, project: project)
+ expect_redis_tracking(event_name)
expect_redis_hll_tracking(event_name)
expect_snowplow_tracking(event_name)
end
@@ -120,13 +133,42 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
end
end
- context 'when method does not exist on property' do
+ context 'when method does not exist on property', :aggregate_failures do
it 'logs error on missing method' do
expect { described_class.track_event(event_name, project: "a_string") }.not_to raise_error
+ expect_redis_tracking(event_name)
expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception)
.with(described_class::InvalidMethodError, event_name: event_name, kwargs: { project: 'a_string' })
end
end
+
+ context 'when send_snowplow_event is false' do
+ it 'logs to Redis and RedisHLL but not Snowplow' do
+ described_class.track_event(event_name, send_snowplow_event: false, user: user, project: project)
+
+ expect_redis_tracking(event_name)
+ expect_redis_hll_tracking(event_name)
+ expect(fake_snowplow).not_to have_received(:event)
+ end
+ end
+ end
+
+ context 'when unique key is not defined' do
+ let(:event_name) { 'p_ci_templates_terraform_base_latest' }
+
+ before do
+ allow(Gitlab::InternalEvents::EventDefinitions).to receive(:unique_property)
+ .with(event_name)
+ .and_return(nil)
+ end
+
+ it 'logs to Redis and Snowplow but not RedisHLL', :aggregate_failures do
+ described_class.track_event(event_name, user: user, project: project)
+
+ expect_redis_tracking(event_name)
+ expect_snowplow_tracking(event_name)
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to have_received(:track_event)
+ end
end
end
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index d0b89afccdc..5fcbecfe6e1 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -106,7 +106,7 @@ RSpec.describe Gitlab::Kubernetes::KubeClient do
describe '#initialize' do
shared_examples 'local address' do
it 'blocks local addresses' do
- expect { client }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
+ expect { client }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
context 'when local requests are allowed' do
@@ -136,7 +136,7 @@ RSpec.describe Gitlab::Kubernetes::KubeClient do
let(:api_url) { 'ssh://192.168.1.2' }
it 'raises an error' do
- expect { client }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
+ expect { client }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
diff --git a/spec/lib/gitlab/legacy_http_spec.rb b/spec/lib/gitlab/legacy_http_spec.rb
new file mode 100644
index 00000000000..07a30b194b6
--- /dev/null
+++ b/spec/lib/gitlab/legacy_http_spec.rb
@@ -0,0 +1,448 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::LegacyHTTP, feature_category: :shared do
+ include StubRequests
+
+ let(:default_options) { Gitlab::HTTP::DEFAULT_TIMEOUT_OPTIONS }
+
+ context 'when allow_local_requests' do
+ it 'sends the request to the correct URI' do
+ stub_full_request('https://example.org:8080', ip_address: '8.8.8.8').to_return(status: 200)
+
+ described_class.get('https://example.org:8080', allow_local_requests: false)
+
+ expect(WebMock).to have_requested(:get, 'https://8.8.8.8:8080').once
+ end
+ end
+
+ context 'when not allow_local_requests' do
+ it 'sends the request to the correct URI' do
+ stub_full_request('https://example.org:8080')
+
+ described_class.get('https://example.org:8080', allow_local_requests: true)
+
+ expect(WebMock).to have_requested(:get, 'https://8.8.8.9:8080').once
+ end
+ end
+
+ context 'when reading the response is too slow' do
+ before_all do
+ # Override Net::HTTP to add a delay between sending each response chunk
+ mocked_http = Class.new(Net::HTTP) do
+ def request(*)
+ super do |response|
+ response.instance_eval do
+ def read_body(*)
+ mock_stream = @body.split(' ')
+ mock_stream.each do |fragment|
+ sleep 0.002.seconds
+
+ yield fragment if block_given?
+ end
+
+ @body
+ end
+ end
+
+ yield response if block_given?
+
+ response
+ end
+ end
+ end
+
+ @original_net_http = Net.send(:remove_const, :HTTP)
+ @webmock_net_http = WebMock::HttpLibAdapters::NetHttpAdapter.instance_variable_get(:@webMockNetHTTP)
+
+ Net.send(:const_set, :HTTP, mocked_http)
+ WebMock::HttpLibAdapters::NetHttpAdapter.instance_variable_set(:@webMockNetHTTP, mocked_http)
+
+ # Reload Gitlab::NetHttpAdapter
+ Gitlab.send(:remove_const, :NetHttpAdapter)
+ load "#{Rails.root}/lib/gitlab/net_http_adapter.rb"
+ end
+
+ before do
+ stub_const("Gitlab::HTTP::DEFAULT_READ_TOTAL_TIMEOUT", 0.001.seconds)
+
+ WebMock.stub_request(:post, /.*/).to_return do
+ { body: "chunk-1 chunk-2", status: 200 }
+ end
+ end
+
+ after(:all) do
+ Net.send(:remove_const, :HTTP)
+ Net.send(:const_set, :HTTP, @original_net_http)
+ WebMock::HttpLibAdapters::NetHttpAdapter.instance_variable_set(:@webMockNetHTTP, @webmock_net_http)
+
+ # Reload Gitlab::NetHttpAdapter
+ Gitlab.send(:remove_const, :NetHttpAdapter)
+ load "#{Rails.root}/lib/gitlab/net_http_adapter.rb"
+ end
+
+ let(:options) { {} }
+
+ subject(:request_slow_responder) { described_class.post('http://example.org', **options) }
+
+ it 'raises an error' do
+ expect { request_slow_responder }.to raise_error(
+ Gitlab::HTTP::ReadTotalTimeout, /Request timed out after ?([0-9]*[.])?[0-9]+ seconds/)
+ end
+
+ context 'and timeout option is greater than DEFAULT_READ_TOTAL_TIMEOUT' do
+ let(:options) { { timeout: 10.seconds } }
+
+ it 'does not raise an error' do
+ expect { request_slow_responder }.not_to raise_error
+ end
+ end
+
+ context 'and stream_body option is truthy' do
+ let(:options) { { stream_body: true } }
+
+ it 'does not raise an error' do
+ expect { request_slow_responder }.not_to raise_error
+ end
+ end
+ end
+
+ it 'calls a block' do
+ WebMock.stub_request(:post, /.*/)
+
+ expect { |b| described_class.post('http://example.org', &b) }.to yield_with_args
+ end
+
+ describe 'allow_local_requests_from_web_hooks_and_services is' do
+ before do
+ WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success')
+ end
+
+ context 'disabled' do
+ before do
+ allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(false)
+ end
+
+ it 'deny requests to localhost' do
+ expect { described_class.get('http://localhost:3003') }.to raise_error(Gitlab::HTTP::BlockedUrlError)
+ end
+
+ it 'deny requests to private network' do
+ expect { described_class.get('http://192.168.1.2:3003') }.to raise_error(Gitlab::HTTP::BlockedUrlError)
+ end
+
+ context 'if allow_local_requests set to true' do
+ it 'override the global value and allow requests to localhost or private network' do
+ stub_full_request('http://localhost:3003')
+
+ expect { described_class.get('http://localhost:3003', allow_local_requests: true) }.not_to raise_error
+ end
+ end
+ end
+
+ context 'enabled' do
+ before do
+ allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(true)
+ end
+
+ it 'allow requests to localhost' do
+ stub_full_request('http://localhost:3003')
+
+ expect { described_class.get('http://localhost:3003') }.not_to raise_error
+ end
+
+ it 'allow requests to private network' do
+ expect { described_class.get('http://192.168.1.2:3003') }.not_to raise_error
+ end
+
+ context 'if allow_local_requests set to false' do
+ it 'override the global value and ban requests to localhost or private network' do
+ expect { described_class.get('http://localhost:3003', allow_local_requests: false) }.to raise_error(
+ Gitlab::HTTP::BlockedUrlError)
+ end
+ end
+ end
+ end
+
+ describe 'handle redirect loops' do
+ before do
+ stub_full_request("http://example.org", method: :any).to_raise(
+ HTTParty::RedirectionTooDeep.new("Redirection Too Deep"))
+ end
+
+ it 'handles GET requests' do
+ expect { described_class.get('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
+ end
+
+ it 'handles POST requests' do
+ expect { described_class.post('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
+ end
+
+ it 'handles PUT requests' do
+ expect { described_class.put('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
+ end
+
+ it 'handles DELETE requests' do
+ expect { described_class.delete('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
+ end
+
+ it 'handles HEAD requests' do
+ expect { described_class.head('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep)
+ end
+ end
+
+ describe 'setting default timeouts' do
+ before do
+ stub_full_request('http://example.org', method: :any)
+ end
+
+ context 'when no timeouts are set' do
+ it 'sets default open and read and write timeouts' do
+ expect(described_class).to receive(:httparty_perform_request).with(
+ Net::HTTP::Get, 'http://example.org', default_options
+ ).and_call_original
+
+ described_class.get('http://example.org')
+ end
+ end
+
+ context 'when :timeout is set' do
+ it 'does not set any default timeouts' do
+ expect(described_class).to receive(:httparty_perform_request).with(
+ Net::HTTP::Get, 'http://example.org', { timeout: 1 }
+ ).and_call_original
+
+ described_class.get('http://example.org', { timeout: 1 })
+ end
+ end
+
+ context 'when :open_timeout is set' do
+ it 'only sets default read and write timeout' do
+ expect(described_class).to receive(:httparty_perform_request).with(
+ Net::HTTP::Get, 'http://example.org', default_options.merge(open_timeout: 1)
+ ).and_call_original
+
+ described_class.get('http://example.org', open_timeout: 1)
+ end
+ end
+
+ context 'when :read_timeout is set' do
+ it 'only sets default open and write timeout' do
+ expect(described_class).to receive(:httparty_perform_request).with(
+ Net::HTTP::Get, 'http://example.org', default_options.merge(read_timeout: 1)
+ ).and_call_original
+
+ described_class.get('http://example.org', read_timeout: 1)
+ end
+ end
+
+ context 'when :write_timeout is set' do
+ it 'only sets default open and read timeout' do
+ expect(described_class).to receive(:httparty_perform_request).with(
+ Net::HTTP::Put, 'http://example.org', default_options.merge(write_timeout: 1)
+ ).and_call_original
+
+ described_class.put('http://example.org', write_timeout: 1)
+ end
+ end
+ end
+
+ describe '.try_get' do
+ let(:path) { 'http://example.org' }
+
+ let(:extra_log_info_proc) do
+ proc do |error, url, options|
+ { klass: error.class, url: url, options: options }
+ end
+ end
+
+ let(:request_options) do
+ default_options.merge({
+ verify: false,
+ basic_auth: { username: 'user', password: 'pass' }
+ })
+ end
+
+ Gitlab::HTTP::HTTP_ERRORS.each do |exception_class|
+ context "with #{exception_class}" do
+ let(:klass) { exception_class }
+
+ context 'with path' do
+ before do
+ expect(described_class).to receive(:httparty_perform_request)
+ .with(Net::HTTP::Get, path, default_options)
+ .and_raise(klass)
+ end
+
+ it 'handles requests without extra_log_info' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(instance_of(klass), {})
+
+ expect(described_class.try_get(path)).to be_nil
+ end
+
+ it 'handles requests with extra_log_info as hash' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(instance_of(klass), { a: :b })
+
+ expect(described_class.try_get(path, extra_log_info: { a: :b })).to be_nil
+ end
+
+ it 'handles requests with extra_log_info as proc' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(instance_of(klass), { url: path, klass: klass, options: {} })
+
+ expect(described_class.try_get(path, extra_log_info: extra_log_info_proc)).to be_nil
+ end
+ end
+
+ context 'with path and options' do
+ before do
+ expect(described_class).to receive(:httparty_perform_request)
+ .with(Net::HTTP::Get, path, request_options)
+ .and_raise(klass)
+ end
+
+ it 'handles requests without extra_log_info' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(instance_of(klass), {})
+
+ expect(described_class.try_get(path, request_options)).to be_nil
+ end
+
+ it 'handles requests with extra_log_info as hash' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(instance_of(klass), { a: :b })
+
+ expect(described_class.try_get(path, **request_options, extra_log_info: { a: :b })).to be_nil
+ end
+
+ it 'handles requests with extra_log_info as proc' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(instance_of(klass), { klass: klass, url: path, options: request_options })
+
+ expect(described_class.try_get(path, **request_options, extra_log_info: extra_log_info_proc)).to be_nil
+ end
+ end
+
+ context 'with path, options, and block' do
+ let(:block) do
+ proc {}
+ end
+
+ before do
+ expect(described_class).to receive(:httparty_perform_request)
+ .with(Net::HTTP::Get, path, request_options, &block)
+ .and_raise(klass)
+ end
+
+ it 'handles requests without extra_log_info' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(instance_of(klass), {})
+
+ expect(described_class.try_get(path, request_options, &block)).to be_nil
+ end
+
+ it 'handles requests with extra_log_info as hash' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(instance_of(klass), { a: :b })
+
+ expect(described_class.try_get(path, **request_options, extra_log_info: { a: :b }, &block)).to be_nil
+ end
+
+ it 'handles requests with extra_log_info as proc' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(instance_of(klass), { klass: klass, url: path, options: request_options })
+
+ expect(
+ described_class.try_get(path, **request_options, extra_log_info: extra_log_info_proc, &block)
+ ).to be_nil
+ end
+ end
+ end
+ end
+ end
+
+ describe 'silent mode', feature_category: :geo_replication do
+ before do
+ stub_full_request("http://example.org", method: :any)
+ stub_application_setting(silent_mode_enabled: silent_mode)
+ end
+
+ context 'when silent mode is enabled' do
+ let(:silent_mode) { true }
+
+ it 'allows GET requests' do
+ expect { described_class.get('http://example.org') }.not_to raise_error
+ end
+
+ it 'allows HEAD requests' do
+ expect { described_class.head('http://example.org') }.not_to raise_error
+ end
+
+ it 'allows OPTIONS requests' do
+ expect { described_class.options('http://example.org') }.not_to raise_error
+ end
+
+ it 'blocks POST requests' do
+ expect { described_class.post('http://example.org') }.to raise_error(Gitlab::HTTP::SilentModeBlockedError)
+ end
+
+ it 'blocks PUT requests' do
+ expect { described_class.put('http://example.org') }.to raise_error(Gitlab::HTTP::SilentModeBlockedError)
+ end
+
+ it 'blocks DELETE requests' do
+ expect { described_class.delete('http://example.org') }.to raise_error(Gitlab::HTTP::SilentModeBlockedError)
+ end
+
+ it 'logs blocked requests' do
+ expect(::Gitlab::AppJsonLogger).to receive(:info).with(
+ message: "Outbound HTTP request blocked",
+ outbound_http_request_method: 'Net::HTTP::Post',
+ silent_mode_enabled: true
+ )
+
+ expect { described_class.post('http://example.org') }.to raise_error(Gitlab::HTTP::SilentModeBlockedError)
+ end
+ end
+
+ context 'when silent mode is disabled' do
+ let(:silent_mode) { false }
+
+ it 'allows GET requests' do
+ expect { described_class.get('http://example.org') }.not_to raise_error
+ end
+
+ it 'allows HEAD requests' do
+ expect { described_class.head('http://example.org') }.not_to raise_error
+ end
+
+ it 'allows OPTIONS requests' do
+ expect { described_class.options('http://example.org') }.not_to raise_error
+ end
+
+ it 'blocks POST requests' do
+ expect { described_class.post('http://example.org') }.not_to raise_error
+ end
+
+ it 'blocks PUT requests' do
+ expect { described_class.put('http://example.org') }.not_to raise_error
+ end
+
+ it 'blocks DELETE requests' do
+ expect { described_class.delete('http://example.org') }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/memory/instrumentation_spec.rb b/spec/lib/gitlab/memory/instrumentation_spec.rb
index 3d58f28ec1e..f287edb7da3 100644
--- a/spec/lib/gitlab/memory/instrumentation_spec.rb
+++ b/spec/lib/gitlab/memory/instrumentation_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe Gitlab::Memory::Instrumentation, feature_category: :application_p
subject do
described_class.with_memory_allocations do
- Array.new(1000).map { '0' * 100 }
+ Array.new(1000).map { '0' * 1000 }
end
end
@@ -52,7 +52,7 @@ RSpec.describe Gitlab::Memory::Instrumentation, feature_category: :application_p
expect(result).to include(
mem_objects: be > 1000,
mem_mallocs: be > 1000,
- mem_bytes: be > 100_000, # 100 items * 100 bytes each
+ mem_bytes: be > 1000_000, # 1000 items * 1000 bytes each
mem_total_bytes: eq(result[:mem_bytes] + 40 * result[:mem_objects])
)
end
diff --git a/spec/lib/gitlab/merge_requests/mergeability/check_result_spec.rb b/spec/lib/gitlab/merge_requests/mergeability/check_result_spec.rb
index 4f437e57600..74aa3528328 100644
--- a/spec/lib/gitlab/merge_requests/mergeability/check_result_spec.rb
+++ b/spec/lib/gitlab/merge_requests/mergeability/check_result_spec.rb
@@ -137,4 +137,19 @@ RSpec.describe Gitlab::MergeRequests::Mergeability::CheckResult do
end
end
end
+
+ describe '#identifier' do
+ let(:payload) { { identifier: 'ci_must_pass' } }
+
+ subject(:identifier) do
+ described_class
+ .new(
+ status: described_class::SUCCESS_STATUS,
+ payload: payload
+ )
+ .identifier
+ end
+
+ it { is_expected.to eq(:ci_must_pass) }
+ end
end
diff --git a/spec/lib/gitlab/metrics/web_transaction_spec.rb b/spec/lib/gitlab/metrics/web_transaction_spec.rb
index dc59fa804c4..ea98c8d7933 100644
--- a/spec/lib/gitlab/metrics/web_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/web_transaction_spec.rb
@@ -64,7 +64,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
describe '#labels' do
context 'when request goes to Grape endpoint' do
before do
- route = double(:route, request_method: 'GET', path: '/:version/projects/:id/archive(.:format)')
+ route = double(:route, request_method: 'GET', path: '/:version/projects/:id/archive(.:format)', origin: '/:version/projects/:id/archive')
endpoint = double(:endpoint, route: route,
options: { for: API::Projects, path: [":id/archive"] },
namespace: "/projects")
@@ -76,7 +76,12 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
end
it 'provides labels with the method and path of the route in the grape endpoint' do
- expect(transaction.labels).to eq({ controller: 'Grape', action: 'GET /projects/:id/archive', feature_category: 'projects' })
+ expect(transaction.labels).to eq({
+ controller: 'Grape',
+ action: 'GET /projects/:id/archive',
+ feature_category: 'projects',
+ endpoint_id: 'GET /:version/projects/:id/archive'
+ })
end
it 'contains only the labels defined for transactions' do
@@ -103,7 +108,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
end
it 'tags a transaction with the name and action of a controller' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT, endpoint_id: 'TestController#show' })
end
it 'contains only the labels defined for transactions' do
@@ -114,7 +119,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
let(:request) { double(:request, format: double(:format, ref: :json)) }
it 'appends the mime type to the transaction action' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT, endpoint_id: 'TestController#show' })
end
end
@@ -122,7 +127,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
let(:request) { double(:request, format: double(:format, ref: 'http://example.com')) }
it 'does not append the MIME type to the transaction action' do
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT, endpoint_id: 'TestController#show' })
end
end
@@ -131,7 +136,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
# This is needed since we're not actually making a request, which would trigger the controller pushing to the context
::Gitlab::ApplicationContext.push(feature_category: 'source_code_management')
- expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: "source_code_management" })
+ expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: 'source_code_management', endpoint_id: 'TestController#show' })
end
end
end
@@ -147,7 +152,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
let(:controller) { double(:controller, class: controller_class, action_name: 'show', request: request) }
let(:transaction_obj) { described_class.new({ 'action_controller.instance' => controller }) }
- let(:labels) { { controller: 'TestController', action: 'show', feature_category: 'projects' } }
+ let(:labels) { { controller: 'TestController', action: 'show', feature_category: 'projects', endpoint_id: 'TestController#show' } }
before do
::Gitlab::ApplicationContext.push(feature_category: 'projects')
diff --git a/spec/lib/gitlab/middleware/handle_malformed_strings_spec.rb b/spec/lib/gitlab/middleware/handle_malformed_strings_spec.rb
index ed1440f23b6..7bc5fd853bf 100644
--- a/spec/lib/gitlab/middleware/handle_malformed_strings_spec.rb
+++ b/spec/lib/gitlab/middleware/handle_malformed_strings_spec.rb
@@ -58,6 +58,39 @@ RSpec.describe Gitlab::Middleware::HandleMalformedStrings do
end
end
+ context 'with POST request' do
+ let(:request_env) do
+ Rack::MockRequest.env_for(
+ '/',
+ method: 'POST',
+ input: input,
+ 'CONTENT_TYPE' => 'application/json'
+ )
+ end
+
+ let(:params) { { method: 'POST' } }
+
+ context 'with valid JSON' do
+ let(:input) { %({"hello": "world"}) }
+
+ it 'returns no error' do
+ env = request_env
+
+ expect(subject.call(env)).not_to eq error_400
+ end
+ end
+
+ context 'with bad JSON' do
+ let(:input) { "{ bad json }" }
+
+ it 'rejects bad JSON with 400 error' do
+ env = request_env
+
+ expect(subject.call(env)).to eq error_400
+ end
+ end
+ end
+
context 'in authorization headers' do
let(:problematic_input) { null_byte }
diff --git a/spec/lib/gitlab/middleware/path_traversal_check_spec.rb b/spec/lib/gitlab/middleware/path_traversal_check_spec.rb
new file mode 100644
index 00000000000..3d334a60c49
--- /dev/null
+++ b/spec/lib/gitlab/middleware/path_traversal_check_spec.rb
@@ -0,0 +1,197 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Gitlab::Middleware::PathTraversalCheck, feature_category: :shared do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:fake_response) { [200, { 'Content-Type' => 'text/plain' }, ['OK']] }
+ let(:fake_app) { ->(_) { fake_response } }
+ let(:middleware) { described_class.new(fake_app) }
+
+ describe '#call' do
+ let(:fullpath) { ::Rack::Request.new(env).fullpath }
+ let(:decoded_fullpath) { CGI.unescape(fullpath) }
+
+ let(:env) do
+ Rack::MockRequest.env_for(
+ path,
+ method: method,
+ params: query_params
+ )
+ end
+
+ subject { middleware.call(env) }
+
+ shared_examples 'no issue' do
+ it 'measures and logs the execution time' do
+ expect(::Gitlab::PathTraversal)
+ .to receive(:check_path_traversal!)
+ .with(decoded_fullpath, skip_decoding: true)
+ .and_call_original
+ expect(::Gitlab::AppLogger)
+ .to receive(:warn)
+ .with({ class_name: described_class.name, duration_ms: instance_of(Float) })
+ .and_call_original
+
+ expect(subject).to eq(fake_response)
+ end
+
+ context 'with log_execution_time_path_traversal_middleware disabled' do
+ before do
+ stub_feature_flags(log_execution_time_path_traversal_middleware: false)
+ end
+
+ it 'does nothing' do
+ expect(::Gitlab::PathTraversal)
+ .to receive(:check_path_traversal!)
+ .with(decoded_fullpath, skip_decoding: true)
+ .and_call_original
+ expect(::Gitlab::AppLogger)
+ .not_to receive(:warn)
+
+ expect(subject).to eq(fake_response)
+ end
+ end
+ end
+
+ shared_examples 'path traversal' do
+ it 'logs the problem and measures the execution time' do
+ expect(::Gitlab::PathTraversal)
+ .to receive(:check_path_traversal!)
+ .with(decoded_fullpath, skip_decoding: true)
+ .and_call_original
+ expect(::Gitlab::AppLogger)
+ .to receive(:warn)
+ .with({ message: described_class::PATH_TRAVERSAL_MESSAGE, path: instance_of(String) })
+ expect(::Gitlab::AppLogger)
+ .to receive(:warn)
+ .with({
+ class_name: described_class.name,
+ duration_ms: instance_of(Float),
+ message: described_class::PATH_TRAVERSAL_MESSAGE,
+ fullpath: fullpath
+ }).and_call_original
+
+ expect(subject).to eq(fake_response)
+ end
+
+ context 'with log_execution_time_path_traversal_middleware disabled' do
+ before do
+ stub_feature_flags(log_execution_time_path_traversal_middleware: false)
+ end
+
+ it 'logs the problem without the execution time' do
+ expect(::Gitlab::PathTraversal)
+ .to receive(:check_path_traversal!)
+ .with(decoded_fullpath, skip_decoding: true)
+ .and_call_original
+ expect(::Gitlab::AppLogger)
+ .to receive(:warn)
+ .with({ message: described_class::PATH_TRAVERSAL_MESSAGE, path: instance_of(String) })
+ expect(::Gitlab::AppLogger)
+ .to receive(:warn)
+ .with({
+ class_name: described_class.name,
+ message: described_class::PATH_TRAVERSAL_MESSAGE,
+ fullpath: fullpath
+ }).and_call_original
+
+ expect(subject).to eq(fake_response)
+ end
+ end
+ end
+
+ # we use Rack request.full_path, this will dump the accessed path and
+ # the query string. The query string is only for GETs requests.
+ # Hence different expectation (when params are set) for GETs and
+ # the other methods (see below)
+ context 'when using get' do
+ let(:method) { 'get' }
+
+ where(:path, :query_params, :shared_example_name) do
+ '/foo/bar' | {} | 'no issue'
+ '/foo/../bar' | {} | 'path traversal'
+ '/foo%2Fbar' | {} | 'no issue'
+ '/foo%2F..%2Fbar' | {} | 'path traversal'
+ '/foo%252F..%252Fbar' | {} | 'no issue'
+ '/foo/bar' | { x: 'foo' } | 'no issue'
+ '/foo/bar' | { x: 'foo/../bar' } | 'path traversal'
+ '/foo/bar' | { x: 'foo%2Fbar' } | 'no issue'
+ '/foo/bar' | { x: 'foo%2F..%2Fbar' } | 'no issue'
+ '/foo/bar' | { x: 'foo%252F..%252Fbar' } | 'no issue'
+ '/foo%2F..%2Fbar' | { x: 'foo%252F..%252Fbar' } | 'path traversal'
+ end
+
+ with_them do
+ it_behaves_like params[:shared_example_name]
+ end
+
+ context 'with a issues search path' do
+ let(:query_params) { {} }
+ let(:path) do
+ 'project/-/issues/?sort=updated_desc&milestone_title=16.0&search=Release%20%252525&first_page_size=20'
+ end
+
+ it_behaves_like 'no issue'
+ end
+ end
+
+ %w[post put post delete patch].each do |http_method|
+ context "when using #{http_method}" do
+ let(:method) { http_method }
+
+ where(:path, :query_params, :shared_example_name) do
+ '/foo/bar' | {} | 'no issue'
+ '/foo/../bar' | {} | 'path traversal'
+ '/foo%2Fbar' | {} | 'no issue'
+ '/foo%2F..%2Fbar' | {} | 'path traversal'
+ '/foo%252F..%252Fbar' | {} | 'no issue'
+ '/foo/bar' | { x: 'foo' } | 'no issue'
+ '/foo/bar' | { x: 'foo/../bar' } | 'no issue'
+ '/foo/bar' | { x: 'foo%2Fbar' } | 'no issue'
+ '/foo/bar' | { x: 'foo%2F..%2Fbar' } | 'no issue'
+ '/foo/bar' | { x: 'foo%252F..%252Fbar' } | 'no issue'
+ '/foo%2F..%2Fbar' | { x: 'foo%252F..%252Fbar' } | 'path traversal'
+ end
+
+ with_them do
+ it_behaves_like params[:shared_example_name]
+ end
+ end
+ end
+
+ context 'with check_path_traversal_middleware disabled' do
+ before do
+ stub_feature_flags(check_path_traversal_middleware: false)
+ end
+
+ where(:path, :query_params) do
+ '/foo/bar' | {}
+ '/foo/../bar' | {}
+ '/foo%2Fbar' | {}
+ '/foo%2F..%2Fbar' | {}
+ '/foo%252F..%252Fbar' | {}
+ '/foo/bar' | { x: 'foo' }
+ '/foo/bar' | { x: 'foo/../bar' }
+ '/foo/bar' | { x: 'foo%2Fbar' }
+ '/foo/bar' | { x: 'foo%2F..%2Fbar' }
+ '/foo/bar' | { x: 'foo%252F..%252Fbar' }
+ end
+
+ with_them do
+ %w[get post put post delete patch].each do |http_method|
+ context "when using #{http_method}" do
+ let(:method) { http_method }
+
+ it 'does not check for path traversals' do
+ expect(::Gitlab::PathTraversal).not_to receive(:check_path_traversal!)
+
+ subject
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/observability_spec.rb b/spec/lib/gitlab/observability_spec.rb
index 04c35f0ee3a..7af2daea11c 100644
--- a/spec/lib/gitlab/observability_spec.rb
+++ b/spec/lib/gitlab/observability_spec.rb
@@ -46,206 +46,54 @@ RSpec.describe Gitlab::Observability, feature_category: :error_tracking do
it { is_expected.to eq("#{described_class.observability_url}/v3/tenant/#{project.id}") }
end
- describe '.build_full_url' do
- let_it_be(:group) { build_stubbed(:group, id: 123) }
- let(:observability_url) { described_class.observability_url }
+ describe '.should_enable_observability_auth_scopes?' do
+ subject { described_class.should_enable_observability_auth_scopes?(resource) }
- it 'returns the full observability url for the given params' do
- url = described_class.build_full_url(group, '/foo?bar=baz', '/')
- expect(url).to eq("https://observe.gitlab.com/-/123/foo?bar=baz")
- end
-
- it 'handles missing / from observability_path' do
- url = described_class.build_full_url(group, 'foo?bar=baz', '/')
- expect(url).to eq("https://observe.gitlab.com/-/123/foo?bar=baz")
- end
-
- it 'sanitises observability_path' do
- url = described_class.build_full_url(group, "/test?groupId=<script>alert('attack!')</script>", '/')
- expect(url).to eq("https://observe.gitlab.com/-/123/test?groupId=alert('attack!')")
- end
-
- context 'when observability_path is missing' do
- it 'builds the url with the fallback_path' do
- url = described_class.build_full_url(group, nil, '/fallback')
- expect(url).to eq("https://observe.gitlab.com/-/123/fallback")
- end
-
- it 'defaults to / if fallback_path is also missing' do
- url = described_class.build_full_url(group, nil, nil)
- expect(url).to eq("https://observe.gitlab.com/-/123/")
+ let(:parent) { build_stubbed(:group) }
+ let(:resource) do
+ build_stubbed(:group, parent: parent).tap do |g|
+ g.namespace_settings = build_stubbed(:namespace_settings, namespace: g)
end
end
- end
- describe '.embeddable_url' do
before do
- stub_config_setting(url: "https://www.gitlab.com")
- # Can't use build/build_stubbed as we want the routes to be generated as well
- create(:group, path: 'test-path', id: 123)
- end
-
- context 'when URL is valid' do
- where(:input, :expected) do
- [
- [
- "https://www.gitlab.com/groups/test-path/-/observability/explore?observability_path=%2Fexplore%3FgroupId%3D14485840%26left%3D%255B%2522now-1h%2522,%2522now%2522,%2522new-sentry.gitlab.net%2522,%257B%257D%255D",
- "https://observe.gitlab.com/-/123/explore?groupId=14485840&left=%5B%22now-1h%22,%22now%22,%22new-sentry.gitlab.net%22,%7B%7D%5D"
- ],
- [
- "https://www.gitlab.com/groups/test-path/-/observability/explore?observability_path=/goto/foo",
- "https://observe.gitlab.com/-/123/goto/foo"
- ]
- ]
- end
-
- with_them do
- it 'returns an embeddable observability url' do
- expect(described_class.embeddable_url(input)).to eq(expected)
- end
- end
+ stub_feature_flags(observability_tracing: parent)
end
- context 'when URL is invalid' do
- where(:input) do
- [
- # direct links to observe.gitlab.com
- "https://observe.gitlab.com/-/123/explore",
- 'https://observe.gitlab.com/v1/auth/start',
-
- # invalid GitLab URL
- "not a link",
- "https://foo.bar/groups/test-path/-/observability/explore?observability_path=/explore",
- "http://www.gitlab.com/groups/test-path/-/observability/explore?observability_path=/explore",
- "https://www.gitlab.com:123/groups/test-path/-/observability/explore?observability_path=/explore",
- "https://www.gitlab.com@example.com/groups/test-path/-/observability/explore?observability_path=/explore",
- "https://www.gitlab.com/groups/test-path/-/observability/explore?observability_path=@example.com",
-
- # invalid group/controller/actions
- "https://www.gitlab.com/groups/INVALID_GROUP/-/observability/explore?observability_path=/explore",
- "https://www.gitlab.com/groups/test-path/-/INVALID_CONTROLLER/explore?observability_path=/explore",
- "https://www.gitlab.com/groups/test-path/-/observability/INVALID_ACTION?observability_path=/explore",
-
- # invalid observablity path
- "https://www.gitlab.com/groups/test-path/-/observability/explore",
- "https://www.gitlab.com/groups/test-path/-/observability/explore?missing_observability_path=/explore",
- "https://www.gitlab.com/groups/test-path/-/observability/explore?observability_path=/not_embeddable",
- "https://www.gitlab.com/groups/test-path/-/observability/explore?observability_path=/datasources",
- "https://www.gitlab.com/groups/test-path/-/observability/explore?observability_path=not a valid path"
- ]
+ describe 'when resource is group' do
+ context 'if observability_tracing FF enabled' do
+ it { is_expected.to be true }
end
- with_them do
- it 'returns nil' do
- expect(described_class.embeddable_url(input)).to be_nil
+ context 'if observability_tracing FF disabled' do
+ before do
+ stub_feature_flags(observability_tracing: false)
end
- end
-
- it 'returns nil if the path detection throws an error' do
- test_url = "https://www.gitlab.com/groups/test-path/-/observability/explore"
- allow(Rails.application.routes).to receive(:recognize_path).with(test_url) {
- raise ActionController::RoutingError, 'test'
- }
- expect(described_class.embeddable_url(test_url)).to be_nil
- end
-
- it 'returns nil if parsing observaboility path throws an error' do
- observability_path = 'some-path'
- test_url = "https://www.gitlab.com/groups/test-path/-/observability/explore?observability_path=#{observability_path}"
-
- allow(URI).to receive(:parse).and_call_original
- allow(URI).to receive(:parse).with(observability_path) {
- raise URI::InvalidURIError, 'test'
- }
- expect(described_class.embeddable_url(test_url)).to be_nil
+ it { is_expected.to be false }
end
end
- end
-
- describe '.allowed_for_action?' do
- let(:group) { build_stubbed(:group) }
- let(:user) { build_stubbed(:user) }
-
- before do
- allow(described_class).to receive(:allowed?).and_call_original
- end
-
- it 'returns false if action is nil' do
- expect(described_class.allowed_for_action?(user, group, nil)).to eq(false)
- end
- describe 'allowed? calls' do
- using RSpec::Parameterized::TableSyntax
+ describe 'when resource is project' do
+ let(:resource) { build_stubbed(:project, namespace: parent) }
- where(:action, :permission) do
- :foo | :admin_observability
- :explore | :read_observability
- :datasources | :admin_observability
- :manage | :admin_observability
- :dashboards | :read_observability
+ context 'if observability_tracing FF enabled' do
+ it { is_expected.to be true }
end
- with_them do
- it "calls allowed? with #{params[:permission]} when actions is #{params[:action]}" do
- described_class.allowed_for_action?(user, group, action)
- expect(described_class).to have_received(:allowed?).with(user, group, permission)
+ context 'if observability_tracing FF disabled' do
+ before do
+ stub_feature_flags(observability_tracing: false)
end
- end
- end
- end
-
- describe '.allowed?' do
- let(:user) { build_stubbed(:user) }
- let(:group) { build_stubbed(:group) }
- let(:test_permission) { :read_observability }
-
- before do
- allow(Ability).to receive(:allowed?).and_return(false)
- end
-
- subject do
- described_class.allowed?(user, group, test_permission)
- end
-
- it 'checks if ability is allowed for the given user and group' do
- allow(Ability).to receive(:allowed?).and_return(true)
-
- subject
-
- expect(Ability).to have_received(:allowed?).with(user, test_permission, group)
- end
-
- it 'checks for admin_observability if permission is missing' do
- described_class.allowed?(user, group)
-
- expect(Ability).to have_received(:allowed?).with(user, :admin_observability, group)
- end
-
- it 'returns true if the ability is allowed' do
- allow(Ability).to receive(:allowed?).and_return(true)
-
- expect(subject).to eq(true)
- end
- it 'returns false if the ability is not allowed' do
- allow(Ability).to receive(:allowed?).and_return(false)
-
- expect(subject).to eq(false)
- end
-
- it 'returns false if observability url is missing' do
- allow(described_class).to receive(:observability_url).and_return("")
-
- expect(subject).to eq(false)
+ it { is_expected.to be false }
+ end
end
- it 'returns false if group is missing' do
- expect(described_class.allowed?(user, nil, :read_observability)).to eq(false)
- end
+ describe 'when resource is not a group or project' do
+ let(:resource) { build_stubbed(:user) }
- it 'returns false if user is missing' do
- expect(described_class.allowed?(nil, group, :read_observability)).to eq(false)
+ it { is_expected.to be false }
end
end
end
diff --git a/spec/lib/gitlab/octokit/middleware_spec.rb b/spec/lib/gitlab/octokit/middleware_spec.rb
index f7063f2c4f2..07936de9e78 100644
--- a/spec/lib/gitlab/octokit/middleware_spec.rb
+++ b/spec/lib/gitlab/octokit/middleware_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe Gitlab::Octokit::Middleware, feature_category: :importers do
shared_examples 'Blocked URL' do
it 'raises an error' do
- expect { middleware.call(env) }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
+ expect { middleware.call(env) }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
@@ -88,7 +88,7 @@ RSpec.describe Gitlab::Octokit::Middleware, feature_category: :importers do
let(:env) { { url: 'ssh://172.16.0.0' } }
it 'raises an error' do
- expect { middleware.call(env) }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
+ expect { middleware.call(env) }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
end
diff --git a/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb b/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb
index 4128f745ce7..effe767e41d 100644
--- a/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb
+++ b/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb
@@ -6,52 +6,24 @@ RSpec.describe Gitlab::Pagination::CursorBasedKeyset do
subject { described_class }
describe '.available_for_type?' do
- context 'with api_keyset_pagination_multi_order FF disabled' do
- before do
- stub_feature_flags(api_keyset_pagination_multi_order: false)
- end
-
- it 'returns true for Group' do
- expect(subject.available_for_type?(Group.all)).to be_truthy
- end
-
- it 'returns true for Ci::Build' do
- expect(subject.available_for_type?(Ci::Build.all)).to be_truthy
- end
-
- it 'returns true for Packages::BuildInfo' do
- expect(subject.available_for_type?(Packages::BuildInfo.all)).to be_truthy
- end
-
- it 'return false for User' do
- expect(subject.available_for_type?(User.all)).to be_falsey
- end
+ it 'returns true for Group' do
+ expect(subject.available_for_type?(Group.all)).to be_truthy
end
- context 'with api_keyset_pagination_multi_order FF enabled' do
- before do
- stub_feature_flags(api_keyset_pagination_multi_order: true)
- end
-
- it 'returns true for Group' do
- expect(subject.available_for_type?(Group.all)).to be_truthy
- end
-
- it 'returns true for Ci::Build' do
- expect(subject.available_for_type?(Ci::Build.all)).to be_truthy
- end
+ it 'returns true for Ci::Build' do
+ expect(subject.available_for_type?(Ci::Build.all)).to be_truthy
+ end
- it 'returns true for Packages::BuildInfo' do
- expect(subject.available_for_type?(Packages::BuildInfo.all)).to be_truthy
- end
+ it 'returns true for Packages::BuildInfo' do
+ expect(subject.available_for_type?(Packages::BuildInfo.all)).to be_truthy
+ end
- it 'returns true for User' do
- expect(subject.available_for_type?(User.all)).to be_truthy
- end
+ it 'returns true for User' do
+ expect(subject.available_for_type?(User.all)).to be_truthy
+ end
- it 'return false for other types of relations' do
- expect(subject.available_for_type?(Issue.all)).to be_falsey
- end
+ it 'return false for other types of relations' do
+ expect(subject.available_for_type?(Issue.all)).to be_falsey
end
end
@@ -100,48 +72,20 @@ RSpec.describe Gitlab::Pagination::CursorBasedKeyset do
let(:order_by) { :id }
let(:sort) { :desc }
- context 'with api_keyset_pagination_multi_order FF disabled' do
- before do
- stub_feature_flags(api_keyset_pagination_multi_order: false)
- end
-
- it 'returns true for Ci::Build' do
- expect(subject.available?(cursor_based_request_context, Ci::Build.all)).to be_truthy
- end
-
- it 'returns true for AuditEvent' do
- expect(subject.available?(cursor_based_request_context, AuditEvent.all)).to be_truthy
- end
-
- it 'returns true for Packages::BuildInfo' do
- expect(subject.available?(cursor_based_request_context, Packages::BuildInfo.all)).to be_truthy
- end
-
- it 'returns false for User' do
- expect(subject.available?(cursor_based_request_context, User.all)).to be_falsey
- end
+ it 'returns true for Ci::Build' do
+ expect(subject.available?(cursor_based_request_context, Ci::Build.all)).to be_truthy
end
- context 'with api_keyset_pagination_multi_order FF enabled' do
- before do
- stub_feature_flags(api_keyset_pagination_multi_order: true)
- end
-
- it 'returns true for Ci::Build' do
- expect(subject.available?(cursor_based_request_context, Ci::Build.all)).to be_truthy
- end
-
- it 'returns true for AuditEvent' do
- expect(subject.available?(cursor_based_request_context, AuditEvent.all)).to be_truthy
- end
+ it 'returns true for AuditEvent' do
+ expect(subject.available?(cursor_based_request_context, AuditEvent.all)).to be_truthy
+ end
- it 'returns true for Packages::BuildInfo' do
- expect(subject.available?(cursor_based_request_context, Packages::BuildInfo.all)).to be_truthy
- end
+ it 'returns true for Packages::BuildInfo' do
+ expect(subject.available?(cursor_based_request_context, Packages::BuildInfo.all)).to be_truthy
+ end
- it 'returns true for User' do
- expect(subject.available?(cursor_based_request_context, User.all)).to be_truthy
- end
+ it 'returns true for User' do
+ expect(subject.available?(cursor_based_request_context, User.all)).to be_truthy
end
end
diff --git a/spec/lib/gitlab/path_traversal_spec.rb b/spec/lib/gitlab/path_traversal_spec.rb
index bba6f8293c2..063919dd985 100644
--- a/spec/lib/gitlab/path_traversal_spec.rb
+++ b/spec/lib/gitlab/path_traversal_spec.rb
@@ -93,6 +93,13 @@ RSpec.describe Gitlab::PathTraversal, feature_category: :shared do
it 'raises for other non-strings' do
expect { check_path_traversal!(%w[/tmp /tmp/../etc/passwd]) }.to raise_error(/Invalid path/)
end
+
+ context 'when skip_decoding is used' do
+ it 'does not detect double encoded chars' do
+ expect(check_path_traversal!('foo%252F..%2Fbar', skip_decoding: true)).to eq('foo%252F..%2Fbar')
+ expect(check_path_traversal!('foo%252F%2E%2E%2Fbar', skip_decoding: true)).to eq('foo%252F%2E%2E%2Fbar')
+ end
+ end
end
describe '.check_allowed_absolute_path!' do
diff --git a/spec/lib/gitlab/prometheus/metric_group_spec.rb b/spec/lib/gitlab/prometheus/metric_group_spec.rb
deleted file mode 100644
index a68cdfe5fb2..00000000000
--- a/spec/lib/gitlab/prometheus/metric_group_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Prometheus::MetricGroup do
- describe '.common_metrics' do
- let!(:project_metric) { create(:prometheus_metric) }
- let!(:common_metric_group_a) { create(:prometheus_metric, :common, group: :aws_elb) }
- let!(:common_metric_group_b_q1) { create(:prometheus_metric, :common, group: :kubernetes) }
- let!(:common_metric_group_b_q2) { create(:prometheus_metric, :common, group: :kubernetes) }
-
- subject { described_class.common_metrics }
-
- it 'returns exactly two groups' do
- expect(subject.map(&:name)).to contain_exactly(
- 'Response metrics (AWS ELB)', 'System metrics (Kubernetes)')
- end
-
- it 'returns exactly three metric queries' do
- expect(subject.flat_map(&:metrics).map(&:id)).to contain_exactly(
- common_metric_group_a.id, common_metric_group_b_q1.id,
- common_metric_group_b_q2.id)
- end
-
- it 'orders by priority' do
- priorities = subject.map(&:priority)
- names = subject.map(&:name)
- expect(priorities).to eq([10, 5])
- expect(names).to eq(['Response metrics (AWS ELB)', 'System metrics (Kubernetes)'])
- end
- end
-
- describe '.for_project' do
- let!(:other_project) { create(:project) }
- let!(:project_metric) { create(:prometheus_metric) }
- let!(:common_metric) { create(:prometheus_metric, :common, group: :aws_elb) }
-
- subject do
- described_class.for_project(other_project)
- .flat_map(&:metrics)
- .map(&:id)
- end
-
- it 'returns exactly one common metric' do
- is_expected.to contain_exactly(common_metric.id)
- end
- end
-end
diff --git a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
deleted file mode 100644
index 66b93d0dd72..00000000000
--- a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Prometheus::Queries::DeploymentQuery do
- let(:environment) { create(:environment, slug: 'environment-slug') }
- let(:deployment) { create(:deployment, environment: environment) }
- let(:client) { double('prometheus_client') }
-
- subject { described_class.new(client) }
-
- around do |example|
- time_without_subsecond_values = Time.local(2008, 9, 1, 12, 0, 0)
- travel_to(time_without_subsecond_values) { example.run }
- end
-
- it 'sends appropriate queries to prometheus' do
- start_time = (deployment.created_at - 30.minutes).to_f
- end_time = (deployment.created_at + 30.minutes).to_f
- created_at = deployment.created_at.to_f
-
- expect(client).to receive(:query_range).with('avg(container_memory_usage_bytes{container_name!="POD",environment="environment-slug"}) / 2^20',
- start_time: start_time, end_time: end_time)
- expect(client).to receive(:query).with('avg(avg_over_time(container_memory_usage_bytes{container_name!="POD",environment="environment-slug"}[30m]))',
- time: created_at)
- expect(client).to receive(:query).with('avg(avg_over_time(container_memory_usage_bytes{container_name!="POD",environment="environment-slug"}[30m]))',
- time: end_time)
-
- expect(client).to receive(:query_range).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[2m])) * 100',
- start_time: start_time, end_time: end_time)
- expect(client).to receive(:query).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[30m])) * 100',
- time: created_at)
- expect(client).to receive(:query).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[30m])) * 100',
- time: end_time)
-
- expect(subject.query(deployment.id)).to eq(memory_values: nil, memory_before: nil, memory_after: nil,
- cpu_values: nil, cpu_before: nil, cpu_after: nil)
- end
-end
diff --git a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
deleted file mode 100644
index 60449aeef7d..00000000000
--- a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
+++ /dev/null
@@ -1,137 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Prometheus::Queries::MatchedMetricQuery do
- include Prometheus::MetricBuilders
-
- let(:metric_group_class) { Gitlab::Prometheus::MetricGroup }
- let(:metric_class) { Gitlab::Prometheus::Metric }
-
- def series_info_with_environment(*more_metrics)
- %w{metric_a metric_b}.concat(more_metrics).map { |metric_name| { '__name__' => metric_name, 'environment' => '' } }
- end
-
- let(:metric_names) { %w{metric_a metric_b} }
- let(:series_info_without_environment) do
- [{ '__name__' => 'metric_a' },
- { '__name__' => 'metric_b' }]
- end
-
- let(:partially_empty_series_info) { [{ '__name__' => 'metric_a', 'environment' => '' }] }
- let(:empty_series_info) { [] }
-
- let(:client) { double('prometheus_client') }
-
- subject { described_class.new(client) }
-
- context 'with one group where two metrics is found' do
- before do
- allow(metric_group_class).to receive(:common_metrics).and_return([simple_metric_group])
- allow(client).to receive(:label_values).and_return(metric_names)
- end
-
- context 'both metrics in the group pass requirements' do
- before do
- allow(client).to receive(:series).and_return(series_info_with_environment)
- end
-
- it 'responds with both metrics as actve' do
- expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 2, metrics_missing_requirements: 0 }])
- end
- end
-
- context 'none of the metrics pass requirements' do
- before do
- allow(client).to receive(:series).and_return(series_info_without_environment)
- end
-
- it 'responds with both metrics missing requirements' do
- expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 0, metrics_missing_requirements: 2 }])
- end
- end
-
- context 'no series information found about the metrics' do
- before do
- allow(client).to receive(:series).and_return(empty_series_info)
- end
-
- it 'responds with both metrics missing requirements' do
- expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 0, metrics_missing_requirements: 2 }])
- end
- end
-
- context 'one of the series info was not found' do
- before do
- allow(client).to receive(:series).and_return(partially_empty_series_info)
- end
- it 'responds with one active and one missing metric' do
- expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 1, metrics_missing_requirements: 1 }])
- end
- end
- end
-
- context 'with one group where only one metric is found' do
- before do
- allow(metric_group_class).to receive(:common_metrics).and_return([simple_metric_group])
- allow(client).to receive(:label_values).and_return('metric_a')
- end
-
- context 'both metrics in the group pass requirements' do
- before do
- allow(client).to receive(:series).and_return(series_info_with_environment)
- end
-
- it 'responds with one metrics as active and no missing requiremens' do
- expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 1, metrics_missing_requirements: 0 }])
- end
- end
-
- context 'no metrics in group pass requirements' do
- before do
- allow(client).to receive(:series).and_return(series_info_without_environment)
- end
-
- it 'responds with one metrics as active and no missing requiremens' do
- expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 0, metrics_missing_requirements: 1 }])
- end
- end
- end
-
- context 'with two groups where metrics are found in each group' do
- let(:second_metric_group) { simple_metric_group(name: 'nameb', metrics: simple_metrics(added_metric_name: 'metric_c')) }
-
- before do
- allow(metric_group_class).to receive(:common_metrics).and_return([simple_metric_group, second_metric_group])
- allow(client).to receive(:label_values).and_return('metric_c')
- end
-
- context 'all metrics in both groups pass requirements' do
- before do
- allow(client).to receive(:series).and_return(series_info_with_environment('metric_c'))
- end
-
- it 'responds with one metrics as active and no missing requiremens' do
- expect(subject.query).to eq([
- { group: 'name', priority: 1, active_metrics: 1, metrics_missing_requirements: 0 },
- { group: 'nameb', priority: 1, active_metrics: 2, metrics_missing_requirements: 0 }
- ]
- )
- end
- end
-
- context 'no metrics in groups pass requirements' do
- before do
- allow(client).to receive(:series).and_return(series_info_without_environment)
- end
-
- it 'responds with one metrics as active and no missing requiremens' do
- expect(subject.query).to eq([
- { group: 'name', priority: 1, active_metrics: 0, metrics_missing_requirements: 1 },
- { group: 'nameb', priority: 1, active_metrics: 0, metrics_missing_requirements: 2 }
- ]
- )
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb b/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb
deleted file mode 100644
index f09fa3548f8..00000000000
--- a/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Prometheus::Queries::ValidateQuery do
- include PrometheusHelpers
-
- let(:api_url) { 'https://prometheus.example.com' }
- let(:client) { Gitlab::PrometheusClient.new(api_url) }
- let(:query) { 'avg(metric)' }
-
- subject { described_class.new(client) }
-
- context 'valid query' do
- before do
- allow(client).to receive(:query).with(query)
- end
-
- it 'passess query to prometheus' do
- expect(subject.query(query)).to eq(valid: true)
-
- expect(client).to have_received(:query).with(query)
- end
- end
-
- context 'invalid query' do
- let(:query) { 'invalid query' }
- let(:error_message) { "invalid parameter 'query': 1:9: parse error: unexpected identifier \"query\"" }
-
- it 'returns invalid' do
- freeze_time do
- stub_prometheus_query_error(
- prometheus_query_with_time_url(query, Time.now),
- error_message
- )
-
- expect(subject.query(query)).to eq(valid: false, error: error_message)
- end
- end
- end
-
- context 'when exceptions occur' do
- context 'Gitlab::HTTP::BlockedUrlError' do
- let(:api_url) { 'http://192.168.1.1' }
-
- let(:message) { "URL is blocked: Requests to the local network are not allowed" }
-
- before do
- stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
- end
-
- it 'catches exception and returns invalid' do
- freeze_time do
- expect(subject.query(query)).to eq(valid: false, error: message)
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb
deleted file mode 100644
index d0947eef2d9..00000000000
--- a/spec/lib/gitlab/prometheus/query_variables_spec.rb
+++ /dev/null
@@ -1,96 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Prometheus::QueryVariables do
- describe '.call' do
- let_it_be_with_refind(:environment) { create(:environment) }
-
- let(:project) { environment.project }
- let(:slug) { environment.slug }
- let(:params) { {} }
-
- subject { described_class.call(environment, **params) }
-
- it { is_expected.to include(ci_environment_slug: slug) }
- it { is_expected.to include(ci_project_name: project.name) }
- it { is_expected.to include(ci_project_namespace: project.namespace.name) }
- it { is_expected.to include(ci_project_path: project.full_path) }
- it { is_expected.to include(ci_environment_name: environment.name) }
-
- it do
- is_expected.to include(environment_filter:
- %[container_name!="POD",environment="#{slug}"])
- end
-
- context 'without deployment platform' do
- it { is_expected.to include(kube_namespace: '') }
- end
-
- context 'with deployment platform' do
- context 'with project cluster' do
- let(:kube_namespace) { environment.deployment_namespace }
-
- before do
- create(:cluster, :project, :provided_by_user, projects: [project])
- end
-
- it { is_expected.to include(kube_namespace: kube_namespace) }
- end
-
- context 'with group cluster' do
- let(:cluster) { create(:cluster, :group, :provided_by_user, groups: [group]) }
- let(:group) { create(:group) }
- let(:project2) { create(:project) }
- let(:kube_namespace) { k8s_ns.namespace }
-
- let!(:k8s_ns) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project, environment: environment) }
- let!(:k8s_ns2) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project2, environment: environment) }
-
- before do
- group.projects << project
- group.projects << project2
- end
-
- it { is_expected.to include(kube_namespace: kube_namespace) }
- end
- end
-
- context '__range' do
- context 'when start_time and end_time are present' do
- let(:params) do
- {
- start_time: Time.rfc3339('2020-05-29T07:23:05.008Z'),
- end_time: Time.rfc3339('2020-05-29T15:23:05.008Z')
- }
- end
-
- it { is_expected.to include(__range: "#{8.hours.to_i}s") }
- end
-
- context 'when start_time and end_time are not present' do
- it { is_expected.to include(__range: nil) }
- end
-
- context 'when end_time is not present' do
- let(:params) do
- {
- start_time: Time.rfc3339('2020-05-29T07:23:05.008Z')
- }
- end
-
- it { is_expected.to include(__range: nil) }
- end
-
- context 'when start_time is not present' do
- let(:params) do
- {
- end_time: Time.rfc3339('2020-05-29T07:23:05.008Z')
- }
- end
-
- it { is_expected.to include(__range: nil) }
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/protocol_access_spec.rb b/spec/lib/gitlab/protocol_access_spec.rb
index 4722ea99608..cae14c3d7cf 100644
--- a/spec/lib/gitlab/protocol_access_spec.rb
+++ b/spec/lib/gitlab/protocol_access_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-RSpec.describe Gitlab::ProtocolAccess do
+RSpec.describe Gitlab::ProtocolAccess, feature_category: :source_code_management do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { create(:group) }
@@ -10,25 +10,34 @@ RSpec.describe Gitlab::ProtocolAccess do
describe ".allowed?" do
where(:protocol, :project, :admin_setting, :namespace_setting, :expected_result) do
- "web" | nil | nil | nil | true
- "ssh" | nil | nil | nil | true
- "http" | nil | nil | nil | true
- "ssh" | nil | "" | nil | true
- "http" | nil | "" | nil | true
- "ssh" | nil | "ssh" | nil | true
- "http" | nil | "http" | nil | true
- "ssh" | nil | "http" | nil | false
- "http" | nil | "ssh" | nil | false
- "ssh" | ref(:p1) | nil | "all" | true
- "http" | ref(:p1) | nil | "all" | true
- "ssh" | ref(:p1) | nil | "ssh" | true
- "http" | ref(:p1) | nil | "http" | true
- "ssh" | ref(:p1) | nil | "http" | false
- "http" | ref(:p1) | nil | "ssh" | false
- "ssh" | ref(:p1) | "" | "all" | true
- "http" | ref(:p1) | "" | "all" | true
- "ssh" | ref(:p1) | "ssh" | "ssh" | true
- "http" | ref(:p1) | "http" | "http" | true
+ "web" | nil | nil | nil | true
+ "ssh" | nil | nil | nil | true
+ "http" | nil | nil | nil | true
+ "ssh_certificates" | nil | nil | nil | true
+ "ssh" | nil | "" | nil | true
+ "http" | nil | "" | nil | true
+ "ssh_certificates" | nil | "" | nil | true
+ "ssh" | nil | "ssh" | nil | true
+ "http" | nil | "http" | nil | true
+ "ssh_certificates" | nil | "ssh_certificates" | nil | true
+ "ssh" | nil | "http" | nil | false
+ "http" | nil | "ssh" | nil | false
+ "ssh_certificates" | nil | "ssh" | nil | false
+ "ssh" | ref(:p1) | nil | "all" | true
+ "http" | ref(:p1) | nil | "all" | true
+ "ssh_certificates" | ref(:p1) | nil | "all" | true
+ "ssh" | ref(:p1) | nil | "ssh" | true
+ "http" | ref(:p1) | nil | "http" | true
+ "ssh_certificates" | ref(:p1) | nil | "ssh_certificates" | true
+ "ssh" | ref(:p1) | nil | "http" | false
+ "http" | ref(:p1) | nil | "ssh" | false
+ "ssh_certificates" | ref(:p1) | nil | "ssh" | false
+ "ssh" | ref(:p1) | "" | "all" | true
+ "http" | ref(:p1) | "" | "all" | true
+ "ssh_certificates" | ref(:p1) | "" | "all" | true
+ "ssh" | ref(:p1) | "ssh" | "ssh" | true
+ "http" | ref(:p1) | "http" | "http" | true
+ "ssh_certificates" | ref(:p1) | "ssh_certificates" | "ssh_certificates" | true
end
with_them do
diff --git a/spec/lib/gitlab/puma/error_handler_spec.rb b/spec/lib/gitlab/puma/error_handler_spec.rb
new file mode 100644
index 00000000000..5b7cdf37af1
--- /dev/null
+++ b/spec/lib/gitlab/puma/error_handler_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Puma::ErrorHandler, feature_category: :shared do
+ subject { described_class.new(is_production) }
+
+ let(:is_production) { true }
+ let(:ex) { StandardError.new('Sample error message') }
+ let(:env) { {} }
+ let(:status_code) { 500 }
+
+ describe '#execute' do
+ it 'captures the exception and returns a Rack response' do
+ allow(Raven.configuration).to receive(:capture_allowed?).and_return(true)
+ expect(Raven).to receive(:capture_exception).with(
+ ex,
+ tags: { handler: 'puma_low_level' },
+ extra: { puma_env: env, status_code: status_code }
+ ).and_call_original
+
+ status, headers, message = subject.execute(ex, env, status_code)
+
+ expect(status).to eq(500)
+ expect(headers).to eq({})
+ expect(message).to eq(described_class::PROD_ERROR_MESSAGE)
+ end
+
+ context 'when capture is not allowed' do
+ it 'returns a Rack response without capturing the exception' do
+ allow(Raven.configuration).to receive(:capture_allowed?).and_return(false)
+ expect(Raven).not_to receive(:capture_exception)
+
+ status, headers, message = subject.execute(ex, env, status_code)
+
+ expect(status).to eq(500)
+ expect(headers).to eq({})
+ expect(message).to eq(described_class::PROD_ERROR_MESSAGE)
+ end
+ end
+
+ context 'when not in production' do
+ let(:is_production) { false }
+
+ it 'returns a Rack response with dev error message' do
+ allow(Raven.configuration).to receive(:capture_allowed?).and_return(true)
+
+ status, headers, message = subject.execute(ex, env, status_code)
+
+ expect(status).to eq(500)
+ expect(headers).to eq({})
+ expect(message).to eq(described_class::DEV_ERROR_MESSAGE)
+ end
+ end
+
+ context 'when status code is nil' do
+ let(:status_code) { 500 }
+
+ it 'defaults to error 500' do
+ allow(Raven.configuration).to receive(:capture_allowed?).and_return(false)
+ expect(Raven).not_to receive(:capture_exception)
+
+ status, headers, message = subject.execute(ex, env, status_code)
+
+ expect(status).to eq(500)
+ expect(headers).to eq({})
+ expect(message).to eq(described_class::PROD_ERROR_MESSAGE)
+ end
+ end
+
+ context 'when status code is provided' do
+ let(:status_code) { 404 }
+
+ it 'uses the provided status code in the response' do
+ allow(Raven.configuration).to receive(:capture_allowed?).and_return(true)
+
+ status, headers, message = subject.execute(ex, env, status_code)
+
+ expect(status).to eq(404)
+ expect(headers).to eq({})
+ expect(message).to eq(described_class::PROD_ERROR_MESSAGE)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/rack_attack/request_spec.rb b/spec/lib/gitlab/rack_attack/request_spec.rb
index 9d2144f75db..92c9acb83cf 100644
--- a/spec/lib/gitlab/rack_attack/request_spec.rb
+++ b/spec/lib/gitlab/rack_attack/request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::RackAttack::Request do
+RSpec.describe Gitlab::RackAttack::Request, feature_category: :rate_limiting do
using RSpec::Parameterized::TableSyntax
let(:path) { '/' }
@@ -38,8 +38,12 @@ RSpec.describe Gitlab::RackAttack::Request do
'/groups' | false
'/foo/api' | false
- '/api' | true
+ '/api' | false
+ '/api/' | true
'/api/v4/groups/1' | true
+
+ '/oauth/tokens' | true
+ '/oauth/userinfo' | true
end
with_them do
@@ -53,6 +57,36 @@ RSpec.describe Gitlab::RackAttack::Request do
it { is_expected.to eq(expected) }
end
end
+
+ context 'when rate_limit_oauth_api feature flag is disabled' do
+ before do
+ stub_feature_flags(rate_limit_oauth_api: false)
+ end
+
+ where(:path, :expected) do
+ '/' | false
+ '/groups' | false
+ '/foo/api' | false
+
+ '/api' | true
+ '/api/v4/groups/1' | true
+
+ '/oauth/tokens' | false
+ '/oauth/userinfo' | false
+ end
+
+ with_them do
+ it { is_expected.to eq(expected) }
+
+ context 'when the application is mounted at a relative URL' do
+ before do
+ stub_config_setting(relative_url_root: '/gitlab/root')
+ end
+
+ it { is_expected.to eq(expected) }
+ end
+ end
+ end
end
describe '#api_internal_request?' do
@@ -196,7 +230,8 @@ RSpec.describe Gitlab::RackAttack::Request do
'/groups' | true
'/foo/api' | true
- '/api' | false
+ '/api' | true
+ '/api/' | false
'/api/v4/groups/1' | false
end
diff --git a/spec/lib/gitlab/redis/multi_store_spec.rb b/spec/lib/gitlab/redis/multi_store_spec.rb
index ce21c2269cc..1745a745ec3 100644
--- a/spec/lib/gitlab/redis/multi_store_spec.rb
+++ b/spec/lib/gitlab/redis/multi_store_spec.rb
@@ -948,6 +948,55 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do
end
end
+ describe '#close' do
+ subject { multi_store.close }
+
+ context 'when using both stores' do
+ before do
+ allow(multi_store).to receive(:use_primary_and_secondary_stores?).and_return(true)
+ end
+
+ it 'closes both stores' do
+ expect(primary_store).to receive(:close)
+ expect(secondary_store).to receive(:close)
+
+ subject
+ end
+ end
+
+ context 'when using only one store' do
+ before do
+ allow(multi_store).to receive(:use_primary_and_secondary_stores?).and_return(false)
+ end
+
+ context 'when using primary_store as default store' do
+ before do
+ allow(multi_store).to receive(:use_primary_store_as_default?).and_return(true)
+ end
+
+ it 'closes primary store' do
+ expect(primary_store).to receive(:close)
+ expect(secondary_store).not_to receive(:close)
+
+ subject
+ end
+ end
+
+ context 'when using secondary_store as default store' do
+ before do
+ allow(multi_store).to receive(:use_primary_store_as_default?).and_return(false)
+ end
+
+ it 'closes secondary store' do
+ expect(primary_store).not_to receive(:close)
+ expect(secondary_store).to receive(:close)
+
+ subject
+ end
+ end
+ end
+ end
+
context 'with unsupported command' do
let(:counter) { Gitlab::Metrics::NullMetric.instance }
diff --git a/spec/lib/gitlab/redis/queues_metadata_spec.rb b/spec/lib/gitlab/redis/queues_metadata_spec.rb
index 693e8074b45..1ac5c3b4e70 100644
--- a/spec/lib/gitlab/redis/queues_metadata_spec.rb
+++ b/spec/lib/gitlab/redis/queues_metadata_spec.rb
@@ -5,39 +5,4 @@ require 'spec_helper'
RSpec.describe Gitlab::Redis::QueuesMetadata, feature_category: :redis do
include_examples "redis_new_instance_shared_examples", 'queues_metadata', Gitlab::Redis::Queues
include_examples "redis_shared_examples"
-
- describe '#pool' do
- let(:config_new_format_host) { "spec/fixtures/config/redis_new_format_host.yml" }
- let(:config_new_format_socket) { "spec/fixtures/config/redis_new_format_socket.yml" }
-
- subject { described_class.pool }
-
- around do |example|
- clear_pool
- example.run
- ensure
- clear_pool
- end
-
- before do
- allow(described_class).to receive(:config_file_name).and_return(config_new_format_host)
-
- allow(described_class).to receive(:config_file_name).and_return(config_new_format_host)
- allow(Gitlab::Redis::Queues).to receive(:config_file_name).and_return(config_new_format_socket)
- end
-
- it 'instantiates an instance of MultiStore' do
- subject.with do |redis_instance|
- expect(redis_instance).to be_instance_of(::Gitlab::Redis::MultiStore)
-
- expect(redis_instance.primary_store.connection[:id]).to eq("redis://test-host:6379/99")
- expect(redis_instance.secondary_store.connection[:id]).to eq("unix:///path/to/redis.sock/0")
-
- expect(redis_instance.instance_name).to eq('QueuesMetadata')
- end
- end
-
- it_behaves_like 'multi store feature flags', :use_primary_and_secondary_stores_for_queues_metadata,
- :use_primary_store_as_default_for_queues_metadata
- end
end
diff --git a/spec/lib/gitlab/redis/workhorse_spec.rb b/spec/lib/gitlab/redis/workhorse_spec.rb
index 46931a6afcb..db5db18c732 100644
--- a/spec/lib/gitlab/redis/workhorse_spec.rb
+++ b/spec/lib/gitlab/redis/workhorse_spec.rb
@@ -2,43 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Redis::Workhorse, feature_category: :scalability do
+RSpec.describe Gitlab::Redis::Workhorse, feature_category: :redis do
include_examples "redis_new_instance_shared_examples", 'workhorse', Gitlab::Redis::SharedState
include_examples "redis_shared_examples"
-
- describe '#pool' do
- let(:config_new_format_host) { "spec/fixtures/config/redis_new_format_host.yml" }
- let(:config_new_format_socket) { "spec/fixtures/config/redis_new_format_socket.yml" }
-
- subject { described_class.pool }
-
- before do
- allow(described_class).to receive(:config_file_name).and_return(config_new_format_host)
-
- # Override rails root to avoid having our fixtures overwritten by `redis.yml` if it exists
- allow(Gitlab::Redis::SharedState).to receive(:rails_root).and_return(mktmpdir)
- allow(Gitlab::Redis::SharedState).to receive(:config_file_name).and_return(config_new_format_socket)
- end
-
- around do |example|
- clear_pool
- example.run
- ensure
- clear_pool
- end
-
- it 'instantiates an instance of MultiStore' do
- subject.with do |redis_instance|
- expect(redis_instance).to be_instance_of(::Gitlab::Redis::MultiStore)
-
- expect(redis_instance.primary_store.connection[:id]).to eq("redis://test-host:6379/99")
- expect(redis_instance.secondary_store.connection[:id]).to eq("unix:///path/to/redis.sock/0")
-
- expect(redis_instance.instance_name).to eq('Workhorse')
- end
- end
-
- it_behaves_like 'multi store feature flags', :use_primary_and_secondary_stores_for_workhorse,
- :use_primary_store_as_default_for_workhorse
- end
end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 02ae3f63918..381f3a80799 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -86,33 +86,6 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do
it { is_expected.to match('<any-Charact3r$|any-Charact3r$>') }
end
- describe '.group_path_regex' do
- subject { described_class.group_path_regex }
-
- it { is_expected.not_to match('?gitlab') }
- it { is_expected.not_to match("Users's something") }
- it { is_expected.not_to match('/source') }
- it { is_expected.not_to match('http:') }
- it { is_expected.not_to match('https:') }
- it { is_expected.not_to match('example.com/?stuff=true') }
- it { is_expected.not_to match('example.com:5000/?stuff=true') }
- it { is_expected.not_to match('http://gitlab.example/gitlab-org/manage/import/gitlab-migration-test') }
- it { is_expected.not_to match('_good_for_me!') }
- it { is_expected.not_to match('good_for+you') }
- it { is_expected.not_to match('source/') }
- it { is_expected.not_to match('.source/full./path') }
-
- it { is_expected.not_to match('source/full') }
- it { is_expected.not_to match('source/full/path') }
- it { is_expected.not_to match('.source/.full/.path') }
-
- it { is_expected.to match('source') }
- it { is_expected.to match('.source') }
- it { is_expected.to match('_source') }
- it { is_expected.to match('domain_namespace') }
- it { is_expected.to match('gitlab-migration-test') }
- end
-
describe '.environment_name_regex' do
subject { described_class.environment_name_regex }
diff --git a/spec/lib/gitlab/saas_spec.rb b/spec/lib/gitlab/saas_spec.rb
index a8656c44831..3be0a6c7bf0 100644
--- a/spec/lib/gitlab/saas_spec.rb
+++ b/spec/lib/gitlab/saas_spec.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
+require 'support/helpers/saas_test_helper'
-RSpec.describe Gitlab::Saas do
+RSpec.describe Gitlab::Saas, feature_category: :shared do
include SaasTestHelper
describe '.canary_toggle_com_url' do
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index d1f19a5e1ba..00e68f73d2d 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -465,6 +465,6 @@ RSpec.describe Gitlab::SearchResults, feature_category: :global_search do
expect(results.objects(scope)).to match_array([milestone_1, milestone_2, milestone_3])
end
- include_examples 'search results filtered by archived', 'search_milestones_hide_archived_projects'
+ include_examples 'search results filtered by archived'
end
end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 049b8d4ed86..22220efaa05 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -13,8 +13,6 @@ RSpec.describe Gitlab::Shell do
described_class.instance_variable_set(:@secret_token, nil)
end
- it { is_expected.to respond_to :remove_repository }
-
describe '.secret_token' do
let(:secret_file) { 'tmp/tests/.secret_shell_test' }
let(:link_file) { 'tmp/tests/shell-secret-test/.gitlab_shell_secret' }
@@ -74,67 +72,11 @@ RSpec.describe Gitlab::Shell do
end
end
- describe 'projects commands' do
- let(:gitlab_shell_path) { File.expand_path('tmp/tests/gitlab-shell') }
- let(:projects_path) { File.join(gitlab_shell_path, 'bin/gitlab-projects') }
-
- before do
- allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(gitlab_shell_path)
- allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
- end
-
- describe '#remove_repository' do
- let!(:project) { create(:project, :repository, :legacy_storage) }
- let(:disk_path) { "#{project.disk_path}.git" }
-
- it 'returns true when the command succeeds' do
- expect(project.repository.raw).to exist
-
- expect(gitlab_shell.remove_repository(project.repository_storage, project.disk_path)).to be(true)
-
- expect(project.repository.raw).not_to exist
- end
- end
-
- describe '#mv_repository' do
- let!(:project2) { create(:project, :repository) }
-
- it 'returns true when the command succeeds' do
- old_repo = project2.repository.raw
- new_path = "project/new_path"
- new_repo = Gitlab::Git::Repository.new(project2.repository_storage, "#{new_path}.git", nil, nil)
-
- expect(old_repo).to exist
- expect(new_repo).not_to exist
-
- expect(gitlab_shell.mv_repository(project2.repository_storage, project2.disk_path, new_path)).to be_truthy
-
- expect(old_repo).not_to exist
- expect(new_repo).to exist
- end
-
- it 'returns false when the command fails' do
- expect(gitlab_shell.mv_repository(project2.repository_storage, project2.disk_path, '')).to be_falsy
- expect(project2.repository.raw).to exist
- end
- end
- end
-
describe 'namespace actions' do
subject { described_class.new }
let(:storage) { Gitlab.config.repositories.storages.each_key.first }
- describe '#add_namespace' do
- it 'creates a namespace' do
- Gitlab::GitalyClient::NamespaceService.allow do
- subject.add_namespace(storage, "mepmep")
-
- expect(Gitlab::GitalyClient::NamespaceService.new(storage).exists?("mepmep")).to be(true)
- end
- end
- end
-
describe '#repository_exists?' do
context 'when the repository does not exist' do
it 'returns false' do
@@ -150,28 +92,5 @@ RSpec.describe Gitlab::Shell do
end
end
end
-
- describe '#remove' do
- it 'removes the namespace' do
- Gitlab::GitalyClient::NamespaceService.allow do
- subject.add_namespace(storage, "mepmep")
- subject.rm_namespace(storage, "mepmep")
-
- expect(Gitlab::GitalyClient::NamespaceService.new(storage).exists?("mepmep")).to be(false)
- end
- end
- end
-
- describe '#mv_namespace' do
- it 'renames the namespace' do
- Gitlab::GitalyClient::NamespaceService.allow do
- subject.add_namespace(storage, "mepmep")
- subject.mv_namespace(storage, "mepmep", "2mep")
-
- expect(Gitlab::GitalyClient::NamespaceService.new(storage).exists?("mepmep")).to be(false)
- expect(Gitlab::GitalyClient::NamespaceService.new(storage).exists?("2mep")).to be(true)
- end
- end
- end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
index 937a1751cc7..7138ad04f69 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
@@ -3,8 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob,
- :clean_gitlab_redis_queues, :clean_gitlab_redis_shared_state, :clean_gitlab_redis_queues_metadata,
- feature_category: :shared do
+ :clean_gitlab_redis_queues_metadata, feature_category: :shared do
using RSpec::Parameterized::TableSyntax
subject(:duplicate_job) do
@@ -79,7 +78,11 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob,
end
end
- shared_examples 'with Redis cookies' do
+ context 'with Redis cookies' do
+ def with_redis(&block)
+ Gitlab::Redis::QueuesMetadata.with(&block)
+ end
+
let(:cookie_key) { "#{Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE}:#{idempotency_key}:cookie:v2" }
let(:cookie) { get_redis_msgpack(cookie_key) }
@@ -413,62 +416,6 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob,
end
end
- context 'with multi-store feature flags turned on' do
- def with_redis(&block)
- Gitlab::Redis::QueuesMetadata.with(&block)
- end
-
- shared_examples 'uses QueuesMetadata' do
- it 'use Gitlab::Redis::QueuesMetadata.with' do
- expect(Gitlab::Redis::QueuesMetadata).to receive(:with).and_call_original
- expect(Gitlab::Redis::Queues).not_to receive(:with)
-
- duplicate_job.check!
- end
- end
-
- context 'when migration is ongoing with double-write' do
- before do
- stub_feature_flags(use_primary_store_as_default_for_queues_metadata: false)
- end
-
- it_behaves_like 'uses QueuesMetadata'
- it_behaves_like 'with Redis cookies'
- end
-
- context 'when migration is completed' do
- before do
- stub_feature_flags(use_primary_and_secondary_stores_for_queues_metadata: false)
- end
-
- it_behaves_like 'uses QueuesMetadata'
- it_behaves_like 'with Redis cookies'
- end
-
- it_behaves_like 'uses QueuesMetadata'
- it_behaves_like 'with Redis cookies'
- end
-
- context 'when both multi-store feature flags are off' do
- def with_redis(&block)
- Gitlab::Redis::Queues.with(&block)
- end
-
- before do
- stub_feature_flags(use_primary_and_secondary_stores_for_queues_metadata: false)
- stub_feature_flags(use_primary_store_as_default_for_queues_metadata: false)
- end
-
- it 'use Gitlab::Redis::Queues' do
- expect(Gitlab::Redis::Queues).to receive(:with).and_call_original
- expect(Gitlab::Redis::QueuesMetadata).not_to receive(:with)
-
- duplicate_job.check!
- end
-
- it_behaves_like 'with Redis cookies'
- end
-
describe '#scheduled?' do
it 'returns false for non-scheduled jobs' do
expect(duplicate_job.scheduled?).to be(false)
diff --git a/spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb b/spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb
index dbab67f5996..5569bc01a6a 100644
--- a/spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/extra_done_log_metadata_spec.rb
@@ -26,10 +26,18 @@ RSpec.describe Gitlab::SidekiqMiddleware::ExtraDoneLogMetadata do
expect(job).to eq({ 'jid' => 123, 'extra.admin_email_worker.key1' => 15, 'extra.admin_email_worker.key2' => 16 })
end
- it 'does not raise when the worker does not respond to #done_log_extra_metadata' do
+ it 'does not raise when the worker does not respond to #logging_extras' do
expect { |b| subject.call(worker_without_application_worker, job, queue, &b) }.to yield_control
expect(job).to eq({ 'jid' => 123 })
end
+
+ it 'still merges logging_extras even when an error is raised during job execution' do
+ worker.log_extra_metadata_on_done(:key1, 15)
+ worker.log_extra_metadata_on_done(:key2, 16)
+ expect { subject.call(worker, job, queue) { raise 'an error' } }.to raise_error(StandardError, 'an error')
+
+ expect(job).to eq({ 'jid' => 123, 'extra.admin_email_worker.key1' => 15, 'extra.admin_email_worker.key2' => 16 })
+ end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb b/spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb
index 620de7e7671..2fa0e44d44f 100644
--- a/spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb
@@ -23,76 +23,76 @@ RSpec.describe Gitlab::SidekiqMiddleware::SkipJobs, feature_category: :scalabili
describe '#call' do
context 'with worker not opted for database health check' do
- describe "with all combinations of drop and defer FFs" do
- using RSpec::Parameterized::TableSyntax
+ let(:metric) { instance_double(Prometheus::Client::Counter, increment: true) }
- let(:metric) { instance_double(Prometheus::Client::Counter, increment: true) }
-
- shared_examples 'runs the job normally' do
- it 'yields control' do
- expect { |b| subject.call(TestWorker.new, job, queue, &b) }.to yield_control
- end
+ shared_examples 'runs the job normally' do
+ it 'yields control' do
+ expect { |b| subject.call(TestWorker.new, job, queue, &b) }.to yield_control
+ end
- it 'does not increment any metric counter' do
- expect(metric).not_to receive(:increment)
+ it 'does not increment any metric counter' do
+ expect(metric).not_to receive(:increment)
- subject.call(TestWorker.new, job, queue) { nil }
- end
+ subject.call(TestWorker.new, job, queue) { nil }
+ end
- it 'does not increment deferred_count' do
- subject.call(TestWorker.new, job, queue) { nil }
+ it 'does not increment deferred_count' do
+ subject.call(TestWorker.new, job, queue) { nil }
- expect(job).not_to include('deferred_count')
- end
+ expect(job).not_to include('deferred_count')
end
+ end
- shared_examples 'drops the job' do
- it 'does not yield control' do
- expect { |b| subject.call(TestWorker.new, job, queue, &b) }.not_to yield_control
- end
+ shared_examples 'drops the job' do
+ it 'does not yield control' do
+ expect { |b| subject.call(TestWorker.new, job, queue, &b) }.not_to yield_control
+ end
- it 'increments counter' do
- expect(metric).to receive(:increment).with({ worker: "TestWorker", action: "dropped" })
+ it 'increments counter' do
+ expect(metric).to receive(:increment).with({ worker: "TestWorker", action: "dropped" })
- subject.call(TestWorker.new, job, queue) { nil }
- end
+ subject.call(TestWorker.new, job, queue) { nil }
+ end
- it 'does not increment deferred_count' do
- subject.call(TestWorker.new, job, queue) { nil }
+ it 'does not increment deferred_count' do
+ subject.call(TestWorker.new, job, queue) { nil }
- expect(job).not_to include('deferred_count')
- end
+ expect(job).not_to include('deferred_count')
+ end
- it 'has dropped field in job equal to true' do
- subject.call(TestWorker.new, job, queue) { nil }
+ it 'has dropped field in job equal to true' do
+ subject.call(TestWorker.new, job, queue) { nil }
- expect(job).to include({ 'dropped' => true })
- end
+ expect(job).to include({ 'dropped' => true })
end
+ end
- shared_examples 'defers the job' do
- it 'does not yield control' do
- expect { |b| subject.call(TestWorker.new, job, queue, &b) }.not_to yield_control
- end
+ shared_examples 'defers the job' do
+ it 'does not yield control' do
+ expect { |b| subject.call(TestWorker.new, job, queue, &b) }.not_to yield_control
+ end
- it 'delays the job' do
- expect(TestWorker).to receive(:perform_in).with(described_class::DELAY, *job['args'])
+ it 'delays the job' do
+ expect(TestWorker).to receive(:perform_in).with(described_class::DELAY, *job['args'])
- subject.call(TestWorker.new, job, queue) { nil }
- end
+ subject.call(TestWorker.new, job, queue) { nil }
+ end
- it 'increments counter' do
- expect(metric).to receive(:increment).with({ worker: "TestWorker", action: "deferred" })
+ it 'increments counter' do
+ expect(metric).to receive(:increment).with({ worker: "TestWorker", action: "deferred" })
- subject.call(TestWorker.new, job, queue) { nil }
- end
+ subject.call(TestWorker.new, job, queue) { nil }
+ end
- it 'has deferred related fields in job payload' do
- subject.call(TestWorker.new, job, queue) { nil }
+ it 'has deferred related fields in job payload' do
+ subject.call(TestWorker.new, job, queue) { nil }
- expect(job).to include({ 'deferred' => true, 'deferred_by' => :feature_flag, 'deferred_count' => 1 })
- end
+ expect(job).to include({ 'deferred' => true, 'deferred_by' => :feature_flag, 'deferred_count' => 1 })
end
+ end
+
+ describe "with all combinations of drop and defer FFs" do
+ using RSpec::Parameterized::TableSyntax
before do
stub_feature_flags("drop_sidekiq_jobs_#{TestWorker.name}": drop_ff)
@@ -112,6 +112,45 @@ RSpec.describe Gitlab::SidekiqMiddleware::SkipJobs, feature_category: :scalabili
it_behaves_like params[:resulting_behavior]
end
end
+
+ describe 'using current_request actor', :request_store do
+ before do
+ allow(Gitlab::Metrics).to receive(:counter).and_call_original
+ allow(Gitlab::Metrics).to receive(:counter).with(described_class::COUNTER, anything).and_return(metric)
+ end
+
+ context 'with drop_sidekiq_jobs FF' do
+ before do
+ stub_feature_flags("drop_sidekiq_jobs_#{TestWorker.name}": Feature.current_request)
+ end
+
+ it_behaves_like 'drops the job'
+
+ context 'for different request' do
+ before do
+ stub_with_new_feature_current_request
+ end
+
+ it_behaves_like 'runs the job normally'
+ end
+ end
+
+ context 'with run_sidekiq_jobs FF' do
+ before do
+ stub_feature_flags("run_sidekiq_jobs_#{TestWorker.name}": Feature.current_request)
+ end
+
+ it_behaves_like 'runs the job normally'
+
+ context 'for different request' do
+ before do
+ stub_with_new_feature_current_request
+ end
+
+ it_behaves_like 'defers the job'
+ end
+ end
+ end
end
context 'with worker opted for database health check' do
diff --git a/spec/lib/gitlab/slash_commands/run_spec.rb b/spec/lib/gitlab/slash_commands/run_spec.rb
index 9d204228d21..5d228a9ba6a 100644
--- a/spec/lib/gitlab/slash_commands/run_spec.rb
+++ b/spec/lib/gitlab/slash_commands/run_spec.rb
@@ -39,16 +39,6 @@ RSpec.describe Gitlab::SlashCommands::Run do
expect(described_class.available?(project)).to eq(false)
end
-
- it 'returns false when chatops is not available' do
- allow(Gitlab::Chat)
- .to receive(:available?)
- .and_return(false)
-
- project = double(:project, builds_enabled?: true)
-
- expect(described_class.available?(project)).to eq(false)
- end
end
describe '.allowed?' do
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index cfd40fb93b5..0f827921a66 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -66,7 +66,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only, feature_category: :sh
include_context 'when instance configured to deny all requests'
it 'blocks the request' do
- expect { subject }.to raise_error(described_class::BlockedUrlError)
+ expect { subject }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
@@ -83,7 +83,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only, feature_category: :sh
let(:arg_value) { proc { true } }
it 'blocks the request' do
- expect { subject }.to raise_error(described_class::BlockedUrlError)
+ expect { subject }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
@@ -99,7 +99,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only, feature_category: :sh
let(:arg_value) { true }
it 'blocks the request' do
- expect { subject }.to raise_error(described_class::BlockedUrlError)
+ expect { subject }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
@@ -228,7 +228,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only, feature_category: :sh
let(:lfs_enabled) { false }
it 'raises an error' do
- expect { subject }.to raise_error(described_class::BlockedUrlError)
+ expect { subject }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
@@ -236,7 +236,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only, feature_category: :sh
let(:lfs_enabled) { true }
it 'raises an error' do
- expect { subject }.to raise_error(described_class::BlockedUrlError)
+ expect { subject }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
end
@@ -251,7 +251,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only, feature_category: :sh
end
it 'raises an error' do
- expect { subject }.to raise_error(described_class::BlockedUrlError)
+ expect { subject }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
@@ -259,7 +259,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only, feature_category: :sh
let(:host) { 'http://127.0.0.1:9000' }
it 'raises an error' do
- expect { subject }.to raise_error(described_class::BlockedUrlError)
+ expect { subject }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
end
@@ -290,7 +290,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only, feature_category: :sh
end
it 'raises an error' do
- expect { subject }.to raise_error(described_class::BlockedUrlError)
+ expect { subject }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
context 'with HTTP_PROXY' do
@@ -324,7 +324,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only, feature_category: :sh
let(:import_url) { "https://example#{'a' * 1024}.com" }
it 'raises an error' do
- expect { subject }.to raise_error(described_class::BlockedUrlError)
+ expect { subject }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
end
@@ -346,7 +346,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only, feature_category: :sh
it 'raises an error' do
stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
- expect { subject }.to raise_error(described_class::BlockedUrlError)
+ expect { subject }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
end
end
end
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 865a8384405..68eb38a1335 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -23,7 +23,8 @@ RSpec.describe Gitlab::UrlBuilder do
:commit | ->(commit) { "/#{commit.project.full_path}/-/commit/#{commit.id}" }
:issue | ->(issue) { "/#{issue.project.full_path}/-/issues/#{issue.iid}" }
[:issue, :task] | ->(issue) { "/#{issue.project.full_path}/-/work_items/#{issue.iid}" }
- :work_item | ->(work_item) { "/#{work_item.project.full_path}/-/work_items/#{work_item.iid}" }
+ [:work_item, :task] | ->(work_item) { "/#{work_item.project.full_path}/-/work_items/#{work_item.iid}" }
+ [:work_item, :issue] | ->(work_item) { "/#{work_item.project.full_path}/-/issues/#{work_item.iid}" }
:merge_request | ->(merge_request) { "/#{merge_request.project.full_path}/-/merge_requests/#{merge_request.iid}" }
:project_milestone | ->(milestone) { "/#{milestone.project.full_path}/-/milestones/#{milestone.iid}" }
:project_snippet | ->(snippet) { "/#{snippet.project.full_path}/-/snippets/#{snippet.id}" }
@@ -59,7 +60,8 @@ RSpec.describe Gitlab::UrlBuilder do
:discussion_note_on_project_snippet | ->(note) { "/#{note.project.full_path}/-/snippets/#{note.noteable_id}#note_#{note.id}" }
:discussion_note_on_personal_snippet | ->(note) { "/-/snippets/#{note.noteable_id}#note_#{note.id}" }
:note_on_personal_snippet | ->(note) { "/-/snippets/#{note.noteable_id}#note_#{note.id}" }
- :package | ->(package) { "/#{package.project.full_path}/-/packages/#{package.id}" }
+ :note_on_abuse_report | ->(note) { "/admin/abuse_reports/#{note.noteable_id}#note_#{note.id}" }
+ :package | ->(package) { "/#{package.project.full_path}/-/packages/#{package.id}" }
end
with_them do
diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb
index 6695736e54c..51d3090c825 100644
--- a/spec/lib/gitlab/usage/metric_definition_spec.rb
+++ b/spec/lib/gitlab/usage/metric_definition_spec.rb
@@ -40,13 +40,10 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping
File.write(path, content)
end
- after do
- # Reset memoized `definitions` result
- described_class.instance_variable_set(:@definitions, nil)
- end
-
- it 'has all definitons valid' do
- expect { described_class.definitions }.not_to raise_error
+ it 'has only valid definitions' do
+ described_class.all.each do |definition|
+ expect { definition.validate! }.not_to raise_error
+ end
end
describe 'not_removed' do
@@ -126,11 +123,13 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping
context 'with data_source redis metric' do
before do
attributes[:data_source] = 'redis'
- attributes[:options] = { prefix: 'web_ide', event: 'views_count', include_usage_prefix: false }
+ attributes[:events] = [
+ { name: 'web_ide_viewed' }
+ ]
end
- it 'returns a ServicePingContext with redis key as event_name' do
- expect(subject.to_h[:data][:event_name]).to eq('WEB_IDE_VIEWS_COUNT')
+ it 'returns a ServicePingContext with first event as event_name' do
+ expect(subject.to_h[:data][:event_name]).to eq('web_ide_viewed')
end
end
@@ -182,20 +181,6 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping
described_class.new(path, attributes).validate!
end
-
- context 'with skip_validation' do
- it 'raise exception if skip_validation: false' do
- expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::MetricDefinition::InvalidError))
-
- described_class.new(path, attributes.merge( { skip_validation: false } )).validate!
- end
-
- it 'does not raise exception if has skip_validation: true' do
- expect(Gitlab::ErrorTracking).not_to receive(:track_and_raise_for_dev_exception)
-
- described_class.new(path, attributes.merge( { skip_validation: true } )).validate!
- end
- end
end
context 'conditional validations' do
@@ -358,71 +343,4 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping
is_expected.to eq([attributes, other_attributes].map(&:deep_stringify_keys).to_yaml)
end
end
-
- describe '.metric_definitions_changed?', :freeze_time do
- let(:metric1) { Dir.mktmpdir('metric1') }
- let(:metric2) { Dir.mktmpdir('metric2') }
-
- before do
- allow(Rails).to receive_message_chain(:env, :development?).and_return(is_dev)
- allow(described_class).to receive(:paths).and_return(
- [
- File.join(metric1, '**', '*.yml'),
- File.join(metric2, '**', '*.yml')
- ]
- )
-
- write_metric(metric1, path, yaml_content)
- write_metric(metric2, path, yaml_content)
- end
-
- after do
- FileUtils.rm_rf(metric1)
- FileUtils.rm_rf(metric2)
- end
-
- context 'in development', :freeze_time do
- let(:is_dev) { true }
-
- it 'has changes on the first invocation' do
- expect(described_class.metric_definitions_changed?).to be_truthy
- end
-
- context 'when no files are changed' do
- it 'does not have changes on the second invocation' do
- described_class.metric_definitions_changed?
-
- expect(described_class.metric_definitions_changed?).to be_falsy
- end
- end
-
- context 'when file is changed' do
- it 'has changes on the next invocation when more than 3 seconds have passed' do
- described_class.metric_definitions_changed?
-
- write_metric(metric1, path, yaml_content)
- travel_to 10.seconds.from_now
-
- expect(described_class.metric_definitions_changed?).to be_truthy
- end
-
- it 'does not have changes on the next invocation when less than 3 seconds have passed' do
- described_class.metric_definitions_changed?
-
- write_metric(metric1, path, yaml_content)
- travel_to 1.second.from_now
-
- expect(described_class.metric_definitions_changed?).to be_falsy
- end
- end
-
- context 'in production' do
- let(:is_dev) { false }
-
- it 'does not detect changes' do
- expect(described_class.metric_definitions_changed?).to be_falsy
- end
- end
- end
- end
end
diff --git a/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb b/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb
index 59b944ac398..18a97447f1c 100644
--- a/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb
@@ -88,10 +88,12 @@ RSpec.describe Gitlab::Usage::Metrics::Aggregates::Sources::PostgresHll, :clean_
describe '.save_aggregated_metrics' do
subject(:save_aggregated_metrics) do
- described_class.save_aggregated_metrics(metric_name: metric_1,
- time_period: time_period,
- recorded_at_timestamp: recorded_at,
- data: data)
+ described_class.save_aggregated_metrics(
+ metric_name: metric_1,
+ time_period: time_period,
+ recorded_at_timestamp: recorded_at,
+ data: data
+ )
end
context 'with compatible data argument' do
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/container_registry_db_enabled_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/container_registry_db_enabled_metric_spec.rb
new file mode 100644
index 00000000000..605764cd7f8
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/container_registry_db_enabled_metric_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::ContainerRegistryDbEnabledMetric, feature_category: :service_ping do
+ let(:expected_value) { Gitlab::CurrentSettings.container_registry_db_enabled }
+
+ it_behaves_like 'a correct instrumented metric value', { time_frame: 'none' }
+end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_internal_pipelines_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_internal_pipelines_metric_spec.rb
index 77c49d448d7..2b6e17f615c 100644
--- a/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_internal_pipelines_metric_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_internal_pipelines_metric_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountCiInternalPipelinesMetric,
-feature_category: :service_ping do
+ feature_category: :service_ping do
let_it_be(:ci_pipeline_1) { create(:ci_pipeline, source: :external, created_at: 3.days.ago) }
let_it_be(:ci_pipeline_2) { create(:ci_pipeline, source: :push, created_at: 3.days.ago) }
let_it_be(:old_pipeline) { create(:ci_pipeline, source: :push, created_at: 2.months.ago) }
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_csv_imports_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_csv_imports_metric_spec.rb
new file mode 100644
index 00000000000..2b481563ecd
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_csv_imports_metric_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountCsvImportsMetric, feature_category: :service_ping do
+ let_it_be(:user) { create(:user) }
+
+ let_it_be(:old_import) { create(:issue_csv_import, user: user, created_at: 2.months.ago) }
+ let_it_be(:new_import) { create(:issue_csv_import, user: user, created_at: 21.days.ago) }
+
+ context 'with all time frame' do
+ let(:expected_value) { 2 }
+ let(:expected_query) do
+ %q{SELECT COUNT("csv_issue_imports"."id") FROM "csv_issue_imports"}
+ end
+
+ it_behaves_like 'a correct instrumented metric value and query', time_frame: 'all'
+ end
+
+ context 'for 28d time frame' do
+ let(:expected_value) { 1 }
+ let(:start) { 30.days.ago.to_fs(:db) }
+ let(:finish) { 2.days.ago.to_fs(:db) }
+ let(:expected_query) do
+ "SELECT COUNT(\"csv_issue_imports\".\"id\") FROM \"csv_issue_imports\" " \
+ "WHERE \"csv_issue_imports\".\"created_at\" " \
+ "BETWEEN '#{start}' AND '#{finish}'"
+ end
+
+ it_behaves_like 'a correct instrumented metric value and query', time_frame: '28d'
+ end
+end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_issues_created_manually_from_alerts_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_issues_created_manually_from_alerts_metric_spec.rb
index 65e514bf345..56b847257a5 100644
--- a/spec/lib/gitlab/usage/metrics/instrumentations/count_issues_created_manually_from_alerts_metric_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_issues_created_manually_from_alerts_metric_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountIssuesCreatedManuallyFromAlertsMetric,
-feature_category: :service_ping do
+ feature_category: :service_ping do
let_it_be(:issue) { create(:issue) }
let_it_be(:issue_with_alert) { create(:issue, :with_alert) }
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_jira_imports_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_jira_imports_metric_spec.rb
new file mode 100644
index 00000000000..9a51c3cc408
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_jira_imports_metric_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountJiraImportsMetric, feature_category: :service_ping do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, creator_id: user.id) }
+
+ let_it_be(:old_import) { create(:jira_import_state, :finished, project: project, created_at: 2.months.ago) }
+ let_it_be(:new_import) { create(:jira_import_state, :finished, project: project, created_at: 21.days.ago) }
+
+ context 'with all time frame' do
+ let(:expected_value) { 2 }
+ let(:expected_query) do
+ %q{SELECT COUNT("jira_imports"."id") FROM "jira_imports"}
+ end
+
+ it_behaves_like 'a correct instrumented metric value and query', time_frame: 'all'
+ end
+
+ context 'for 28d time frame' do
+ let(:expected_value) { 1 }
+ let(:start) { 30.days.ago.to_fs(:db) }
+ let(:finish) { 2.days.ago.to_fs(:db) }
+ let(:expected_query) do
+ "SELECT COUNT(\"jira_imports\".\"id\") FROM \"jira_imports\" WHERE \"jira_imports\".\"created_at\" " \
+ "BETWEEN '#{start}' AND '#{finish}'"
+ end
+
+ it_behaves_like 'a correct instrumented metric value and query', time_frame: '28d'
+ end
+end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_packages_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_packages_metric_spec.rb
new file mode 100644
index 00000000000..9a2e5c27c1d
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_packages_metric_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountPackagesMetric, feature_category: :service_ping do
+ before_all do
+ create :package, created_at: 2.months.ago
+ create :package, created_at: 21.days.ago
+ create :package, created_at: 7.days.ago
+ end
+
+ context "with all time frame" do
+ let(:expected_value) { 3 }
+ let(:expected_query) do
+ 'SELECT COUNT("packages_packages"."id") FROM "packages_packages"'
+ end
+
+ it_behaves_like 'a correct instrumented metric value and query', { time_frame: 'all' }
+ end
+
+ context "with 28d time frame" do
+ let(:expected_value) { 2 }
+ let(:start) { 30.days.ago.to_fs(:db) }
+ let(:finish) { 2.days.ago.to_fs(:db) }
+ let(:expected_query) do
+ 'SELECT COUNT("packages_packages"."id") FROM "packages_packages" ' \
+ 'WHERE "packages_packages"."created_at" ' \
+ "BETWEEN '#{start}' AND '#{finish}'"
+ end
+
+ it_behaves_like 'a correct instrumented metric value and query', { time_frame: '28d' }
+ end
+end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_metric_spec.rb
new file mode 100644
index 00000000000..28185fb9df4
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_metric_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountProjectsMetric, feature_category: :service_ping do
+ before_all do
+ create :project, created_at: 2.months.ago
+ create :project, created_at: 21.days.ago
+ create :project, created_at: 7.days.ago
+ end
+
+ context "with all time frame" do
+ let(:expected_value) { 3 }
+ let(:expected_query) do
+ 'SELECT COUNT("projects"."id") FROM "projects"'
+ end
+
+ it_behaves_like 'a correct instrumented metric value and query', { time_frame: 'all' }
+ end
+
+ context "with 28d time frame" do
+ let(:expected_value) { 2 }
+ let(:start) { 30.days.ago.to_fs(:db) }
+ let(:finish) { 2.days.ago.to_fs(:db) }
+ let(:expected_query) do
+ 'SELECT COUNT("projects"."id") FROM "projects" ' \
+ 'WHERE "projects"."created_at" ' \
+ "BETWEEN '#{start}' AND '#{finish}'"
+ end
+
+ it_behaves_like 'a correct instrumented metric value and query', { time_frame: '28d' }
+ end
+end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_cta_clicked_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_cta_clicked_metric_spec.rb
index cb94da11d58..91ad81c4291 100644
--- a/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_cta_clicked_metric_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_cta_clicked_metric_spec.rb
@@ -9,10 +9,10 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::InProductMarketingEmail
let(:options) { { track: 'verify', series: 0 } }
let(:expected_value) { 2 }
let(:expected_query) do
- 'SELECT COUNT("in_product_marketing_emails"."id") FROM "in_product_marketing_emails"' \
- ' WHERE "in_product_marketing_emails"."cta_clicked_at" IS NOT NULL' \
- ' AND "in_product_marketing_emails"."series" = 0'\
- ' AND "in_product_marketing_emails"."track" = 1'
+ 'SELECT COUNT("in_product_marketing_emails"."id") FROM "in_product_marketing_emails" ' \
+ 'WHERE "in_product_marketing_emails"."cta_clicked_at" IS NOT NULL ' \
+ 'AND "in_product_marketing_emails"."series" = 0 ' \
+ 'AND "in_product_marketing_emails"."track" = 1'
end
before do
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_sent_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_sent_metric_spec.rb
index 0cc82773d56..3c51368f396 100644
--- a/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_sent_metric_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_sent_metric_spec.rb
@@ -8,9 +8,9 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::InProductMarketingEmail
let(:email_attributes) { { track: 'verify', series: 0 } }
let(:expected_value) { 2 }
let(:expected_query) do
- 'SELECT COUNT("in_product_marketing_emails"."id") FROM "in_product_marketing_emails"' \
- ' WHERE "in_product_marketing_emails"."series" = 0'\
- ' AND "in_product_marketing_emails"."track" = 1'
+ 'SELECT COUNT("in_product_marketing_emails"."id") FROM "in_product_marketing_emails" ' \
+ 'WHERE "in_product_marketing_emails"."series" = 0 ' \
+ 'AND "in_product_marketing_emails"."track" = 1'
end
before do
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/incoming_email_encrypted_secrets_enabled_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/incoming_email_encrypted_secrets_enabled_metric_spec.rb
index b1b193c8d04..ad1f231a12d 100644
--- a/spec/lib/gitlab/usage/metrics/instrumentations/incoming_email_encrypted_secrets_enabled_metric_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/incoming_email_encrypted_secrets_enabled_metric_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Usage::Metrics::Instrumentations::IncomingEmailEncryptedSecretsEnabledMetric,
-feature_category: :service_ping do
+ feature_category: :service_ping do
it_behaves_like 'a correct instrumented metric value', { time_frame: 'none', data_source: 'ruby' } do
let(:expected_value) { ::Gitlab::Email::IncomingEmail.encrypted_secrets.active? }
end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/service_desk_email_encrypted_secrets_enabled_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/service_desk_email_encrypted_secrets_enabled_metric_spec.rb
index ea239e53d01..dae7f17a3b6 100644
--- a/spec/lib/gitlab/usage/metrics/instrumentations/service_desk_email_encrypted_secrets_enabled_metric_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/service_desk_email_encrypted_secrets_enabled_metric_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Usage::Metrics::Instrumentations::ServiceDeskEmailEncryptedSecretsEnabledMetric,
-feature_category: :service_ping do
+ feature_category: :service_ping do
it_behaves_like 'a correct instrumented metric value', { time_frame: 'none', data_source: 'ruby' } do
let(:expected_value) { ::Gitlab::Email::ServiceDeskEmail.encrypted_secrets.active? }
end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/total_count_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/total_count_metric_spec.rb
new file mode 100644
index 00000000000..f3aa1ba4f88
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/total_count_metric_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::TotalCountMetric, :clean_gitlab_redis_shared_state,
+ feature_category: :product_analytics_data_management do
+ before do
+ allow(Gitlab::InternalEvents::EventDefinitions).to receive(:known_event?).and_return(true)
+ end
+
+ context 'with multiple similar events' do
+ let(:expected_value) { 10 }
+
+ before do
+ 10.times do
+ Gitlab::InternalEvents.track_event('my_event')
+ end
+ end
+
+ it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', events: [{ name: 'my_event' }] }
+ end
+
+ context 'with multiple different events' do
+ let(:expected_value) { 2 }
+
+ before do
+ Gitlab::InternalEvents.track_event('my_event1')
+ Gitlab::InternalEvents.track_event('my_event2')
+ end
+
+ it_behaves_like 'a correct instrumented metric value',
+ { time_frame: 'all', events: [{ name: 'my_event1' }, { name: 'my_event2' }] }
+ end
+
+ describe '.redis_key' do
+ it 'adds the key prefix to the event name' do
+ expect(described_class.redis_key('my_event')).to eq('{event_counters}_my_event')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage/metrics/query_spec.rb b/spec/lib/gitlab/usage/metrics/query_spec.rb
index 750d340551a..418bbf322d0 100644
--- a/spec/lib/gitlab/usage/metrics/query_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/query_spec.rb
@@ -75,9 +75,9 @@ RSpec.describe Gitlab::Usage::Metrics::Query do
describe '.histogram' do
it 'returns the histogram sql' do
- expect(described_class.for(:histogram, AlertManagement::HttpIntegration.active,
- :project_id, buckets: 1..2, bucket_size: 101))
- .to match(/^WITH "count_cte" AS MATERIALIZED/)
+ expect(described_class.for(
+ :histogram, AlertManagement::HttpIntegration.active, :project_id, buckets: 1..2, bucket_size: 101
+ )).to match(/^WITH "count_cte" AS MATERIALIZED/)
end
end
diff --git a/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb
index eeef9406841..2c9506dd498 100644
--- a/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb
@@ -17,24 +17,24 @@ RSpec.describe Gitlab::UsageDataCounters::CiTemplateUniqueCounter, feature_categ
described_class.ci_template_event_name(expanded_template_name, config_source)
end
- it "has an event defined for template" do
+ it 'has an event defined for template' do
expect do
subject
end.not_to raise_error
end
- it "tracks template" do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).to(receive(:track_event)).with(template_name, values: project.id)
+ it 'tracks template' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter)
+ .to receive(:track_event).with(template_name, values: project.id).once
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter)
+ .to receive(:track_event).with('ci_template_included', values: project.id).once
subject
end
- it_behaves_like 'Snowplow event tracking with RedisHLL context' do
- let(:category) { described_class.to_s }
- let(:action) { 'ci_templates_unique' }
+ it_behaves_like 'internal event tracking' do
+ let(:event) { 'ci_template_included' }
let(:namespace) { project.namespace }
- let(:label) { 'redis_hll_counters.ci_templates.ci_templates_total_unique_counts_monthly' }
- let(:context) { [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: template_name).to_context] }
end
end
diff --git a/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb
index ab92b59c845..71e9e7a8e7d 100644
--- a/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
end
context 'for web IDE edit actions' do
- let(:action) { described_class::EDIT_BY_WEB_IDE }
+ let(:event) { described_class::EDIT_BY_WEB_IDE }
it_behaves_like 'tracks and counts action' do
def track_action(params)
@@ -49,7 +49,7 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
end
context 'for SFE edit actions' do
- let(:action) { described_class::EDIT_BY_SFE }
+ let(:event) { described_class::EDIT_BY_SFE }
it_behaves_like 'tracks and counts action' do
def track_action(params)
@@ -63,7 +63,7 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
end
context 'for snippet editor edit actions' do
- let(:action) { described_class::EDIT_BY_SNIPPET_EDITOR }
+ let(:event) { described_class::EDIT_BY_SNIPPET_EDITOR }
it_behaves_like 'tracks and counts action' do
def track_action(params)
diff --git a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
index 21a820deaa4..2c2bdbeb3e6 100644
--- a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue title edit actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_TITLE_CHANGED }
+ let(:event) { described_class::ISSUE_TITLE_CHANGED }
subject(:track_event) { described_class.track_issue_title_changed_action(author: user, project: project) }
end
@@ -21,7 +21,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue description edit actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_DESCRIPTION_CHANGED }
+ let(:event) { described_class::ISSUE_DESCRIPTION_CHANGED }
subject(:track_event) { described_class.track_issue_description_changed_action(author: user, project: project) }
end
@@ -29,7 +29,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue assignee edit actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_ASSIGNEE_CHANGED }
+ let(:event) { described_class::ISSUE_ASSIGNEE_CHANGED }
subject(:track_event) { described_class.track_issue_assignee_changed_action(author: user, project: project) }
end
@@ -37,7 +37,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue make confidential actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_MADE_CONFIDENTIAL }
+ let(:event) { described_class::ISSUE_MADE_CONFIDENTIAL }
subject(:track_event) { described_class.track_issue_made_confidential_action(author: user, project: project) }
end
@@ -45,7 +45,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue make visible actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_MADE_VISIBLE }
+ let(:event) { described_class::ISSUE_MADE_VISIBLE }
subject(:track_event) { described_class.track_issue_made_visible_action(author: user, project: project) }
end
@@ -53,7 +53,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue created actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_CREATED }
+ let(:event) { described_class::ISSUE_CREATED }
let(:project) { nil }
subject(:track_event) { described_class.track_issue_created_action(author: user, namespace: namespace) }
@@ -62,7 +62,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue closed actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_CLOSED }
+ let(:event) { described_class::ISSUE_CLOSED }
subject(:track_event) { described_class.track_issue_closed_action(author: user, project: project) }
end
@@ -70,7 +70,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue reopened actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_REOPENED }
+ let(:event) { described_class::ISSUE_REOPENED }
subject(:track_event) { described_class.track_issue_reopened_action(author: user, project: project) }
end
@@ -78,7 +78,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue label changed actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_LABEL_CHANGED }
+ let(:event) { described_class::ISSUE_LABEL_CHANGED }
subject(:track_event) { described_class.track_issue_label_changed_action(author: user, project: project) }
end
@@ -86,7 +86,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue label milestone actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_MILESTONE_CHANGED }
+ let(:event) { described_class::ISSUE_MILESTONE_CHANGED }
subject(:track_event) { described_class.track_issue_milestone_changed_action(author: user, project: project) }
end
@@ -94,7 +94,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue cross-referenced actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_CROSS_REFERENCED }
+ let(:event) { described_class::ISSUE_CROSS_REFERENCED }
subject(:track_event) { described_class.track_issue_cross_referenced_action(author: user, project: project) }
end
@@ -102,7 +102,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue moved actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_MOVED }
+ let(:event) { described_class::ISSUE_MOVED }
subject(:track_event) { described_class.track_issue_moved_action(author: user, project: project) }
end
@@ -110,7 +110,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue cloned actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_CLONED }
+ let(:event) { described_class::ISSUE_CLONED }
subject(:track_event) { described_class.track_issue_cloned_action(author: user, project: project) }
end
@@ -118,7 +118,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue relate actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_RELATED }
+ let(:event) { described_class::ISSUE_RELATED }
subject(:track_event) { described_class.track_issue_related_action(author: user, project: project) }
end
@@ -126,7 +126,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue unrelate actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_UNRELATED }
+ let(:event) { described_class::ISSUE_UNRELATED }
subject(:track_event) { described_class.track_issue_unrelated_action(author: user, project: project) }
end
@@ -134,7 +134,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue marked as duplicate actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_MARKED_AS_DUPLICATE }
+ let(:event) { described_class::ISSUE_MARKED_AS_DUPLICATE }
subject(:track_event) { described_class.track_issue_marked_as_duplicate_action(author: user, project: project) }
end
@@ -142,7 +142,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue locked actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_LOCKED }
+ let(:event) { described_class::ISSUE_LOCKED }
subject(:track_event) { described_class.track_issue_locked_action(author: user, project: project) }
end
@@ -150,7 +150,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue unlocked actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_UNLOCKED }
+ let(:event) { described_class::ISSUE_UNLOCKED }
subject(:track_event) { described_class.track_issue_unlocked_action(author: user, project: project) }
end
@@ -158,7 +158,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue designs added actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_DESIGNS_ADDED }
+ let(:event) { described_class::ISSUE_DESIGNS_ADDED }
subject(:track_event) { described_class.track_issue_designs_added_action(author: user, project: project) }
end
@@ -166,7 +166,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue designs modified actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_DESIGNS_MODIFIED }
+ let(:event) { described_class::ISSUE_DESIGNS_MODIFIED }
subject(:track_event) { described_class.track_issue_designs_modified_action(author: user, project: project) }
end
@@ -174,7 +174,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue designs removed actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_DESIGNS_REMOVED }
+ let(:event) { described_class::ISSUE_DESIGNS_REMOVED }
subject(:track_event) { described_class.track_issue_designs_removed_action(author: user, project: project) }
end
@@ -182,7 +182,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue due date changed actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_DUE_DATE_CHANGED }
+ let(:event) { described_class::ISSUE_DUE_DATE_CHANGED }
subject(:track_event) { described_class.track_issue_due_date_changed_action(author: user, project: project) }
end
@@ -190,7 +190,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue time estimate changed actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_TIME_ESTIMATE_CHANGED }
+ let(:event) { described_class::ISSUE_TIME_ESTIMATE_CHANGED }
subject(:track_event) { described_class.track_issue_time_estimate_changed_action(author: user, project: project) }
end
@@ -198,7 +198,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue time spent changed actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_TIME_SPENT_CHANGED }
+ let(:event) { described_class::ISSUE_TIME_SPENT_CHANGED }
subject(:track_event) { described_class.track_issue_time_spent_changed_action(author: user, project: project) }
end
@@ -206,7 +206,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue comment added actions', :snowplow do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_COMMENT_ADDED }
+ let(:event) { described_class::ISSUE_COMMENT_ADDED }
subject(:track_event) { described_class.track_issue_comment_added_action(author: user, project: project) }
end
@@ -214,7 +214,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue comment edited actions', :snowplow do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_COMMENT_EDITED }
+ let(:event) { described_class::ISSUE_COMMENT_EDITED }
subject(:track_event) { described_class.track_issue_comment_edited_action(author: user, project: project) }
end
@@ -222,7 +222,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue comment removed actions', :snowplow do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_COMMENT_REMOVED }
+ let(:event) { described_class::ISSUE_COMMENT_REMOVED }
subject(:track_event) { described_class.track_issue_comment_removed_action(author: user, project: project) }
end
@@ -230,7 +230,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
context 'for Issue design comment removed actions' do
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::ISSUE_DESIGN_COMMENT_REMOVED }
+ let(:event) { described_class::ISSUE_DESIGN_COMMENT_REMOVED }
subject(:track_event) { described_class.track_issue_design_comment_removed_action(author: user, project: project) }
end
diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
index 53eee62b386..c3a718e669a 100644
--- a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
@@ -64,7 +64,7 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl
end
it_behaves_like 'internal event tracking' do
- let(:action) { described_class::MR_USER_CREATE_ACTION }
+ let(:event) { described_class::MR_USER_CREATE_ACTION }
let(:project) { target_project }
let(:namespace) { project.namespace }
end
diff --git a/spec/lib/gitlab/usage_data_queries_spec.rb b/spec/lib/gitlab/usage_data_queries_spec.rb
index 3ec7bf33623..6d30947167c 100644
--- a/spec/lib/gitlab/usage_data_queries_spec.rb
+++ b/spec/lib/gitlab/usage_data_queries_spec.rb
@@ -72,17 +72,18 @@ RSpec.describe Gitlab::UsageDataQueries do
describe '.add' do
it 'returns the combined raw SQL with an inner query' do
- expect(described_class.add('SELECT COUNT("users"."id") FROM "users"',
- 'SELECT COUNT("issues"."id") FROM "issues"'))
- .to eq('SELECT (SELECT COUNT("users"."id") FROM "users") + (SELECT COUNT("issues"."id") FROM "issues")')
+ expect(described_class.add(
+ 'SELECT COUNT("users"."id") FROM "users"',
+ 'SELECT COUNT("issues"."id") FROM "issues"'
+ )).to eq('SELECT (SELECT COUNT("users"."id") FROM "users") + (SELECT COUNT("issues"."id") FROM "issues")')
end
end
describe '.histogram' do
it 'returns the histogram sql' do
- expect(described_class.histogram(AlertManagement::HttpIntegration.active,
- :project_id, buckets: 1..2, bucket_size: 101))
- .to match(/^WITH "count_cte" AS MATERIALIZED/)
+ expect(described_class.histogram(
+ AlertManagement::HttpIntegration.active, :project_id, buckets: 1..2, bucket_size: 101
+ )).to match(/^WITH "count_cte" AS MATERIALIZED/)
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 143d0484392..6f188aa408e 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -17,7 +17,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
it 'includes basic top and second level keys' do
is_expected.to include(:counts)
- is_expected.to include(:counts_monthly)
is_expected.to include(:counts_weekly)
is_expected.to include(:license)
@@ -152,8 +151,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
it 'includes accurate usage_activity_by_stage data' do
for_defined_days_back do
user = create(:user)
- project = create(:project, :repository_private,
- :test_repo, :remote_mirror, creator: user)
+ project = create(:project, :repository_private, :test_repo, :remote_mirror, creator: user)
create(:merge_request, source_project: project)
create(:deploy_key, user: user)
create(:key, user: user)
@@ -293,22 +291,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
bulk_imports: {
gitlab_v1: 2
},
- project_imports: {
- bitbucket: 2,
- bitbucket_server: 2,
- git: 2,
- gitea: 2,
- github: 2,
- gitlab_migration: 2,
- gitlab_project: 2,
- manifest: 2,
- total: 16
- },
- issue_imports: {
- jira: 2,
- fogbugz: 2,
- csv: 2
- },
group_imports: {
group_import: 2,
gitlab_migration: 2
@@ -320,22 +302,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
bulk_imports: {
gitlab_v1: 1
},
- project_imports: {
- bitbucket: 1,
- bitbucket_server: 1,
- git: 1,
- gitea: 1,
- github: 1,
- gitlab_migration: 1,
- gitlab_project: 1,
- manifest: 1,
- total: 8
- },
- issue_imports: {
- jira: 1,
- fogbugz: 1,
- csv: 1
- },
group_imports: {
group_import: 1,
gitlab_migration: 1
@@ -623,28 +589,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
end
end
- describe '.system_usage_data_monthly' do
- let_it_be(:project) { create(:project, created_at: 3.days.ago) }
-
- before do
- create(:package, project: project, created_at: 3.days.ago)
- create(:package, created_at: 2.months.ago, project: project)
-
- for_defined_days_back do
- create(:product_analytics_event, project: project, se_category: 'epics', se_action: 'promote')
- end
- end
-
- subject { described_class.system_usage_data_monthly }
-
- it 'gathers monthly usage counts correctly' do
- counts_monthly = subject[:counts_monthly]
-
- expect(counts_monthly[:projects]).to eq(1)
- expect(counts_monthly[:packages]).to eq(1)
- end
- end
-
context 'when not relying on database records' do
describe '.features_usage_data_ce' do
subject { described_class.features_usage_data_ce }
@@ -885,8 +829,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic
it 'gathers Service Desk data' do
create_list(:issue, 2, :confidential, author: Users::Internal.support_bot, project: project)
- expect(subject).to eq(service_desk_enabled_projects: 1,
- service_desk_issues: 2)
+ expect(subject).to eq(service_desk_enabled_projects: 1, service_desk_issues: 2)
end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 9bc1ebaebcb..cca18cb05c7 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -371,50 +371,13 @@ RSpec.describe Gitlab::Workhorse, feature_category: :shared do
subject(:cleanup_key) { described_class.cleanup_key(key) }
- shared_examples 'cleans up key' do |redis = Gitlab::Redis::Workhorse|
- before do
- described_class.set_key_and_notify(key, value)
- end
-
- it 'deletes the key' do
- expect { cleanup_key }
- .to change { redis.with { |c| c.exists?(key) } }.from(true).to(false)
- end
+ before do
+ described_class.set_key_and_notify(key, value)
end
- it_behaves_like 'cleans up key'
-
- context 'when workhorse migration feature flags are disabled' do
- before do
- stub_feature_flags(
- use_primary_and_secondary_stores_for_workhorse: false,
- use_primary_store_as_default_for_workhorse: false
- )
- end
-
- it_behaves_like 'cleans up key', Gitlab::Redis::SharedState
- end
-
- context 'when either workhorse migration feature flags are enabled' do
- context 'when use_primary_and_secondary_stores_for_workhorse is enabled' do
- before do
- stub_feature_flags(
- use_primary_store_as_default_for_workhorse: false
- )
- end
-
- it_behaves_like 'cleans up key'
- end
-
- context 'when use_primary_store_as_default_for_workhorse is enabled' do
- before do
- stub_feature_flags(
- use_primary_and_secondary_stores_for_workhorse: false
- )
- end
-
- it_behaves_like 'cleans up key'
- end
+ it 'deletes the key' do
+ expect { cleanup_key }
+ .to change { Gitlab::Redis::Workhorse.with { |c| c.exists?(key) } }.from(true).to(false)
end
end
@@ -424,13 +387,13 @@ RSpec.describe Gitlab::Workhorse, feature_category: :shared do
subject { described_class.set_key_and_notify(key, value, overwrite: overwrite) }
- shared_examples 'set and notify' do |redis = Gitlab::Redis::Workhorse|
+ shared_examples 'set and notify' do
it 'set and return the same value' do
is_expected.to eq(value)
end
it 'set and notify' do
- expect(redis).to receive(:with).and_call_original
+ expect(Gitlab::Redis::Workhorse).to receive(:with).and_call_original
expect_any_instance_of(::Redis).to receive(:publish)
.with(described_class::NOTIFICATION_PREFIX + 'test-key', "test-value")
@@ -442,39 +405,6 @@ RSpec.describe Gitlab::Workhorse, feature_category: :shared do
let(:overwrite) { true }
it_behaves_like 'set and notify'
-
- context 'when workhorse migration feature flags are disabled' do
- before do
- stub_feature_flags(
- use_primary_and_secondary_stores_for_workhorse: false,
- use_primary_store_as_default_for_workhorse: false
- )
- end
-
- it_behaves_like 'set and notify', Gitlab::Redis::SharedState
- end
-
- context 'when either workhorse migration feature flags are enabled' do
- context 'when use_primary_and_secondary_stores_for_workhorse is enabled' do
- before do
- stub_feature_flags(
- use_primary_store_as_default_for_workhorse: false
- )
- end
-
- it_behaves_like 'set and notify'
- end
-
- context 'when use_primary_store_as_default_for_workhorse is enabled' do
- before do
- stub_feature_flags(
- use_primary_and_secondary_stores_for_workhorse: false
- )
- end
-
- it_behaves_like 'set and notify'
- end
- end
end
context 'when we set an existing key' do