summaryrefslogtreecommitdiff
path: root/spec/lib
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-07-20 15:40:28 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-20 15:40:28 +0000
commitb595cb0c1dec83de5bdee18284abe86614bed33b (patch)
tree8c3d4540f193c5ff98019352f554e921b3a41a72 /spec/lib
parent2f9104a328fc8a4bddeaa4627b595166d24671d0 (diff)
downloadgitlab-ce-b595cb0c1dec83de5bdee18284abe86614bed33b.tar.gz
Add latest changes from gitlab-org/gitlab@15-2-stable-eev15.2.0-rc42
Diffstat (limited to 'spec/lib')
-rw-r--r--spec/lib/api/ci/helpers/runner_spec.rb26
-rw-r--r--spec/lib/api/entities/ci/job_request/service_spec.rb51
-rw-r--r--spec/lib/api/entities/deploy_key_spec.rb9
-rw-r--r--spec/lib/api/helpers/pagination_strategies_spec.rb15
-rw-r--r--spec/lib/atlassian/jira_connect/jwt/asymmetric_spec.rb3
-rw-r--r--spec/lib/backup/gitaly_backup_spec.rb4
-rw-r--r--spec/lib/banzai/filter/footnote_filter_spec.rb13
-rw-r--r--spec/lib/banzai/reference_parser/snippet_parser_spec.rb2
-rw-r--r--spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb65
-rw-r--r--spec/lib/bulk_imports/pipeline/runner_spec.rb155
-rw-r--r--spec/lib/bulk_imports/retry_pipeline_error_spec.rb13
-rw-r--r--spec/lib/container_registry/gitlab_api_client_spec.rb11
-rw-r--r--spec/lib/error_tracking/collector/dsn_spec.rb34
-rw-r--r--spec/lib/error_tracking/stacktrace_builder_spec.rb29
-rw-r--r--spec/lib/feature_spec.rb10
-rw-r--r--spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb23
-rw-r--r--spec/lib/generators/model/mocks/migration_file.txt26
-rw-r--r--spec/lib/generators/model/mocks/model_file.txt2
-rw-r--r--spec/lib/generators/model/mocks/spec_file.txt5
-rw-r--r--spec/lib/generators/model/model_generator_spec.rb47
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb6
-rw-r--r--spec/lib/gitlab/application_rate_limiter/base_strategy_spec.rb17
-rw-r--r--spec/lib/gitlab/application_rate_limiter/increment_per_action_spec.rb51
-rw-r--r--spec/lib/gitlab/application_rate_limiter/increment_per_actioned_resource_spec.rb52
-rw-r--r--spec/lib/gitlab/application_rate_limiter_spec.rb60
-rw-r--r--spec/lib/gitlab/auth/ldap/user_spec.rb18
-rw-r--r--spec/lib/gitlab/auth_spec.rb11
-rw-r--r--spec/lib/gitlab/background_migration/backfill_ci_runner_semver_spec.rb54
-rw-r--r--spec/lib/gitlab/background_migration/backfill_imported_issue_search_data_spec.rb106
-rw-r--r--spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb5
-rw-r--r--spec/lib/gitlab/background_migration/batched_migration_job_spec.rb64
-rw-r--r--spec/lib/gitlab/background_migration/batching_strategies/backfill_project_statistics_with_container_registry_size_batching_strategy_spec.rb138
-rw-r--r--spec/lib/gitlab/background_migration/batching_strategies/dismissed_vulnerabilities_strategy_spec.rb119
-rw-r--r--spec/lib/gitlab/background_migration/batching_strategies/loose_index_scan_batching_strategy_spec.rb67
-rw-r--r--spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb22
-rw-r--r--spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_inactive_public_projects_spec.rb81
-rw-r--r--spec/lib/gitlab/background_migration/populate_operation_visibility_permissions_from_operations_spec.rb80
-rw-r--r--spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb30
-rw-r--r--spec/lib/gitlab/background_migration/set_correct_vulnerability_state_spec.rb81
-rw-r--r--spec/lib/gitlab/background_migration/update_delayed_project_removal_to_null_for_user_namespaces_spec.rb49
-rw-r--r--spec/lib/gitlab/bare_repository_import/importer_spec.rb9
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb11
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb9
-rw-r--r--spec/lib/gitlab/changelog/config_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/build/duration_parser_spec.rb (renamed from spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb)2
-rw-r--r--spec/lib/gitlab/ci/build/image_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb64
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/config/external/context_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/external/file/project_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/jwt_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/create_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/reports/coverage_report_generator_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/reports/test_report_spec.rb (renamed from spec/lib/gitlab/ci/reports/test_reports_spec.rb)2
-rw-r--r--spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/runner/metrics_spec.rb41
-rw-r--r--spec/lib/gitlab/ci/runner_releases_spec.rb196
-rw-r--r--spec/lib/gitlab/ci/runner_upgrade_check_spec.rb170
-rw-r--r--spec/lib/gitlab/ci/status/stage/factory_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/status/stage/play_manual_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/tags/bulk_insert_spec.rb70
-rw-r--r--spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/variables/builder_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/yaml_processor/feature_flags_spec.rb80
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb94
-rw-r--r--spec/lib/gitlab/content_security_policy/config_loader_spec.rb13
-rw-r--r--spec/lib/gitlab/data_builder/deployment_spec.rb10
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb11
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_job_spec.rb6
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb63
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_spec.rb49
-rw-r--r--spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb61
-rw-r--r--spec/lib/gitlab/database/background_migration/health_status_spec.rb77
-rw-r--r--spec/lib/gitlab/database/each_database_spec.rb27
-rw-r--r--spec/lib/gitlab/database/gitlab_schema_spec.rb19
-rw-r--r--spec/lib/gitlab/database/loose_foreign_keys_spec.rb13
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb29
-rw-r--r--spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb24
-rw-r--r--spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb2
-rw-r--r--spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb4
-rw-r--r--spec/lib/gitlab/database/postgres_autovacuum_activity_spec.rb32
-rw-r--r--spec/lib/gitlab/database/reindexing_spec.rb2
-rw-r--r--spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb8
-rw-r--r--spec/lib/gitlab/database_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/base_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb8
-rw-r--r--spec/lib/gitlab/diff/formatters/image_formatter_spec.rb1
-rw-r--r--spec/lib/gitlab/diff/formatters/text_formatter_spec.rb1
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb19
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb80
-rw-r--r--spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb113
-rw-r--r--spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb138
-rw-r--r--spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb6
-rw-r--r--spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb6
-rw-r--r--spec/lib/gitlab/elasticsearch/logs/lines_spec.rb97
-rw-r--r--spec/lib/gitlab/elasticsearch/logs/pods_spec.rb35
-rw-r--r--spec/lib/gitlab/email/handler/service_desk_handler_spec.rb18
-rw-r--r--spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb1
-rw-r--r--spec/lib/gitlab/email/message/in_product_marketing/experience_spec.rb115
-rw-r--r--spec/lib/gitlab/email/message/in_product_marketing_spec.rb1
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb48
-rw-r--r--spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb436
-rw-r--r--spec/lib/gitlab/error_tracking/processor/sanitizer_processor_spec.rb114
-rw-r--r--spec/lib/gitlab/error_tracking_spec.rb20
-rw-r--r--spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/git/attributes_parser_spec.rb14
-rw-r--r--spec/lib/gitlab/git/blame_spec.rb16
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb25
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb75
-rw-r--r--spec/lib/gitlab/git/conflict/parser_spec.rb79
-rw-r--r--spec/lib/gitlab/git/object_pool_spec.rb12
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb266
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb56
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb3
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb17
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_service_spec.rb60
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb58
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb61
-rw-r--r--spec/lib/gitlab/github_import/importer/events/closed_spec.rb72
-rw-r--r--spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb96
-rw-r--r--spec/lib/gitlab/github_import/importer/events/renamed_spec.rb68
-rw-r--r--spec/lib/gitlab/github_import/importer/events/reopened_spec.rb56
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb112
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb128
-rw-r--r--spec/lib/gitlab/github_import/markdown_text_spec.rb48
-rw-r--r--spec/lib/gitlab/github_import/representation/issue_event_spec.rb156
-rw-r--r--spec/lib/gitlab/github_import/single_endpoint_notes_importing_spec.rb29
-rw-r--r--spec/lib/gitlab/gitlab_import/importer_spec.rb4
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb261
-rw-r--r--spec/lib/gitlab/grape_logging/loggers/response_logger_spec.rb31
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/connection_generic_keyset_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb6
-rw-r--r--spec/lib/gitlab/harbor/client_spec.rb269
-rw-r--r--spec/lib/gitlab/harbor/query_spec.rb375
-rw-r--r--spec/lib/gitlab/hash_digest/facade_spec.rb36
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb52
-rw-r--r--spec/lib/gitlab/http_connection_adapter_spec.rb12
-rw-r--r--spec/lib/gitlab/http_spec.rb56
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml6
-rw-r--r--spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb23
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb81
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml2
-rw-r--r--spec/lib/gitlab/issuable/clone/attributes_rewriter_spec.rb93
-rw-r--r--spec/lib/gitlab/issuable/clone/copy_resource_events_service_spec.rb91
-rw-r--r--spec/lib/gitlab/jira_import/issue_serializer_spec.rb7
-rw-r--r--spec/lib/gitlab/jira_import/issues_importer_spec.rb19
-rw-r--r--spec/lib/gitlab/lograge/custom_options_spec.rb17
-rw-r--r--spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb2
-rw-r--r--spec/lib/gitlab/memory/watchdog_spec.rb308
-rw-r--r--spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb47
-rw-r--r--spec/lib/gitlab/metrics/memory_spec.rb40
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/sli_spec.rb8
-rw-r--r--spec/lib/gitlab/pages/cache_control_spec.rb42
-rw-r--r--spec/lib/gitlab/pages/deployment_update_spec.rb140
-rw-r--r--spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb16
-rw-r--r--spec/lib/gitlab/pagination/keyset/order_spec.rb24
-rw-r--r--spec/lib/gitlab/quick_actions/users_extractor_spec.rb93
-rw-r--r--spec/lib/gitlab/redis/multi_store_spec.rb22
-rw-r--r--spec/lib/gitlab/regex_spec.rb14
-rw-r--r--spec/lib/gitlab/security/scan_configuration_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb77
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb10
-rw-r--r--spec/lib/gitlab/tracking/destinations/snowplow_micro_spec.rb79
-rw-r--r--spec/lib/gitlab/tracking/destinations/snowplow_spec.rb2
-rw-r--r--spec/lib/gitlab/tracking/incident_management_spec.rb2
-rw-r--r--spec/lib/gitlab/tracking/standard_context_spec.rb27
-rw-r--r--spec/lib/gitlab/tracking_spec.rb59
-rw-r--r--spec/lib/gitlab/tree_summary_spec.rb146
-rw-r--r--spec/lib/gitlab/usage/metric_definition_spec.rb4
-rw-r--r--spec/lib/gitlab/usage/metric_spec.rb2
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/unique_active_users_metric_spec.rb31
-rw-r--r--spec/lib/gitlab/usage/service_ping/instrumented_payload_spec.rb50
-rw-r--r--spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb30
-rw-r--r--spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb107
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb34
-rw-r--r--spec/lib/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb40
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb71
-rw-r--r--spec/lib/gitlab/user_access_spec.rb34
-rw-r--r--spec/lib/gitlab/version_info_spec.rb159
-rw-r--r--spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb29
-rw-r--r--spec/lib/gitlab/x509/certificate_spec.rb62
-rw-r--r--spec/lib/gitlab/x509/commit_spec.rb41
-rw-r--r--spec/lib/gitlab/x509/signature_spec.rb2
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb38
-rw-r--r--spec/lib/initializer_connections_spec.rb63
-rw-r--r--spec/lib/json_web_token/rsa_token_spec.rb2
-rw-r--r--spec/lib/learn_gitlab/onboarding_spec.rb35
-rw-r--r--spec/lib/sidebars/projects/menus/monitor_menu_spec.rb28
-rw-r--r--spec/lib/sidebars/projects/menus/settings_menu_spec.rb19
-rw-r--r--spec/lib/unnested_in_filters/dsl_spec.rb31
-rw-r--r--spec/lib/unnested_in_filters/rewriter_spec.rb157
206 files changed, 7510 insertions, 2157 deletions
diff --git a/spec/lib/api/ci/helpers/runner_spec.rb b/spec/lib/api/ci/helpers/runner_spec.rb
index 37277e7dcbd..6801d16d13e 100644
--- a/spec/lib/api/ci/helpers/runner_spec.rb
+++ b/spec/lib/api/ci/helpers/runner_spec.rb
@@ -66,4 +66,30 @@ RSpec.describe API::Ci::Helpers::Runner do
expect(helper.current_runner).to eq(runner)
end
end
+
+ describe '#track_runner_authentication', :prometheus do
+ subject { helper.track_runner_authentication }
+
+ let(:runner) { create(:ci_runner, token: 'foo') }
+
+ it 'increments gitlab_ci_runner_authentication_success_total' do
+ allow(helper).to receive(:params).and_return(token: runner.token)
+
+ success_counter = ::Gitlab::Ci::Runner::Metrics.runner_authentication_success_counter
+ failure_counter = ::Gitlab::Ci::Runner::Metrics.runner_authentication_failure_counter
+ expect { subject }.to change { success_counter.get(runner_type: 'instance_type') }.by(1)
+ .and not_change { success_counter.get(runner_type: 'project_type') }
+ .and not_change { failure_counter.get }
+ end
+
+ it 'increments gitlab_ci_runner_authentication_failure_total' do
+ allow(helper).to receive(:params).and_return(token: 'invalid')
+
+ success_counter = ::Gitlab::Ci::Runner::Metrics.runner_authentication_success_counter
+ failure_counter = ::Gitlab::Ci::Runner::Metrics.runner_authentication_failure_counter
+ expect { subject }.to change { failure_counter.get }.by(1)
+ .and not_change { success_counter.get(runner_type: 'instance_type') }
+ .and not_change { success_counter.get(runner_type: 'project_type') }
+ end
+ end
end
diff --git a/spec/lib/api/entities/ci/job_request/service_spec.rb b/spec/lib/api/entities/ci/job_request/service_spec.rb
new file mode 100644
index 00000000000..47c2c4e04c9
--- /dev/null
+++ b/spec/lib/api/entities/ci/job_request/service_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Ci::JobRequest::Service do
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'name' }]}
+ let(:service) do
+ instance_double(
+ ::Gitlab::Ci::Build::Image,
+ name: 'image_name',
+ entrypoint: ['foo'],
+ ports: ports,
+ pull_policy: ['if-not-present'],
+ alias: 'alias',
+ command: 'command',
+ variables: [{ key: 'key', value: 'value' }]
+ )
+ end
+
+ let(:entity) { described_class.new(service) }
+
+ subject(:result) { entity.as_json }
+
+ it 'exposes attributes' do
+ expect(result).to eq(
+ name: 'image_name',
+ entrypoint: ['foo'],
+ ports: ports,
+ pull_policy: ['if-not-present'],
+ alias: 'alias',
+ command: 'command',
+ variables: [{ key: 'key', value: 'value' }]
+ )
+ end
+
+ context 'when the ports param is nil' do
+ let(:ports) { nil }
+
+ it 'does not return the ports' do
+ expect(subject[:ports]).to be_nil
+ end
+ end
+
+ context 'when the FF ci_docker_image_pull_policy is disabled' do
+ before do
+ stub_feature_flags(ci_docker_image_pull_policy: false)
+ end
+
+ it { is_expected.not_to have_key(:pull_policy) }
+ end
+end
diff --git a/spec/lib/api/entities/deploy_key_spec.rb b/spec/lib/api/entities/deploy_key_spec.rb
index 6427d6eac8f..50a27418488 100644
--- a/spec/lib/api/entities/deploy_key_spec.rb
+++ b/spec/lib/api/entities/deploy_key_spec.rb
@@ -15,8 +15,15 @@ RSpec.describe API::Entities::DeployKey do
title: deploy_key.title,
created_at: deploy_key.created_at,
expires_at: deploy_key.expires_at,
- key: deploy_key.key
+ key: deploy_key.key,
+ fingerprint_sha256: deploy_key.fingerprint_sha256
)
+
+ is_expected.to include(fingerprint: deploy_key.fingerprint) unless Gitlab::FIPS.enabled?
+ end
+
+ context 'when in FIPS mode', :fips_mode do
+ it { is_expected.not_to have_key(:fingerprint) }
end
end
end
diff --git a/spec/lib/api/helpers/pagination_strategies_spec.rb b/spec/lib/api/helpers/pagination_strategies_spec.rb
index e8a4243b407..16cc10182b0 100644
--- a/spec/lib/api/helpers/pagination_strategies_spec.rb
+++ b/spec/lib/api/helpers/pagination_strategies_spec.rb
@@ -55,9 +55,10 @@ RSpec.describe API::Helpers::PaginationStrategies do
allow(subject).to receive(:keyset_pagination_enabled?).and_return(false)
end
- context 'when keyset pagination is available for the relation' do
+ context 'when keyset pagination is available and enforced for the relation' do
before do
allow(Gitlab::Pagination::Keyset).to receive(:available_for_type?).and_return(true)
+ allow(Gitlab::Pagination::CursorBasedKeyset).to receive(:enforced_for_type?).and_return(true)
end
context 'when a request scope is given' do
@@ -70,6 +71,18 @@ RSpec.describe API::Helpers::PaginationStrategies do
subject.paginator(relation, request_scope)
end
+
+ context 'when keyset pagination is not enforced' do
+ before do
+ allow(Gitlab::Pagination::CursorBasedKeyset).to receive(:enforced_for_type?).and_return(false)
+ end
+
+ it 'returns no errors' do
+ expect(subject).not_to receive(:error!)
+
+ subject.paginator(relation, request_scope)
+ end
+ end
end
context 'when the scope limit is not exceeded' do
diff --git a/spec/lib/atlassian/jira_connect/jwt/asymmetric_spec.rb b/spec/lib/atlassian/jira_connect/jwt/asymmetric_spec.rb
index 12ed47a1025..b3157dd15fb 100644
--- a/spec/lib/atlassian/jira_connect/jwt/asymmetric_spec.rb
+++ b/spec/lib/atlassian/jira_connect/jwt/asymmetric_spec.rb
@@ -4,6 +4,8 @@ require 'spec_helper'
RSpec.describe Atlassian::JiraConnect::Jwt::Asymmetric do
describe '#valid?' do
+ let_it_be(:private_key) { OpenSSL::PKey::RSA.generate 3072 }
+
subject(:asymmetric_jwt) { described_class.new(jwt, verification_claims) }
let(:verification_claims) { jwt_claims }
@@ -12,7 +14,6 @@ RSpec.describe Atlassian::JiraConnect::Jwt::Asymmetric do
let(:client_key) { '1234' }
let(:public_key_id) { '123e4567-e89b-12d3-a456-426614174000' }
let(:jwt_headers) { { kid: public_key_id } }
- let(:private_key) { OpenSSL::PKey::RSA.generate 2048 }
let(:jwt) { JWT.encode(jwt_claims, private_key, 'RS256', jwt_headers) }
let(:public_key) { private_key.public_key }
let(:install_keys_url) { "https://connect-install-keys.atlassian.com/#{public_key_id}" }
diff --git a/spec/lib/backup/gitaly_backup_spec.rb b/spec/lib/backup/gitaly_backup_spec.rb
index ab198fcbe1f..3a9c4dfe3fb 100644
--- a/spec/lib/backup/gitaly_backup_spec.rb
+++ b/spec/lib/backup/gitaly_backup_spec.rb
@@ -16,8 +16,8 @@ RSpec.describe Backup::GitalyBackup do
let(:expected_env) do
{
- 'SSL_CERT_FILE' => OpenSSL::X509::DEFAULT_CERT_FILE,
- 'SSL_CERT_DIR' => OpenSSL::X509::DEFAULT_CERT_DIR
+ 'SSL_CERT_FILE' => Gitlab::X509::Certificate.default_cert_file,
+ 'SSL_CERT_DIR' => Gitlab::X509::Certificate.default_cert_dir
}.merge(ENV)
end
diff --git a/spec/lib/banzai/filter/footnote_filter_spec.rb b/spec/lib/banzai/filter/footnote_filter_spec.rb
index 5ac7d3af733..26bca571fdc 100644
--- a/spec/lib/banzai/filter/footnote_filter_spec.rb
+++ b/spec/lib/banzai/filter/footnote_filter_spec.rb
@@ -56,6 +56,19 @@ RSpec.describe Banzai::Filter::FootnoteFilter do
it 'properly adds the necessary ids and classes' do
expect(doc.to_html).to eq filtered_footnote.strip
end
+
+ context 'when GITLAB_TEST_FOOTNOTE_ID is set' do
+ let(:test_footnote_id) { '42' }
+ let(:identifier) { test_footnote_id }
+
+ before do
+ stub_env('GITLAB_TEST_FOOTNOTE_ID', test_footnote_id)
+ end
+
+ it 'uses the test footnote ID instead of a random number' do
+ expect(doc.to_html).to eq filtered_footnote.strip
+ end
+ end
end
context 'when detecting footnotes' do
diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
index 3459784708f..e8ef4e7f6e3 100644
--- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe Banzai::ReferenceParser::SnippetParser do
end
before do
- project.add_user(project_member, :developer)
+ project.add_member(project_member, :developer)
end
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb b/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb
index c42ca9bef3b..d775cf6b026 100644
--- a/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb
+++ b/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb
@@ -4,12 +4,12 @@ require 'spec_helper'
RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
describe '#transform' do
- let_it_be(:user) { create(:user) }
let_it_be(:parent) { create(:group) }
- let_it_be(:bulk_import) { create(:bulk_import, user: user) }
- let_it_be(:entity) do
- create(
+ let(:bulk_import) { build_stubbed(:bulk_import) }
+
+ let(:entity) do
+ build_stubbed(
:bulk_import_entity,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
@@ -18,8 +18,8 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
)
end
- let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
- let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
+ let(:tracker) { build_stubbed(:bulk_import_tracker, entity: entity) }
+ let(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:data) do
{
@@ -87,14 +87,63 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
end
context 'when destination namespace is empty' do
- it 'does not set parent id' do
- entity.update!(destination_namespace: '')
+ before do
+ entity.destination_namespace = ''
+ end
+ it 'does not set parent id' do
transformed_data = subject.transform(context, data)
expect(transformed_data).not_to have_key('parent_id')
end
end
end
+
+ describe 'group name transformation' do
+ context 'when destination namespace is empty' do
+ before do
+ entity.destination_namespace = ''
+ end
+
+ it 'does not transform name' do
+ transformed_data = subject.transform(context, data)
+
+ expect(transformed_data['name']).to eq('Source Group Name')
+ end
+ end
+
+ context 'when destination namespace is present' do
+ context 'when destination namespace does not have a group with same name' do
+ it 'does not transform name' do
+ transformed_data = subject.transform(context, data)
+
+ expect(transformed_data['name']).to eq('Source Group Name')
+ end
+ end
+
+ context 'when destination namespace already have a group with the same name' do
+ before do
+ create(:group, parent: parent, name: 'Source Group Name', path: 'group_1')
+ create(:group, parent: parent, name: 'Source Group Name(1)', path: 'group_2')
+ create(:group, parent: parent, name: 'Source Group Name(2)', path: 'group_3')
+ create(:group, parent: parent, name: 'Source Group Name(1)(1)', path: 'group_4')
+ end
+
+ it 'makes the name unique by appeding a counter', :aggregate_failures do
+ transformed_data = subject.transform(context, data.merge('name' => 'Source Group Name'))
+ expect(transformed_data['name']).to eq('Source Group Name(3)')
+
+ transformed_data = subject.transform(context, data.merge('name' => 'Source Group Name(2)'))
+ expect(transformed_data['name']).to eq('Source Group Name(2)(1)')
+
+ transformed_data = subject.transform(context, data.merge('name' => 'Source Group Name(1)'))
+ expect(transformed_data['name']).to eq('Source Group Name(1)(2)')
+
+ transformed_data = subject.transform(context, data.merge('name' => 'Source Group Name(1)(1)'))
+ expect(transformed_data['name']).to eq('Source Group Name(1)(1)(1)')
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/bulk_imports/pipeline/runner_spec.rb b/spec/lib/bulk_imports/pipeline/runner_spec.rb
index 7235b7c95cd..810271818ae 100644
--- a/spec/lib/bulk_imports/pipeline/runner_spec.rb
+++ b/spec/lib/bulk_imports/pipeline/runner_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe BulkImports::Pipeline::Runner do
Class.new do
def initialize(options = {}); end
- def transform(context); end
+ def transform(context, data); end
end
end
@@ -23,7 +23,7 @@ RSpec.describe BulkImports::Pipeline::Runner do
Class.new do
def initialize(options = {}); end
- def load(context); end
+ def load(context, data); end
end
end
@@ -44,11 +44,73 @@ RSpec.describe BulkImports::Pipeline::Runner do
end
let_it_be_with_reload(:entity) { create(:bulk_import_entity) }
- let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
- let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker, extra: :data) }
+
+ let(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let(:context) { BulkImports::Pipeline::Context.new(tracker, extra: :data) }
subject { BulkImports::MyPipeline.new(context) }
+ shared_examples 'failed pipeline' do |exception_class, exception_message|
+ it 'logs import failure' do
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger).to receive(:error)
+ .with(
+ log_params(
+ context,
+ pipeline_step: :extractor,
+ pipeline_class: 'BulkImports::MyPipeline',
+ exception_class: exception_class,
+ exception_message: exception_message
+ )
+ )
+ end
+
+ expect { subject.run }
+ .to change(entity.failures, :count).by(1)
+
+ failure = entity.failures.first
+
+ expect(failure).to be_present
+ expect(failure.pipeline_class).to eq('BulkImports::MyPipeline')
+ expect(failure.pipeline_step).to eq('extractor')
+ expect(failure.exception_class).to eq(exception_class)
+ expect(failure.exception_message).to eq(exception_message)
+ end
+
+ context 'when pipeline is marked to abort on failure' do
+ before do
+ BulkImports::MyPipeline.abort_on_failure!
+ end
+
+ it 'logs a warn message and marks entity and tracker as failed' do
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger).to receive(:warn)
+ .with(
+ log_params(
+ context,
+ message: 'Aborting entity migration due to pipeline failure',
+ pipeline_class: 'BulkImports::MyPipeline'
+ )
+ )
+ end
+
+ subject.run
+
+ expect(entity.failed?).to eq(true)
+ expect(tracker.failed?).to eq(true)
+ end
+ end
+
+ context 'when pipeline is not marked to abort on failure' do
+ it 'does not mark entity as failed' do
+ subject.run
+
+ expect(tracker.failed?).to eq(true)
+ expect(entity.failed?).to eq(false)
+ end
+ end
+ end
+
describe 'pipeline runner' do
context 'when entity is not marked as failed' do
it 'runs pipeline extractor, transformer, loader' do
@@ -145,70 +207,65 @@ RSpec.describe BulkImports::Pipeline::Runner do
end
end
- context 'when exception is raised' do
+ context 'when the exception BulkImports::NetworkError is raised' do
before do
allow_next_instance_of(BulkImports::Extractor) do |extractor|
- allow(extractor).to receive(:extract).with(context).and_raise(StandardError, 'Error!')
+ allow(extractor).to receive(:extract).with(context).and_raise(
+ BulkImports::NetworkError.new(
+ 'Net::ReadTimeout',
+ response: instance_double(HTTParty::Response, code: reponse_status_code, headers: {})
+ )
+ )
end
end
- it 'logs import failure' do
- expect_next_instance_of(Gitlab::Import::Logger) do |logger|
- expect(logger).to receive(:error)
- .with(
- log_params(
- context,
- pipeline_step: :extractor,
- pipeline_class: 'BulkImports::MyPipeline',
- exception_class: 'StandardError',
- exception_message: 'Error!'
- )
- )
- end
+ context 'when exception is retriable' do
+ let(:reponse_status_code) { 429 }
- expect { subject.run }
- .to change(entity.failures, :count).by(1)
+ it 'raises the exception BulkImports::RetryPipelineError' do
+ expect { subject.run }.to raise_error(BulkImports::RetryPipelineError)
+ end
+ end
- failure = entity.failures.first
+ context 'when exception is not retriable' do
+ let(:reponse_status_code) { 503 }
- expect(failure).to be_present
- expect(failure.pipeline_class).to eq('BulkImports::MyPipeline')
- expect(failure.pipeline_step).to eq('extractor')
- expect(failure.exception_class).to eq('StandardError')
- expect(failure.exception_message).to eq('Error!')
+ it_behaves_like 'failed pipeline', 'BulkImports::NetworkError', 'Net::ReadTimeout'
end
+ end
- context 'when pipeline is marked to abort on failure' do
- before do
- BulkImports::MyPipeline.abort_on_failure!
- end
-
- it 'logs a warn message and marks entity as failed' do
- expect_next_instance_of(Gitlab::Import::Logger) do |logger|
- expect(logger).to receive(:warn)
- .with(
- log_params(
- context,
- message: 'Pipeline failed',
- pipeline_class: 'BulkImports::MyPipeline'
+ context 'when a retriable BulkImports::NetworkError exception is raised while extracting the next page' do
+ before do
+ call_count = 0
+ allow_next_instance_of(BulkImports::Extractor) do |extractor|
+ allow(extractor).to receive(:extract).with(context).twice do
+ if call_count.zero?
+ call_count += 1
+ extracted_data(has_next_page: true)
+ else
+ raise(
+ BulkImports::NetworkError.new(
+ response: instance_double(HTTParty::Response, code: 429, headers: {})
)
)
+ end
end
-
- subject.run
-
- expect(entity.status_name).to eq(:failed)
- expect(tracker.status_name).to eq(:failed)
end
end
- context 'when pipeline is not marked to abort on failure' do
- it 'does not mark entity as failed' do
- subject.run
+ it 'raises the exception BulkImports::RetryPipelineError' do
+ expect { subject.run }.to raise_error(BulkImports::RetryPipelineError)
+ end
+ end
- expect(entity.failed?).to eq(false)
+ context 'when the exception StandardError is raised' do
+ before do
+ allow_next_instance_of(BulkImports::Extractor) do |extractor|
+ allow(extractor).to receive(:extract).with(context).and_raise(StandardError, 'Error!')
end
end
+
+ it_behaves_like 'failed pipeline', 'StandardError', 'Error!'
end
end
diff --git a/spec/lib/bulk_imports/retry_pipeline_error_spec.rb b/spec/lib/bulk_imports/retry_pipeline_error_spec.rb
new file mode 100644
index 00000000000..9d96407b03a
--- /dev/null
+++ b/spec/lib/bulk_imports/retry_pipeline_error_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::RetryPipelineError do
+ describe '#retry_delay' do
+ it 'returns retry_delay' do
+ exception = described_class.new('Error!', 60)
+
+ expect(exception.retry_delay).to eq(60)
+ end
+ end
+end
diff --git a/spec/lib/container_registry/gitlab_api_client_spec.rb b/spec/lib/container_registry/gitlab_api_client_spec.rb
index 16d2c42f332..f2c627734a3 100644
--- a/spec/lib/container_registry/gitlab_api_client_spec.rb
+++ b/spec/lib/container_registry/gitlab_api_client_spec.rb
@@ -316,6 +316,17 @@ RSpec.describe ContainerRegistry::GitlabApiClient do
it { is_expected.to eq(nil) }
end
+
+ context 'with uppercase path' do
+ let(:path) { 'foo/Bar' }
+
+ before do
+ expect(Auth::ContainerRegistryAuthenticationService).to receive(:pull_nested_repositories_access_token).with(path.downcase).and_return(token)
+ stub_repository_details(path, sizing: :self_with_descendants, status_code: 200, respond_with: response)
+ end
+
+ it { is_expected.to eq(555) }
+ end
end
def stub_pre_import(path, status_code, pre:)
diff --git a/spec/lib/error_tracking/collector/dsn_spec.rb b/spec/lib/error_tracking/collector/dsn_spec.rb
deleted file mode 100644
index 3aa8719fe38..00000000000
--- a/spec/lib/error_tracking/collector/dsn_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe ErrorTracking::Collector::Dsn do
- describe '.build_url' do
- let(:setting) do
- {
- protocol: 'https',
- https: true,
- port: 443,
- host: 'gitlab.example.com',
- relative_url_root: nil
- }
- end
-
- subject { described_class.build_url('abcdef1234567890', 778) }
-
- it 'returns a valid URL without explicit port' do
- stub_config_setting(setting)
-
- is_expected.to eq('https://abcdef1234567890@gitlab.example.com/api/v4/error_tracking/collector/778')
- end
-
- context 'with non-standard port' do
- it 'returns a valid URL with custom port' do
- setting[:port] = 4567
- stub_config_setting(setting)
-
- is_expected.to eq('https://abcdef1234567890@gitlab.example.com:4567/api/v4/error_tracking/collector/778')
- end
- end
- end
-end
diff --git a/spec/lib/error_tracking/stacktrace_builder_spec.rb b/spec/lib/error_tracking/stacktrace_builder_spec.rb
index 46d0bde8122..57eead13fc0 100644
--- a/spec/lib/error_tracking/stacktrace_builder_spec.rb
+++ b/spec/lib/error_tracking/stacktrace_builder_spec.rb
@@ -56,6 +56,35 @@ RSpec.describe ErrorTracking::StacktraceBuilder do
end
end
+ context 'when exception payload is a list' do
+ let(:payload_file) { 'error_tracking/go_two_exception_event.json' }
+
+ it 'extracts a stracktrace' do
+ expected_entry = {
+ 'lineNo' => 54,
+ 'context' => [
+ [49, "\t// Set the timeout to the maximum duration the program can afford to wait."],
+ [50, "\tdefer sentry.Flush(2 * time.Second)"],
+ [51, ""],
+ [52, "\tresp, err := http.Get(os.Args[1])"],
+ [53, "\tif err != nil {"],
+ [54, "\t\tsentry.CaptureException(err)"],
+ [55, "\t\tlog.Printf(\"reported to Sentry: %s\", err)"],
+ [56, "\t\treturn"],
+ [57, "\t}"],
+ [58, "\tdefer resp.Body.Close()"],
+ [59, ""]
+ ],
+ 'filename' => nil,
+ 'function' => 'main',
+ 'colNo' => 0
+ }
+
+ expect(stacktrace).to be_kind_of(Array)
+ expect(stacktrace.first).to eq(expected_entry)
+ end
+ end
+
context 'with empty payload' do
let(:payload) { {} }
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 6e32db09426..4db3f04717b 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -512,6 +512,11 @@ RSpec.describe Feature, stub_feature_flags: false do
let(:expected_extra) { { "extra.thing" => "true" } }
end
+ # This is documented to return true, modify doc/administration/feature_flags.md if it changes
+ it 'returns true' do
+ expect(subject).to be true
+ end
+
context 'when thing is an actor' do
let(:thing) { create(:project) }
@@ -533,6 +538,11 @@ RSpec.describe Feature, stub_feature_flags: false do
let(:expected_extra) { { "extra.thing" => "false" } }
end
+ # This is documented to return true, modify doc/administration/feature_flags.md if it changes
+ it 'returns true' do
+ expect(subject).to be true
+ end
+
context 'when thing is an actor' do
let(:thing) { create(:project) }
diff --git a/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb b/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb
index 4cba9732c22..b6e1d59f6c0 100644
--- a/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb
+++ b/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb
@@ -38,6 +38,29 @@ RSpec.describe Gitlab::UsageMetricDefinition::RedisHllGenerator, :silence_stdout
expect(monthly_metric_definition["instrumentation_class"]).to eq('RedisHLLMetric')
end
+ context 'with multiple events', :aggregate_failures do
+ let(:event_2) { 'i_test_event_2' }
+ let(:args) { [category, event, event_2] }
+
+ it 'creates metric definition files' do
+ described_class.new(args).invoke_all
+
+ [event, event_2].each do |event|
+ weekly_metric_definition_path = Dir.glob(File.join(temp_dir, "metrics/counts_7d/*#{event}_weekly.yml")).first
+ monthly_metric_definition_path = Dir.glob(File.join(temp_dir, "metrics/counts_28d/*#{event}_monthly.yml")).first
+
+ weekly_metric_definition = YAML.safe_load(File.read(weekly_metric_definition_path))
+ monthly_metric_definition = YAML.safe_load(File.read(monthly_metric_definition_path))
+
+ expect(weekly_metric_definition).to include("key_path" => "redis_hll_counters.test_category.#{event}_weekly")
+ expect(monthly_metric_definition).to include("key_path" => "redis_hll_counters.test_category.#{event}_monthly")
+
+ expect(weekly_metric_definition["instrumentation_class"]).to eq('RedisHLLMetric')
+ expect(monthly_metric_definition["instrumentation_class"]).to eq('RedisHLLMetric')
+ end
+ end
+ end
+
context 'with ee option' do
let(:weekly_metric_definition_path) { Dir.glob(File.join(temp_dir, 'ee/config/metrics/counts_7d/*i_test_event_weekly.yml')).first }
let(:monthly_metric_definition_path) { Dir.glob(File.join(temp_dir, 'ee/config/metrics/counts_28d/*i_test_event_monthly.yml')).first }
diff --git a/spec/lib/generators/model/mocks/migration_file.txt b/spec/lib/generators/model/mocks/migration_file.txt
new file mode 100644
index 00000000000..e92c2d2b534
--- /dev/null
+++ b/spec/lib/generators/model/mocks/migration_file.txt
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+# See https://docs.gitlab.com/ee/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreateModelGeneratorTestFoos < Gitlab::Database::Migration[2.0]
+ # When using the methods "add_concurrent_index" or "remove_concurrent_index"
+ # you must disable the use of transactions
+ # as these methods can not run in an existing transaction.
+ # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure
+ # that either of them is the _only_ method called in the migration,
+ # any other changes should go in a separate migration.
+ # This ensures that upon failure _only_ the index creation or removing fails
+ # and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ # disable_ddl_transaction!
+
+ def change
+ create_table :model_generator_test_foos do |t|
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/spec/lib/generators/model/mocks/model_file.txt b/spec/lib/generators/model/mocks/model_file.txt
new file mode 100644
index 00000000000..066db4bfd76
--- /dev/null
+++ b/spec/lib/generators/model/mocks/model_file.txt
@@ -0,0 +1,2 @@
+class ModelGeneratorTestFoo < ApplicationRecord
+end
diff --git a/spec/lib/generators/model/mocks/spec_file.txt b/spec/lib/generators/model/mocks/spec_file.txt
new file mode 100644
index 00000000000..efd700df0a1
--- /dev/null
+++ b/spec/lib/generators/model/mocks/spec_file.txt
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe ModelGeneratorTestFoo, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/lib/generators/model/model_generator_spec.rb b/spec/lib/generators/model/model_generator_spec.rb
new file mode 100644
index 00000000000..0e770190d25
--- /dev/null
+++ b/spec/lib/generators/model/model_generator_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Model::ModelGenerator do
+ let(:args) { ['ModelGeneratorTestFoo'] }
+ let(:options) { { 'migration' => true, 'timestamps' => true, 'indexes' => true, 'test_framework' => :rspec } }
+ let(:temp_dir) { Dir.mktmpdir }
+ let(:migration_file_path) { Dir.glob(File.join(temp_dir, 'db/migrate/*create_model_generator_test_foos.rb')).first }
+ let(:model_file_path) { File.join(temp_dir, 'app/models/model_generator_test_foo.rb') }
+ let(:spec_file_path) { File.join(temp_dir, 'spec/models/model_generator_test_foo_spec.rb') }
+
+ subject { described_class.new(args, options, { destination_root: temp_dir }) }
+
+ context 'when generating a model' do
+ after do
+ FileUtils.rm_rf(temp_dir)
+ end
+
+ it 'creates the model file with the right content' do
+ subject.invoke_all
+
+ expect(File).to exist(model_file_path)
+ mock_model_file_content = File.read(File.expand_path('./mocks/model_file.txt', __dir__))
+ model_file_content = File.read(model_file_path)
+ expect(model_file_content).to eq(mock_model_file_content)
+ end
+
+ it 'creates the migration file with the right content' do
+ subject.invoke_all
+
+ expect(File).to exist(migration_file_path)
+ mock_migration_file_content = File.read(File.expand_path('./mocks/migration_file.txt', __dir__))
+ migration_file_content = File.read(migration_file_path)
+ expect(migration_file_content).to eq(mock_migration_file_content)
+ end
+
+ it 'creates the spec file with the right content' do
+ subject.invoke_all
+
+ expect(File).to exist(spec_file_path)
+ mock_spec_file_content = File.read(File.expand_path('./mocks/spec_file.txt', __dir__))
+ spec_file_content = File.read(spec_file_path)
+ expect(spec_file_content).to eq(mock_spec_file_content)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
index ec394bb9f05..34d5158a5ab 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
describe '#serialized_records' do
shared_context 'when records are loaded by maintainer' do
before do
- project.add_user(user, Gitlab::Access::DEVELOPER)
+ project.add_member(user, Gitlab::Access::DEVELOPER)
end
it 'returns all records' do
@@ -72,7 +72,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
context 'when records are loaded by guest' do
before do
- project.add_user(user, Gitlab::Access::GUEST)
+ project.add_member(user, Gitlab::Access::GUEST)
end
it 'filters out confidential issues' do
@@ -124,7 +124,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
end
before do
- project.add_user(user, Gitlab::Access::DEVELOPER)
+ project.add_member(user, Gitlab::Access::DEVELOPER)
stub_const('Gitlab::Analytics::CycleAnalytics::RecordsFetcher::MAX_RECORDS', 2)
end
diff --git a/spec/lib/gitlab/application_rate_limiter/base_strategy_spec.rb b/spec/lib/gitlab/application_rate_limiter/base_strategy_spec.rb
new file mode 100644
index 00000000000..b34ac538b24
--- /dev/null
+++ b/spec/lib/gitlab/application_rate_limiter/base_strategy_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ApplicationRateLimiter::BaseStrategy do
+ describe '#increment' do
+ it 'raises NotImplementedError' do
+ expect { subject.increment('cache_key', 0) }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#read' do
+ it 'raises NotImplementedError' do
+ expect { subject.read('cache_key') }.to raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/application_rate_limiter/increment_per_action_spec.rb b/spec/lib/gitlab/application_rate_limiter/increment_per_action_spec.rb
new file mode 100644
index 00000000000..b74d2360711
--- /dev/null
+++ b/spec/lib/gitlab/application_rate_limiter/increment_per_action_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ApplicationRateLimiter::IncrementPerAction, :freeze_time, :clean_gitlab_redis_rate_limiting do
+ let(:cache_key) { 'test' }
+ let(:expiry) { 60 }
+
+ subject(:counter) { described_class.new }
+
+ def increment
+ counter.increment(cache_key, expiry)
+ end
+
+ describe '#increment' do
+ it 'increments per call' do
+ expect(increment).to eq 1
+ expect(increment).to eq 2
+ expect(increment).to eq 3
+ end
+
+ it 'sets time to live (TTL) for the key' do
+ def ttl
+ Gitlab::Redis::RateLimiting.with { |r| r.ttl(cache_key) }
+ end
+
+ key_does_not_exist = -2
+
+ expect(ttl).to eq key_does_not_exist
+ expect { increment }.to change { ttl }.by(a_value > 0)
+ end
+ end
+
+ describe '#read' do
+ def read
+ counter.read(cache_key)
+ end
+
+ it 'returns 0 when there is no data' do
+ expect(read).to eq 0
+ end
+
+ it 'returns the correct value', :aggregate_failures do
+ increment
+ expect(read).to eq 1
+
+ increment
+ expect(read).to eq 2
+ end
+ end
+end
diff --git a/spec/lib/gitlab/application_rate_limiter/increment_per_actioned_resource_spec.rb b/spec/lib/gitlab/application_rate_limiter/increment_per_actioned_resource_spec.rb
new file mode 100644
index 00000000000..1f3ae2d749a
--- /dev/null
+++ b/spec/lib/gitlab/application_rate_limiter/increment_per_actioned_resource_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ApplicationRateLimiter::IncrementPerActionedResource,
+ :freeze_time, :clean_gitlab_redis_rate_limiting do
+ let(:cache_key) { 'test' }
+ let(:expiry) { 60 }
+
+ def increment(resource_key)
+ described_class.new(resource_key).increment(cache_key, expiry)
+ end
+
+ describe '#increment' do
+ it 'increments per resource', :aggregate_failures do
+ expect(increment('resource_1')).to eq(1)
+ expect(increment('resource_1')).to eq(1)
+ expect(increment('resource_2')).to eq(2)
+ expect(increment('resource_2')).to eq(2)
+ expect(increment('resource_3')).to eq(3)
+ end
+
+ it 'sets time to live (TTL) for the key' do
+ def ttl
+ Gitlab::Redis::RateLimiting.with { |r| r.ttl(cache_key) }
+ end
+
+ key_does_not_exist = -2
+
+ expect(ttl).to eq key_does_not_exist
+ expect { increment('resource_1') }.to change { ttl }.by(a_value > 0)
+ end
+ end
+
+ describe '#read' do
+ def read
+ described_class.new(nil).read(cache_key)
+ end
+
+ it 'returns 0 when there is no data' do
+ expect(read).to eq 0
+ end
+
+ it 'returns the correct value', :aggregate_failures do
+ increment 'r1'
+ expect(read).to eq 1
+
+ increment 'r2'
+ expect(read).to eq 2
+ end
+ end
+end
diff --git a/spec/lib/gitlab/application_rate_limiter_spec.rb b/spec/lib/gitlab/application_rate_limiter_spec.rb
index efe78cd3a35..177ce1134d8 100644
--- a/spec/lib/gitlab/application_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/application_rate_limiter_spec.rb
@@ -13,8 +13,8 @@ RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_rate_limiting
interval: 2.minutes
},
another_action: {
- threshold: 2,
- interval: 3.minutes
+ threshold: -> { 2 },
+ interval: -> { 3.minutes }
}
}
end
@@ -70,6 +70,44 @@ RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_rate_limiting
end
end
+ describe 'counting actions once per unique resource' do
+ let(:scope) { [user, project] }
+
+ let(:start_time) { Time.current.beginning_of_hour }
+ let(:project1) { instance_double(Project, id: '1') }
+ let(:project2) { instance_double(Project, id: '2') }
+
+ it 'returns true when unique actioned resources count exceeds threshold' do
+ travel_to(start_time) do
+ expect(subject.throttled?(:test_action, scope: scope, resource: project1)).to eq(false)
+ end
+
+ travel_to(start_time + 1.minute) do
+ expect(subject.throttled?(:test_action, scope: scope, resource: project2)).to eq(true)
+ end
+ end
+
+ it 'returns false when unique actioned resource count does not exceed threshold' do
+ travel_to(start_time) do
+ expect(subject.throttled?(:test_action, scope: scope, resource: project1)).to eq(false)
+ end
+
+ travel_to(start_time + 1.minute) do
+ expect(subject.throttled?(:test_action, scope: scope, resource: project1)).to eq(false)
+ end
+ end
+
+ it 'returns false when interval has elapsed' do
+ travel_to(start_time) do
+ expect(subject.throttled?(:test_action, scope: scope, resource: project1)).to eq(false)
+ end
+
+ travel_to(start_time + 2.minutes) do
+ expect(subject.throttled?(:test_action, scope: scope, resource: project2)).to eq(false)
+ end
+ end
+ end
+
shared_examples 'throttles based on key and scope' do
let(:start_time) { Time.current.beginning_of_hour }
@@ -91,7 +129,7 @@ RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_rate_limiting
travel_to(start_time) do
expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
- # another_action has a threshold of 3 so we simulate 2 requests
+ # another_action has a threshold of 2 so we simulate 2 requests
expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
end
@@ -189,4 +227,20 @@ RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_rate_limiting
end
end
end
+
+ context 'when interval is 0' do
+ let(:rate_limits) { { test_action: { threshold: 1, interval: 0 } } }
+ let(:scope) { user }
+ let(:start_time) { Time.current.beginning_of_hour }
+
+ it 'returns false' do
+ travel_to(start_time) do
+ expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
+ end
+
+ travel_to(start_time + 1.minute) do
+ expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/auth/ldap/user_spec.rb b/spec/lib/gitlab/auth/ldap/user_spec.rb
index da0bb5fe675..b471a89b491 100644
--- a/spec/lib/gitlab/auth/ldap/user_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/user_spec.rb
@@ -49,6 +49,24 @@ RSpec.describe Gitlab::Auth::Ldap::User do
end
end
+ describe '#valid_sign_in?' do
+ before do
+ gl_user.save!
+ end
+
+ it 'returns true' do
+ expect(Gitlab::Auth::Ldap::Access).to receive(:allowed?).and_return(true)
+ expect(ldap_user.valid_sign_in?).to be true
+ end
+
+ it 'returns false if the GitLab user is not valid' do
+ gl_user.update_column(:username, nil)
+
+ expect(Gitlab::Auth::Ldap::Access).not_to receive(:allowed?)
+ expect(ldap_user.valid_sign_in?).to be false
+ end
+ end
+
describe 'find or create' do
it "finds the user if already existing" do
create(:omniauth_user, extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain')
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index f5a74956174..1e869df0988 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -481,6 +481,17 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end
it_behaves_like 'with an invalid access token'
+
+ context 'when the token belongs to a group via project share' do
+ let_it_be(:invited_group) { create(:group) }
+
+ before do
+ invited_group.add_maintainer(project_bot_user)
+ create(:project_group_link, group: invited_group, project: project)
+ end
+
+ it_behaves_like 'with a valid access token'
+ end
end
end
end
diff --git a/spec/lib/gitlab/background_migration/backfill_ci_runner_semver_spec.rb b/spec/lib/gitlab/background_migration/backfill_ci_runner_semver_spec.rb
new file mode 100644
index 00000000000..7c78d8b0305
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_ci_runner_semver_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillCiRunnerSemver, :migration, schema: 20220601151900 do
+ let(:ci_runners) { table(:ci_runners, database: :ci) }
+
+ subject do
+ described_class.new(
+ start_id: 10,
+ end_id: 15,
+ batch_table: :ci_runners,
+ batch_column: :id,
+ sub_batch_size: 10,
+ pause_ms: 0,
+ connection: Ci::ApplicationRecord.connection)
+ end
+
+ describe '#perform' do
+ it 'populates semver column on all runners in range' do
+ ci_runners.create!(id: 10, runner_type: 1, version: %q(HEAD-fd84d97))
+ ci_runners.create!(id: 11, runner_type: 1, version: %q(v1.2.3))
+ ci_runners.create!(id: 12, runner_type: 1, version: %q(2.1.0))
+ ci_runners.create!(id: 13, runner_type: 1, version: %q(11.8.0~beta.935.g7f6d2abc))
+ ci_runners.create!(id: 14, runner_type: 1, version: %q(13.2.2/1.1.0))
+ ci_runners.create!(id: 15, runner_type: 1, version: %q('14.3.4'))
+
+ subject.perform
+
+ expect(ci_runners.all).to contain_exactly(
+ an_object_having_attributes(id: 10, semver: nil),
+ an_object_having_attributes(id: 11, semver: '1.2.3'),
+ an_object_having_attributes(id: 12, semver: '2.1.0'),
+ an_object_having_attributes(id: 13, semver: '11.8.0'),
+ an_object_having_attributes(id: 14, semver: '13.2.2'),
+ an_object_having_attributes(id: 15, semver: '14.3.4')
+ )
+ end
+
+ it 'skips runners that already have semver value' do
+ ci_runners.create!(id: 10, runner_type: 1, version: %q(1.2.4), semver: '1.2.3')
+ ci_runners.create!(id: 11, runner_type: 1, version: %q(1.2.5))
+ ci_runners.create!(id: 12, runner_type: 1, version: %q(HEAD), semver: '1.2.4')
+
+ subject.perform
+
+ expect(ci_runners.all).to contain_exactly(
+ an_object_having_attributes(id: 10, semver: '1.2.3'),
+ an_object_having_attributes(id: 11, semver: '1.2.5'),
+ an_object_having_attributes(id: 12, semver: '1.2.4')
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_imported_issue_search_data_spec.rb b/spec/lib/gitlab/background_migration/backfill_imported_issue_search_data_spec.rb
new file mode 100644
index 00000000000..e363a5a6b20
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_imported_issue_search_data_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillImportedIssueSearchData,
+ :migration,
+ schema: 20220707075300 do
+ let!(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
+ let!(:issue_search_data_table) { table(:issue_search_data) }
+
+ let!(:user) { table(:users).create!(email: 'author@example.com', username: 'author', projects_limit: 10) }
+ let!(:project) do
+ table(:projects)
+ .create!(
+ namespace_id: namespace.id,
+ creator_id: user.id,
+ name: 'projecty',
+ path: 'path',
+ project_namespace_id: namespace.id)
+ end
+
+ let!(:issue) do
+ table(:issues).create!(
+ project_id: project.id,
+ title: 'Patterson',
+ description: FFaker::HipsterIpsum.paragraph
+ )
+ end
+
+ let(:migration) do
+ described_class.new(start_id: issue.id,
+ end_id: issue.id + 30,
+ batch_table: :issues,
+ batch_column: :id,
+ sub_batch_size: 2,
+ pause_ms: 0,
+ connection: ApplicationRecord.connection)
+ end
+
+ let(:perform_migration) { migration.perform }
+
+ context 'when issue has search data record' do
+ let!(:issue_search_data) { issue_search_data_table.create!(project_id: project.id, issue_id: issue.id) }
+
+ it 'does not create or update any search data records' do
+ expect { perform_migration }
+ .to not_change { issue_search_data_table.count }
+ .and not_change { issue_search_data }
+
+ expect(issue_search_data_table.count).to eq(1)
+ end
+ end
+
+ context 'when issue has no search data record' do
+ let(:title_node) { "'#{issue.title.downcase}':1A" }
+
+ it 'creates search data records' do
+ expect { perform_migration }
+ .to change { issue_search_data_table.count }.from(0).to(1)
+
+ expect(issue_search_data_table.find_by(project_id: project.id).issue_id)
+ .to eq(issue.id)
+
+ expect(issue_search_data_table.find_by(project_id: project.id).search_vector)
+ .to include(title_node)
+ end
+ end
+
+ context 'error handling' do
+ let!(:issue2) do
+ table(:issues).create!(
+ project_id: project.id,
+ title: 'Chatterton',
+ description: FFaker::HipsterIpsum.paragraph
+ )
+ end
+
+ before do
+ issue.update!(description: Array.new(30_000) { SecureRandom.hex }.join(' '))
+ end
+
+ let(:title_node2) { "'#{issue2.title.downcase}':1A" }
+
+ it 'skips insertion for that issue but continues with migration' do
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |logger|
+ expect(logger)
+ .to receive(:error)
+ .with(a_hash_including(message: /string is too long for tsvector/, model_id: issue.id))
+ end
+
+ expect { perform_migration }.to change { issue_search_data_table.count }.from(0).to(1)
+ expect(issue_search_data_table.find_by(issue_id: issue.id)).to eq(nil)
+ expect(issue_search_data_table.find_by(issue_id: issue2.id).search_vector)
+ .to include(title_node2)
+ end
+
+ it 're-raises exceptions' do
+ allow(migration)
+ .to receive(:update_search_data_individually)
+ .and_raise(ActiveRecord::StatementTimeout)
+
+ expect { perform_migration }.to raise_error(ActiveRecord::StatementTimeout)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
index cfa03db52fe..b5122af5cd4 100644
--- a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
@@ -47,10 +47,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migrat
before do
allow(snippet_with_repo).to receive(:disk_path).and_return(disk_path(snippet_with_repo))
- TestEnv.copy_repo(snippet_with_repo,
- bare_repo: TestEnv.factory_repo_path_bare,
- refs: TestEnv::BRANCH_SHA)
-
+ raw_repository(snippet_with_repo).create_from_bundle(TestEnv.factory_repo_bundle_path)
raw_repository(snippet_with_empty_repo).create_repository
end
diff --git a/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb b/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb
index f8b3a8681f0..98866bb765f 100644
--- a/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb
+++ b/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb
@@ -92,5 +92,69 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do
end
end
end
+
+ context 'when the subclass uses distinct each batch' do
+ let(:job_instance) do
+ job_class.new(start_id: 1,
+ end_id: 100,
+ batch_table: '_test_table',
+ batch_column: 'from_column',
+ sub_batch_size: 2,
+ pause_ms: 10,
+ connection: connection)
+ end
+
+ let(:job_class) do
+ Class.new(described_class) do
+ def perform(*job_arguments)
+ distinct_each_batch(operation_name: :insert) do |sub_batch|
+ sub_batch.pluck(:from_column).each do |value|
+ connection.execute("INSERT INTO _test_insert_table VALUES (#{value})")
+ end
+
+ sub_batch.size
+ end
+ end
+ end
+ end
+
+ let(:test_table) { table(:_test_table) }
+ let(:test_insert_table) { table(:_test_insert_table) }
+
+ before do
+ allow(job_instance).to receive(:sleep)
+
+ connection.create_table :_test_table do |t|
+ t.timestamps_with_timezone null: false
+ t.integer :from_column, null: false
+ end
+
+ connection.create_table :_test_insert_table, id: false do |t|
+ t.integer :to_column
+ t.index :to_column, unique: true
+ end
+
+ test_table.create!(id: 1, from_column: 5)
+ test_table.create!(id: 2, from_column: 10)
+ test_table.create!(id: 3, from_column: 10)
+ test_table.create!(id: 4, from_column: 5)
+ test_table.create!(id: 5, from_column: 15)
+ end
+
+ after do
+ connection.drop_table(:_test_table)
+ connection.drop_table(:_test_insert_table)
+ end
+
+ it 'calls the operation for each distinct batch' do
+ expect { perform_job }.to change { test_insert_table.pluck(:to_column) }.from([]).to([5, 10, 15])
+ end
+
+ it 'stores the affected rows' do
+ perform_job
+
+ expect(job_instance.batch_metrics.affected_rows[:insert]).to contain_exactly(2, 1)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/background_migration/batching_strategies/backfill_project_statistics_with_container_registry_size_batching_strategy_spec.rb b/spec/lib/gitlab/background_migration/batching_strategies/backfill_project_statistics_with_container_registry_size_batching_strategy_spec.rb
new file mode 100644
index 00000000000..94e9bcf9207
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/batching_strategies/backfill_project_statistics_with_container_registry_size_batching_strategy_spec.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::BackfillProjectStatisticsWithContainerRegistrySizeBatchingStrategy, '#next_batch' do # rubocop:disable Layout/LineLength
+ let(:batching_strategy) { described_class.new(connection: ActiveRecord::Base.connection) }
+ let(:namespace) { table(:namespaces) }
+ let(:project) { table(:projects) }
+ let(:container_repositories) { table(:container_repositories) }
+
+ let!(:group) do
+ namespace.create!(
+ name: 'namespace1', type: 'Group', path: 'space1'
+ )
+ end
+
+ let!(:proj_namespace1) do
+ namespace.create!(
+ name: 'proj1', path: 'proj1', type: 'Project', parent_id: group.id
+ )
+ end
+
+ let!(:proj_namespace2) do
+ namespace.create!(
+ name: 'proj2', path: 'proj2', type: 'Project', parent_id: group.id
+ )
+ end
+
+ let!(:proj_namespace3) do
+ namespace.create!(
+ name: 'proj3', path: 'proj3', type: 'Project', parent_id: group.id
+ )
+ end
+
+ let!(:proj1) do
+ project.create!(
+ name: 'proj1', path: 'proj1', namespace_id: group.id, project_namespace_id: proj_namespace1.id
+ )
+ end
+
+ let!(:proj2) do
+ project.create!(
+ name: 'proj2', path: 'proj2', namespace_id: group.id, project_namespace_id: proj_namespace2.id
+ )
+ end
+
+ let!(:proj3) do
+ project.create!(
+ name: 'proj3', path: 'proj3', namespace_id: group.id, project_namespace_id: proj_namespace3.id
+ )
+ end
+
+ let!(:con1) do
+ container_repositories.create!(
+ project_id: proj1.id,
+ name: "ContReg_#{proj1.id}:1",
+ migration_state: 'import_done',
+ created_at: Date.new(2022, 01, 20)
+ )
+ end
+
+ let!(:con2) do
+ container_repositories.create!(
+ project_id: proj1.id,
+ name: "ContReg_#{proj1.id}:2",
+ migration_state: 'import_done',
+ created_at: Date.new(2022, 01, 20)
+ )
+ end
+
+ let!(:con3) do
+ container_repositories.create!(
+ project_id: proj2.id,
+ name: "ContReg_#{proj2.id}:1",
+ migration_state: 'import_done',
+ created_at: Date.new(2022, 01, 20)
+ )
+ end
+
+ let!(:con4) do
+ container_repositories.create!(
+ project_id: proj3.id,
+ name: "ContReg_#{proj3.id}:1",
+ migration_state: 'default',
+ created_at: Date.new(2022, 02, 20)
+ )
+ end
+
+ let!(:con5) do
+ container_repositories.create!(
+ project_id: proj3.id,
+ name: "ContReg_#{proj3.id}:2",
+ migration_state: 'default',
+ created_at: Date.new(2022, 02, 20)
+ )
+ end
+
+ it { expect(described_class).to be < Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy }
+
+ context 'when starting on the first batch' do
+ it 'returns the bounds of the next batch' do
+ batch_bounds = batching_strategy.next_batch(
+ :container_repositories,
+ :project_id,
+ batch_min_value: con1.project_id,
+ batch_size: 3,
+ job_arguments: []
+ )
+ expect(batch_bounds).to eq([con1.project_id, con4.project_id])
+ end
+ end
+
+ context 'when additional batches remain' do
+ it 'returns the bounds of the next batch' do
+ batch_bounds = batching_strategy.next_batch(
+ :container_repositories,
+ :project_id,
+ batch_min_value: con3.project_id,
+ batch_size: 3,
+ job_arguments: []
+ )
+
+ expect(batch_bounds).to eq([con3.project_id, con5.project_id])
+ end
+ end
+
+ context 'when no additional batches remain' do
+ it 'returns nil' do
+ batch_bounds = batching_strategy.next_batch(:container_repositories,
+ :project_id,
+ batch_min_value: con5.project_id + 1,
+ batch_size: 1, job_arguments: []
+ )
+
+ expect(batch_bounds).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/batching_strategies/dismissed_vulnerabilities_strategy_spec.rb b/spec/lib/gitlab/background_migration/batching_strategies/dismissed_vulnerabilities_strategy_spec.rb
new file mode 100644
index 00000000000..f96c7de50f2
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/batching_strategies/dismissed_vulnerabilities_strategy_spec.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::DismissedVulnerabilitiesStrategy, '#next_batch' do
+ let(:batching_strategy) { described_class.new(connection: ActiveRecord::Base.connection) }
+ let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
+ let(:users) { table(:users) }
+ let(:user) { create_user! }
+ let(:project) do
+ table(:projects).create!(
+ namespace_id: namespace.id,
+ project_namespace_id: namespace.id,
+ packages_enabled: false)
+ end
+
+ let(:vulnerabilities) { table(:vulnerabilities) }
+
+ let!(:vulnerability1) do
+ create_vulnerability!(
+ project_id: project.id,
+ author_id: user.id,
+ dismissed_at: Time.current
+ )
+ end
+
+ let!(:vulnerability2) do
+ create_vulnerability!(
+ project_id: project.id,
+ author_id: user.id,
+ dismissed_at: Time.current
+ )
+ end
+
+ let!(:vulnerability3) do
+ create_vulnerability!(
+ project_id: project.id,
+ author_id: user.id,
+ dismissed_at: Time.current
+ )
+ end
+
+ let!(:vulnerability4) do
+ create_vulnerability!(
+ project_id: project.id,
+ author_id: user.id,
+ dismissed_at: nil
+ )
+ end
+
+ it { expect(described_class).to be < Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy }
+
+ context 'when starting on the first batch' do
+ it 'returns the bounds of the next batch' do
+ batch_bounds = batching_strategy.next_batch(
+ :vulnerabilities,
+ :id,
+ batch_min_value: vulnerability1.id,
+ batch_size: 2,
+ job_arguments: []
+ )
+ expect(batch_bounds).to eq([vulnerability1.id, vulnerability2.id])
+ end
+ end
+
+ context 'when additional batches remain' do
+ it 'returns the bounds of the next batch and skips the records that do not have `dismissed_at` set' do
+ batch_bounds = batching_strategy.next_batch(
+ :vulnerabilities,
+ :id,
+ batch_min_value: vulnerability3.id,
+ batch_size: 2,
+ job_arguments: []
+ )
+
+ expect(batch_bounds).to eq([vulnerability3.id, vulnerability3.id])
+ end
+ end
+
+ context 'when no additional batches remain' do
+ it 'returns nil' do
+ batch_bounds = batching_strategy.next_batch(
+ :vulnerabilities,
+ :id,
+ batch_min_value: vulnerability4.id + 1,
+ batch_size: 1,
+ job_arguments: []
+ )
+
+ expect(batch_bounds).to be_nil
+ end
+ end
+
+ private
+
+ def create_vulnerability!(
+ project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0, state: 1, dismissed_at: nil
+ )
+ vulnerabilities.create!(
+ project_id: project_id,
+ author_id: author_id,
+ title: title,
+ severity: severity,
+ confidence: confidence,
+ report_type: report_type,
+ state: state,
+ dismissed_at: dismissed_at
+ )
+ end
+
+ def create_user!(name: "Example User", email: "user@example.com", user_type: nil)
+ users.create!(
+ name: name,
+ email: email,
+ username: name,
+ projects_limit: 10
+ )
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/batching_strategies/loose_index_scan_batching_strategy_spec.rb b/spec/lib/gitlab/background_migration/batching_strategies/loose_index_scan_batching_strategy_spec.rb
new file mode 100644
index 00000000000..1a00fd7c8b3
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/batching_strategies/loose_index_scan_batching_strategy_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::LooseIndexScanBatchingStrategy, '#next_batch' do
+ let(:batching_strategy) { described_class.new(connection: ActiveRecord::Base.connection) }
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:issues) { table(:issues) }
+
+ let!(:namespace1) { namespaces.create!(name: 'ns1', path: 'ns1') }
+ let!(:namespace2) { namespaces.create!(name: 'ns2', path: 'ns2') }
+ let!(:namespace3) { namespaces.create!(name: 'ns3', path: 'ns3') }
+ let!(:namespace4) { namespaces.create!(name: 'ns4', path: 'ns4') }
+ let!(:namespace5) { namespaces.create!(name: 'ns5', path: 'ns5') }
+ let!(:project1) { projects.create!(name: 'p1', namespace_id: namespace1.id, project_namespace_id: namespace1.id) }
+ let!(:project2) { projects.create!(name: 'p2', namespace_id: namespace2.id, project_namespace_id: namespace2.id) }
+ let!(:project3) { projects.create!(name: 'p3', namespace_id: namespace3.id, project_namespace_id: namespace3.id) }
+ let!(:project4) { projects.create!(name: 'p4', namespace_id: namespace4.id, project_namespace_id: namespace4.id) }
+ let!(:project5) { projects.create!(name: 'p5', namespace_id: namespace5.id, project_namespace_id: namespace5.id) }
+
+ let!(:issue1) { issues.create!(title: 'title', description: 'description', project_id: project2.id) }
+ let!(:issue2) { issues.create!(title: 'title', description: 'description', project_id: project1.id) }
+ let!(:issue3) { issues.create!(title: 'title', description: 'description', project_id: project2.id) }
+ let!(:issue4) { issues.create!(title: 'title', description: 'description', project_id: project3.id) }
+ let!(:issue5) { issues.create!(title: 'title', description: 'description', project_id: project2.id) }
+ let!(:issue6) { issues.create!(title: 'title', description: 'description', project_id: project4.id) }
+ let!(:issue7) { issues.create!(title: 'title', description: 'description', project_id: project5.id) }
+
+ it { expect(described_class).to be < Gitlab::BackgroundMigration::BatchingStrategies::BaseStrategy }
+
+ context 'when starting on the first batch' do
+ it 'returns the bounds of the next batch' do
+ batch_bounds = batching_strategy
+ .next_batch(:issues, :project_id, batch_min_value: project1.id, batch_size: 2, job_arguments: [])
+
+ expect(batch_bounds).to eq([project1.id, project2.id])
+ end
+ end
+
+ context 'when additional batches remain' do
+ it 'returns the bounds of the next batch' do
+ batch_bounds = batching_strategy
+ .next_batch(:issues, :project_id, batch_min_value: project2.id, batch_size: 3, job_arguments: [])
+
+ expect(batch_bounds).to eq([project2.id, project4.id])
+ end
+ end
+
+ context 'when on the final batch' do
+ it 'returns the bounds of the next batch' do
+ batch_bounds = batching_strategy
+ .next_batch(:issues, :project_id, batch_min_value: project4.id, batch_size: 3, job_arguments: [])
+
+ expect(batch_bounds).to eq([project4.id, project5.id])
+ end
+ end
+
+ context 'when no additional batches remain' do
+ it 'returns nil' do
+ batch_bounds = batching_strategy
+ .next_batch(:issues, :project_id, batch_min_value: project5.id + 1, batch_size: 1, job_arguments: [])
+
+ expect(batch_bounds).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb b/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb
index 521e2067744..943b5744b64 100644
--- a/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb
+++ b/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb
@@ -45,10 +45,30 @@ RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchi
end
end
+ context 'when job_class is provided with a batching_scope' do
+ let(:job_class) do
+ Class.new(described_class) do
+ def self.batching_scope(relation, job_arguments:)
+ min_id = job_arguments.first
+
+ relation.where.not(type: 'Project').where('id >= ?', min_id)
+ end
+ end
+ end
+
+ it 'applies the batching scope' do
+ expect(job_class).to receive(:batching_scope).and_call_original
+
+ batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace4.id, batch_size: 3, job_arguments: [1], job_class: job_class)
+
+ expect(batch_bounds).to eq([namespace4.id, namespace4.id])
+ end
+ end
+
context 'additional filters' do
let(:strategy_with_filters) do
Class.new(described_class) do
- def apply_additional_filters(relation, job_arguments:)
+ def apply_additional_filters(relation, job_arguments:, job_class: nil)
min_id = job_arguments.first
relation.where.not(type: 'Project').where('id >= ?', min_id)
diff --git a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_inactive_public_projects_spec.rb b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_inactive_public_projects_spec.rb
new file mode 100644
index 00000000000..f5a2dc91185
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_inactive_public_projects_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForInactivePublicProjects, :migration do
+ let(:namespaces_table) { table(:namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:project_settings_table) { table(:project_settings) }
+
+ subject(:perform_migration) do
+ described_class.new(start_id: projects_table.minimum(:id),
+ end_id: projects_table.maximum(:id),
+ batch_table: :projects,
+ batch_column: :id,
+ sub_batch_size: 2,
+ pause_ms: 0,
+ connection: ActiveRecord::Base.connection)
+ .perform
+ end
+
+ let(:queries) { ActiveRecord::QueryRecorder.new { perform_migration } }
+
+ let(:namespace_1) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-1') }
+ let(:project_namespace_2) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-2', type: 'Project') }
+ let(:project_namespace_3) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-3', type: 'Project') }
+ let(:project_namespace_4) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-4', type: 'Project') }
+ let(:project_namespace_5) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-5', type: 'Project') }
+
+ let(:project_1) do
+ projects_table
+ .create!(
+ name: 'proj-1', path: 'path-1', namespace_id: namespace_1.id,
+ project_namespace_id: project_namespace_2.id, visibility_level: 0
+ )
+ end
+
+ let(:project_2) do
+ projects_table
+ .create!(
+ name: 'proj-2', path: 'path-2', namespace_id: namespace_1.id,
+ project_namespace_id: project_namespace_3.id, visibility_level: 10
+ )
+ end
+
+ let(:project_3) do
+ projects_table
+ .create!(
+ name: 'proj-3', path: 'path-3', namespace_id: namespace_1.id,
+ project_namespace_id: project_namespace_4.id, visibility_level: 20, last_activity_at: '2021-01-01'
+ )
+ end
+
+ let(:project_4) do
+ projects_table
+ .create!(
+ name: 'proj-4', path: 'path-4', namespace_id: namespace_1.id,
+ project_namespace_id: project_namespace_5.id, visibility_level: 20, last_activity_at: '2022-01-01'
+ )
+ end
+
+ before do
+ project_settings_table.create!(project_id: project_1.id, legacy_open_source_license_available: true)
+ project_settings_table.create!(project_id: project_2.id, legacy_open_source_license_available: true)
+ project_settings_table.create!(project_id: project_3.id, legacy_open_source_license_available: true)
+ project_settings_table.create!(project_id: project_4.id, legacy_open_source_license_available: true)
+ end
+
+ it 'sets `legacy_open_source_license_available` attribute to false for inactive, public projects',
+ :aggregate_failures do
+ expect(queries.count).to eq(5)
+
+ expect(migrated_attribute(project_1.id)).to be_truthy
+ expect(migrated_attribute(project_2.id)).to be_truthy
+ expect(migrated_attribute(project_3.id)).to be_falsey
+ expect(migrated_attribute(project_4.id)).to be_truthy
+ end
+
+ def migrated_attribute(project_id)
+ project_settings_table.find(project_id).legacy_open_source_license_available
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/populate_operation_visibility_permissions_from_operations_spec.rb b/spec/lib/gitlab/background_migration/populate_operation_visibility_permissions_from_operations_spec.rb
new file mode 100644
index 00000000000..1ebdca136a3
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/populate_operation_visibility_permissions_from_operations_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::PopulateOperationVisibilityPermissionsFromOperations do
+ let(:namespaces) { table(:namespaces) }
+ let(:project_features) { table(:project_features) }
+ let(:projects) { table(:projects) }
+
+ let(:namespace) { namespaces.create!(name: 'user', path: 'user') }
+
+ let(:proj_namespace1) { namespaces.create!(name: 'proj1', path: 'proj1', type: 'Project', parent_id: namespace.id) }
+ let(:proj_namespace2) { namespaces.create!(name: 'proj2', path: 'proj2', type: 'Project', parent_id: namespace.id) }
+ let(:proj_namespace3) { namespaces.create!(name: 'proj3', path: 'proj3', type: 'Project', parent_id: namespace.id) }
+
+ let(:project1) { create_project('test1', proj_namespace1) }
+ let(:project2) { create_project('test2', proj_namespace2) }
+ let(:project3) { create_project('test3', proj_namespace3) }
+
+ let!(:record1) { create_project_feature(project1) }
+ let!(:record2) { create_project_feature(project2, 20) }
+ let!(:record3) { create_project_feature(project3) }
+
+ let(:sub_batch_size) { 2 }
+ let(:start_id) { record1.id }
+ let(:end_id) { record3.id }
+ let(:batch_table) { :project_features }
+ let(:batch_column) { :id }
+ let(:pause_ms) { 1 }
+ let(:connection) { ApplicationRecord.connection }
+
+ let(:job) do
+ described_class.new(
+ start_id: start_id,
+ end_id: end_id,
+ batch_table: batch_table,
+ batch_column: batch_column,
+ sub_batch_size: sub_batch_size,
+ pause_ms: pause_ms,
+ connection: connection
+ )
+ end
+
+ subject(:perform) { job.perform }
+
+ it 'updates all project settings records from their operations_access_level', :aggregate_failures do
+ perform
+
+ expect_project_features_match_operations_access_level(record1)
+ expect_project_features_match_operations_access_level(record2)
+ expect_project_features_match_operations_access_level(record3)
+ end
+
+ private
+
+ def expect_project_features_match_operations_access_level(record)
+ record.reload
+ expect(record.monitor_access_level).to eq(record.operations_access_level)
+ expect(record.infrastructure_access_level).to eq(record.operations_access_level)
+ expect(record.feature_flags_access_level).to eq(record.operations_access_level)
+ expect(record.environments_access_level).to eq(record.operations_access_level)
+ end
+
+ def create_project(proj_name, proj_namespace)
+ projects.create!(
+ namespace_id: namespace.id,
+ project_namespace_id: proj_namespace.id,
+ name: proj_name,
+ path: proj_name
+ )
+ end
+
+ def create_project_feature(project, operations_access_level = 10)
+ project_features.create!(
+ project_id: project.id,
+ pages_access_level: 10,
+ operations_access_level: operations_access_level
+ )
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb b/spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb
index a54c840dd8e..8d71b117107 100644
--- a/spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb
+++ b/spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb
@@ -73,26 +73,6 @@ RSpec.describe Gitlab::BackgroundMigration::RecalculateVulnerabilitiesOccurrence
subject { described_class.new.perform(start_id, end_id) }
- context 'when the migration is disabled by the feature flag' do
- let(:start_id) { 1 }
- let(:end_id) { 1001 }
-
- before do
- stub_feature_flags(migrate_vulnerability_finding_uuids: false)
- end
-
- it 'logs the info message and does not run the migration' do
- expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance|
- expect(instance).to receive(:info).once.with(message: 'Migration is disabled by the feature flag',
- migrator: 'RecalculateVulnerabilitiesOccurrencesUuid',
- start_id: start_id,
- end_id: end_id)
- end
-
- subject
- end
- end
-
context "when finding has a UUIDv4" do
before do
@uuid_v4 = create_finding!(
@@ -474,6 +454,16 @@ RSpec.describe Gitlab::BackgroundMigration::RecalculateVulnerabilitiesOccurrence
allow(Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_exception).with(expected_error).once
end
+
+ it_behaves_like 'marks background migration job records' do
+ let(:arguments) { [1, 4] }
+ subject { described_class.new }
+ end
+ end
+
+ it_behaves_like 'marks background migration job records' do
+ let(:arguments) { [1, 4] }
+ subject { described_class.new }
end
private
diff --git a/spec/lib/gitlab/background_migration/set_correct_vulnerability_state_spec.rb b/spec/lib/gitlab/background_migration/set_correct_vulnerability_state_spec.rb
new file mode 100644
index 00000000000..d5b98e49a31
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/set_correct_vulnerability_state_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::SetCorrectVulnerabilityState do
+ let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
+ let(:users) { table(:users) }
+ let(:user) { create_user! }
+ let(:project) do
+ table(:projects).create!(
+ namespace_id: namespace.id,
+ project_namespace_id: namespace.id,
+ packages_enabled: false)
+ end
+
+ let(:vulnerabilities) { table(:vulnerabilities) }
+
+ let!(:vulnerability_with_dismissed_at) do
+ create_vulnerability!(
+ project_id: project.id,
+ author_id: user.id,
+ dismissed_at: Time.current
+ )
+ end
+
+ let!(:vulnerability_without_dismissed_at) do
+ create_vulnerability!(
+ project_id: project.id,
+ author_id: user.id,
+ dismissed_at: nil
+ )
+ end
+
+ let(:detected_state) { 1 }
+ let(:dismissed_state) { 2 }
+
+ subject(:perform_migration) do
+ described_class.new(start_id: vulnerability_with_dismissed_at.id,
+ end_id: vulnerability_without_dismissed_at.id,
+ batch_table: :vulnerabilities,
+ batch_column: :id,
+ sub_batch_size: 1,
+ pause_ms: 0,
+ connection: ActiveRecord::Base.connection)
+ .perform
+ end
+
+ it 'changes vulnerability state to `dismissed` when dismissed_at is not nil' do
+ expect { perform_migration }.to change { vulnerability_with_dismissed_at.reload.state }.to(dismissed_state)
+ end
+
+ it 'does not change the state when dismissed_at is nil' do
+ expect { perform_migration }.not_to change { vulnerability_without_dismissed_at.reload.state }
+ end
+
+ private
+
+ def create_vulnerability!(
+ project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0, state: 1, dismissed_at: nil
+ )
+ vulnerabilities.create!(
+ project_id: project_id,
+ author_id: author_id,
+ title: title,
+ severity: severity,
+ confidence: confidence,
+ report_type: report_type,
+ state: state,
+ dismissed_at: dismissed_at
+ )
+ end
+
+ def create_user!(name: "Example User", email: "user@example.com", user_type: nil)
+ users.create!(
+ name: name,
+ email: email,
+ username: name,
+ projects_limit: 10
+ )
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/update_delayed_project_removal_to_null_for_user_namespaces_spec.rb b/spec/lib/gitlab/background_migration/update_delayed_project_removal_to_null_for_user_namespaces_spec.rb
new file mode 100644
index 00000000000..980a7771f4c
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/update_delayed_project_removal_to_null_for_user_namespaces_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::UpdateDelayedProjectRemovalToNullForUserNamespaces,
+ :migration do
+ let(:namespaces_table) { table(:namespaces) }
+ let(:namespace_settings_table) { table(:namespace_settings) }
+
+ subject(:perform_migration) do
+ described_class.new(
+ start_id: 1,
+ end_id: 30,
+ batch_table: :namespace_settings,
+ batch_column: :namespace_id,
+ sub_batch_size: 2,
+ pause_ms: 0,
+ connection: ActiveRecord::Base.connection
+ ).perform
+ end
+
+ before do
+ namespaces_table.create!(id: 1, name: 'group_namespace', path: 'path-1', type: 'Group')
+ namespaces_table.create!(id: 2, name: 'user_namespace', path: 'path-2', type: 'User')
+ namespaces_table.create!(id: 3, name: 'user_three_namespace', path: 'path-3', type: 'User')
+ namespaces_table.create!(id: 4, name: 'group_four_namespace', path: 'path-4', type: 'Group')
+ namespaces_table.create!(id: 5, name: 'group_five_namespace', path: 'path-5', type: 'Group')
+
+ namespace_settings_table.create!(namespace_id: 1, delayed_project_removal: false)
+ namespace_settings_table.create!(namespace_id: 2, delayed_project_removal: false)
+ namespace_settings_table.create!(namespace_id: 3, delayed_project_removal: nil)
+ namespace_settings_table.create!(namespace_id: 4, delayed_project_removal: true)
+ namespace_settings_table.create!(namespace_id: 5, delayed_project_removal: nil)
+ end
+
+ it 'updates `delayed_project_removal` column to null for user namespaces', :aggregate_failures do
+ expect(ActiveRecord::QueryRecorder.new { perform_migration }.count).to eq(7)
+
+ expect(migrated_attribute(1)).to be_falsey
+ expect(migrated_attribute(2)).to be_nil
+ expect(migrated_attribute(3)).to be_nil
+ expect(migrated_attribute(4)).to be_truthy
+ expect(migrated_attribute(5)).to be_nil
+ end
+
+ def migrated_attribute(namespace_id)
+ namespace_settings_table.find(namespace_id).delayed_project_removal
+ end
+end
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index b0d721a74ce..8fb903154f3 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -2,12 +2,12 @@
require 'spec_helper'
-RSpec.describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
+RSpec.describe Gitlab::BareRepositoryImport::Importer do
let!(:admin) { create(:admin) }
let!(:base_dir) { Dir.mktmpdir + '/' }
let(:bare_repository) { Gitlab::BareRepositoryImport::Repository.new(base_dir, File.join(base_dir, "#{project_path}.git")) }
let(:gitlab_shell) { Gitlab::Shell.new }
- let(:source_project) { TEST_REPO_PATH }
+ let(:source_project) { TestEnv.factory_repo_bundle_path }
subject(:importer) { described_class.new(admin, bare_repository) }
@@ -17,8 +17,6 @@ RSpec.describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
after do
FileUtils.rm_rf(base_dir)
- TestEnv.clean_test_path
- ensure_seeds
end
shared_examples 'importing a repository' do
@@ -150,7 +148,6 @@ RSpec.describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
end
context 'with a repository already on disk' do
- let!(:base_dir) { TestEnv.repos_path }
# This is a quick way to get a valid repository instead of copying an
# existing one. Since it's not persisted, the importer will try to
# create the project.
@@ -193,8 +190,6 @@ RSpec.describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
def prepare_repository(project_path, source_project)
repo_path = File.join(base_dir, project_path)
- return create_bare_repository(repo_path) unless source_project
-
cmd = %W(#{Gitlab.config.git.bin_path} clone --bare #{source_project} #{repo_path})
system(git_env, *cmd, chdir: SEED_STORAGE_PATH, out: '/dev/null', err: '/dev/null')
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index bf115046744..d29447ee376 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -59,18 +59,15 @@ RSpec.describe ::Gitlab::BareRepositoryImport::Repository do
let(:root_path) { TestEnv.repos_path }
let(:repo_path) { File.join(root_path, "#{hashed_path}.git") }
let(:wiki_path) { File.join(root_path, "#{hashed_path}.wiki.git") }
+ let(:raw_repository) { Gitlab::Git::Repository.new('default', "#{hashed_path}.git", nil, nil) }
before do
- TestEnv.create_bare_repository(repo_path)
-
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository = Rugged::Repository.new(repo_path)
- repository.config['gitlab.fullpath'] = 'to/repo'
- end
+ raw_repository.create_repository
+ raw_repository.set_full_path(full_path: 'to/repo')
end
after do
- FileUtils.rm_rf(repo_path)
+ raw_repository.remove
end
subject { described_class.new(root_path, repo_path) }
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index b723c31c4aa..e0a7044e5f9 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -328,6 +328,7 @@ RSpec.describe Gitlab::BitbucketImport::Importer do
expect(project.issues.where(state_id: Issue.available_states[:closed]).size).to eq(5)
expect(project.issues.where(state_id: Issue.available_states[:opened]).size).to eq(2)
+ expect(project.issues.map(&:namespace_id).uniq).to match_array([project.project_namespace_id])
end
describe 'wiki import' do
@@ -362,6 +363,14 @@ RSpec.describe Gitlab::BitbucketImport::Importer do
expect(project.issues.where("description LIKE ?", '%reporter3%').size).to eq(1)
expect(importer.errors).to be_empty
end
+
+ it 'sets work item type on new issues' do
+ allow(importer).to receive(:import_wiki)
+
+ importer.execute
+
+ expect(project.issues.map(&:work_item_type_id).uniq).to contain_exactly(WorkItems::Type.default_issue_type.id)
+ end
end
context 'metrics' do
diff --git a/spec/lib/gitlab/changelog/config_spec.rb b/spec/lib/gitlab/changelog/config_spec.rb
index 600682d30ad..92cad366cfd 100644
--- a/spec/lib/gitlab/changelog/config_spec.rb
+++ b/spec/lib/gitlab/changelog/config_spec.rb
@@ -20,6 +20,18 @@ RSpec.describe Gitlab::Changelog::Config do
described_class.from_git(project)
end
+ it "retrieves the specified configuration from git" do
+ allow(project.repository)
+ .to receive(:changelog_config).with('HEAD', 'specified_changelog_config.yml')
+ .and_return("---\ndate_format: '%Y'")
+
+ expect(described_class)
+ .to receive(:from_hash)
+ .with(project, { 'date_format' => '%Y' }, nil)
+
+ described_class.from_git(project, nil, 'specified_changelog_config.yml')
+ end
+
it 'returns the default configuration when no YAML file exists in Git' do
allow(project.repository)
.to receive(:changelog_config)
diff --git a/spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb b/spec/lib/gitlab/ci/build/duration_parser_spec.rb
index 889878cf3ef..7f5ff1eb0ee 100644
--- a/spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb
+++ b/spec/lib/gitlab/ci/build/duration_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Build::Artifacts::ExpireInParser do
+RSpec.describe Gitlab::Ci::Build::DurationParser do
describe '.validate_duration', :request_store do
subject { described_class.validate_duration(value) }
diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb
index 8f77a1f60ad..4895077a731 100644
--- a/spec/lib/gitlab/ci/build/image_spec.rb
+++ b/spec/lib/gitlab/ci/build/image_spec.rb
@@ -98,9 +98,11 @@ RSpec.describe Gitlab::Ci::Build::Image do
let(:service_entrypoint) { '/bin/sh' }
let(:service_alias) { 'db' }
let(:service_command) { 'sleep 30' }
+ let(:pull_policy) { %w[always if-not-present] }
let(:job) do
create(:ci_build, options: { services: [{ name: service_image_name, entrypoint: service_entrypoint,
- alias: service_alias, command: service_command, ports: [80] }] })
+ alias: service_alias, command: service_command, ports: [80],
+ pull_policy: pull_policy }] })
end
it 'fabricates an non-empty array of objects' do
@@ -114,6 +116,7 @@ RSpec.describe Gitlab::Ci::Build::Image do
expect(subject.first.entrypoint).to eq(service_entrypoint)
expect(subject.first.alias).to eq(service_alias)
expect(subject.first.command).to eq(service_command)
+ expect(subject.first.pull_policy).to eq(pull_policy)
port = subject.first.ports.first
expect(port.number).to eq 80
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
index 4ac8bf61738..3892b88598a 100644
--- a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
@@ -6,19 +6,43 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do
describe '#satisfied_by?' do
subject { described_class.new(globs).satisfied_by?(pipeline, context) }
- it_behaves_like 'a glob matching rule' do
+ context 'a glob matching rule' do
+ using RSpec::Parameterized::TableSyntax
+
let(:pipeline) { build(:ci_pipeline) }
let(:context) {}
before do
allow(pipeline).to receive(:modified_paths).and_return(files.keys)
end
+
+ # rubocop:disable Layout/LineLength
+ where(:case_name, :globs, :files, :satisfied) do
+ 'exact top-level match' | ['Dockerfile'] | { 'Dockerfile' => '', 'Gemfile' => '' } | true
+ 'exact top-level match' | { paths: ['Dockerfile'] } | { 'Dockerfile' => '', 'Gemfile' => '' } | true
+ 'exact top-level no match' | { paths: ['Dockerfile'] } | { 'Gemfile' => '' } | false
+ 'pattern top-level match' | { paths: ['Docker*'] } | { 'Dockerfile' => '', 'Gemfile' => '' } | true
+ 'pattern top-level no match' | ['Docker*'] | { 'Gemfile' => '' } | false
+ 'pattern top-level no match' | { paths: ['Docker*'] } | { 'Gemfile' => '' } | false
+ 'exact nested match' | { paths: ['project/build.properties'] } | { 'project/build.properties' => '' } | true
+ 'exact nested no match' | { paths: ['project/build.properties'] } | { 'project/README.md' => '' } | false
+ 'pattern nested match' | { paths: ['src/**/*.go'] } | { 'src/gitlab.com/goproject/goproject.go' => '' } | true
+ 'pattern nested no match' | { paths: ['src/**/*.go'] } | { 'src/gitlab.com/goproject/README.md' => '' } | false
+ 'ext top-level match' | { paths: ['*.go'] } | { 'main.go' => '', 'cmd/goproject/main.go' => '' } | true
+ 'ext nested no match' | { paths: ['*.go'] } | { 'cmd/goproject/main.go' => '' } | false
+ 'ext slash no match' | { paths: ['/*.go'] } | { 'main.go' => '', 'cmd/goproject/main.go' => '' } | false
+ end
+ # rubocop:enable Layout/LineLength
+
+ with_them do
+ it { is_expected.to eq(satisfied) }
+ end
end
context 'when pipeline is nil' do
let(:pipeline) {}
let(:context) {}
- let(:globs) { [] }
+ let(:globs) { { paths: [] } }
it { is_expected.to be_truthy }
end
@@ -26,8 +50,8 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do
context 'when using variable expansion' do
let(:pipeline) { build(:ci_pipeline) }
let(:modified_paths) { ['helm/test.txt'] }
- let(:globs) { ['$HELM_DIR/**/*'] }
- let(:context) { double('context') }
+ let(:globs) { { paths: ['$HELM_DIR/**/*'] } }
+ let(:context) { instance_double(Gitlab::Ci::Build::Context::Base) }
before do
allow(pipeline).to receive(:modified_paths).and_return(modified_paths)
@@ -58,7 +82,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do
end
context 'when variable expansion does not match' do
- let(:globs) { ['path/with/$in/it/*'] }
+ let(:globs) { { paths: ['path/with/$in/it/*'] } }
let(:modified_paths) { ['path/with/$in/it/file.txt'] }
before do
diff --git a/spec/lib/gitlab/ci/build/rules/rule_spec.rb b/spec/lib/gitlab/ci/build/rules/rule_spec.rb
index f905e229415..ac73b665f3a 100644
--- a/spec/lib/gitlab/ci/build/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule_spec.rb
@@ -14,10 +14,14 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule do
let(:ci_build) { build(:ci_build, pipeline: pipeline) }
let(:rule) { described_class.new(rule_hash) }
+ before do
+ allow(pipeline).to receive(:modified_paths).and_return(['file.rb'])
+ end
+
describe '#matches?' do
subject { rule.matches?(pipeline, seed) }
- context 'with one matching clause' do
+ context 'with one matching clause if' do
let(:rule_hash) do
{ if: '$VAR == null', when: 'always' }
end
@@ -25,9 +29,17 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule do
it { is_expected.to eq(true) }
end
+ context 'with one matching clause changes' do
+ let(:rule_hash) do
+ { changes: { paths: ['**/*'] }, when: 'always' }
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
context 'with two matching clauses' do
let(:rule_hash) do
- { if: '$VAR == null', changes: '**/*', when: 'always' }
+ { if: '$VAR == null', changes: { paths: ['**/*'] }, when: 'always' }
end
it { is_expected.to eq(true) }
@@ -35,7 +47,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule do
context 'with a matching and non-matching clause' do
let(:rule_hash) do
- { if: '$VAR != null', changes: '$VAR == null', when: 'always' }
+ { if: '$VAR != null', changes: { paths: ['invalid.xyz'] }, when: 'always' }
end
it { is_expected.to eq(false) }
@@ -43,7 +55,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule do
context 'with two non-matching clauses' do
let(:rule_hash) do
- { if: '$VAR != null', changes: 'README', when: 'always' }
+ { if: '$VAR != null', changes: { paths: ['README'] }, when: 'always' }
end
it { is_expected.to eq(false) }
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index bd1ab5d8c41..0fa6d4f8804 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -9,6 +9,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
before do
stub_feature_flags(ci_docker_image_pull_policy: true)
+
+ entry.compose!
end
let(:entry) { described_class.new(config) }
@@ -129,19 +131,16 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
describe '#valid?' do
it 'is valid' do
- entry.compose!
-
expect(entry).to be_valid
end
context 'when the feature flag ci_docker_image_pull_policy is disabled' do
before do
stub_feature_flags(ci_docker_image_pull_policy: false)
+ entry.compose!
end
it 'is not valid' do
- entry.compose!
-
expect(entry).not_to be_valid
expect(entry.errors).to include('image config contains unknown keys: pull_policy')
end
@@ -150,8 +149,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
describe '#value' do
it "returns value" do
- entry.compose!
-
expect(entry.value).to eq(
name: 'image:1.0',
pull_policy: ['if-not-present']
@@ -161,11 +158,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
context 'when the feature flag ci_docker_image_pull_policy is disabled' do
before do
stub_feature_flags(ci_docker_image_pull_policy: false)
+ entry.compose!
end
it 'is not valid' do
- entry.compose!
-
expect(entry.value).to eq(
name: 'image:1.0'
)
diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb
index 3ed4a9f263f..295561b3c4d 100644
--- a/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb
@@ -37,7 +37,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule::Changes do
it { is_expected.not_to be_valid }
it 'reports an error about invalid policy' do
- expect(entry.errors).to include(/should be an array of strings/)
+ expect(entry.errors).to include(/should be an array or a hash/)
end
end
@@ -64,7 +64,59 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule::Changes do
it 'returns information about errors' do
expect(entry.errors)
- .to include(/should be an array of strings/)
+ .to include(/should be an array or a hash/)
+ end
+ end
+
+ context 'with paths' do
+ context 'when paths is an array of strings' do
+ let(:config) { { paths: %w[app/ lib/] } }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when paths is not an array' do
+ let(:config) { { paths: 'string' } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns information about errors' do
+ expect(entry.errors)
+ .to include(/should be an array of strings/)
+ end
+ end
+
+ context 'when paths is an array of integers' do
+ let(:config) { { paths: [1, 2] } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns information about errors' do
+ expect(entry.errors)
+ .to include(/should be an array of strings/)
+ end
+ end
+
+ context 'when paths is an array of long strings' do
+ let(:config) { { paths: ['a'] * 51 } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns information about errors' do
+ expect(entry.errors)
+ .to include(/has too many entries \(maximum 50\)/)
+ end
+ end
+
+ context 'when paths is nil' do
+ let(:config) { { paths: nil } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns information about errors' do
+ expect(entry.errors)
+ .to include(/should be an array of strings/)
+ end
end
end
end
@@ -75,6 +127,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule::Changes do
context 'when using a string array' do
let(:config) { %w[app/ lib/ spec/ other/* paths/**/*.rb] }
+ it { is_expected.to eq(paths: config) }
+ end
+
+ context 'with paths' do
+ let(:config) do
+ { paths: ['app/', 'lib/'] }
+ end
+
it { is_expected.to eq(config) }
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
index 89d349efe8f..93f4a66bfb6 100644
--- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
@@ -115,7 +115,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do
it { is_expected.not_to be_valid }
it 'reports an error about invalid policy' do
- expect(subject.errors).to include(/should be an array of strings/)
+ expect(subject.errors).to include(/should be an array or a hash/)
end
end
@@ -411,7 +411,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do
context 'when using a changes: clause' do
let(:config) { { changes: %w[app/ lib/ spec/ other/* paths/**/*.rb] } }
- it { is_expected.to eq(config) }
+ it { is_expected.to eq(changes: { paths: %w[app/ lib/ spec/ other/* paths/**/*.rb] }) }
+
+ context 'when using changes with paths' do
+ let(:config) { { changes: { paths: %w[app/ lib/ spec/ other/* paths/**/*.rb] } } }
+
+ it { is_expected.to eq(config) }
+ end
end
context 'when default value has been provided' do
@@ -426,7 +432,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do
end
it 'does not add to provided configuration' do
- expect(entry.value).to eq(config)
+ expect(entry.value).to eq(changes: { paths: %w[app/**/*.rb] })
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/rules_spec.rb
index cfec33003e4..b0871f2345e 100644
--- a/spec/lib/gitlab/ci/config/entry/rules_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules_spec.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require 'fast_spec_helper'
-require 'support/helpers/stub_feature_flags'
require_dependency 'active_model'
RSpec.describe Gitlab::Ci::Config::Entry::Rules do
@@ -12,13 +11,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do
end
let(:metadata) { { allowed_when: %w[always never] } }
- let(:entry) { factory.create! }
- describe '.new' do
- subject { entry }
+ subject(:entry) { factory.create! }
+ describe '.new' do
before do
- subject.compose!
+ entry.compose!
end
context 'with a list of rule rule' do
@@ -73,7 +71,11 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do
end
describe '#value' do
- subject { entry.value }
+ subject(:value) { entry.value }
+
+ before do
+ entry.compose!
+ end
context 'with a list of rule rule' do
let(:config) do
@@ -99,7 +101,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do
{ if: '$SKIP', when: 'never' }
end
- it { is_expected.to eq([config]) }
+ it { is_expected.to eq([]) }
end
context 'with nested rules' do
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index 2795cc9dddf..3c000fd09ed 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -1,14 +1,19 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
+require 'support/helpers/stubbed_feature'
+require 'support/helpers/stub_feature_flags'
RSpec.describe Gitlab::Ci::Config::Entry::Service do
- let(:entry) { described_class.new(config) }
+ include StubFeatureFlags
before do
+ stub_feature_flags(ci_docker_image_pull_policy: true)
entry.compose!
end
+ subject(:entry) { described_class.new(config) }
+
context 'when configuration is a string' do
let(:config) { 'postgresql:9.5' }
@@ -90,6 +95,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
end
end
+ describe '#pull_policy' do
+ it "returns nil" do
+ expect(entry.pull_policy).to be_nil
+ end
+ end
+
context 'when configuration has ports' do
let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
let(:config) do
@@ -134,6 +145,49 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
end
end
end
+
+ context 'when configuration has pull_policy' do
+ let(:config) { { name: 'postgresql:9.5', pull_policy: 'if-not-present' } }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+
+ context 'when the feature flag ci_docker_image_pull_policy is disabled' do
+ before do
+ stub_feature_flags(ci_docker_image_pull_policy: false)
+ entry.compose!
+ end
+
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include('service config contains unknown keys: pull_policy')
+ end
+ end
+ end
+
+ describe '#value' do
+ it "returns value" do
+ expect(entry.value).to eq(
+ name: 'postgresql:9.5',
+ pull_policy: ['if-not-present']
+ )
+ end
+
+ context 'when the feature flag ci_docker_image_pull_policy is disabled' do
+ before do
+ stub_feature_flags(ci_docker_image_pull_policy: false)
+ end
+
+ it 'is not valid' do
+ expect(entry.value).to eq(
+ name: 'postgresql:9.5'
+ )
+ end
+ end
+ end
+ end
end
context 'when entry value is not correct' do
diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb
index 800c563cd0b..40702e75404 100644
--- a/spec/lib/gitlab/ci/config/external/context_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/context_spec.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::Context do
- let(:project) { double('Project') }
+ let(:project) { build(:project) }
let(:user) { double('User') }
let(:sha) { '12345' }
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'a', 'value' => 'b' }]) }
@@ -126,7 +126,7 @@ RSpec.describe Gitlab::Ci::Config::External::Context do
end
context 'with attributes' do
- let(:new_attributes) { { project: double, user: double, sha: '56789' } }
+ let(:new_attributes) { { project: build(:project), user: double, sha: '56789' } }
it_behaves_like 'a mutated context'
end
diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
index 77e542cf933..72a85c9b03d 100644
--- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
@@ -177,6 +177,22 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
expect(project_file.error_message).to include("Project `xxxxxxxxxxxxxxxxxxxxxxx` not found or access denied!")
end
end
+
+ context 'when a project contained in an array is used with a masked variable' do
+ let(:variables) do
+ Gitlab::Ci::Variables::Collection.new([
+ { key: 'VAR1', value: 'a_secret_variable_value', masked: true }
+ ])
+ end
+
+ let(:params) do
+ { project: ['a_secret_variable_value'], file: '/file.yml' }
+ end
+
+ it 'does not raise an error' do
+ expect { valid? }.not_to raise_error
+ end
+ end
end
describe '#expand_context' do
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 7e1b31fea6a..e74fdc2071b 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -232,11 +232,9 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
image: 'image:1.0' }
end
- before do
- stub_const("#{described_class}::MAX_INCLUDES", 2)
- end
-
it 'does not raise an exception' do
+ allow(context).to receive(:max_includes).and_return(2)
+
expect { subject }.not_to raise_error
end
end
@@ -250,11 +248,9 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
image: 'image:1.0' }
end
- before do
- stub_const("#{described_class}::MAX_INCLUDES", 1)
- end
-
it 'raises an exception' do
+ allow(context).to receive(:max_includes).and_return(1)
+
expect { subject }.to raise_error(described_class::TooManyIncludesError)
end
@@ -264,6 +260,8 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
end
it 'raises an exception' do
+ allow(context).to receive(:max_includes).and_return(1)
+
expect { subject }.to raise_error(described_class::TooManyIncludesError)
end
end
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index 15a0ff40aa4..841a46e197d 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -323,11 +323,9 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
end
context 'when too many includes is included' do
- before do
- stub_const('Gitlab::Ci::Config::External::Mapper::MAX_INCLUDES', 1)
- end
-
it 'raises an error' do
+ allow(context).to receive(:max_includes).and_return(1)
+
expect { subject }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError, /Maximum of 1 nested/)
end
end
diff --git a/spec/lib/gitlab/ci/jwt_spec.rb b/spec/lib/gitlab/ci/jwt_spec.rb
index 179e2efc0c7..147801b6217 100644
--- a/spec/lib/gitlab/ci/jwt_spec.rb
+++ b/spec/lib/gitlab/ci/jwt_spec.rb
@@ -48,6 +48,7 @@ RSpec.describe Gitlab::Ci::Jwt do
expect(payload[:ref_protected]).to eq(build.protected.to_s)
expect(payload[:environment]).to be_nil
expect(payload[:environment_protected]).to be_nil
+ expect(payload[:deployment_tier]).to be_nil
end
end
@@ -96,7 +97,7 @@ RSpec.describe Gitlab::Ci::Jwt do
end
describe 'environment' do
- let(:environment) { build_stubbed(:environment, project: project, name: 'production') }
+ let(:environment) { build_stubbed(:environment, project: project, name: 'production', tier: 'production') }
let(:build) do
build_stubbed(
:ci_build,
@@ -114,6 +115,19 @@ RSpec.describe Gitlab::Ci::Jwt do
it 'has correct values for environment attributes' do
expect(payload[:environment]).to eq('production')
expect(payload[:environment_protected]).to eq('false')
+ expect(payload[:deployment_tier]).to eq('production')
+ end
+
+ describe 'deployment_tier' do
+ context 'when build options specifies a different deployment_tier' do
+ before do
+ build.options[:environment] = { name: environment.name, deployment_tier: 'development' }
+ end
+
+ it 'uses deployment_tier from build options' do
+ expect(payload[:deployment_tier]).to eq('development')
+ end
+ end
end
end
end
@@ -121,8 +135,8 @@ RSpec.describe Gitlab::Ci::Jwt do
describe '.for_build' do
shared_examples 'generating JWT for build' do
context 'when signing key is present' do
- let(:rsa_key) { OpenSSL::PKey::RSA.generate(1024) }
- let(:rsa_key_data) { rsa_key.to_s }
+ let_it_be(:rsa_key) { OpenSSL::PKey::RSA.generate(3072) }
+ let_it_be(:rsa_key_data) { rsa_key.to_s }
it 'generates JWT with key id' do
_payload, headers = JWT.decode(jwt, rsa_key.public_key, true, { algorithm: 'RS256' })
diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb
index 375841ce236..cbf92f8fa83 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::CreateDeployments do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
- let(:stage) { build(:ci_stage_entity, project: project, statuses: [job]) }
+ let(:stage) { build(:ci_stage, project: project, statuses: [job]) }
let(:pipeline) { create(:ci_pipeline, project: project, stages: [stage]) }
let(:command) do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
index 9057c4e99df..eba0db0adfb 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do
context 'tags persistence' do
let(:stage) do
- build(:ci_stage_entity, pipeline: pipeline, project: project)
+ build(:ci_stage, pipeline: pipeline, project: project)
end
let(:job) do
@@ -77,7 +77,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do
context 'without tags' do
it 'extracts an empty tag list' do
- expect(CommitStatus)
+ expect(Gitlab::Ci::Tags::BulkInsert)
.to receive(:bulk_insert_tags!)
.with([job])
.and_call_original
@@ -95,7 +95,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do
end
it 'bulk inserts tags' do
- expect(CommitStatus)
+ expect(Gitlab::Ci::Tags::BulkInsert)
.to receive(:bulk_insert_tags!)
.with([job])
.and_call_original
diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb
index 6a7d9b58a05..e07a3ca9033 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments do
let(:project) { create(:project) }
let(:user) { create(:user) }
- let(:stage) { build(:ci_stage_entity, project: project, statuses: [job]) }
+ let(:stage) { build(:ci_stage, project: project, statuses: [job]) }
let(:pipeline) { build(:ci_pipeline, project: project, stages: [stage]) }
let(:command) do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb
index 571455d6279..f14dd70a753 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureResourceGroups do
let(:project) { create(:project) }
let(:user) { create(:user) }
- let(:stage) { build(:ci_stage_entity, project: project, statuses: [job]) }
+ let(:stage) { build(:ci_stage, project: project, statuses: [job]) }
let(:pipeline) { build(:ci_pipeline, project: project, stages: [stage]) }
let!(:environment) { create(:environment, name: 'production', project: project) }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
index cebc4c02d11..eeac0c85a77 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
@@ -84,7 +84,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
end
- it 'respects the defined payload schema', :saas do
+ it 'respects the defined payload schema' do
expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
expect(params[:body]).to match_schema('/external_validation')
expect(params[:timeout]).to eq(described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT)
@@ -235,6 +235,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
it 'logs the authorization' do
+ allow(Gitlab::AppLogger).to receive(:info)
+
expect(Gitlab::AppLogger).to receive(:info).with(message: 'Pipeline not authorized', project_id: project.id, user_id: user.id)
perform!
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 49505d397c2..040f3ab5830 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -858,14 +858,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
context 'with an explicit `when: never`' do
where(:rule_set) do
[
- [[{ changes: %w[*/**/*.rb], when: 'never' }, { changes: %w[*/**/*.rb], when: 'always' }]],
- [[{ changes: %w[app/models/ci/pipeline.rb], when: 'never' }, { changes: %w[app/models/ci/pipeline.rb], when: 'always' }]],
- [[{ changes: %w[spec/**/*.rb], when: 'never' }, { changes: %w[spec/**/*.rb], when: 'always' }]],
- [[{ changes: %w[*.yml], when: 'never' }, { changes: %w[*.yml], when: 'always' }]],
- [[{ changes: %w[.*.yml], when: 'never' }, { changes: %w[.*.yml], when: 'always' }]],
- [[{ changes: %w[**/*], when: 'never' }, { changes: %w[**/*], when: 'always' }]],
- [[{ changes: %w[*/**/*.rb *.yml], when: 'never' }, { changes: %w[*/**/*.rb *.yml], when: 'always' }]],
- [[{ changes: %w[.*.yml **/*], when: 'never' }, { changes: %w[.*.yml **/*], when: 'always' }]]
+ [[{ changes: { paths: %w[*/**/*.rb] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb] }, when: 'always' }]],
+ [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }]],
+ [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'never' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'always' }]],
+ [[{ changes: { paths: %w[*.yml] }, when: 'never' }, { changes: { paths: %w[*.yml] }, when: 'always' }]],
+ [[{ changes: { paths: %w[.*.yml] }, when: 'never' }, { changes: { paths: %w[.*.yml] }, when: 'always' }]],
+ [[{ changes: { paths: %w[**/*] }, when: 'never' }, { changes: { paths: %w[**/*] }, when: 'always' }]],
+ [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }]],
+ [[{ changes: { paths: %w[.*.yml **/*] }, when: 'never' }, { changes: { paths: %w[.*.yml **/*] }, when: 'always' }]]
]
end
@@ -881,14 +881,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
context 'with an explicit `when: always`' do
where(:rule_set) do
[
- [[{ changes: %w[*/**/*.rb], when: 'always' }, { changes: %w[*/**/*.rb], when: 'never' }]],
- [[{ changes: %w[app/models/ci/pipeline.rb], when: 'always' }, { changes: %w[app/models/ci/pipeline.rb], when: 'never' }]],
- [[{ changes: %w[spec/**/*.rb], when: 'always' }, { changes: %w[spec/**/*.rb], when: 'never' }]],
- [[{ changes: %w[*.yml], when: 'always' }, { changes: %w[*.yml], when: 'never' }]],
- [[{ changes: %w[.*.yml], when: 'always' }, { changes: %w[.*.yml], when: 'never' }]],
- [[{ changes: %w[**/*], when: 'always' }, { changes: %w[**/*], when: 'never' }]],
- [[{ changes: %w[*/**/*.rb *.yml], when: 'always' }, { changes: %w[*/**/*.rb *.yml], when: 'never' }]],
- [[{ changes: %w[.*.yml **/*], when: 'always' }, { changes: %w[.*.yml **/*], when: 'never' }]]
+ [[{ changes: { paths: %w[*/**/*.rb] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb] }, when: 'never' }]],
+ [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }]],
+ [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'always' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'never' }]],
+ [[{ changes: { paths: %w[*.yml] }, when: 'always' }, { changes: { paths: %w[*.yml] }, when: 'never' }]],
+ [[{ changes: { paths: %w[.*.yml] }, when: 'always' }, { changes: { paths: %w[.*.yml] }, when: 'never' }]],
+ [[{ changes: { paths: %w[**/*] }, when: 'always' }, { changes: { paths: %w[**/*] }, when: 'never' }]],
+ [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }]],
+ [[{ changes: { paths: %w[.*.yml **/*] }, when: 'always' }, { changes: { paths: %w[.*.yml **/*] }, when: 'never' }]]
]
end
@@ -904,14 +904,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
context 'without an explicit when: value' do
where(:rule_set) do
[
- [[{ changes: %w[*/**/*.rb] }]],
- [[{ changes: %w[app/models/ci/pipeline.rb] }]],
- [[{ changes: %w[spec/**/*.rb] }]],
- [[{ changes: %w[*.yml] }]],
- [[{ changes: %w[.*.yml] }]],
- [[{ changes: %w[**/*] }]],
- [[{ changes: %w[*/**/*.rb *.yml] }]],
- [[{ changes: %w[.*.yml **/*] }]]
+ [[{ changes: { paths: %w[*/**/*.rb] } }]],
+ [[{ changes: { paths: %w[app/models/ci/pipeline.rb] } }]],
+ [[{ changes: { paths: %w[spec/**/*.rb] } }]],
+ [[{ changes: { paths: %w[*.yml] } }]],
+ [[{ changes: { paths: %w[.*.yml] } }]],
+ [[{ changes: { paths: %w[**/*] } }]],
+ [[{ changes: { paths: %w[*/**/*.rb *.yml] } }]],
+ [[{ changes: { paths: %w[.*.yml **/*] } }]]
]
end
diff --git a/spec/lib/gitlab/ci/reports/coverage_report_generator_spec.rb b/spec/lib/gitlab/ci/reports/coverage_report_generator_spec.rb
index eec218346c2..f116b175fc7 100644
--- a/spec/lib/gitlab/ci/reports/coverage_report_generator_spec.rb
+++ b/spec/lib/gitlab/ci/reports/coverage_report_generator_spec.rb
@@ -75,16 +75,6 @@ RSpec.describe Gitlab::Ci::Reports::CoverageReportGenerator, factory_default: :k
end
it_behaves_like 'having a coverage report'
-
- context 'when feature flag ci_child_pipeline_coverage_reports is disabled' do
- before do
- stub_feature_flags(ci_child_pipeline_coverage_reports: false)
- end
-
- it 'returns empty coverage reports' do
- expect(subject).to be_empty
- end
- end
end
context 'when both parent and child pipeline have builds with coverage reports' do
diff --git a/spec/lib/gitlab/ci/reports/test_reports_spec.rb b/spec/lib/gitlab/ci/reports/test_report_spec.rb
index 24c00de3731..539510bca9e 100644
--- a/spec/lib/gitlab/ci/reports/test_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_report_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Reports::TestReports do
+RSpec.describe Gitlab::Ci::Reports::TestReport do
include TestReportsHelper
let(:test_reports) { described_class.new }
diff --git a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
index 3483dddca3a..ac64e4699fe 100644
--- a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
@@ -6,8 +6,8 @@ RSpec.describe Gitlab::Ci::Reports::TestReportsComparer do
include TestReportsHelper
let(:comparer) { described_class.new(base_reports, head_reports) }
- let(:base_reports) { Gitlab::Ci::Reports::TestReports.new }
- let(:head_reports) { Gitlab::Ci::Reports::TestReports.new }
+ let(:base_reports) { Gitlab::Ci::Reports::TestReport.new }
+ let(:head_reports) { Gitlab::Ci::Reports::TestReport.new }
describe '#suite_comparers' do
subject { comparer.suite_comparers }
diff --git a/spec/lib/gitlab/ci/runner/metrics_spec.rb b/spec/lib/gitlab/ci/runner/metrics_spec.rb
new file mode 100644
index 00000000000..3c459271092
--- /dev/null
+++ b/spec/lib/gitlab/ci/runner/metrics_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Runner::Metrics, :prometheus do
+ subject { described_class.new }
+
+ describe '#increment_runner_authentication_success_counter' do
+ it 'increments count for same type' do
+ expect { subject.increment_runner_authentication_success_counter(runner_type: 'instance_type') }
+ .to change { described_class.runner_authentication_success_counter.get(runner_type: 'instance_type') }.by(1)
+ end
+
+ it 'does not increment count for different type' do
+ expect { subject.increment_runner_authentication_success_counter(runner_type: 'group_type') }
+ .to not_change { described_class.runner_authentication_success_counter.get(runner_type: 'project_type') }
+ end
+
+ it 'does not increment failure count' do
+ expect { subject.increment_runner_authentication_success_counter(runner_type: 'project_type') }
+ .to not_change { described_class.runner_authentication_failure_counter.get }
+ end
+
+ it 'throws ArgumentError for invalid runner type' do
+ expect { subject.increment_runner_authentication_success_counter(runner_type: 'unknown_type') }
+ .to raise_error(ArgumentError, 'unknown runner type: unknown_type')
+ end
+ end
+
+ describe '#increment_runner_authentication_failure_counter' do
+ it 'increments count' do
+ expect { subject.increment_runner_authentication_failure_counter }
+ .to change { described_class.runner_authentication_failure_counter.get }.by(1)
+ end
+
+ it 'does not increment success count' do
+ expect { subject.increment_runner_authentication_failure_counter }
+ .to not_change { described_class.runner_authentication_success_counter.get(runner_type: 'instance_type') }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/runner_releases_spec.rb b/spec/lib/gitlab/ci/runner_releases_spec.rb
index 9e4a8739c0f..576eb02ad83 100644
--- a/spec/lib/gitlab/ci/runner_releases_spec.rb
+++ b/spec/lib/gitlab/ci/runner_releases_spec.rb
@@ -5,16 +5,25 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::RunnerReleases do
subject { described_class.instance }
- describe '#releases' do
- before do
- subject.reset!
+ let(:runner_releases_url) { 'the release API URL' }
- stub_application_setting(public_runner_releases_url: 'the release API URL')
- allow(Gitlab::HTTP).to receive(:try_get).with('the release API URL').once { mock_http_response(response) }
- end
+ def releases
+ subject.releases
+ end
+
+ def releases_by_minor
+ subject.releases_by_minor
+ end
+
+ before do
+ subject.reset_backoff!
- def releases
- subject.releases
+ stub_application_setting(public_runner_releases_url: runner_releases_url)
+ end
+
+ describe 'caching behavior', :use_clean_rails_memory_store_caching do
+ before do
+ allow(Gitlab::HTTP).to receive(:get).with(runner_releases_url, anything).once { mock_http_response(response) }
end
shared_examples 'requests that follow cache status' do |validity_period|
@@ -25,9 +34,14 @@ RSpec.describe Gitlab::Ci::RunnerReleases do
releases
travel followup_request_interval do
- expect(Gitlab::HTTP).not_to receive(:try_get)
+ expect(Gitlab::HTTP).not_to receive(:get)
- expect(releases).to eq(expected_result)
+ if expected_releases
+ expected_result_by_minor = expected_releases.group_by(&:without_patch).transform_values(&:max)
+ end
+
+ expect(releases).to eq(expected_releases)
+ expect(releases_by_minor).to eq(expected_result_by_minor)
end
end
end
@@ -40,75 +54,189 @@ RSpec.describe Gitlab::Ci::RunnerReleases do
releases
travel followup_request_interval do
- expect(Gitlab::HTTP).to receive(:try_get).with('the release API URL').once { mock_http_response(followup_response) }
-
- expect(releases).to eq((expected_result || []) + [Gitlab::VersionInfo.new(14, 9, 2)])
+ expect(Gitlab::HTTP).to receive(:get)
+ .with(runner_releases_url, anything)
+ .once { mock_http_response(followup_response) }
+
+ new_releases = (expected_releases || []) + [Gitlab::VersionInfo.new(14, 9, 2)]
+ new_releases_by_minor_version = (expected_releases_by_minor || {}).merge(
+ Gitlab::VersionInfo.new(14, 9, 0) => Gitlab::VersionInfo.new(14, 9, 2)
+ )
+ expect(releases).to eq(new_releases)
+ expect(releases_by_minor).to eq(new_releases_by_minor_version)
end
end
end
end
- context 'when response is nil' do
- let(:response) { nil }
- let(:expected_result) { nil }
-
- it 'returns nil' do
- expect(releases).to be_nil
- end
-
- it_behaves_like 'requests that follow cache status', 5.seconds
-
+ shared_examples 'a service implementing exponential backoff' do |opts|
it 'performs exponential backoff on requests', :aggregate_failures do
start_time = Time.now.utc.change(usec: 0)
http_call_timestamp_offsets = []
- allow(Gitlab::HTTP).to receive(:try_get).with('the release API URL') do
+ allow(Gitlab::HTTP).to receive(:get).with(runner_releases_url, anything) do
http_call_timestamp_offsets << Time.now.utc - start_time
+
+ raise Net::OpenTimeout if opts&.dig(:raise_timeout)
+
mock_http_response(response)
end
# An initial HTTP request fails
travel_to(start_time)
- subject.reset!
+ subject.reset_backoff!
expect(releases).to be_nil
+ expect(releases_by_minor).to be_nil
# Successive failed requests result in HTTP requests only after specific backoff periods
backoff_periods = [5, 10, 20, 40, 80, 160, 320, 640, 1280, 2560, 3600].map(&:seconds)
backoff_periods.each do |period|
travel(period - 1.second)
expect(releases).to be_nil
+ expect(releases_by_minor).to be_nil
travel 1.second
expect(releases).to be_nil
+ expect(releases_by_minor).to be_nil
end
expect(http_call_timestamp_offsets).to eq([0, 5, 15, 35, 75, 155, 315, 635, 1275, 2555, 5115, 8715])
# Finally a successful HTTP request results in releases being returned
- allow(Gitlab::HTTP).to receive(:try_get).with('the release API URL').once { mock_http_response([{ 'name' => 'v14.9.1' }]) }
+ allow(Gitlab::HTTP).to receive(:get)
+ .with(runner_releases_url, anything)
+ .once { mock_http_response([{ 'name' => 'v14.9.1-beta1-ee' }]) }
travel 1.hour
expect(releases).not_to be_nil
+ expect(releases_by_minor).not_to be_nil
end
end
+ context 'when request results in timeout' do
+ let(:response) { }
+ let(:expected_releases) { nil }
+ let(:expected_releases_by_minor) { nil }
+
+ it_behaves_like 'requests that follow cache status', 5.seconds
+ it_behaves_like 'a service implementing exponential backoff', raise_timeout: true
+ end
+
+ context 'when response is nil' do
+ let(:response) { nil }
+ let(:expected_releases) { nil }
+ let(:expected_releases_by_minor) { nil }
+
+ it_behaves_like 'requests that follow cache status', 5.seconds
+ it_behaves_like 'a service implementing exponential backoff'
+ end
+
context 'when response is not nil' do
- let(:response) { [{ 'name' => 'v14.9.1' }, { 'name' => 'v14.9.0' }] }
- let(:expected_result) { [Gitlab::VersionInfo.new(14, 9, 0), Gitlab::VersionInfo.new(14, 9, 1)] }
+ let(:response) { [{ 'name' => 'v14.9.1-beta1-ee' }, { 'name' => 'v14.9.0' }] }
+ let(:expected_releases) do
+ [
+ Gitlab::VersionInfo.new(14, 9, 0),
+ Gitlab::VersionInfo.new(14, 9, 1, '-beta1-ee')
+ ]
+ end
+
+ let(:expected_releases_by_minor) do
+ {
+ Gitlab::VersionInfo.new(14, 9, 0) => Gitlab::VersionInfo.new(14, 9, 1, '-beta1-ee')
+ }
+ end
+
+ it_behaves_like 'requests that follow cache status', 1.day
+ end
+ end
+
+ describe '#releases', :use_clean_rails_memory_store_caching do
+ before do
+ allow(Gitlab::HTTP).to receive(:get).with(runner_releases_url, anything).once { mock_http_response(response) }
+ end
+
+ context 'when response is nil' do
+ let(:response) { nil }
+ let(:expected_result) { nil }
+
+ it 'returns nil' do
+ expect(releases).to be_nil
+ end
+ end
+
+ context 'when response is not nil' do
+ let(:response) { [{ 'name' => 'v14.9.1-beta1-ee' }, { 'name' => 'v14.9.0' }] }
+ let(:expected_result) do
+ [
+ Gitlab::VersionInfo.new(14, 9, 0),
+ Gitlab::VersionInfo.new(14, 9, 1, '-beta1-ee')
+ ]
+ end
it 'returns parsed and sorted Gitlab::VersionInfo objects' do
expect(releases).to eq(expected_result)
end
+ end
- it_behaves_like 'requests that follow cache status', 1.day
+ context 'when response contains unexpected input type' do
+ let(:response) { 'error' }
+
+ it { expect(releases).to be_nil }
+ end
+
+ context 'when response contains unexpected input array' do
+ let(:response) { ['error'] }
+
+ it { expect(releases).to be_nil }
+ end
+ end
+
+ describe '#releases_by_minor', :use_clean_rails_memory_store_caching do
+ before do
+ allow(Gitlab::HTTP).to receive(:get).with(runner_releases_url, anything).once { mock_http_response(response) }
end
- def mock_http_response(response)
- http_response = instance_double(HTTParty::Response)
+ context 'when response is nil' do
+ let(:response) { nil }
+ let(:expected_result) { nil }
- allow(http_response).to receive(:success?).and_return(response.present?)
- allow(http_response).to receive(:parsed_response).and_return(response)
+ it 'returns nil' do
+ expect(releases_by_minor).to be_nil
+ end
+ end
- http_response
+ context 'when response is not nil' do
+ let(:response) { [{ 'name' => 'v14.9.1-beta1-ee' }, { 'name' => 'v14.9.0' }, { 'name' => 'v14.8.1' }] }
+ let(:expected_result) do
+ {
+ Gitlab::VersionInfo.new(14, 8, 0) => Gitlab::VersionInfo.new(14, 8, 1),
+ Gitlab::VersionInfo.new(14, 9, 0) => Gitlab::VersionInfo.new(14, 9, 1, '-beta1-ee')
+ }
+ end
+
+ it 'returns parsed and grouped Gitlab::VersionInfo objects' do
+ expect(releases_by_minor).to eq(expected_result)
+ end
end
+
+ context 'when response contains unexpected input type' do
+ let(:response) { 'error' }
+
+ it { expect(releases_by_minor).to be_nil }
+ end
+
+ context 'when response contains unexpected input array' do
+ let(:response) { ['error'] }
+
+ it { expect(releases_by_minor).to be_nil }
+ end
+ end
+
+ def mock_http_response(response)
+ http_response = instance_double(HTTParty::Response)
+
+ allow(http_response).to receive(:success?).and_return(!response.nil?)
+ allow(http_response).to receive(:parsed_response).and_return(response)
+
+ http_response
end
end
diff --git a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
index 0353432741b..f2507a24b10 100644
--- a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
+++ b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
@@ -3,84 +3,156 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
- include StubVersion
using RSpec::Parameterized::TableSyntax
describe '#check_runner_upgrade_status' do
subject(:result) { described_class.instance.check_runner_upgrade_status(runner_version) }
+ let(:gitlab_version) { '14.1.1' }
+ let(:parsed_runner_version) { ::Gitlab::VersionInfo.parse(runner_version, parse_suffix: true) }
+
before do
- runner_releases_double = instance_double(Gitlab::Ci::RunnerReleases)
+ allow(described_class.instance).to receive(:gitlab_version)
+ .and_return(::Gitlab::VersionInfo.parse(gitlab_version))
+ end
+
+ context 'with failing Gitlab::Ci::RunnerReleases request' do
+ let(:runner_version) { '14.1.123' }
+ let(:runner_releases_double) { instance_double(Gitlab::Ci::RunnerReleases) }
+
+ before do
+ allow(Gitlab::Ci::RunnerReleases).to receive(:instance).and_return(runner_releases_double)
+ allow(runner_releases_double).to receive(:releases).and_return(nil)
+ end
- allow(Gitlab::Ci::RunnerReleases).to receive(:instance).and_return(runner_releases_double)
- allow(runner_releases_double).to receive(:releases).and_return(available_runner_releases.map { |v| ::Gitlab::VersionInfo.parse(v) })
+ it 'returns :error' do
+ is_expected.to eq({ error: parsed_runner_version })
+ end
end
- context 'with available_runner_releases configured up to 14.1.1' do
- let(:available_runner_releases) { %w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2 14.1.0 14.1.1 14.1.1-rc3] }
+ context 'with available_runner_releases configured' do
+ before do
+ url = ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url
- context 'with nil runner_version' do
- let(:runner_version) { nil }
+ WebMock.stub_request(:get, url).to_return(
+ body: available_runner_releases.map { |v| { name: v } }.to_json,
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' }
+ )
+ end
- it 'returns :invalid' do
- is_expected.to eq(:invalid)
+ context 'with no available runner releases' do
+ let(:available_runner_releases) do
+ %w[]
end
- end
- context 'with invalid runner_version' do
- let(:runner_version) { 'junk' }
+ context 'with Gitlab::VERSION set to 14.1.1' do
+ let(:gitlab_version) { '14.1.1' }
- it 'raises ArgumentError' do
- expect { subject }.to raise_error(ArgumentError)
+ context 'with runner_version from last minor release' do
+ let(:runner_version) { 'v14.0.1' }
+
+ it 'returns :not_available' do
+ is_expected.to eq({ not_available: parsed_runner_version })
+ end
+ end
end
end
- context 'with Gitlab::VERSION set to 14.1.123' do
- before do
- stub_version('14.1.123', 'deadbeef')
+ context 'up to 14.1.1' do
+ let(:available_runner_releases) do
+ %w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2-rc1 14.0.2 14.1.0 14.1.1]
+ end
+
+ context 'with nil runner_version' do
+ let(:runner_version) { nil }
- described_class.instance.reset!
+ it 'returns :invalid_version' do
+ is_expected.to match({ invalid_version: anything })
+ end
end
- context 'with a runner_version that is too recent' do
- let(:runner_version) { 'v14.2.0' }
+ context 'with invalid runner_version' do
+ let(:runner_version) { 'junk' }
- it 'returns :not_available' do
- is_expected.to eq(:not_available)
+ it 'returns :invalid_version' do
+ is_expected.to match({ invalid_version: anything })
end
end
- end
- context 'with Gitlab::VERSION set to 14.0.1' do
- before do
- stub_version('14.0.1', 'deadbeef')
+ context 'with Gitlab::VERSION set to 14.1.123' do
+ let(:gitlab_version) { '14.1.123' }
+
+ context 'with a runner_version that is too recent' do
+ let(:runner_version) { 'v14.2.0' }
- described_class.instance.reset!
+ it 'returns :not_available' do
+ is_expected.to eq({ not_available: parsed_runner_version })
+ end
+ end
+ end
+
+ context 'with Gitlab::VERSION set to 14.0.1' do
+ let(:gitlab_version) { '14.0.1' }
+
+ context 'with valid params' do
+ where(:runner_version, :expected_result, :expected_suggested_version) do
+ 'v15.0.0' | :not_available | '15.0.0' # not available since the GitLab instance is still on 14.x, a major version might be incompatible, and a patch upgrade is not available
+ 'v14.1.0-rc3' | :recommended | '14.1.1' # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes
+ 'v14.1.0~beta.1574.gf6ea9389' | :recommended | '14.1.1' # suffixes are correctly handled
+ 'v14.1.0/1.1.0' | :recommended | '14.1.1' # suffixes are correctly handled
+ 'v14.1.0' | :recommended | '14.1.1' # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes
+ 'v14.0.1' | :recommended | '14.0.2' # recommended upgrade since 14.0.2 is available
+ 'v14.0.2-rc1' | :recommended | '14.0.2' # recommended upgrade since 14.0.2 is available and we'll move out of a release candidate
+ 'v14.0.2' | :not_available | '14.0.2' # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version
+ 'v13.10.1' | :available | '14.0.2' # available upgrade: 14.0.2
+ 'v13.10.1~beta.1574.gf6ea9389' | :recommended | '13.10.1' # suffixes are correctly handled, official 13.10.1 is available
+ 'v13.10.1/1.1.0' | :recommended | '13.10.1' # suffixes are correctly handled, official 13.10.1 is available
+ 'v13.10.0' | :recommended | '13.10.1' # recommended upgrade since 13.10.1 is available
+ 'v13.9.2' | :recommended | '14.0.2' # recommended upgrade since backports are no longer released for this version
+ 'v13.9.0' | :recommended | '14.0.2' # recommended upgrade since backports are no longer released for this version
+ 'v13.8.1' | :recommended | '14.0.2' # recommended upgrade since build is too old (missing in records)
+ 'v11.4.1' | :recommended | '14.0.2' # recommended upgrade since build is too old (missing in records)
+ end
+
+ with_them do
+ it { is_expected.to eq({ expected_result => Gitlab::VersionInfo.parse(expected_suggested_version) }) }
+ end
+ end
end
- context 'with valid params' do
- where(:runner_version, :expected_result) do
- 'v15.0.0' | :not_available # not available since the GitLab instance is still on 14.x and a major version might be incompatible
- 'v14.1.0-rc3' | :recommended # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes
- 'v14.1.0~beta.1574.gf6ea9389' | :recommended # suffixes are correctly handled
- 'v14.1.0/1.1.0' | :recommended # suffixes are correctly handled
- 'v14.1.0' | :recommended # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes
- 'v14.0.1' | :recommended # recommended upgrade since 14.0.2 is available
- 'v14.0.2' | :not_available # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version
- 'v13.10.1' | :available # available upgrade: 14.1.1
- 'v13.10.1~beta.1574.gf6ea9389' | :available # suffixes are correctly handled
- 'v13.10.1/1.1.0' | :available # suffixes are correctly handled
- 'v13.10.0' | :recommended # recommended upgrade since 13.10.1 is available
- 'v13.9.2' | :recommended # recommended upgrade since backports are no longer released for this version
- 'v13.9.0' | :recommended # recommended upgrade since backports are no longer released for this version
- 'v13.8.1' | :recommended # recommended upgrade since build is too old (missing in records)
- 'v11.4.1' | :recommended # recommended upgrade since build is too old (missing in records)
+ context 'with Gitlab::VERSION set to 13.9.0' do
+ let(:gitlab_version) { '13.9.0' }
+
+ context 'with valid params' do
+ where(:runner_version, :expected_result, :expected_suggested_version) do
+ 'v14.0.0' | :recommended | '14.0.2' # recommended upgrade since 14.0.2 is available, even though the GitLab instance is still on 13.x and a major version might be incompatible
+ 'v13.10.1' | :not_available | '13.10.1' # not available since 13.10.1 is already ahead of GitLab instance version and is the latest patch update for 13.10.x
+ 'v13.10.0' | :recommended | '13.10.1' # recommended upgrade since 13.10.1 is available
+ 'v13.9.2' | :not_available | '13.9.2' # not_available even though backports are no longer released for this version because the runner is already on the same version as the GitLab version
+ 'v13.9.0' | :recommended | '13.9.2' # recommended upgrade since backports are no longer released for this version
+ 'v13.8.1' | :recommended | '13.9.2' # recommended upgrade since build is too old (missing in records)
+ 'v11.4.1' | :recommended | '13.9.2' # recommended upgrade since build is too old (missing in records)
+ end
+
+ with_them do
+ it { is_expected.to eq({ expected_result => Gitlab::VersionInfo.parse(expected_suggested_version) }) }
+ end
end
+ end
+ end
+
+ context 'up to 15.1.0' do
+ let(:available_runner_releases) { %w[14.9.1 14.9.2 14.10.0 14.10.1 15.0.0 15.1.0] }
+
+ context 'with Gitlab::VERSION set to 15.2.0-pre' do
+ let(:gitlab_version) { '15.2.0-pre' }
+
+ context 'with unknown runner version' do
+ let(:runner_version) { '14.11.0~beta.29.gd0c550e3' }
- with_them do
- it 'returns symbol representing expected upgrade status' do
- is_expected.to be_a(Symbol)
- is_expected.to eq(expected_result)
+ it 'recommends 15.1.0 since 14.11 is an unknown release and 15.1.0 is available' do
+ is_expected.to eq({ recommended: Gitlab::VersionInfo.new(15, 1, 0) })
end
end
end
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index e0f5531f370..35d44281072 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -7,9 +7,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
- let(:stage) do
- build(:ci_stage, pipeline: pipeline, name: 'test')
- end
+ let(:stage) { create(:ci_stage, pipeline: pipeline) }
subject do
described_class.new(stage, user)
@@ -26,11 +24,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory do
context 'when stage has a core status' do
(Ci::HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status|
context "when core status is #{core_status}" do
- before do
- create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status)
- create(:commit_status, pipeline: pipeline, stage: 'test', status: core_status)
- create(:ci_build, pipeline: pipeline, stage: 'build', status: :failed)
- end
+ let(:stage) { create(:ci_stage, pipeline: pipeline, status: core_status) }
it "fabricates a core status #{core_status}" do
expect(status).to be_a(
@@ -48,12 +42,12 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory do
context 'when stage has warnings' do
let(:stage) do
- build(:ci_stage, name: 'test', status: :success, pipeline: pipeline)
+ create(:ci_stage, status: :success, pipeline: pipeline)
end
before do
create(:ci_build, :allowed_to_fail, :failed,
- stage: 'test', pipeline: stage.pipeline)
+ stage_id: stage.id, pipeline: stage.pipeline)
end
it 'fabricates extended "success with warnings" status' do
@@ -70,11 +64,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory do
context 'when stage has manual builds' do
(Ci::HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status|
context "when status is #{core_status}" do
- before do
- create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status)
- create(:commit_status, pipeline: pipeline, stage: 'test', status: core_status)
- create(:ci_build, pipeline: pipeline, stage: 'build', status: :manual)
- end
+ let(:stage) { create(:ci_stage, pipeline: pipeline, status: core_status) }
it 'fabricates a play manual status' do
expect(status).to be_a(Gitlab::Ci::Status::Stage::PlayManual)
diff --git a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
index 25b79ff2099..9fdaddc083e 100644
--- a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::PlayManual do
end
describe '#action_path' do
- let(:stage) { create(:ci_stage_entity, status: 'manual') }
+ let(:stage) { create(:ci_stage, status: 'manual') }
let(:pipeline) { stage.pipeline }
let(:play_manual) { stage.detailed_status(create(:user)) }
@@ -46,25 +46,25 @@ RSpec.describe Gitlab::Ci::Status::Stage::PlayManual do
subject { described_class.matches?(stage, user) }
context 'when stage is skipped' do
- let(:stage) { create(:ci_stage_entity, status: :skipped) }
+ let(:stage) { create(:ci_stage, status: :skipped) }
it { is_expected.to be_truthy }
end
context 'when stage is manual' do
- let(:stage) { create(:ci_stage_entity, status: :manual) }
+ let(:stage) { create(:ci_stage, status: :manual) }
it { is_expected.to be_truthy }
end
context 'when stage is scheduled' do
- let(:stage) { create(:ci_stage_entity, status: :scheduled) }
+ let(:stage) { create(:ci_stage, status: :scheduled) }
it { is_expected.to be_truthy }
end
context 'when stage is success' do
- let(:stage) { create(:ci_stage_entity, status: :success) }
+ let(:stage) { create(:ci_stage, status: :success) }
context 'and does not have manual builds' do
it { is_expected.to be_falsy }
diff --git a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb
index 6c4f69fb036..5ab859241c6 100644
--- a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb
+++ b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do
let(:error_message) do
<<~MESSAGE
A mechanism depending on internals of 'act-as-taggable-on` has been designed
- to bulk insert tags for Ci::Build records.
+ to bulk insert tags for Ci::Build/Ci::Runner records.
Please review the code carefully before updating the gem version
https://gitlab.com/gitlab-org/gitlab/-/issues/350053
MESSAGE
@@ -27,6 +27,21 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do
it { expect(ActsAsTaggableOn::VERSION).to eq(acceptable_version), error_message }
end
+ describe '.bulk_insert_tags!' do
+ let(:inserter) { instance_double(described_class) }
+
+ it 'delegates to bulk insert class' do
+ expect(Gitlab::Ci::Tags::BulkInsert)
+ .to receive(:new)
+ .with(statuses)
+ .and_return(inserter)
+
+ expect(inserter).to receive(:insert!)
+
+ described_class.bulk_insert_tags!(statuses)
+ end
+ end
+
describe '#insert!' do
context 'without tags' do
it { expect(service.insert!).to be_falsey }
@@ -44,6 +59,50 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do
expect(job.reload.tag_list).to match_array(%w[tag1 tag2])
expect(other_job.reload.tag_list).to match_array(%w[tag2 tag3 tag4])
end
+
+ it 'persists taggings' do
+ service.insert!
+
+ expect(job.taggings.size).to eq(2)
+ expect(other_job.taggings.size).to eq(3)
+
+ expect(Ci::Build.tagged_with('tag1')).to include(job)
+ expect(Ci::Build.tagged_with('tag2')).to include(job, other_job)
+ expect(Ci::Build.tagged_with('tag3')).to include(other_job)
+ end
+
+ it 'strips tags' do
+ job.tag_list = [' taga', 'tagb ', ' tagc ']
+
+ service.insert!
+ expect(job.tags.map(&:name)).to match_array(%w[taga tagb tagc])
+ end
+
+ context 'when batching inserts for tags' do
+ before do
+ stub_const("#{described_class}::TAGS_BATCH_SIZE", 2)
+ end
+
+ it 'inserts tags in batches' do
+ recorder = ActiveRecord::QueryRecorder.new { service.insert! }
+ count = recorder.log.count { |query| query.include?('INSERT INTO "tags"') }
+
+ expect(count).to eq(2)
+ end
+ end
+
+ context 'when batching inserts for taggings' do
+ before do
+ stub_const("#{described_class}::TAGGINGS_BATCH_SIZE", 2)
+ end
+
+ it 'inserts taggings in batches' do
+ recorder = ActiveRecord::QueryRecorder.new { service.insert! }
+ count = recorder.log.count { |query| query.include?('INSERT INTO "taggings"') }
+
+ expect(count).to eq(3)
+ end
+ end
end
context 'with tags for only one job' do
@@ -57,6 +116,15 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do
expect(job.reload.tag_list).to match_array(%w[tag1 tag2])
expect(other_job.reload.tag_list).to be_empty
end
+
+ it 'persists taggings' do
+ service.insert!
+
+ expect(job.taggings.size).to eq(2)
+
+ expect(Ci::Build.tagged_with('tag1')).to include(job)
+ expect(Ci::Build.tagged_with('tag2')).to include(job)
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb
index 27de8324206..65fd2b016ac 100644
--- a/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb
@@ -34,6 +34,16 @@ RSpec.describe 'Deploy-ECS.gitlab-ci.yml' do
expect(build_names).to include('production_ecs')
end
+ context 'when the DAST template is also included' do
+ let(:dast_template) { Gitlab::Template::GitlabCiYmlTemplate.find('Security/DAST') }
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content + dast_template.content)
+ end
+
+ include_examples 'no pipeline yaml error'
+ end
+
context 'when running a pipeline for a branch' do
let(:pipeline_branch) { 'test_branch' }
diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb
index b0704ad7f50..8ec0846bdca 100644
--- a/spec/lib/gitlab/ci/variables/builder_spec.rb
+++ b/spec/lib/gitlab/ci/variables/builder_spec.rb
@@ -166,9 +166,8 @@ RSpec.describe Gitlab::Ci::Variables::Builder do
allow(builder).to receive(:secret_instance_variables) { [var('J', 10), var('K', 10)] }
allow(builder).to receive(:secret_group_variables) { [var('K', 11), var('L', 11)] }
allow(builder).to receive(:secret_project_variables) { [var('L', 12), var('M', 12)] }
- allow(job).to receive(:trigger_request) { double(user_variables: [var('M', 13), var('N', 13)]) }
- allow(pipeline).to receive(:variables) { [var('N', 14), var('O', 14)] }
- allow(pipeline).to receive(:pipeline_schedule) { double(job_variables: [var('O', 15), var('P', 15)]) }
+ allow(pipeline).to receive(:variables) { [var('M', 13), var('N', 13)] }
+ allow(pipeline).to receive(:pipeline_schedule) { double(job_variables: [var('N', 14), var('O', 14)]) }
end
it 'returns variables in order depending on resource hierarchy' do
@@ -185,8 +184,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder do
var('K', 11), var('L', 11),
var('L', 12), var('M', 12),
var('M', 13), var('N', 13),
- var('N', 14), var('O', 14),
- var('O', 15), var('P', 15)])
+ var('N', 14), var('O', 14)])
end
it 'overrides duplicate keys depending on resource hierarchy' do
@@ -198,7 +196,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder do
'I' => '9', 'J' => '10',
'K' => '11', 'L' => '12',
'M' => '13', 'N' => '14',
- 'O' => '15', 'P' => '15')
+ 'O' => '14')
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor/feature_flags_spec.rb b/spec/lib/gitlab/ci/yaml_processor/feature_flags_spec.rb
new file mode 100644
index 00000000000..0bd9563d191
--- /dev/null
+++ b/spec/lib/gitlab/ci/yaml_processor/feature_flags_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::YamlProcessor::FeatureFlags do
+ let(:feature_flag) { :my_feature_flag }
+
+ context 'when the actor is set' do
+ let(:actor) { double }
+ let(:another_actor) { double }
+
+ it 'checks the feature flag using the given actor' do
+ described_class.with_actor(actor) do
+ expect(Feature).to receive(:enabled?).with(feature_flag, actor)
+
+ described_class.enabled?(feature_flag)
+ end
+ end
+
+ it 'returns the value of the block' do
+ result = described_class.with_actor(actor) do
+ :test
+ end
+
+ expect(result).to eq(:test)
+ end
+
+ it 'restores the existing actor if any' do
+ described_class.with_actor(actor) do
+ described_class.with_actor(another_actor) do
+ expect(Feature).to receive(:enabled?).with(feature_flag, another_actor)
+
+ described_class.enabled?(feature_flag)
+ end
+
+ expect(Feature).to receive(:enabled?).with(feature_flag, actor)
+ described_class.enabled?(feature_flag)
+ end
+ end
+
+ it 'restores the actor to nil after the block' do
+ described_class.with_actor(actor) do
+ expect(Thread.current[described_class::ACTOR_KEY]).to eq(actor)
+ end
+
+ expect(Thread.current[described_class::ACTOR_KEY]).to be nil
+ end
+ end
+
+ context 'when feature flag is checked outside the "with_actor" block' do
+ it 'raises an error on dev/test environment' do
+ expect { described_class.enabled?(feature_flag) }.to raise_error(described_class::NoActorError)
+ end
+
+ context 'when on production' do
+ before do
+ allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false)
+ end
+
+ it 'checks the feature flag without actor' do
+ expect(Feature).to receive(:enabled?).with(feature_flag, nil)
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_and_raise_for_dev_exception)
+ .and_call_original
+
+ described_class.enabled?(feature_flag)
+ end
+ end
+ end
+
+ context 'when actor is explicitly nil' do
+ it 'checks the feature flag without actor' do
+ described_class.with_actor(nil) do
+ expect(Feature).to receive(:enabled?).with(feature_flag, nil)
+
+ described_class.enabled?(feature_flag)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 3dd9ca35881..22bc6b0db59 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -70,7 +70,7 @@ module Gitlab
options: { script: ['rspec'] },
rules: [
{ if: '$CI_COMMIT_REF_NAME == "master"' },
- { changes: %w[README.md] }
+ { changes: { paths: %w[README.md] } }
],
allow_failure: false,
when: 'on_success',
@@ -980,7 +980,7 @@ module Gitlab
it { is_expected.to be_valid }
- it "returns image and service when defined" do
+ it "returns with image" do
expect(processor.stage_builds_attributes("test")).to contain_exactly({
stage: "test",
stage_idx: 2,
@@ -1010,6 +1010,51 @@ module Gitlab
end
end
end
+
+ context 'when a service has pull_policy' do
+ let(:config) do
+ <<~YAML
+ services:
+ - name: postgres:11.9
+ pull_policy: if-not-present
+
+ test:
+ script: exit 0
+ YAML
+ end
+
+ it { is_expected.to be_valid }
+
+ it "returns with service" do
+ expect(processor.stage_builds_attributes("test")).to contain_exactly({
+ stage: "test",
+ stage_idx: 2,
+ name: "test",
+ only: { refs: %w[branches tags] },
+ options: {
+ script: ["exit 0"],
+ services: [{ name: "postgres:11.9", pull_policy: ["if-not-present"] }]
+ },
+ allow_failure: false,
+ when: "on_success",
+ job_variables: [],
+ root_variables_inheritance: true,
+ scheduling_type: :stage
+ })
+ end
+
+ context 'when the feature flag ci_docker_image_pull_policy is disabled' do
+ before do
+ stub_feature_flags(ci_docker_image_pull_policy: false)
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it "returns no job" do
+ expect(processor.jobs).to eq({})
+ end
+ end
+ end
end
describe 'Variables' do
@@ -2848,6 +2893,51 @@ module Gitlab
end
end
+ describe 'Rules' do
+ context 'changes' do
+ let(:config) do
+ <<~YAML
+ rspec:
+ script: exit 0
+ rules:
+ - changes: [README.md]
+ YAML
+ end
+
+ it 'returns builds with correct rules' do
+ expect(processor.builds.size).to eq(1)
+ expect(processor.builds[0]).to match(
+ hash_including(
+ name: "rspec",
+ rules: [{ changes: { paths: ["README.md"] } }]
+ )
+ )
+ end
+
+ context 'with paths' do
+ let(:config) do
+ <<~YAML
+ rspec:
+ script: exit 0
+ rules:
+ - changes:
+ paths: [README.md]
+ YAML
+ end
+
+ it 'returns builds with correct rules' do
+ expect(processor.builds.size).to eq(1)
+ expect(processor.builds[0]).to match(
+ hash_including(
+ name: "rspec",
+ rules: [{ changes: { paths: ["README.md"] } }]
+ )
+ )
+ end
+ end
+ end
+ end
+
describe '#execute' do
subject { Gitlab::Ci::YamlProcessor.new(content).execute }
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 109e83be294..616fe15c1a6 100644
--- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
+++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
@@ -92,11 +92,11 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
context 'when sentry is configured' do
before do
stub_sentry_settings
- stub_config_setting(host: 'example.com')
+ stub_config_setting(host: 'gitlab.example.com')
end
it 'adds sentry path to CSP without user' do
- expect(directives['connect_src']).to eq("'self' ws://example.com dummy://example.com/43")
+ expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com dummy://example.com")
end
end
@@ -146,7 +146,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
let(:snowplow_micro_url) { "http://#{snowplow_micro_hostname}/" }
before do
- stub_env('SNOWPLOW_MICRO_ENABLE', 1)
+ stub_config(snowplow_micro: { enabled: true })
allow(Gitlab::Tracking).to receive(:collector_hostname).and_return(snowplow_micro_hostname)
end
@@ -169,9 +169,9 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
expect(directives['connect_src']).to match(Regexp.new(snowplow_micro_url))
end
- context 'when not enabled using ENV[SNOWPLOW_MICRO_ENABLE]' do
+ context 'when not enabled using config' do
before do
- stub_env('SNOWPLOW_MICRO_ENABLE', nil)
+ stub_config(snowplow_micro: { enabled: false })
end
it 'does not add Snowplow Micro URL to connect-src' do
@@ -220,10 +220,11 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
expect(policy.directives['base-uri']).to be_nil
end
- it 'returns default values for directives not defined by the user' do
+ it 'returns default values for directives not defined by the user or with <default_value> and disables directives set to false' do
# Explicitly disabling script_src and setting report_uri
csp_config[:directives] = {
script_src: false,
+ style_src: '<default_value>',
report_uri: 'https://example.org'
}
diff --git a/spec/lib/gitlab/data_builder/deployment_spec.rb b/spec/lib/gitlab/data_builder/deployment_spec.rb
index e8fe80f75cb..8ee57542d43 100644
--- a/spec/lib/gitlab/data_builder/deployment_spec.rb
+++ b/spec/lib/gitlab/data_builder/deployment_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
it 'returns the object kind for a deployment' do
deployment = build(:deployment, deployable: nil, environment: create(:environment))
- data = described_class.build(deployment, Time.current)
+ data = described_class.build(deployment, 'success', Time.current)
expect(data[:object_kind]).to eq('deployment')
end
@@ -23,7 +23,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
expected_commit_url = Gitlab::UrlBuilder.build(commit)
status_changed_at = Time.current
- data = described_class.build(deployment, status_changed_at)
+ data = described_class.build(deployment, 'failed', status_changed_at)
expect(data[:status]).to eq('failed')
expect(data[:status_changed_at]).to eq(status_changed_at)
@@ -42,7 +42,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
it 'does not include the deployable URL when there is no deployable' do
deployment = create(:deployment, status: :failed, deployable: nil)
- data = described_class.build(deployment, Time.current)
+ data = described_class.build(deployment, 'failed', Time.current)
expect(data[:deployable_url]).to be_nil
end
@@ -51,7 +51,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:deployment) { create(:deployment, project: project) }
- subject(:data) { described_class.build(deployment, Time.current) }
+ subject(:data) { described_class.build(deployment, 'created', Time.current) }
before(:all) do
project.repository.remove
@@ -69,7 +69,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do
context 'when deployed_by is nil' do
let_it_be(:deployment) { create(:deployment, user: nil, deployable: nil) }
- subject(:data) { described_class.build(deployment, Time.current) }
+ subject(:data) { described_class.build(deployment, 'created', Time.current) }
before(:all) do
deployment.user = nil
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index c2bd20798f1..469812c80fc 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -36,6 +36,7 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
expect(build_data).to be_a(Hash)
expect(build_data[:id]).to eq(build.id)
expect(build_data[:status]).to eq(build.status)
+ expect(build_data[:failure_reason]).to be_nil
expect(build_data[:allow_failure]).to eq(build.allow_failure)
expect(build_data[:environment]).to be_nil
expect(runner_data).to eq(nil)
@@ -197,4 +198,14 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
end
end
end
+
+ describe '.build failed' do
+ let(:build) { create(:ci_build, :failed, pipeline: pipeline, failure_reason: :script_failure) }
+ let(:data) { described_class.build(pipeline) }
+ let(:build_data) { data[:builds].last }
+
+ it 'has failure_reason' do
+ expect(build_data[:failure_reason]).to eq(build.failure_reason)
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb
index c39f6a78e93..a7b3670da7c 100644
--- a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb
@@ -220,6 +220,12 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d
expect(described_class.created_since(fixed_time)).to contain_exactly(stuck_job, failed_job, max_attempts_failed_job)
end
end
+
+ describe '.blocked_by_max_attempts' do
+ it 'returns blocked jobs' do
+ expect(described_class.blocked_by_max_attempts).to contain_exactly(max_attempts_failed_job)
+ end
+ end
end
describe 'delegated batched_migration attributes' do
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
index 97459d4a7be..b8ff78be333 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
@@ -14,6 +14,11 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
end
end
+ before do
+ allow(Gitlab::Database::BackgroundMigration::HealthStatus).to receive(:evaluate)
+ .and_return(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::Normal)
+ end
+
describe '#run_migration_job' do
shared_examples_for 'it has completed the migration' do
it 'does not create and run a migration job' do
@@ -59,13 +64,48 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
sub_batch_size: migration.sub_batch_size)
end
- it 'optimizes the migration after executing the job' do
- migration.update!(min_value: event1.id, max_value: event2.id)
+ context 'migration health' do
+ let(:health_status) { Gitlab::Database::BackgroundMigration::HealthStatus }
+ let(:stop_signal) { health_status::Signals::Stop.new(:indicator, reason: 'Take a break') }
+ let(:normal_signal) { health_status::Signals::Normal.new(:indicator, reason: 'All good') }
+ let(:not_available_signal) { health_status::Signals::NotAvailable.new(:indicator, reason: 'Indicator is disabled') }
+ let(:unknown_signal) { health_status::Signals::Unknown.new(:indicator, reason: 'Something went wrong') }
- expect(migration_wrapper).to receive(:perform).ordered
- expect(migration).to receive(:optimize!).ordered
+ before do
+ migration.update!(min_value: event1.id, max_value: event2.id)
+ expect(migration_wrapper).to receive(:perform)
+ end
- runner.run_migration_job(migration)
+ it 'puts migration on hold on stop signal' do
+ expect(health_status).to receive(:evaluate).and_return(stop_signal)
+
+ expect { runner.run_migration_job(migration) }.to change { migration.on_hold? }
+ .from(false).to(true)
+ end
+
+ it 'optimizes migration on normal signal' do
+ expect(health_status).to receive(:evaluate).and_return(normal_signal)
+
+ expect(migration).to receive(:optimize!)
+
+ expect { runner.run_migration_job(migration) }.not_to change { migration.on_hold? }
+ end
+
+ it 'optimizes migration on no signal' do
+ expect(health_status).to receive(:evaluate).and_return(not_available_signal)
+
+ expect(migration).to receive(:optimize!)
+
+ expect { runner.run_migration_job(migration) }.not_to change { migration.on_hold? }
+ end
+
+ it 'optimizes migration on unknown signal' do
+ expect(health_status).to receive(:evaluate).and_return(unknown_signal)
+
+ expect(migration).to receive(:optimize!)
+
+ expect { runner.run_migration_job(migration) }.not_to change { migration.on_hold? }
+ end
end
end
@@ -362,6 +402,8 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
.with(gitlab_schemas, 'CopyColumnUsingBackgroundMigrationJob', table_name, column_name, job_arguments)
.and_return(batched_migration)
+ expect(batched_migration).to receive(:reset_attempts_of_blocked_jobs!).and_call_original
+
expect(batched_migration).to receive(:finalize!).and_call_original
expect do
@@ -380,8 +422,15 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
end
context 'when migration fails to complete' do
+ let(:error_message) do
+ "Batched migration #{batched_migration.job_class_name} could not be completed and a manual action is required."\
+ "Check the admin panel at (`/admin/background_migrations`) for more details."
+ end
+
it 'raises an error' do
- batched_migration.batched_jobs.with_status(:failed).update_all(attempts: Gitlab::Database::BackgroundMigration::BatchedJob::MAX_ATTEMPTS)
+ allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:find_for_configuration).and_return(batched_migration)
+
+ allow(batched_migration).to receive(:finished?).and_return(false)
expect do
runner.finalize(
@@ -390,7 +439,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
column_name,
job_arguments
)
- end.to raise_error described_class::FailedToFinalize
+ end.to raise_error(described_class::FailedToFinalize, error_message)
end
end
end
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
index 8819171cfd0..55f607c0cb0 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
@@ -157,6 +157,27 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
end
+ describe '#reset_attempts_of_blocked_jobs!' do
+ let!(:migration) { create(:batched_background_migration) }
+ let(:max_attempts) { Gitlab::Database::BackgroundMigration::BatchedJob::MAX_ATTEMPTS }
+
+ before do
+ create(:batched_background_migration_job, attempts: max_attempts - 1, batched_migration: migration)
+ create(:batched_background_migration_job, attempts: max_attempts + 1, batched_migration: migration)
+ create(:batched_background_migration_job, attempts: max_attempts + 1, batched_migration: migration)
+ end
+
+ it 'sets the number of attempts to zero for blocked jobs' do
+ migration.reset_attempts_of_blocked_jobs!
+
+ expect(migration.batched_jobs.size).to eq(3)
+
+ migration.batched_jobs.blocked_by_max_attempts.each do |job|
+ expect(job.attempts).to be_zero
+ end
+ end
+ end
+
describe '#interval_elapsed?' do
context 'when the migration has no last_job' do
let(:batched_migration) { build(:batched_background_migration) }
@@ -322,6 +343,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
describe '#retry_failed_jobs!' do
let(:batched_migration) { create(:batched_background_migration, status: 'failed') }
+ let(:job_class) { Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJob }
subject(:retry_failed_jobs) { batched_migration.retry_failed_jobs! }
@@ -335,7 +357,8 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
anything,
batch_min_value: 6,
batch_size: 5,
- job_arguments: batched_migration.job_arguments
+ job_arguments: batched_migration.job_arguments,
+ job_class: job_class
).and_return([6, 10])
end
end
@@ -570,6 +593,30 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
end
+ describe '#on_hold?', :freeze_time do
+ subject { migration.on_hold? }
+
+ let(:migration) { create(:batched_background_migration) }
+
+ it 'returns false if no on_hold_until is set' do
+ migration.on_hold_until = nil
+
+ expect(subject).to be_falsey
+ end
+
+ it 'returns false if on_hold_until has passed' do
+ migration.on_hold_until = 1.minute.ago
+
+ expect(subject).to be_falsey
+ end
+
+ it 'returns true if on_hold_until is in the future' do
+ migration.on_hold_until = 1.minute.from_now
+
+ expect(subject).to be_truthy
+ end
+ end
+
describe '.for_configuration' do
let!(:attributes) do
{
diff --git a/spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb b/spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb
new file mode 100644
index 00000000000..21204814f17
--- /dev/null
+++ b/spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::AutovacuumActiveOnTable do
+ include Database::DatabaseHelpers
+
+ let(:connection) { Gitlab::Database.database_base_models[:main].connection }
+
+ around do |example|
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ example.run
+ end
+ end
+
+ describe '#evaluate' do
+ subject { described_class.new(context).evaluate }
+
+ before do
+ swapout_view_for_table(:postgres_autovacuum_activity)
+ end
+
+ let(:context) { Gitlab::Database::BackgroundMigration::HealthStatus::Context.new(tables) }
+ let(:tables) { [table] }
+ let(:table) { 'users' }
+
+ context 'without autovacuum activity' do
+ it 'returns Normal signal' do
+ expect(subject).to be_a(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::Normal)
+ end
+
+ it 'remembers the indicator class' do
+ expect(subject.indicator_class).to eq(described_class)
+ end
+ end
+
+ context 'with autovacuum activity' do
+ before do
+ create(:postgres_autovacuum_activity, table: table, table_identifier: "public.#{table}")
+ end
+
+ it 'returns Stop signal' do
+ expect(subject).to be_a(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::Stop)
+ end
+
+ it 'explains why' do
+ expect(subject.reason).to include('autovacuum running on: table public.users')
+ end
+
+ it 'remembers the indicator class' do
+ expect(subject.indicator_class).to eq(described_class)
+ end
+
+ it 'returns NoSignal signal in case the feature flag is disabled' do
+ stub_feature_flags(batched_migrations_health_status_autovacuum: false)
+
+ expect(subject).to be_a(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::NotAvailable)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/background_migration/health_status_spec.rb b/spec/lib/gitlab/database/background_migration/health_status_spec.rb
new file mode 100644
index 00000000000..6d0430dcbbb
--- /dev/null
+++ b/spec/lib/gitlab/database/background_migration/health_status_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::BackgroundMigration::HealthStatus do
+ let(:connection) { Gitlab::Database.database_base_models[:main].connection }
+
+ around do |example|
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ example.run
+ end
+ end
+
+ describe '.evaluate' do
+ subject(:evaluate) { described_class.evaluate(migration, indicator_class) }
+
+ let(:migration) { build(:batched_background_migration, :active) }
+
+ let(:health_status) { 'Gitlab::Database::BackgroundMigration::HealthStatus' }
+ let(:indicator_class) { class_double("#{health_status}::Indicators::AutovacuumActiveOnTable") }
+ let(:indicator) { instance_double("#{health_status}::Indicators::AutovacuumActiveOnTable") }
+
+ before do
+ allow(indicator_class).to receive(:new).with(migration.health_context).and_return(indicator)
+ end
+
+ it 'returns a signal' do
+ signal = instance_double("#{health_status}::Signals::Normal", log_info?: false)
+
+ expect(indicator).to receive(:evaluate).and_return(signal)
+
+ expect(evaluate).to eq(signal)
+ end
+
+ it 'logs interesting signals' do
+ signal = instance_double("#{health_status}::Signals::Stop", log_info?: true)
+
+ expect(indicator).to receive(:evaluate).and_return(signal)
+ expect(described_class).to receive(:log_signal).with(signal, migration)
+
+ evaluate
+ end
+
+ it 'does not log signals of no interest' do
+ signal = instance_double("#{health_status}::Signals::Normal", log_info?: false)
+
+ expect(indicator).to receive(:evaluate).and_return(signal)
+ expect(described_class).not_to receive(:log_signal)
+
+ evaluate
+ end
+
+ context 'on indicator error' do
+ let(:error) { RuntimeError.new('everything broken') }
+
+ before do
+ expect(indicator).to receive(:evaluate).and_raise(error)
+ end
+
+ it 'does not fail' do
+ expect { evaluate }.not_to raise_error
+ end
+
+ it 'returns Unknown signal' do
+ expect(evaluate).to be_an_instance_of(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::Unknown)
+ expect(evaluate.reason).to eq("unexpected error: everything broken (RuntimeError)")
+ end
+
+ it 'reports the exception to error tracking' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+ .with(error, migration_id: migration.id, job_class_name: migration.job_class_name)
+
+ evaluate
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/each_database_spec.rb b/spec/lib/gitlab/database/each_database_spec.rb
index 8345cdfb8fb..2a6eb8f779d 100644
--- a/spec/lib/gitlab/database/each_database_spec.rb
+++ b/spec/lib/gitlab/database/each_database_spec.rb
@@ -4,9 +4,10 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::EachDatabase do
describe '.each_database_connection', :add_ci_connection do
+ let(:database_base_models) { { main: ActiveRecord::Base, ci: Ci::ApplicationRecord }.with_indifferent_access }
+
before do
- allow(Gitlab::Database).to receive(:database_base_models)
- .and_return({ main: ActiveRecord::Base, ci: Ci::ApplicationRecord }.with_indifferent_access)
+ allow(Gitlab::Database).to receive(:database_base_models_with_gitlab_shared).and_return(database_base_models)
end
it 'yields each connection after connecting SharedModel' do
@@ -60,12 +61,20 @@ RSpec.describe Gitlab::Database::EachDatabase do
end
context 'when shared connections are not included' do
+ def clear_memoization(key)
+ Gitlab::Database.remove_instance_variable(key) if Gitlab::Database.instance_variable_defined?(key)
+ end
+
+ before do
+ allow(Gitlab::Database).to receive(:database_base_models).and_return(database_base_models)
+
+ # Clear the memoization because the return of Gitlab::Database#schemas_to_base_models depends stubbed value
+ clear_memoization(:@schemas_to_base_models)
+ clear_memoization(:@schemas_to_base_models_ee)
+ end
+
it 'only yields the unshared connections' do
- if Gitlab::Database.has_config?(:ci)
- expect(Gitlab::Database).to receive(:db_config_share_with).exactly(3).times.and_return(nil, 'main', 'main')
- else
- expect(Gitlab::Database).to receive(:db_config_share_with).twice.and_return(nil, 'main')
- end
+ expect(Gitlab::Database).to receive(:db_config_share_with).exactly(3).times.and_return(nil, 'main', 'main')
expect { |b| described_class.each_database_connection(include_shared: false, &b) }
.to yield_successive_args([ActiveRecord::Base.connection, 'main'])
@@ -79,7 +88,7 @@ RSpec.describe Gitlab::Database::EachDatabase do
let(:model2) { Class.new(Gitlab::Database::SharedModel) }
before do
- allow(Gitlab::Database).to receive(:database_base_models)
+ allow(Gitlab::Database).to receive(:database_base_models_with_gitlab_shared)
.and_return({ main: ActiveRecord::Base, ci: Ci::ApplicationRecord }.with_indifferent_access)
end
@@ -136,7 +145,7 @@ RSpec.describe Gitlab::Database::EachDatabase do
let(:ci_model) { Class.new(Ci::ApplicationRecord) }
before do
- allow(Gitlab::Database).to receive(:database_base_models)
+ allow(Gitlab::Database).to receive(:database_base_models_with_gitlab_shared)
.and_return({ main: ActiveRecord::Base, ci: Ci::ApplicationRecord }.with_indifferent_access)
allow(main_model).to receive_message_chain('connection_db_config.name').and_return('main')
diff --git a/spec/lib/gitlab/database/gitlab_schema_spec.rb b/spec/lib/gitlab/database/gitlab_schema_spec.rb
index 611b2fbad72..72950895022 100644
--- a/spec/lib/gitlab/database/gitlab_schema_spec.rb
+++ b/spec/lib/gitlab/database/gitlab_schema_spec.rb
@@ -3,26 +3,27 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::GitlabSchema do
describe '.tables_to_schema' do
- subject { described_class.tables_to_schema }
-
it 'all tables have assigned a known gitlab_schema' do
- is_expected.to all(
- match([be_a(String), be_in([:gitlab_internal, :gitlab_shared, :gitlab_main, :gitlab_ci])])
+ expect(described_class.tables_to_schema).to all(
+ match([be_a(String), be_in(Gitlab::Database.schemas_to_base_models.keys.map(&:to_sym))])
)
end
# This being run across different databases indirectly also tests
# a general consistency of structure across databases
- Gitlab::Database.database_base_models.each do |db_config_name, db_class|
- let(:db_data_sources) { db_class.connection.data_sources }
-
+ Gitlab::Database.database_base_models.select { |k, _| k != 'geo' }.each do |db_config_name, db_class|
context "for #{db_config_name} using #{db_class}" do
+ let(:db_data_sources) { db_class.connection.data_sources }
+
+ # The Geo database does not share the same structure as all decomposed databases
+ subject { described_class.tables_to_schema.select { |_, v| v != :gitlab_geo } }
+
it 'new data sources are added' do
missing_tables = db_data_sources.to_set - subject.keys
expect(missing_tables).to be_empty, \
"Missing table(s) #{missing_tables.to_a} not found in #{described_class}.tables_to_schema. " \
- "Any new tables must be added to lib/gitlab/database/gitlab_schemas.yml."
+ "Any new tables must be added to #{described_class::GITLAB_SCHEMAS_FILE}."
end
it 'non-existing data sources are removed' do
@@ -30,7 +31,7 @@ RSpec.describe Gitlab::Database::GitlabSchema do
expect(extra_tables).to be_empty, \
"Extra table(s) #{extra_tables.to_a} found in #{described_class}.tables_to_schema. " \
- "Any removed or renamed tables must be removed from lib/gitlab/database/gitlab_schemas.yml."
+ "Any removed or renamed tables must be removed from #{described_class::GITLAB_SCHEMAS_FILE}."
end
end
end
diff --git a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb
index ed11699e494..87a3e0f81e4 100644
--- a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb
@@ -63,19 +63,22 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do
Gitlab::Database.schemas_to_base_models.fetch(parent_table_schema)
end
- it 'all `to_table` tables are present' do
+ it 'all `to_table` tables are present', :aggregate_failures do
definitions.each do |definition|
base_models_for(definition.to_table).each do |model|
- expect(model.connection).to be_table_exist(definition.to_table)
+ expect(model.connection).to be_table_exist(definition.to_table),
+ "Table #{definition.from_table} does not exist"
end
end
end
- it 'all `from_table` tables are present' do
+ it 'all `from_table` tables are present', :aggregate_failures do
definitions.each do |definition|
base_models_for(definition.from_table).each do |model|
- expect(model.connection).to be_table_exist(definition.from_table)
- expect(model.connection).to be_column_exist(definition.from_table, definition.column)
+ expect(model.connection).to be_table_exist(definition.from_table),
+ "Table #{definition.from_table} does not exist"
+ expect(model.connection).to be_column_exist(definition.from_table, definition.column),
+ "Column #{definition.column} in #{definition.from_table} does not exist"
end
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index e09016b2b2b..3ccc3a17862 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -2477,6 +2477,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
describe '#backfill_iids' do
include MigrationsHelpers
+ let_it_be(:issue_base_type_enum) { 0 }
+ let_it_be(:issue_type) { table(:work_item_types).find_by(base_type: issue_base_type_enum) }
+
let(:issue_class) do
Class.new(ActiveRecord::Base) do
include AtomicInternalId
@@ -2490,6 +2493,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
scope: :project,
init: ->(s, _scope) { s&.project&.issues&.maximum(:iid) },
presence: false
+
+ before_validation -> { self.work_item_type_id = ::WorkItems::Type.default_issue_type.id }
end
end
@@ -2515,7 +2520,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
it 'generates iids properly for models created after the migration when iids are backfilled' do
project = setup
- issue_a = issues.create!(project_id: project.id)
+ issue_a = issues.create!(project_id: project.id, work_item_type_id: issue_type.id)
model.backfill_iids('issues')
@@ -2528,14 +2533,14 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
it 'generates iids properly for models created after the migration across multiple projects' do
project_a = setup
project_b = setup
- issues.create!(project_id: project_a.id)
- issues.create!(project_id: project_b.id)
- issues.create!(project_id: project_b.id)
+ issues.create!(project_id: project_a.id, work_item_type_id: issue_type.id)
+ issues.create!(project_id: project_b.id, work_item_type_id: issue_type.id)
+ issues.create!(project_id: project_b.id, work_item_type_id: issue_type.id)
model.backfill_iids('issues')
- issue_a = issue_class.create!(project_id: project_a.id)
- issue_b = issue_class.create!(project_id: project_b.id)
+ issue_a = issue_class.create!(project_id: project_a.id, work_item_type_id: issue_type.id)
+ issue_b = issue_class.create!(project_id: project_b.id, work_item_type_id: issue_type.id)
expect(issue_a.iid).to eq(2)
expect(issue_b.iid).to eq(3)
@@ -2545,7 +2550,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
it 'generates an iid' do
project_a = setup
project_b = setup
- issue_a = issues.create!(project_id: project_a.id)
+ issue_a = issues.create!(project_id: project_a.id, work_item_type_id: issue_type.id)
model.backfill_iids('issues')
@@ -2559,8 +2564,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
context 'when a row already has an iid set in the database' do
it 'backfills iids' do
project = setup
- issue_a = issues.create!(project_id: project.id, iid: 1)
- issue_b = issues.create!(project_id: project.id, iid: 2)
+ issue_a = issues.create!(project_id: project.id, work_item_type_id: issue_type.id, iid: 1)
+ issue_b = issues.create!(project_id: project.id, work_item_type_id: issue_type.id, iid: 2)
model.backfill_iids('issues')
@@ -2571,9 +2576,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
it 'backfills for multiple projects' do
project_a = setup
project_b = setup
- issue_a = issues.create!(project_id: project_a.id, iid: 1)
- issue_b = issues.create!(project_id: project_b.id, iid: 1)
- issue_c = issues.create!(project_id: project_a.id, iid: 2)
+ issue_a = issues.create!(project_id: project_a.id, work_item_type_id: issue_type.id, iid: 1)
+ issue_b = issues.create!(project_id: project_b.id, work_item_type_id: issue_type.id, iid: 1)
+ issue_c = issues.create!(project_id: project_a.id, work_item_type_id: issue_type.id, iid: 2)
model.backfill_iids('issues')
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 f3414727245..5bfb2516ba1 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
@@ -173,17 +173,6 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
expect(Gitlab::Database::BackgroundMigration::BatchedMigration.last).to be_finished
end
-
- context 'when within transaction' do
- before do
- allow(migration).to receive(:transaction_open?).and_return(true)
- end
-
- it 'does raise an exception' do
- expect { migration.queue_batched_background_migration('MyJobClass', :events, :id, job_interval: 5.minutes)}
- .to raise_error /`queue_batched_background_migration` cannot be run inside a transaction./
- end
- end
end
end
@@ -301,12 +290,8 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
end
describe '#delete_batched_background_migration' do
- let(:transaction_open) { false }
-
before do
expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_dml_mode!)
-
- allow(migration).to receive(:transaction_open?).and_return(transaction_open)
end
context 'when migration exists' do
@@ -360,15 +345,6 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
end.not_to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }
end
end
-
- context 'when within transaction' do
- let(:transaction_open) { true }
-
- it 'raises an exception' do
- expect { migration.delete_batched_background_migration('MyJobClass', :projects, :id, [[:id], [:id_convert_to_bigint]]) }
- .to raise_error /`#delete_batched_background_migration` cannot be run inside a transaction./
- end
- end
end
describe '#gitlab_schema_from_context' do
diff --git a/spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb b/spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb
index d197f39be40..c6327de98d1 100644
--- a/spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb
+++ b/spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Gitlab::Database::Migrations::ReestablishedConnectionStack do
end
describe '#with_restored_connection_stack' do
- Gitlab::Database.database_base_models.each do |db_config_name, _|
+ Gitlab::Database.database_base_models_with_gitlab_shared.each do |db_config_name, _|
context db_config_name do
it_behaves_like "reconfigures connection stack", db_config_name do
it 'does restore connection hierarchy' do
diff --git a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb
index 2f3d44f6f8f..f1f72d71e1a 100644
--- a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb
+++ b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb
@@ -68,10 +68,10 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
end
context 'with multiple jobs to run' do
- it 'runs all jobs created within the last 48 hours' do
+ it 'runs all jobs created within the last 3 hours' do
old_migration = define_background_migration(migration_name)
- travel 3.days
+ travel 4.hours
new_migration = define_background_migration('NewMigration') { travel 1.second }
migration.queue_batched_background_migration('NewMigration', table_name, :id,
diff --git a/spec/lib/gitlab/database/postgres_autovacuum_activity_spec.rb b/spec/lib/gitlab/database/postgres_autovacuum_activity_spec.rb
new file mode 100644
index 00000000000..c1ac8f0c9cd
--- /dev/null
+++ b/spec/lib/gitlab/database/postgres_autovacuum_activity_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::PostgresAutovacuumActivity, type: :model do
+ include Database::DatabaseHelpers
+
+ it { is_expected.to be_a Gitlab::Database::SharedModel }
+
+ describe '.for_tables' do
+ subject { described_class.for_tables(tables) }
+
+ let(:tables) { %w[foo test] }
+
+ before do
+ swapout_view_for_table(:postgres_autovacuum_activity)
+
+ # unrelated
+ create(:postgres_autovacuum_activity, table: 'bar')
+
+ tables.each do |table|
+ create(:postgres_autovacuum_activity, table: table)
+ end
+
+ expect(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_primary).and_yield
+ end
+
+ it 'returns autovacuum activity for queries tables' do
+ expect(subject.map(&:table).sort).to eq(tables)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/reindexing_spec.rb b/spec/lib/gitlab/database/reindexing_spec.rb
index 0c576505e07..976b9896dfa 100644
--- a/spec/lib/gitlab/database/reindexing_spec.rb
+++ b/spec/lib/gitlab/database/reindexing_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::Database::Reindexing do
include Database::DatabaseHelpers
describe '.invoke' do
- let(:databases) { Gitlab::Database.database_base_models }
+ let(:databases) { Gitlab::Database.database_base_models_with_gitlab_shared }
let(:databases_count) { databases.count }
it 'cleans up any leftover indexes' do
diff --git a/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb b/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
index e676e5fe034..68c29bad287 100644
--- a/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
+++ b/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
@@ -38,7 +38,11 @@ RSpec.describe Gitlab::DatabaseImporters::InstanceAdministrators::CreateGroup do
end
end
- context 'with application settings and admin users', :do_not_mock_admin_mode_setting do
+ context(
+ 'with application settings and admin users',
+ :do_not_mock_admin_mode_setting,
+ :do_not_stub_snowplow_by_default
+ ) do
let(:group) { result[:group] }
let(:application_setting) { Gitlab::CurrentSettings.current_application_settings }
@@ -109,7 +113,7 @@ RSpec.describe Gitlab::DatabaseImporters::InstanceAdministrators::CreateGroup do
admin2 = create(:user, :admin)
existing_group.add_owner(user)
- existing_group.add_users([admin1, admin2], Gitlab::Access::MAINTAINER)
+ existing_group.add_members([admin1, admin2], Gitlab::Access::MAINTAINER)
application_setting.instance_administrators_group_id = existing_group.id
end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 064613074cd..452a662bdcb 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -337,7 +337,7 @@ RSpec.describe Gitlab::Database do
let(:model2) { Class.new(base_model) }
before do
- allow(described_class).to receive(:database_base_models)
+ allow(described_class).to receive(:database_base_models_using_load_balancing)
.and_return({ model1: model1, model2: model2 }.with_indifferent_access)
end
diff --git a/spec/lib/gitlab/dependency_linker/base_linker_spec.rb b/spec/lib/gitlab/dependency_linker/base_linker_spec.rb
index 678d4a90e8d..2811bc859da 100644
--- a/spec/lib/gitlab/dependency_linker/base_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/base_linker_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe Gitlab::DependencyLinker::BaseLinker do
it 'only converts valid links' do
expect(subject).to eq(
<<~CONTENT
- <span><span>#{link('http://')}</span><span>#{link('\n', url: '%5Cn')}</span><span>#{link('javascript:alert(1)', url: nil)}</span></span>
+ <span><span>#{link('http://', url: nil)}</span><span>#{link('\n', url: nil)}</span><span>#{link('javascript:alert(1)', url: nil)}</span></span>
<span><span>#{link('https://gitlab.com/gitlab-org/gitlab')}</span></span>
CONTENT
)
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 34f4bdde3b5..28557aab830 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -129,6 +129,14 @@ RSpec.describe Gitlab::Diff::File do
expect(diff_file.rendered).to be_kind_of(Gitlab::Diff::Rendered::Notebook::DiffFile)
end
+ context 'when collapsed' do
+ it 'is nil' do
+ expect(diff).to receive(:collapsed?).and_return(true)
+
+ expect(diff_file.rendered).to be_nil
+ end
+ end
+
context 'when too large' do
it 'is nil' do
expect(diff).to receive(:too_large?).and_return(true)
diff --git a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
index 579776d44aa..73c0d0dba88 100644
--- a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
@@ -10,7 +10,6 @@ RSpec.describe Gitlab::Diff::Formatters::ImageFormatter do
head_sha: 789,
old_path: 'old_image.png',
new_path: 'new_image.png',
- file_identifier_hash: '777',
position_type: 'image'
}
end
diff --git a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
index b6bdc5ff493..290585d0991 100644
--- a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
@@ -10,7 +10,6 @@ RSpec.describe Gitlab::Diff::Formatters::TextFormatter do
head_sha: 789,
old_path: 'old_path.txt',
new_path: 'new_path.txt',
- file_identifier_hash: '777',
line_range: nil
}
end
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
index e643b58ee32..5350dda5fb2 100644
--- a/spec/lib/gitlab/diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
- let(:merge_request) { create(:merge_request_with_diffs) }
+ let_it_be(:merge_request) { create(:merge_request_with_diffs) }
+
let(:diff_hash) do
{ ".gitignore-false-false-false" =>
[{ line_code: nil, rich_text: nil, text: "@@ -17,3 +17,4 @@ rerun.txt", type: "match", index: 0, old_pos: 17, new_pos: 17 },
@@ -229,10 +230,10 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
end
describe 'metrics' do
- let(:transaction) { Gitlab::Metrics::WebTransaction.new({} ) }
+ let(:transaction) { Gitlab::Metrics::WebTransaction.new({}) }
before do
- allow(cache).to receive(:current_transaction).and_return(transaction)
+ allow(::Gitlab::Metrics::WebTransaction).to receive(:current).and_return(transaction)
end
it 'observes :gitlab_redis_diff_caching_memory_usage_bytes' do
@@ -241,6 +242,18 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
cache.write_if_empty
end
+
+ it 'records hit ratio metrics' do
+ expect(transaction)
+ .to receive(:increment).with(:gitlab_redis_diff_caching_requests_total).exactly(5).times
+ expect(transaction)
+ .to receive(:increment).with(:gitlab_redis_diff_caching_hits_total).exactly(4).times
+
+ 5.times do
+ cache = described_class.new(merge_request.diffs)
+ cache.write_if_empty
+ end
+ end
end
describe '#key' do
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index c9a20f40462..bb3522eb579 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -574,86 +574,6 @@ RSpec.describe Gitlab::Diff::Position do
end
end
- describe '#find_diff_file_from' do
- context "position for a diff file that has changed from symlink to regular file" do
- let(:commit) { project.commit("81e6355ce4e1544a3524b230952c12455de0777b") }
-
- let(:old_symlink_file_identifier_hash) { "bfa430463f33619872d52a6b85ced59c973e42dc" }
- let(:new_regular_file_identifier_hash) { "e25b60c2e5ffb977d2b1431b96c6f7800c3c3529" }
- let(:file_identifier_hash) { new_regular_file_identifier_hash }
-
- let(:args) do
- {
- file_identifier_hash: file_identifier_hash,
- old_path: "symlink",
- new_path: "symlink",
- old_line: nil,
- new_line: 1,
- diff_refs: commit.diff_refs
- }
- end
-
- let(:diffable) { commit.diff_refs.compare_in(project) }
-
- subject(:diff_file) { described_class.new(args).find_diff_file_from(diffable) }
-
- context 'when file_identifier_hash is disabled' do
- before do
- stub_feature_flags(file_identifier_hash: false)
- end
-
- it "returns the first diff file" do
- expect(diff_file.file_identifier_hash).to eq(old_symlink_file_identifier_hash)
- end
- end
-
- context 'when file_identifier_hash is enabled' do
- before do
- stub_feature_flags(file_identifier_hash: true)
- end
-
- context 'for new regular file' do
- it "returns the correct diff file" do
- expect(diff_file.file_identifier_hash).to eq(new_regular_file_identifier_hash)
- end
- end
-
- context 'for old symlink file' do
- let(:args) do
- {
- file_identifier_hash: old_symlink_file_identifier_hash,
- old_path: "symlink",
- new_path: "symlink",
- old_line: 1,
- new_line: nil,
- diff_refs: commit.diff_refs
- }
- end
-
- it "returns the correct diff file" do
- expect(diff_file.file_identifier_hash).to eq(old_symlink_file_identifier_hash)
- end
- end
-
- context 'when file_identifier_hash is missing' do
- let(:file_identifier_hash) { nil }
-
- it "returns the first diff file" do
- expect(diff_file.file_identifier_hash).to eq(old_symlink_file_identifier_hash)
- end
- end
-
- context 'when file_identifier_hash cannot be found' do
- let(:file_identifier_hash) { "missingidentifier" }
-
- it "returns nil" do
- expect(diff_file).to be_nil
- end
- end
- end
- end
- end
-
describe '#==' do
let(:commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") }
diff --git a/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb
index 1414056ad6a..563480d214b 100644
--- a/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer/image_strategy_spec.rb
@@ -234,118 +234,5 @@ RSpec.describe Gitlab::Diff::PositionTracer::ImageStrategy do
end
end
end
-
- describe 'symlink scenarios' do
- let(:new_file) { old_file_status == :new }
- let(:deleted_file) { old_file_status == :deleted }
- let(:renamed_file) { old_file_status == :renamed }
-
- let(:file_identifier) { "#{file_name}-#{new_file}-#{deleted_file}-#{renamed_file}" }
- let(:file_identifier_hash) { Digest::SHA1.hexdigest(file_identifier) }
- let(:old_position) { position(old_path: file_name, new_path: file_name, position_type: 'image', file_identifier_hash: file_identifier_hash) }
-
- let(:update_file_commit) do
- initial_commit
-
- update_file(
- branch_name,
- file_name,
- Base64.encode64('morecontent')
- )
- end
-
- let(:delete_file_commit) do
- initial_commit
-
- delete_file(branch_name, file_name)
- end
-
- let(:create_second_file_commit) do
- initial_commit
-
- create_file(
- branch_name,
- second_file_name,
- Base64.encode64('morecontent')
- )
- end
-
- before do
- stub_feature_flags(file_identifier_hash: true)
- end
-
- describe 'from symlink to image' do
- let(:initial_commit) { project.commit('a19c7f9a147e35e535c797cf148d29c24dac5544') }
- let(:symlink_to_image_commit) { project.commit('8cfca8420812e5bd7479aa32cf33e0c95a3ca576') }
- let(:branch_name) { 'diff-files-symlink-to-image' }
- let(:file_name) { 'symlink-to-image.png' }
-
- context "when the old position is on the new image file" do
- let(:old_file_status) { :new }
-
- context "when the image file's content was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_image_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) }
-
- it "returns the new position" do
- expect_new_position(
- old_path: file_name,
- new_path: file_name
- )
- end
- end
-
- context "when the image file's content was changed between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_image_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, update_file_commit) }
- let(:change_diff_refs) { diff_refs(symlink_to_image_commit, update_file_commit) }
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name
- )
- end
- end
-
- context "when the image file was removed between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_image_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, delete_file_commit) }
- let(:change_diff_refs) { diff_refs(symlink_to_image_commit, delete_file_commit) }
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name
- )
- end
- end
- end
- end
-
- describe 'from image to symlink' do
- let(:initial_commit) { project.commit('d10dcdfbbb2b59a959a5f5d66a4adf28f0ea4008') }
- let(:image_to_symlink_commit) { project.commit('3e94fdaa60da8aed38401b91bc56be70d54ca424') }
- let(:branch_name) { 'diff-files-image-to-symlink' }
- let(:file_name) { 'image-to-symlink.png' }
-
- context "when the old position is on the added image file" do
- let(:old_file_status) { :new }
-
- context "when the image file gets changed to a symlink between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit.parent, initial_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit.parent, image_to_symlink_commit) }
- let(:change_diff_refs) { diff_refs(initial_commit, image_to_symlink_commit) }
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name
- )
- end
- end
- end
- end
- end
end
end
diff --git a/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
index ea56a87dec2..2b21084d8e5 100644
--- a/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb
@@ -1860,143 +1860,5 @@ RSpec.describe Gitlab::Diff::PositionTracer::LineStrategy, :clean_gitlab_redis_c
end
end
end
-
- describe 'symlink scenarios' do
- let(:new_file) { old_file_status == :new }
- let(:deleted_file) { old_file_status == :deleted }
- let(:renamed_file) { old_file_status == :renamed }
-
- let(:file_identifier) { "#{file_name}-#{new_file}-#{deleted_file}-#{renamed_file}" }
- let(:file_identifier_hash) { Digest::SHA1.hexdigest(file_identifier) }
-
- let(:update_line_commit) do
- update_file(
- branch_name,
- file_name,
- <<-CONTENT.strip_heredoc
- A
- BB
- C
- CONTENT
- )
- end
-
- let(:delete_file_commit) do
- delete_file(branch_name, file_name)
- end
-
- let(:create_second_file_commit) do
- create_file(
- branch_name,
- second_file_name,
- <<-CONTENT.strip_heredoc
- D
- E
- CONTENT
- )
- end
-
- before do
- stub_feature_flags(file_identifier_hash: true)
- end
-
- describe 'from symlink to text' do
- let(:initial_commit) { project.commit('0e5b363105e9176a77bac94d7ff6d8c4fb35c3eb') }
- let(:symlink_to_text_commit) { project.commit('689815e617abc6889f1fded4834d2dd7d942a58e') }
- let(:branch_name) { 'diff-files-symlink-to-text' }
- let(:file_name) { 'symlink-to-text.txt' }
- let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 3, file_identifier_hash: file_identifier_hash) }
-
- before do
- create_branch('diff-files-symlink-to-text-test', branch_name)
- end
-
- context "when the old position is on the new text file" do
- let(:old_file_status) { :new }
-
- context "when the text file's content was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) }
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- new_line: old_position.new_line
- )
- end
- end
-
- context "when the text file's content has change, but the line was unchanged between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
-
- it "returns the new position" do
- expect_new_position(
- new_path: old_position.new_path,
- new_line: old_position.new_line
- )
- end
- end
-
- context "when the text file's line was changed between the old and the new diff" do
- let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2, file_identifier_hash: file_identifier_hash) }
-
- let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
- let(:change_diff_refs) { diff_refs(symlink_to_text_commit, update_line_commit) }
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 2,
- new_line: nil
- )
- end
- end
-
- context "when the text file was removed between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit, delete_file_commit) }
- let(:change_diff_refs) { diff_refs(symlink_to_text_commit, delete_file_commit) }
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 3,
- new_line: nil
- )
- end
- end
- end
-
- describe 'from text to symlink' do
- let(:initial_commit) { project.commit('3db7bd90bab8ce8f02c9818590b84739a2e97230') }
- let(:text_to_symlink_commit) { project.commit('5e2c2708c2e403dece5dd25759369150aac51644') }
- let(:branch_name) { 'diff-files-text-to-symlink' }
- let(:file_name) { 'text-to-symlink.txt' }
-
- context "when the position is on the added text file" do
- let(:old_file_status) { :new }
-
- context "when the text file gets changed to a symlink between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit.parent, initial_commit) }
- let(:new_diff_refs) { diff_refs(initial_commit.parent, text_to_symlink_commit) }
- let(:change_diff_refs) { diff_refs(initial_commit, text_to_symlink_commit) }
-
- it "returns the position of the change" do
- expect_change_position(
- old_path: file_name,
- new_path: file_name,
- old_line: 3,
- new_line: nil
- )
- end
- end
- end
- end
- end
- end
end
end
diff --git a/spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb b/spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb
index cb046548880..42ab2d1d063 100644
--- a/spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb
+++ b/spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb
@@ -39,7 +39,7 @@ RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFileHelper do
where(:case, :transformed_blocks, :result) do
'if transformed diff is empty' | [] | 0
'if the transformed line does not map to any in the original file' | [{ source_line: nil }] | 0
- 'if the transformed line maps to a line in the source file' | [{ source_line: 2 }] | 3
+ 'if the transformed line maps to a line in the source file' | [{ source_line: 3 }] | 3
end
with_them do
@@ -81,8 +81,8 @@ RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFileHelper do
let(:blocks) do
{
- from: [0, 2, 1, nil, nil, 3].map { |i| { source_line: i } },
- to: [0, 1, nil, 2, nil, 3].map { |i| { source_line: i } }
+ from: [1, 3, 2, nil, nil, 4].map { |i| { source_line: i } },
+ to: [1, 2, nil, 3, nil, 4].map { |i| { source_line: i } }
}
end
diff --git a/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb b/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb
index c38684a6dc3..b5137f9db6b 100644
--- a/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb
+++ b/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb
@@ -144,7 +144,7 @@ RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFile do
context 'has image' do
it 'replaces rich text with img to the embedded image' do
- expect(nb_file.highlighted_diff_lines[58].rich_text).to include('<img')
+ expect(nb_file.highlighted_diff_lines[56].rich_text).to include('<img')
end
it 'adds image to src' do
@@ -159,11 +159,11 @@ RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFile do
let(:commit) { project.commit("4963fefc990451a8ad34289ce266b757456fc88c") }
it 'prevents injected html to be rendered as html' do
- expect(nb_file.highlighted_diff_lines[45].rich_text).not_to include('<div>Hello')
+ expect(nb_file.highlighted_diff_lines[43].rich_text).not_to include('<div>Hello')
end
it 'keeps the injected html as part of the string' do
- expect(nb_file.highlighted_diff_lines[45].rich_text).to end_with('/div&gt;">')
+ expect(nb_file.highlighted_diff_lines[43].rich_text).to end_with('/div&gt;">')
end
end
end
diff --git a/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb b/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb
deleted file mode 100644
index f93c1aa1974..00000000000
--- a/spec/lib/gitlab/elasticsearch/logs/lines_spec.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Elasticsearch::Logs::Lines do
- let(:client) { Elasticsearch::Transport::Client }
-
- let(:es_message_1) { { timestamp: "2019-12-13T14:35:34.034Z", pod: "production-6866bc8974-m4sk4", message: "10.8.2.1 - - [25/Oct/2019:08:03:22 UTC] \"GET / HTTP/1.1\" 200 13" } }
- let(:es_message_2) { { timestamp: "2019-12-13T14:35:35.034Z", pod: "production-6866bc8974-m4sk4", message: "10.8.2.1 - - [27/Oct/2019:23:49:54 UTC] \"GET / HTTP/1.1\" 200 13" } }
- let(:es_message_3) { { timestamp: "2019-12-13T14:35:36.034Z", pod: "production-6866bc8974-m4sk4", message: "10.8.2.1 - - [04/Nov/2019:23:09:24 UTC] \"GET / HTTP/1.1\" 200 13" } }
- let(:es_message_4) { { timestamp: "2019-12-13T14:35:37.034Z", pod: "production-6866bc8974-m4sk4", message: "- -\u003e /" } }
-
- let(:es_response) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/logs_response.json')) }
-
- subject { described_class.new(client) }
-
- let(:namespace) { "autodevops-deploy-9-production" }
- let(:pod_name) { "production-6866bc8974-m4sk4" }
- let(:container_name) { "auto-deploy-app" }
- let(:search) { "foo +bar "}
- let(:start_time) { "2019-12-13T14:35:34.034Z" }
- let(:end_time) { "2019-12-13T14:35:34.034Z" }
- let(:cursor) { "9999934,1572449784442" }
-
- let(:body) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/query.json')) }
- let(:body_with_container) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/query_with_container.json')) }
- let(:body_with_search) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/query_with_search.json')) }
- let(:body_with_times) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/query_with_times.json')) }
- let(:body_with_start_time) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/query_with_start_time.json')) }
- let(:body_with_end_time) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/query_with_end_time.json')) }
- let(:body_with_cursor) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/query_with_cursor.json')) }
- let(:body_with_filebeat_6) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/query_with_filebeat_6.json')) }
-
- RSpec::Matchers.define :a_hash_equal_to_json do |expected|
- match do |actual|
- actual.as_json == expected
- end
- end
-
- describe '#pod_logs' do
- it 'returns the logs as an array' do
- expect(client).to receive(:search).with(body: a_hash_equal_to_json(body)).and_return(es_response)
-
- result = subject.pod_logs(namespace, pod_name: pod_name)
- expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
- end
-
- it 'can further filter the logs by container name' do
- expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_container)).and_return(es_response)
-
- result = subject.pod_logs(namespace, pod_name: pod_name, container_name: container_name)
- expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
- end
-
- it 'can further filter the logs by search' do
- expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_search)).and_return(es_response)
-
- result = subject.pod_logs(namespace, pod_name: pod_name, search: search)
- expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
- end
-
- it 'can further filter the logs by start_time and end_time' do
- expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_times)).and_return(es_response)
-
- result = subject.pod_logs(namespace, pod_name: pod_name, start_time: start_time, end_time: end_time)
- expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
- end
-
- it 'can further filter the logs by only start_time' do
- expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_start_time)).and_return(es_response)
-
- result = subject.pod_logs(namespace, pod_name: pod_name, start_time: start_time)
- expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
- end
-
- it 'can further filter the logs by only end_time' do
- expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_end_time)).and_return(es_response)
-
- result = subject.pod_logs(namespace, pod_name: pod_name, end_time: end_time)
- expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
- end
-
- it 'can search after a cursor' do
- expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_cursor)).and_return(es_response)
-
- result = subject.pod_logs(namespace, pod_name: pod_name, cursor: cursor)
- expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
- end
-
- it 'can search on filebeat 6' do
- expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_filebeat_6)).and_return(es_response)
-
- result = subject.pod_logs(namespace, pod_name: pod_name, chart_above_v2: false)
- expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
- end
- end
-end
diff --git a/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb b/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb
deleted file mode 100644
index 07fa0980d36..00000000000
--- a/spec/lib/gitlab/elasticsearch/logs/pods_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Elasticsearch::Logs::Pods do
- let(:client) { Elasticsearch::Transport::Client }
-
- let(:es_query) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/pods_query.json'), symbolize_names: true) }
- let(:es_response) { Gitlab::Json.parse(fixture_file('lib/elasticsearch/pods_response.json')) }
- let(:namespace) { "autodevops-deploy-9-production" }
-
- subject { described_class.new(client) }
-
- describe '#pods' do
- it 'returns the pods' do
- expect(client).to receive(:search).with(body: es_query).and_return(es_response)
-
- result = subject.pods(namespace)
- expect(result).to eq([
- {
- name: "runner-gitlab-runner-7bbfb5dcb5-p6smb",
- container_names: %w[runner-gitlab-runner]
- },
- {
- name: "elastic-stack-elasticsearch-master-1",
- container_names: %w[elasticsearch chown sysctl]
- },
- {
- name: "ingress-nginx-ingress-controller-76449bcc8d-8qgl6",
- container_names: %w[nginx-ingress-controller]
- }
- ])
- end
- 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 6e7806c5d53..d0aba70081b 100644
--- a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
@@ -52,14 +52,6 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
expect(new_issue.issue_email_participants.first.email).to eq(author_email)
end
- it 'attaches existing CRM contact' do
- contact = create(:contact, group: group, email: author_email)
- receiver.execute
- new_issue = Issue.last
-
- expect(new_issue.issue_customer_relations_contacts.last.contact).to eq(contact)
- end
-
it 'sends thank you email' do
expect { receiver.execute }.to have_enqueued_job.on_queue('mailers')
end
@@ -77,6 +69,16 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
context 'when everything is fine' do
it_behaves_like 'a new issue request'
+ it 'attaches existing CRM contacts' do
+ contact = create(:contact, group: group, email: author_email)
+ contact2 = create(:contact, group: group, email: "cc@example.com")
+ contact3 = create(:contact, group: group, email: "kk@example.org")
+ receiver.execute
+ new_issue = Issue.last
+
+ expect(new_issue.issue_customer_relations_contacts.map(&:contact)).to contain_exactly(contact, contact2, contact3)
+ end
+
context 'with legacy incoming email address' do
let(:email_raw) { fixture_file('emails/service_desk_legacy.eml') }
diff --git a/spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb
index dfa18c27d5e..ab6b1cd6171 100644
--- a/spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb
+++ b/spec/lib/gitlab/email/message/in_product_marketing/base_spec.rb
@@ -99,7 +99,6 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Base do
:verify | true
:trial | true
:team | true
- :experience | true
end
with_them do
diff --git a/spec/lib/gitlab/email/message/in_product_marketing/experience_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/experience_spec.rb
deleted file mode 100644
index 8cd2345822e..00000000000
--- a/spec/lib/gitlab/email/message/in_product_marketing/experience_spec.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Email::Message::InProductMarketing::Experience do
- let_it_be(:group) { build(:group) }
- let_it_be(:user) { build(:user) }
-
- subject(:message) { described_class.new(group: group, user: user, series: series)}
-
- describe 'public methods' do
- context 'with series 0' do
- let(:series) { 0 }
-
- it 'returns value for series', :aggregate_failures do
- expect(message.subject_line).to be_present
- expect(message.tagline).to be_nil
- expect(message.title).to be_present
- expect(message.subtitle).to be_present
- expect(message.body_line1).to be_present
- expect(message.body_line2).to be_present
- expect(message.cta_text).to be_nil
- end
-
- describe 'feedback URL' do
- before do
- allow(message).to receive(:onboarding_progress).and_return(1)
- allow(message).to receive(:show_invite_link).and_return(true)
- end
-
- subject do
- message.feedback_link(1)
- end
-
- it { is_expected.to start_with(Gitlab::Saas.com_url) }
-
- context 'when in development' do
- let(:root_url) { 'http://example.com' }
-
- before do
- allow(message).to receive(:root_url).and_return(root_url)
- stub_rails_env('development')
- end
-
- it { is_expected.to start_with(root_url) }
- end
- end
-
- describe 'feedback URL show_invite_link query param' do
- let(:user_access) { GroupMember::DEVELOPER }
- let(:preferred_language) { 'en' }
-
- before do
- allow(message).to receive(:onboarding_progress).and_return(1)
- allow(group).to receive(:max_member_access_for_user).and_return(user_access)
- allow(user).to receive(:preferred_language).and_return(preferred_language)
- end
-
- subject do
- uri = URI.parse(message.feedback_link(1))
- Rack::Utils.parse_query(uri.query).with_indifferent_access[:show_invite_link]
- end
-
- it { is_expected.to eq('true') }
-
- context 'with less than developer access' do
- let(:user_access) { GroupMember::GUEST }
-
- it { is_expected.to eq('false') }
- end
-
- context 'with preferred language other than English' do
- let(:preferred_language) { 'nl' }
-
- it { is_expected.to eq('false') }
- end
- end
-
- describe 'feedback URL show_incentive query param' do
- let(:show_invite_link) { true }
- let(:member_count) { 2 }
- let(:query) do
- uri = URI.parse(message.feedback_link(1))
- Rack::Utils.parse_query(uri.query).with_indifferent_access
- end
-
- before do
- allow(message).to receive(:onboarding_progress).and_return(1)
- allow(message).to receive(:show_invite_link).and_return(show_invite_link)
- allow(group).to receive(:member_count).and_return(member_count)
- end
-
- subject { query[:show_incentive] }
-
- it { is_expected.to eq('true') }
-
- context 'with only one member' do
- let(:member_count) { 1 }
-
- it "is not present" do
- expect(query).not_to have_key(:show_incentive)
- end
- end
-
- context 'show_invite_link is false' do
- let(:show_invite_link) { false }
-
- it "is not present" do
- expect(query).not_to have_key(:show_incentive)
- end
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/email/message/in_product_marketing_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing_spec.rb
index 40351bef8b9..1c59d9c8208 100644
--- a/spec/lib/gitlab/email/message/in_product_marketing_spec.rb
+++ b/spec/lib/gitlab/email/message/in_product_marketing_spec.rb
@@ -17,7 +17,6 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing do
:verify | described_class::Verify
:trial | described_class::Trial
:team | described_class::Team
- :experience | described_class::Experience
end
with_them do
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index 4c1fbb93c13..b0c67cdafe1 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -45,16 +45,26 @@ RSpec.describe Gitlab::EncodingHelper do
end
context 'with corrupted diff' do
+ let(:project) { create(:project, :empty_repo) }
+ let(:repository) { project.repository }
+ let(:content) { fixture_file('encoding/Japanese.md') }
let(:corrupted_diff) do
- with_empty_bare_repository do |repo|
- content = File.read(Rails.root.join(
- 'spec/fixtures/encoding/Japanese.md').to_s)
- commit_a = commit(repo, 'Japanese.md', content)
- commit_b = commit(repo, 'Japanese.md',
- content.sub('[TODO: Link]', '[現在作業中です: Link]'))
-
- repo.diff(commit_a, commit_b).each_line.map(&:content).join
- end
+ commit_a = repository.create_file(
+ project.creator,
+ 'Japanese.md',
+ content,
+ branch_name: 'HEAD',
+ message: 'Create Japanese.md'
+ )
+ commit_b = repository.update_file(
+ project.creator,
+ 'Japanese.md',
+ content.sub('[TODO: Link]', '[現在作業中です: Link]'),
+ branch_name: 'HEAD',
+ message: 'Update Japanese.md'
+ )
+
+ repository.diff(commit_a, commit_b).map(&:diff).join
end
let(:cleaned_diff) do
@@ -69,26 +79,6 @@ RSpec.describe Gitlab::EncodingHelper do
it 'does not corrupt data but remove invalid characters' do
expect(encoded_diff).to eq(cleaned_diff)
end
-
- def commit(repo, path, content)
- oid = repo.write(content, :blob)
- index = repo.index
-
- index.read_tree(repo.head.target.tree) unless repo.empty?
-
- index.add(path: path, oid: oid, mode: 0100644)
- user = { name: 'Test', email: 'test@example.com' }
-
- Rugged::Commit.create(
- repo,
- tree: index.write_tree(repo),
- author: user,
- committer: user,
- message: "Update #{path}",
- parents: repo.empty? ? [] : [repo.head.target].compact,
- update_ref: 'HEAD'
- )
- end
end
end
diff --git a/spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb b/spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb
new file mode 100644
index 00000000000..81e2a410962
--- /dev/null
+++ b/spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb
@@ -0,0 +1,436 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ErrorTracking::ErrorRepository::OpenApiStrategy do
+ include AfterNextHelpers
+
+ let(:project) { build_stubbed(:project) }
+ let(:api_exception) { ErrorTrackingOpenAPI::ApiError.new(code: 500, response_body: 'b' * 101) }
+
+ subject(:repository) { Gitlab::ErrorTracking::ErrorRepository.build(project) }
+
+ before do
+ # Disabled in spec_helper by default thus we need to enable it here.
+ stub_feature_flags(use_click_house_database_for_error_tracking: true)
+ end
+
+ shared_examples 'exception logging' do
+ it 'logs error' do
+ expect(Gitlab::AppLogger).to receive(:error).with({
+ 'open_api.http_code' => api_exception.code,
+ 'open_api.response_body' => api_exception.response_body.truncate(100)
+ })
+
+ subject
+ end
+ end
+
+ shared_examples 'no logging' do
+ it 'does not log anything' do
+ expect(Gitlab::AppLogger).not_to receive(:debug)
+ expect(Gitlab::AppLogger).not_to receive(:info)
+ expect(Gitlab::AppLogger).not_to receive(:error)
+ end
+ end
+
+ describe '#report_error' do
+ let(:params) do
+ {
+ name: 'anything',
+ description: 'anything',
+ actor: 'anything',
+ platform: 'anything',
+ environment: 'anything',
+ level: 'anything',
+ occurred_at: Time.zone.now,
+ payload: {}
+ }
+ end
+
+ subject { repository.report_error(**params) }
+
+ it 'is not implemented' do
+ expect { subject }.to raise_error(NotImplementedError, 'Use ingestion endpoint')
+ end
+ end
+
+ describe '#find_error' do
+ let(:error) { build(:error_tracking_open_api_error, project_id: project.id) }
+
+ subject { repository.find_error(error.fingerprint) }
+
+ before do
+ allow_next_instance_of(ErrorTrackingOpenAPI::ErrorsApi) do |open_api|
+ allow(open_api).to receive(:get_error).with(project.id, error.fingerprint)
+ .and_return(error)
+
+ allow(open_api).to receive(:list_events)
+ .with(project.id, error.fingerprint, { sort: 'occurred_at_asc', limit: 1 })
+ .and_return(list_events_asc)
+
+ allow(open_api).to receive(:list_events)
+ .with(project.id, error.fingerprint, { sort: 'occurred_at_desc', limit: 1 })
+ .and_return(list_events_desc)
+ end
+ end
+
+ context 'when request succeeds' do
+ context 'without events returned' do
+ let(:list_events_asc) { [] }
+ let(:list_events_desc) { [] }
+
+ include_examples 'no logging'
+
+ it 'returns detailed error' do
+ is_expected.to have_attributes(
+ id: error.fingerprint.to_s,
+ title: error.name,
+ message: error.description,
+ culprit: error.actor,
+ first_seen: error.first_seen_at.to_s,
+ last_seen: error.last_seen_at.to_s,
+ count: error.event_count,
+ user_count: error.approximated_user_count,
+ project_id: error.project_id,
+ status: error.status,
+ tags: { level: nil, logger: nil },
+ external_url: "http://localhost/#{project.full_path}/-/error_tracking/#{error.fingerprint}/details",
+ external_base_url: "http://localhost/#{project.full_path}",
+ integrated: true
+ )
+ end
+
+ it 'returns no first and last release version' do
+ is_expected.to have_attributes(
+ first_release_version: nil,
+ last_release_version: nil
+ )
+ end
+ end
+
+ context 'with events returned' do
+ let(:first_event) { build(:error_tracking_open_api_error_event, project_id: project.id) }
+ let(:first_release) { parse_json(first_event.payload).fetch('release') }
+ let(:last_event) { build(:error_tracking_open_api_error_event, :golang, project_id: project.id) }
+ let(:last_release) { parse_json(last_event.payload).fetch('release') }
+
+ let(:list_events_asc) { [first_event] }
+ let(:list_events_desc) { [last_event] }
+
+ include_examples 'no logging'
+
+ it 'returns first and last release version' do
+ expect(first_release).to be_present
+ expect(last_release).to be_present
+
+ is_expected.to have_attributes(
+ first_release_version: first_release,
+ last_release_version: last_release
+ )
+ end
+
+ def parse_json(content)
+ Gitlab::Json.parse(content)
+ end
+ end
+ end
+
+ context 'when request fails' do
+ before do
+ allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:get_error)
+ .with(project.id, error.fingerprint)
+ .and_raise(api_exception)
+ end
+
+ include_examples 'exception logging'
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#list_errors' do
+ let(:errors) { [] }
+ let(:response_with_info) { [errors, 200, headers] }
+ let(:result_errors) { result.first }
+ let(:result_pagination) { result.last }
+
+ let(:headers) do
+ {
+ 'link' => [
+ '<url?cursor=next_cursor&param>; rel="next"',
+ '<url?cursor=prev_cursor&param>; rel="prev"'
+ ].join(', ')
+ }
+ end
+
+ subject(:result) { repository.list_errors(**params) }
+
+ before do
+ allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:list_errors_with_http_info)
+ .with(project.id, kind_of(Hash))
+ .and_return(response_with_info)
+ end
+
+ context 'with errors' do
+ let(:limit) { 3 }
+ let(:params) { { limit: limit } }
+ let(:errors_size) { limit }
+ let(:errors) { build_list(:error_tracking_open_api_error, errors_size, project_id: project.id) }
+
+ include_examples 'no logging'
+
+ it 'maps errors to models' do
+ # All errors are identical
+ error = errors.first
+
+ expect(result_errors).to all(
+ have_attributes(
+ id: error.fingerprint.to_s,
+ title: error.name,
+ message: error.description,
+ culprit: error.actor,
+ first_seen: error.first_seen_at,
+ last_seen: error.last_seen_at,
+ status: error.status,
+ count: error.event_count,
+ user_count: error.approximated_user_count
+ ))
+ end
+
+ context 'when n errors are returned' do
+ let(:errors_size) { limit }
+
+ include_examples 'no logging'
+
+ it 'returns the amount of errors' do
+ expect(result_errors.size).to eq(3)
+ end
+
+ it 'cursor links are preserved' do
+ expect(result_pagination).to have_attributes(
+ prev: 'prev_cursor',
+ next: 'next_cursor'
+ )
+ end
+ end
+
+ context 'when less errors than requested are returned' do
+ let(:errors_size) { limit - 1 }
+
+ include_examples 'no logging'
+
+ it 'returns the amount of errors' do
+ expect(result_errors.size).to eq(2)
+ end
+
+ it 'cursor link for next is removed' do
+ expect(result_pagination).to have_attributes(
+ prev: 'prev_cursor',
+ next: nil
+ )
+ end
+ end
+ end
+
+ context 'with params' do
+ let(:params) do
+ {
+ filters: { status: 'resolved', something: 'different' },
+ query: 'search term',
+ sort: 'first_seen',
+ limit: 2,
+ cursor: 'abc'
+ }
+ end
+
+ include_examples 'no logging'
+
+ it 'passes provided params to client' do
+ passed_params = {
+ sort: 'first_seen_desc',
+ status: 'resolved',
+ query: 'search term',
+ cursor: 'abc',
+ limit: 2
+ }
+
+ expect_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:list_errors_with_http_info)
+ .with(project.id, passed_params)
+ .and_return(response_with_info)
+
+ subject
+ end
+ end
+
+ context 'without explicit params' do
+ let(:params) { {} }
+
+ include_examples 'no logging'
+
+ it 'passes default params to client' do
+ passed_params = {
+ sort: 'last_seen_desc',
+ limit: 20,
+ cursor: {}
+ }
+
+ expect_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:list_errors_with_http_info)
+ .with(project.id, passed_params)
+ .and_return(response_with_info)
+
+ subject
+ end
+ end
+
+ context 'when request fails' do
+ let(:params) { {} }
+
+ before do
+ allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:list_errors_with_http_info)
+ .with(project.id, kind_of(Hash))
+ .and_raise(api_exception)
+ end
+
+ include_examples 'exception logging'
+
+ specify do
+ expect(result_errors).to eq([])
+ expect(result_pagination).to have_attributes(
+ next: nil,
+ prev: nil
+ )
+ end
+ end
+ end
+
+ describe '#last_event_for' do
+ let(:params) { { sort: 'occurred_at_desc', limit: 1 } }
+ let(:event) { build(:error_tracking_open_api_error_event, project_id: project.id) }
+ let(:error) { build(:error_tracking_open_api_error, project_id: project.id, fingerprint: event.fingerprint) }
+
+ subject { repository.last_event_for(error.fingerprint) }
+
+ context 'when both event and error is returned' do
+ before do
+ allow_next_instance_of(ErrorTrackingOpenAPI::ErrorsApi) do |open_api|
+ allow(open_api).to receive(:list_events).with(project.id, error.fingerprint, params)
+ .and_return([event])
+
+ allow(open_api).to receive(:get_error).with(project.id, error.fingerprint)
+ .and_return(error)
+ end
+ end
+
+ include_examples 'no logging'
+
+ it 'returns mapped error event' do
+ is_expected.to have_attributes(
+ issue_id: event.fingerprint.to_s,
+ date_received: error.last_seen_at,
+ stack_trace_entries: kind_of(Array)
+ )
+ end
+ end
+
+ context 'when event is not returned' do
+ before do
+ allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:list_events)
+ .with(project.id, event.fingerprint, params)
+ .and_return([])
+ end
+
+ include_examples 'no logging'
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when list_events request fails' do
+ before do
+ allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:list_events)
+ .with(project.id, event.fingerprint, params)
+ .and_raise(api_exception)
+ end
+
+ include_examples 'exception logging'
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when error is not returned' do
+ before do
+ allow_next_instance_of(ErrorTrackingOpenAPI::ErrorsApi) do |open_api|
+ allow(open_api).to receive(:list_events).with(project.id, error.fingerprint, params)
+ .and_return([event])
+
+ allow(open_api).to receive(:get_error).with(project.id, error.fingerprint)
+ .and_return(nil)
+ end
+ end
+
+ include_examples 'no logging'
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when get_error request fails' do
+ before do
+ allow_next_instance_of(ErrorTrackingOpenAPI::ErrorsApi) do |open_api|
+ allow(open_api).to receive(:list_events).with(project.id, error.fingerprint, params)
+ .and_return([event])
+
+ allow(open_api).to receive(:get_error).with(project.id, error.fingerprint)
+ .and_raise(api_exception)
+ end
+ end
+
+ include_examples 'exception logging'
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#update_error' do
+ let(:error) { build(:error_tracking_open_api_error, project_id: project.id) }
+ let(:update_params) { { status: 'resolved' } }
+ let(:passed_body) { ErrorTrackingOpenAPI::ErrorUpdatePayload.new(update_params) }
+
+ subject { repository.update_error(error.fingerprint, **update_params) }
+
+ before do
+ allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:update_error)
+ .with(project.id, error.fingerprint, passed_body)
+ .and_return(:anything)
+ end
+
+ context 'when update succeeds' do
+ include_examples 'no logging'
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when update fails' do
+ before do
+ allow_next(ErrorTrackingOpenAPI::ErrorsApi).to receive(:update_error)
+ .with(project.id, error.fingerprint, passed_body)
+ .and_raise(api_exception)
+ end
+
+ include_examples 'exception logging'
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ describe '#dsn_url' do
+ let(:public_key) { 'abc' }
+ let(:config) { ErrorTrackingOpenAPI::Configuration.default }
+
+ subject { repository.dsn_url(public_key) }
+
+ it do
+ is_expected
+ .to eq("#{config.scheme}://#{public_key}@#{config.host}/errortracking/api/v1/projects/api/#{project.id}")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/error_tracking/processor/sanitizer_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/sanitizer_processor_spec.rb
new file mode 100644
index 00000000000..9673bfc5cd3
--- /dev/null
+++ b/spec/lib/gitlab/error_tracking/processor/sanitizer_processor_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ErrorTracking::Processor::SanitizerProcessor, :sentry do
+ describe '.call' do
+ let(:event) { Sentry.get_current_client.event_from_exception(exception) }
+ let(:result_hash) { described_class.call(event).to_hash }
+
+ before do
+ data.each do |key, value|
+ event.send("#{key}=", value)
+ end
+ end
+
+ after do
+ Sentry.get_current_scope.clear
+ end
+
+ context 'when event attributes contains sensitive information' do
+ let(:exception) { RuntimeError.new }
+ let(:data) do
+ {
+ contexts: {
+ jwt: 'abcdef',
+ controller: 'GraphController#execute'
+ },
+ tags: {
+ variables: %w[some sensitive information'],
+ deep_hash: {
+ sharedSecret: 'secret123'
+ }
+ },
+ user: {
+ email: 'a@a.com',
+ password: 'nobodyknows'
+ },
+ extra: {
+ issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/-/issues/1',
+ my_token: '[FILTERED]',
+ another_token: '[FILTERED]'
+ }
+ }
+ end
+
+ it 'filters sensitive attributes' do
+ expect_next_instance_of(ActiveSupport::ParameterFilter) do |instance|
+ expect(instance).to receive(:filter).exactly(4).times.and_call_original
+ end
+
+ expect(result_hash).to include(
+ contexts: {
+ jwt: '[FILTERED]',
+ controller: 'GraphController#execute'
+ },
+ tags: {
+ variables: '[FILTERED]',
+ deep_hash: {
+ sharedSecret: '[FILTERED]'
+ }
+ },
+ user: {
+ email: 'a@a.com',
+ password: '[FILTERED]'
+ },
+ extra: {
+ issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/-/issues/1',
+ my_token: '[FILTERED]',
+ another_token: '[FILTERED]'
+ }
+ )
+ end
+ end
+
+ context 'when request contains sensitive information' do
+ let(:exception) { RuntimeError.new }
+ let(:data) { {} }
+
+ before do
+ event.rack_env = {
+ 'HTTP_AUTHORIZATION' => 'Bearer 123456',
+ 'HTTP_PRIVATE_TOKEN' => 'abcdef',
+ 'HTTP_JOB_TOKEN' => 'secret123',
+ 'HTTP_GITLAB_WORKHORSE_PROXY_START' => 123456,
+ 'HTTP_COOKIE' => 'yummy_cookie=choco; tasty_cookie=strawberry',
+ 'QUERY_STRING' => 'token=secret&access_token=secret&job_token=secret&private_token=secret',
+ 'Content-Type' => 'application/json',
+ 'rack.input' => StringIO.new('{"name":"new_project", "some_token":"value"}')
+ }
+ end
+
+ it 'filters sensitive headers' do
+ expect(result_hash[:request][:headers]).to include(
+ 'Authorization' => '[FILTERED]',
+ 'Private-Token' => '[FILTERED]',
+ 'Job-Token' => '[FILTERED]',
+ 'Gitlab-Workhorse-Proxy-Start' => '123456'
+ )
+ end
+
+ it 'filters query string parameters' do
+ expect(result_hash[:request][:query_string]).not_to include('secret')
+ end
+
+ it 'removes cookies' do
+ expect(result_hash[:request][:cookies]).to be_empty
+ end
+
+ it 'removes data' do
+ expect(result_hash[:request][:data]).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb
index 1ade3a51c55..fd859ae40fb 100644
--- a/spec/lib/gitlab/error_tracking_spec.rb
+++ b/spec/lib/gitlab/error_tracking_spec.rb
@@ -424,5 +424,25 @@ RSpec.describe Gitlab::ErrorTracking do
end
end
end
+
+ context 'when request contains sensitive information' do
+ before do
+ Sentry.get_current_scope.set_rack_env({
+ 'HTTP_AUTHORIZATION' => 'Bearer 123456',
+ 'HTTP_PRIVATE_TOKEN' => 'abcdef',
+ 'HTTP_JOB_TOKEN' => 'secret123'
+ })
+ end
+
+ it 'filters sensitive data' do
+ track_exception
+
+ expect(sentry_event.to_hash[:request][:headers]).to include(
+ 'Authorization' => '[FILTERED]',
+ 'Private-Token' => '[FILTERED]',
+ 'Job-Token' => '[FILTERED]'
+ )
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb
index 96cd70b4ff1..a5115989e6b 100644
--- a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb
+++ b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Git::AttributesAtRefParser, :seed_helper do
+RSpec.describe Gitlab::Git::AttributesAtRefParser do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/git/attributes_parser_spec.rb b/spec/lib/gitlab/git/attributes_parser_spec.rb
index 4bc39921e85..295d62fa052 100644
--- a/spec/lib/gitlab/git/attributes_parser_spec.rb
+++ b/spec/lib/gitlab/git/attributes_parser_spec.rb
@@ -2,9 +2,8 @@
require 'spec_helper'
-RSpec.describe Gitlab::Git::AttributesParser, :seed_helper do
- let(:attributes_path) { File.join(SEED_STORAGE_PATH, 'with-git-attributes.git', 'info', 'attributes') }
- let(:data) { File.read(attributes_path) }
+RSpec.describe Gitlab::Git::AttributesParser do
+ let(:data) { fixture_file('gitlab/git/gitattributes') }
subject { described_class.new(data) }
@@ -141,11 +140,12 @@ RSpec.describe Gitlab::Git::AttributesParser, :seed_helper do
expect { |b| subject.each_line(&b) }.to yield_successive_args(*args)
end
- it 'does not yield when the attributes file has an unsupported encoding' do
- path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git', 'info', 'attributes')
- attrs = described_class.new(File.read(path))
+ context 'unsupported encoding' do
+ let(:data) { fixture_file('gitlab/git/gitattributes_invalid') }
- expect { |b| attrs.each_line(&b) }.not_to yield_control
+ it 'does not yield' do
+ expect { |b| subject.each_line(&b) }.not_to yield_control
+ end
end
end
end
diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb
index 7dd7460b142..e514e128785 100644
--- a/spec/lib/gitlab/git/blame_spec.rb
+++ b/spec/lib/gitlab/git/blame_spec.rb
@@ -2,10 +2,10 @@
require "spec_helper"
-RSpec.describe Gitlab::Git::Blame, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
-
- let(:sha) { SeedRepo::Commit::ID }
+RSpec.describe Gitlab::Git::Blame do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository.raw }
+ let(:sha) { TestEnv::BRANCH_SHA['master'] }
let(:path) { 'CONTRIBUTING.md' }
let(:range) { nil }
@@ -37,19 +37,17 @@ RSpec.describe Gitlab::Git::Blame, :seed_helper do
end
context "ISO-8859 encoding" do
- let(:sha) { SeedRepo::EncodingCommit::ID }
let(:path) { 'encoding/iso8859.txt' }
it 'converts to UTF-8' do
expect(result.size).to eq(1)
expect(result.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
- expect(result.first[:line]).to eq("Ä ü")
+ expect(result.first[:line]).to eq("Äü")
expect(result.first[:line]).to be_utf8
end
end
context "unknown encoding" do
- let(:sha) { SeedRepo::EncodingCommit::ID }
let(:path) { 'encoding/iso8859.txt' }
it 'converts to UTF-8' do
@@ -59,14 +57,12 @@ RSpec.describe Gitlab::Git::Blame, :seed_helper do
expect(result.size).to eq(1)
expect(result.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
- expect(result.first[:line]).to eq(" ")
+ expect(result.first[:line]).to eq("")
expect(result.first[:line]).to be_utf8
end
end
context "renamed file" do
- let(:project) { create(:project, :repository) }
- let(:repository) { project.repository.raw_repository }
let(:commit) { project.commit('blame-on-renamed') }
let(:sha) { commit.id }
let(:path) { 'files/plain_text/renamed' }
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index 3cc52863976..97cd4777b4d 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -4,9 +4,6 @@ require "spec_helper"
RSpec.describe Gitlab::Git::Branch, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
- let(:rugged) do
- Rugged::Repository.new(File.join(TestEnv.repos_path, repository.relative_path))
- end
subject { repository.branches }
@@ -81,20 +78,6 @@ RSpec.describe Gitlab::Git::Branch, :seed_helper do
end
let(:user) { create(:user) }
- let(:committer) { { email: user.email, name: user.name } }
- let(:params) do
- parents = [rugged.head.target]
- tree = parents.first.tree
-
- {
- message: +'commit message',
- author: committer,
- committer: committer,
- tree: tree,
- parents: parents
- }
- end
-
let(:stale_sha) { travel_to(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago - 5.days) { create_commit } }
let(:active_sha) { travel_to(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago + 5.days) { create_commit } }
let(:future_sha) { travel_to(100.days.since) { create_commit } }
@@ -137,7 +120,11 @@ RSpec.describe Gitlab::Git::Branch, :seed_helper do
it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) }
def create_commit
- params[:message].delete!(+"\r")
- Rugged::Commit.create(rugged, params.merge(committer: committer.merge(time: Time.now)))
+ repository.multi_action(
+ user,
+ branch_name: 'HEAD',
+ message: 'commit message',
+ actions: []
+ ).newrev
end
end
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index de342444c15..da77d8ee5d6 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -3,68 +3,8 @@
require "spec_helper"
RSpec.describe Gitlab::Git::Commit, :seed_helper do
- include GitHelpers
-
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
- let(:rugged_repo) do
- Rugged::Repository.new(File.join(TestEnv.repos_path, TEST_REPO_PATH))
- end
-
let(:commit) { described_class.find(repository, SeedRepo::Commit::ID) }
- let(:rugged_commit) { rugged_repo.lookup(SeedRepo::Commit::ID) }
-
- describe "Commit info" do
- before do
- @committer = {
- email: 'mike@smith.com',
- name: "Mike Smith",
- time: Time.new(2000, 1, 1, 0, 0, 0, "+08:00")
- }
-
- @author = {
- email: 'john@smith.com',
- name: "John Smith",
- time: Time.new(2000, 1, 1, 0, 0, 0, "-08:00")
- }
-
- @parents = [rugged_repo.head.target]
- @gitlab_parents = @parents.map { |c| described_class.find(repository, c.oid) }
- @tree = @parents.first.tree
-
- sha = Rugged::Commit.create(
- rugged_repo,
- author: @author,
- committer: @committer,
- tree: @tree,
- parents: @parents,
- message: "Refactoring specs",
- update_ref: "HEAD"
- )
-
- @raw_commit = rugged_repo.lookup(sha)
- @commit = described_class.find(repository, sha)
- end
-
- it { expect(@commit.short_id).to eq(@raw_commit.oid[0..10]) }
- it { expect(@commit.id).to eq(@raw_commit.oid) }
- it { expect(@commit.sha).to eq(@raw_commit.oid) }
- it { expect(@commit.safe_message).to eq(@raw_commit.message) }
- it { expect(@commit.created_at).to eq(@raw_commit.committer[:time]) }
- it { expect(@commit.date).to eq(@raw_commit.committer[:time]) }
- it { expect(@commit.author_email).to eq(@author[:email]) }
- it { expect(@commit.author_name).to eq(@author[:name]) }
- it { expect(@commit.committer_name).to eq(@committer[:name]) }
- it { expect(@commit.committer_email).to eq(@committer[:email]) }
- it { expect(@commit.different_committer?).to be_truthy }
- it { expect(@commit.parents).to eq(@gitlab_parents) }
- it { expect(@commit.parent_id).to eq(@parents.first.oid) }
- it { expect(@commit.no_commit_message).to eq("No commit message") }
-
- after do
- # Erase the new commit so other tests get the original repo
- rugged_repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
- end
- end
describe "Commit info from gitaly commit" do
let(:subject) { (+"My commit").force_encoding('ASCII-8BIT') }
@@ -132,7 +72,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
shared_examples '.find' do
it "returns first head commit if without params" do
expect(described_class.last(repository).id).to eq(
- rugged_repo.head.target.oid
+ repository.commit.sha
)
end
@@ -622,19 +562,6 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
end
end
- skip 'move this test to gitaly-ruby' do
- RSpec.describe '#init_from_rugged' do
- let(:gitlab_commit) { described_class.new(repository, rugged_commit) }
-
- subject { gitlab_commit }
-
- describe '#id' do
- subject { super().id }
- it { is_expected.to eq(SeedRepo::Commit::ID) }
- end
- end
- end
-
describe '#init_from_hash' do
let(:commit) { described_class.new(repository, sample_commit_hash) }
diff --git a/spec/lib/gitlab/git/conflict/parser_spec.rb b/spec/lib/gitlab/git/conflict/parser_spec.rb
index 02b00f711b4..7d81af92412 100644
--- a/spec/lib/gitlab/git/conflict/parser_spec.rb
+++ b/spec/lib/gitlab/git/conflict/parser_spec.rb
@@ -86,43 +86,68 @@ RSpec.describe Gitlab::Git::Conflict::Parser do
CONFLICT
end
- let(:lines) do
- described_class.parse(text, our_path: 'files/ruby/regex.rb', their_path: 'files/ruby/regex.rb')
- end
+ shared_examples_for 'successful parsing' do
+ let(:lines) do
+ described_class.parse(content, our_path: 'files/ruby/regex.rb', their_path: 'files/ruby/regex.rb')
+ end
- let(:old_line_numbers) do
- lines.select { |line| line[:type] != 'new' }.map { |line| line[:line_old] }
- end
+ let(:old_line_numbers) do
+ lines.select { |line| line[:type] != 'new' }.map { |line| line[:line_old] }
+ end
- let(:new_line_numbers) do
- lines.select { |line| line[:type] != 'old' }.map { |line| line[:line_new] }
- end
+ let(:new_line_numbers) do
+ lines.select { |line| line[:type] != 'old' }.map { |line| line[:line_new] }
+ end
+
+ let(:line_indexes) { lines.map { |line| line[:line_obj_index] } }
- let(:line_indexes) { lines.map { |line| line[:line_obj_index] } }
+ it 'sets our lines as new lines' do
+ expect(lines[8..13]).to all(include(type: 'new'))
+ expect(lines[26..27]).to all(include(type: 'new'))
+ expect(lines[56..57]).to all(include(type: 'new'))
+ end
- it 'sets our lines as new lines' do
- expect(lines[8..13]).to all(include(type: 'new'))
- expect(lines[26..27]).to all(include(type: 'new'))
- expect(lines[56..57]).to all(include(type: 'new'))
+ it 'sets their lines as old lines' do
+ expect(lines[14..19]).to all(include(type: 'old'))
+ expect(lines[28..29]).to all(include(type: 'old'))
+ expect(lines[58..59]).to all(include(type: 'old'))
+ end
+
+ it 'sets non-conflicted lines as both' do
+ expect(lines[0..7]).to all(include(type: nil))
+ expect(lines[20..25]).to all(include(type: nil))
+ expect(lines[30..55]).to all(include(type: nil))
+ expect(lines[60..62]).to all(include(type: nil))
+ end
+
+ it 'sets consecutive line numbers for line_obj_index, line_old, and line_new' do
+ expect(line_indexes).to eq(0.upto(62).to_a)
+ expect(old_line_numbers).to eq(1.upto(53).to_a)
+ expect(new_line_numbers).to eq(1.upto(53).to_a)
+ end
end
- it 'sets their lines as old lines' do
- expect(lines[14..19]).to all(include(type: 'old'))
- expect(lines[28..29]).to all(include(type: 'old'))
- expect(lines[58..59]).to all(include(type: 'old'))
+ context 'content has LF endings' do
+ let(:content) { text }
+
+ it_behaves_like 'successful parsing'
end
- it 'sets non-conflicted lines as both' do
- expect(lines[0..7]).to all(include(type: nil))
- expect(lines[20..25]).to all(include(type: nil))
- expect(lines[30..55]).to all(include(type: nil))
- expect(lines[60..62]).to all(include(type: nil))
+ context 'content has CRLF endings' do
+ let(:content) { text.gsub("\n", "\r\n") }
+
+ it_behaves_like 'successful parsing'
end
- it 'sets consecutive line numbers for line_obj_index, line_old, and line_new' do
- expect(line_indexes).to eq(0.upto(62).to_a)
- expect(old_line_numbers).to eq(1.upto(53).to_a)
- expect(new_line_numbers).to eq(1.upto(53).to_a)
+ context 'content has mixed LF and CRLF endings' do
+ # Simulate mixed line endings by only changing some of the lines to CRLF
+ let(:content) do
+ text.each_line.map.with_index do |line, index|
+ index.odd? ? line.gsub("\n", "\r\n") : line
+ end.join
+ end
+
+ it_behaves_like 'successful parsing'
end
end
diff --git a/spec/lib/gitlab/git/object_pool_spec.rb b/spec/lib/gitlab/git/object_pool_spec.rb
index 91960ebbede..3b1eb0319f8 100644
--- a/spec/lib/gitlab/git/object_pool_spec.rb
+++ b/spec/lib/gitlab/git/object_pool_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe Gitlab::Git::ObjectPool do
- include RepoHelpers
-
let(:pool_repository) { create(:pool_repository) }
let(:source_repository) { pool_repository.source_project.repository }
@@ -80,8 +78,6 @@ RSpec.describe Gitlab::Git::ObjectPool do
end
describe '#fetch' do
- let(:source_repository_path) { File.join(TestEnv.repos_path, source_repository.relative_path) }
- let(:source_repository_rugged) { Rugged::Repository.new(source_repository_path) }
let(:commit_count) { source_repository.commit_count }
context "when the object's pool repository exists" do
@@ -106,7 +102,13 @@ RSpec.describe Gitlab::Git::ObjectPool do
end
it 'fetches objects from the source repository' do
- new_commit_id = new_commit_edit_old_file(source_repository_rugged).oid
+ new_commit_id = source_repository.create_file(
+ pool_repository.source_project.owner,
+ 'a.file',
+ 'This is a file',
+ branch_name: source_repository.root_ref,
+ message: 'Add a file'
+ )
expect(subject.repository.exists?).to be false
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 47688c4b3e6..e20d5b928c4 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -352,12 +352,30 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
repository.create_branch('left-branch')
repository.create_branch('right-branch')
- left.times do
- new_commit_edit_new_file_on_branch(repository_rugged, 'encoding/CHANGELOG', 'left-branch', 'some more content for a', 'some stuff')
+ left.times do |i|
+ repository.multi_action(
+ user,
+ branch_name: 'left-branch',
+ message: 'some more content for a',
+ actions: [{
+ action: i == 0 ? :create : :update,
+ file_path: 'encoding/CHANGELOG',
+ content: 'some stuff'
+ }]
+ )
end
- right.times do
- new_commit_edit_new_file_on_branch(repository_rugged, 'encoding/CHANGELOG', 'right-branch', 'some more content for b', 'some stuff')
+ right.times do |i|
+ repository.multi_action(
+ user,
+ branch_name: 'right-branch',
+ message: 'some more content for b',
+ actions: [{
+ action: i == 0 ? :create : :update,
+ file_path: 'encoding/CHANGELOG',
+ content: 'some stuff'
+ }]
+ )
end
end
@@ -367,8 +385,8 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
it 'returns the correct count bounding at max_count' do
- branch_a_sha = repository_rugged.branches['left-branch'].target.oid
- branch_b_sha = repository_rugged.branches['right-branch'].target.oid
+ branch_a_sha = repository.find_branch('left-branch').dereferenced_target.sha
+ branch_b_sha = repository.find_branch('right-branch').dereferenced_target.sha
count = repository.diverging_commit_count(branch_a_sha, branch_b_sha, max_count: 1000)
@@ -392,12 +410,30 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
repository.create_branch('left-branch')
repository.create_branch('right-branch')
- left.times do
- new_commit_edit_new_file_on_branch(repository_rugged, 'encoding/CHANGELOG', 'left-branch', 'some more content for a', 'some stuff')
+ left.times do |i|
+ repository.multi_action(
+ user,
+ branch_name: 'left-branch',
+ message: 'some more content for a',
+ actions: [{
+ action: i == 0 ? :create : :update,
+ file_path: 'encoding/CHANGELOG',
+ content: 'some stuff'
+ }]
+ )
end
- right.times do
- new_commit_edit_new_file_on_branch(repository_rugged, 'encoding/CHANGELOG', 'right-branch', 'some more content for b', 'some stuff')
+ right.times do |i|
+ repository.multi_action(
+ user,
+ branch_name: 'right-branch',
+ message: 'some more content for b',
+ actions: [{
+ action: i == 0 ? :create : :update,
+ file_path: 'encoding/CHANGELOG',
+ content: 'some stuff'
+ }]
+ )
end
end
@@ -407,8 +443,8 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
it 'returns the correct count bounding at max_count' do
- branch_a_sha = repository_rugged.branches['left-branch'].target.oid
- branch_b_sha = repository_rugged.branches['right-branch'].target.oid
+ branch_a_sha = repository.find_branch('left-branch').dereferenced_target.sha
+ branch_b_sha = repository.find_branch('right-branch').dereferenced_target.sha
results = repository.diverging_commit_count(branch_a_sha, branch_b_sha, max_count: max_count)
@@ -469,16 +505,14 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
it 'deletes the ref' do
repository.delete_refs('refs/heads/feature')
- expect(repository_rugged.references['refs/heads/feature']).to be_nil
+ expect(repository.find_branch('feature')).to be_nil
end
it 'deletes all refs' do
refs = %w[refs/heads/wip refs/tags/v1.1.0]
repository.delete_refs(*refs)
- refs.each do |ref|
- expect(repository_rugged.references[ref]).to be_nil
- end
+ expect(repository.list_refs(refs)).to be_empty
end
it 'does not fail when deleting an empty list of refs' do
@@ -491,7 +525,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
describe '#branch_names_contains_sha' do
- let(:head_id) { repository_rugged.head.target.oid }
+ let(:head_id) { repository.commit.id }
let(:new_branch) { head_id }
let(:utf8_branch) { 'branch-é' }
@@ -525,7 +559,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
it 'does not error when dereferenced_target is nil' do
blob_id = repository.blob_at('master', 'README.md').id
- repository_rugged.tags.create("refs/tags/blob-tag", blob_id)
+ repository.add_tag("refs/tags/blob-tag", user: user, target: blob_id)
expect { subject }.not_to raise_error
end
@@ -559,14 +593,31 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
describe '#search_files_by_content' do
let(:repository) { mutable_repository }
- let(:repository_rugged) { mutable_repository_rugged }
let(:ref) { 'search-files-by-content-branch' }
let(:content) { 'foobarbazmepmep' }
before do
repository.create_branch(ref)
- new_commit_edit_new_file_on_branch(repository_rugged, 'encoding/CHANGELOG', ref, 'committing something', content)
- new_commit_edit_new_file_on_branch(repository_rugged, 'anotherfile', ref, 'committing something', content)
+ repository.multi_action(
+ user,
+ branch_name: ref,
+ message: 'committing something',
+ actions: [{
+ action: :create,
+ file_path: 'encoding/CHANGELOG',
+ content: content
+ }]
+ )
+ repository.multi_action(
+ user,
+ branch_name: ref,
+ message: 'committing something',
+ actions: [{
+ action: :create,
+ file_path: 'anotherfile',
+ content: content
+ }]
+ )
end
after do
@@ -669,14 +720,42 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
before do
# Add new commits so that there's a renamed file in the commit history
- @commit_with_old_name_id = new_commit_edit_old_file(repository_rugged).oid
- @rename_commit_id = new_commit_move_file(repository_rugged).oid
- @commit_with_new_name_id = new_commit_edit_new_file(repository_rugged, "encoding/CHANGELOG", "Edit encoding/CHANGELOG", "I'm a new changelog with different text").oid
+ @commit_with_old_name_id = repository.multi_action(
+ user,
+ branch_name: repository.root_ref,
+ message: 'Update CHANGELOG',
+ actions: [{
+ action: :update,
+ file_path: 'CHANGELOG',
+ content: 'CHANGELOG'
+ }]
+ ).newrev
+ @rename_commit_id = repository.multi_action(
+ user,
+ branch_name: repository.root_ref,
+ message: 'Move CHANGELOG to encoding/',
+ actions: [{
+ action: :move,
+ previous_path: 'CHANGELOG',
+ file_path: 'encoding/CHANGELOG',
+ content: 'CHANGELOG'
+ }]
+ ).newrev
+ @commit_with_new_name_id = repository.multi_action(
+ user,
+ branch_name: repository.root_ref,
+ message: 'Edit encoding/CHANGELOG',
+ actions: [{
+ action: :update,
+ file_path: 'encoding/CHANGELOG',
+ content: "I'm a new changelog with different text"
+ }]
+ ).newrev
end
after do
# Erase our commits so other tests get the original repo
- repository_rugged.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
+ repository.write_ref(repository.root_ref, SeedRepo::LastCommit::ID)
end
context "where 'follow' == true" do
@@ -1649,27 +1728,28 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
describe '#gitattribute' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_GITATTRIBUTES_REPO_PATH, '', 'group/project') }
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
- after do
- ensure_seeds
- end
+ context 'with gitattributes' do
+ before do
+ repository.copy_gitattributes('gitattributes')
+ end
- it 'returns matching language attribute' do
- expect(repository.gitattribute("custom-highlighting/test.gitlab-custom", 'gitlab-language')).to eq('ruby')
- end
+ it 'returns matching language attribute' do
+ expect(repository.gitattribute("custom-highlighting/test.gitlab-custom", 'gitlab-language')).to eq('ruby')
+ end
- it 'returns matching language attribute with additional options' do
- expect(repository.gitattribute("custom-highlighting/test.gitlab-cgi", 'gitlab-language')).to eq('erb?parent=json')
- end
+ it 'returns matching language attribute with additional options' do
+ expect(repository.gitattribute("custom-highlighting/test.gitlab-cgi", 'gitlab-language')).to eq('erb?parent=json')
+ end
- it 'returns nil if nothing matches' do
- expect(repository.gitattribute("report.xslt", 'gitlab-language')).to eq(nil)
+ it 'returns nil if nothing matches' do
+ expect(repository.gitattribute("report.xslt", 'gitlab-language')).to eq(nil)
+ end
end
- context 'without gitattributes file' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
-
+ context 'without gitattributes' do
it 'returns nil' do
expect(repository.gitattribute("README.md", 'gitlab-language')).to eq(nil)
end
@@ -1760,25 +1840,13 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
describe '#languages' do
it 'returns exactly the expected results' do
languages = repository.languages('4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6')
- expected_languages = [
- { value: 66.63, label: "Ruby", color: "#701516", highlight: "#701516" },
- { value: 22.96, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" },
- { value: 7.9, label: "HTML", color: "#e34c26", highlight: "#e34c26" },
- { value: 2.51, label: "CoffeeScript", color: "#244776", highlight: "#244776" }
- ]
-
- expect(languages.size).to eq(expected_languages.size)
-
- expected_languages.size.times do |i|
- a = expected_languages[i]
- b = languages[i]
- expect(a.keys.sort).to eq(b.keys.sort)
- expect(a[:value]).to be_within(0.1).of(b[:value])
-
- non_float_keys = a.keys - [:value]
- expect(a.values_at(*non_float_keys)).to eq(b.values_at(*non_float_keys))
- end
+ expect(languages).to match_array([
+ { value: a_value_within(0.1).of(66.7), label: "Ruby", color: "#701516", highlight: "#701516" },
+ { value: a_value_within(0.1).of(22.96), label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" },
+ { value: a_value_within(0.1).of(7.9), label: "HTML", color: "#e34c26", highlight: "#e34c26" },
+ { value: a_value_within(0.1).of(2.51), label: "CoffeeScript", color: "#244776", highlight: "#244776" }
+ ])
end
it "uses the repository's HEAD when no ref is passed" do
@@ -1818,12 +1886,18 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
context 'when the branch exists' do
context 'when the commit does not exist locally' do
let(:source_branch) { 'new-branch-for-fetch-source-branch' }
- let(:source_path) { File.join(TestEnv.repos_path, source_repository.relative_path) }
- let(:source_rugged) { Rugged::Repository.new(source_path) }
- let(:new_oid) { new_commit_edit_old_file(source_rugged).oid }
- before do
- source_rugged.branches.create(source_branch, new_oid)
+ let!(:new_oid) do
+ source_repository.multi_action(
+ user,
+ branch_name: source_branch,
+ message: 'Add a file',
+ actions: [{
+ action: :create,
+ file_path: 'a.file',
+ content: 'This is a file.'
+ }]
+ ).newrev
end
it 'writes the ref' do
@@ -1869,7 +1943,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
it "removes the branch from the repo" do
repository.rm_branch(branch_name, user: user)
- expect(repository_rugged.branches[branch_name]).to be_nil
+ expect(repository.find_branch(branch_name)).to be_nil
end
end
@@ -2290,11 +2364,23 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
context 'when the diff contains a rename' do
- let(:end_sha) { new_commit_move_file(repository_rugged).oid }
+ let(:end_sha) do
+ repository.multi_action(
+ user,
+ branch_name: repository.root_ref,
+ message: 'Move CHANGELOG to encoding/',
+ actions: [{
+ action: :move,
+ previous_path: 'CHANGELOG',
+ file_path: 'encoding/CHANGELOG',
+ content: 'CHANGELOG'
+ }]
+ ).newrev
+ end
after do
# Erase our commits so other tests get the original repo
- repository_rugged.references.update('refs/heads/master', SeedRepo::LastCommit::ID)
+ repository.write_ref(repository.root_ref, SeedRepo::LastCommit::ID)
end
it 'does not include the renamed file in the sparse checkout' do
@@ -2342,24 +2428,15 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
def create_remote_branch(remote_name, branch_name, source_branch_name)
- source_branch = repository.branches.find { |branch| branch.name == source_branch_name }
- repository_rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", source_branch.dereferenced_target.sha)
- end
-
- def refs(dir)
- IO.popen(%W[git -C #{dir} for-each-ref], &:read).split("\n").map do |line|
- line.split("\t").last
- end
+ source_branch = repository.find_branch(source_branch_name)
+ repository.write_ref("refs/remotes/#{remote_name}/#{branch_name}", source_branch.dereferenced_target.sha)
end
describe '#disconnect_alternates' do
let(:project) { create(:project, :repository) }
let(:pool_repository) { create(:pool_repository) }
let(:repository) { project.repository }
- let(:repository_path) { File.join(TestEnv.repos_path, repository.relative_path) }
let(:object_pool) { pool_repository.object_pool }
- let(:object_pool_path) { File.join(TestEnv.repos_path, object_pool.repository.relative_path) }
- let(:object_pool_rugged) { Rugged::Repository.new(object_pool_path) }
before do
object_pool.create # rubocop:disable Rails/SaveBang
@@ -2369,25 +2446,24 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
expect { repository.disconnect_alternates }.not_to raise_error
end
- it 'removes the alternates file' do
- object_pool.link(repository)
-
- alternates_file = File.join(repository_path, "objects", "info", "alternates")
- expect(File.exist?(alternates_file)).to be_truthy
-
- repository.disconnect_alternates
-
- expect(File.exist?(alternates_file)).to be_falsey
- end
-
it 'can still access objects in the object pool' do
object_pool.link(repository)
- new_commit = new_commit_edit_old_file(object_pool_rugged)
- expect(repository.commit(new_commit.oid).id).to eq(new_commit.oid)
+ new_commit_id = object_pool.repository.multi_action(
+ project.owner,
+ branch_name: object_pool.repository.root_ref,
+ message: 'Add a file',
+ actions: [{
+ action: :create,
+ file_path: 'a.file',
+ content: 'This is a file.'
+ }]
+ ).newrev
+
+ expect(repository.commit(new_commit_id).id).to eq(new_commit_id)
repository.disconnect_alternates
- expect(repository.commit(new_commit.oid).id).to eq(new_commit.oid)
+ expect(repository.commit(new_commit_id).id).to eq(new_commit_id)
end
end
@@ -2483,7 +2559,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
it 'mirrors the source repository' do
subject
- expect(refs(new_repository_path)).to eq(refs(repository_path))
+ expect(new_repository.list_refs(['refs/'])).to eq(repository.list_refs(['refs/']))
end
end
@@ -2495,7 +2571,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
it 'mirrors the source repository' do
subject
- expect(refs(new_repository_path)).to eq(refs(repository_path))
+ expect(new_repository.list_refs(['refs/'])).to eq(repository.list_refs(['refs/']))
end
context 'with keep-around refs' do
@@ -2511,8 +2587,8 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
it 'includes the temporary and keep-around refs' do
subject
- expect(refs(new_repository_path)).to include(keep_around_ref)
- expect(refs(new_repository_path)).to include(tmp_ref)
+ expect(new_repository.list_refs([keep_around_ref]).map(&:name)).to match_array([keep_around_ref])
+ expect(new_repository.list_refs([tmp_ref]).map(&:name)).to match_array([tmp_ref])
end
end
end
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index 97ba177da71..172d7a3f27b 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -3,6 +3,8 @@
require "spec_helper"
RSpec.describe Gitlab::Git::Tree, :seed_helper do
+ let_it_be(:user) { create(:user) }
+
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
shared_examples :repo do
@@ -85,51 +87,29 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do
context :flat_path do
let(:filename) { 'files/flat/path/correct/content.txt' }
- let(:sha) { create_file(filename) }
let(:path) { 'files/flat' }
# rubocop: disable Rails/FindBy
# This is not ActiveRecord where..first
let(:subdir_file) { entries.first }
# rubocop: enable Rails/FindBy
- let(:repository_rugged) { Rugged::Repository.new(File.join(SEED_STORAGE_PATH, TEST_REPO_PATH)) }
-
- it { expect(subdir_file.flat_path).to eq('files/flat/path/correct') }
- end
-
- def create_file(path)
- oid = repository_rugged.write('test', :blob)
- index = repository_rugged.index
- index.add(path: filename, oid: oid, mode: 0100644)
-
- options = commit_options(
- repository_rugged,
- index,
- repository_rugged.head.target,
- 'HEAD',
- 'Add new file')
+ let!(:sha) do
+ repository.multi_action(
+ user,
+ branch_name: 'HEAD',
+ message: "Create #{filename}",
+ actions: [{
+ action: :create,
+ file_path: filename,
+ contents: 'test'
+ }]
+ ).newrev
+ end
- Rugged::Commit.create(repository_rugged, options)
- end
+ after do
+ ensure_seeds
+ end
- # Build the options hash that's passed to Rugged::Commit#create
- def commit_options(repo, index, target, ref, message)
- options = {}
- options[:tree] = index.write_tree(repo)
- options[:author] = {
- email: "test@example.com",
- name: "Test Author",
- time: Time.gm(2014, "mar", 3, 20, 15, 1)
- }
- options[:committer] = {
- email: "test@example.com",
- name: "Test Author",
- time: Time.gm(2014, "mar", 3, 20, 15, 1)
- }
- options[:message] ||= message
- options[:parents] = repo.empty? ? [] : [target].compact
- options[:update_ref] = ref
-
- options
+ it { expect(subdir_file.flat_path).to eq('files/flat/path/correct') }
end
end
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 3a34d39c722..d5d1bef7bff 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::GitalyClient::CommitService do
- let(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
+
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 4320c5460da..e04895d975f 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -2,9 +2,6 @@
require 'spec_helper'
-require 'google/rpc/status_pb'
-require 'google/protobuf/well_known_types'
-
RSpec.describe Gitlab::GitalyClient::OperationService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
@@ -188,7 +185,7 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
end
shared_examples 'a failed branch deletion' do
- it 'raises a PreRecieveError' do
+ it 'raises a PreReceiveError' do
expect_any_instance_of(Gitaly::OperationService::Stub)
.to receive(:user_delete_branch).with(request, kind_of(Hash))
.and_raise(custom_hook_error)
@@ -288,7 +285,7 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
end
shared_examples 'a failed merge' do
- it 'raises a PreRecieveError' do
+ it 'raises a PreReceiveError' do
expect_any_instance_of(Gitaly::OperationService::Stub)
.to receive(:user_merge_branch).with(kind_of(Enumerator), kind_of(Hash))
.and_raise(custom_hook_error)
@@ -816,14 +813,4 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
end
end
end
-
- def new_detailed_error(error_code, error_message, details)
- status_error = Google::Rpc::Status.new(
- code: error_code,
- message: error_message,
- details: [Google::Protobuf::Any.pack(details)]
- )
-
- GRPC::BadStatus.new(error_code, error_message, { "grpc-status-details-bin" => Google::Rpc::Status.encode(status_error) })
- end
end
diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
index 2e37c98a591..566bdbacf4a 100644
--- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
@@ -241,30 +241,70 @@ RSpec.describe Gitlab::GitalyClient::RefService do
end
end
- describe '#ref_exists?', :seed_helper do
- it 'finds the master branch ref' do
- expect(client.ref_exists?('refs/heads/master')).to eq(true)
- end
+ describe '#ref_exists?' do
+ let(:ref) { 'refs/heads/master' }
- it 'returns false for an illegal tag name ref' do
- expect(client.ref_exists?('refs/tags/.this-tag-name-is-illegal')).to eq(false)
- end
+ it 'sends a ref_exists message' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:ref_exists)
+ .with(gitaly_request_with_params(ref: ref), kind_of(Hash))
+ .and_return(double('ref_exists_response', value: true))
- it 'raises an argument error if the ref name parameter does not start with refs/' do
- expect { client.ref_exists?('reXXXXX') }.to raise_error(ArgumentError)
+ expect(client.ref_exists?(ref)).to be true
end
end
describe '#delete_refs' do
let(:prefixes) { %w(refs/heads refs/keep-around) }
+ subject(:delete_refs) { client.delete_refs(except_with_prefixes: prefixes) }
+
it 'sends a delete_refs message' do
expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:delete_refs)
.with(gitaly_request_with_params(except_with_prefix: prefixes), kind_of(Hash))
.and_return(double('delete_refs_response', git_error: ""))
- client.delete_refs(except_with_prefixes: prefixes)
+ delete_refs
+ end
+
+ context 'with a references locked error' do
+ let(:references_locked_error) do
+ new_detailed_error(
+ GRPC::Core::StatusCodes::FAILED_PRECONDITION,
+ "error message",
+ Gitaly::DeleteRefsError.new(references_locked: Gitaly::ReferencesLockedError.new))
+ end
+
+ it 'raises ReferencesLockedError' do
+ expect_any_instance_of(Gitaly::RefService::Stub).to receive(:delete_refs)
+ .with(gitaly_request_with_params(except_with_prefix: prefixes), kind_of(Hash))
+ .and_raise(references_locked_error)
+
+ expect { delete_refs }.to raise_error(Gitlab::Git::ReferencesLockedError)
+ end
+ end
+
+ context 'with a invalid format error' do
+ let(:invalid_refs) {['\invali.\d/1', '\.invali/d/2']}
+ let(:invalid_reference_format_error) do
+ new_detailed_error(
+ GRPC::Core::StatusCodes::INVALID_ARGUMENT,
+ "error message",
+ Gitaly::DeleteRefsError.new(invalid_format: Gitaly::InvalidRefFormatError.new(refs: invalid_refs)))
+ end
+
+ it 'raises InvalidRefFormatError' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:delete_refs)
+ .with(gitaly_request_with_params(except_with_prefix: prefixes), kind_of(Hash))
+ .and_raise(invalid_reference_format_error)
+
+ expect { delete_refs }.to raise_error do |error|
+ expect(error).to be_a(Gitlab::Git::InvalidRefFormatError)
+ expect(error.message).to eq("references have an invalid format: #{invalid_refs.join(",")}")
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index ba4ea1069d8..a3840ca843f 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -358,11 +358,7 @@ RSpec.describe Gitlab::GitalyClient do
end
end
- context 'when RequestStore is enabled and the maximum number of calls is not enforced by a feature flag', :request_store do
- before do
- stub_feature_flags(gitaly_enforce_requests_limits: false)
- end
-
+ shared_examples 'enforces maximum allowed Gitaly calls' do
it 'allows up the maximum number of allowed calls' do
expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS) }.not_to raise_error
end
@@ -408,6 +404,18 @@ RSpec.describe Gitlab::GitalyClient do
end
end
+ context 'when RequestStore is enabled and the maximum number of calls is enforced by a feature flag', :request_store do
+ include_examples 'enforces maximum allowed Gitaly calls'
+ end
+
+ context 'when RequestStore is enabled and the maximum number of calls is not enforced by a feature flag', :request_store do
+ before do
+ stub_feature_flags(gitaly_enforce_requests_limits: false)
+ end
+
+ include_examples 'enforces maximum allowed Gitaly calls'
+ end
+
context 'in production and when RequestStore is enabled', :request_store do
before do
stub_rails_env('production')
@@ -537,4 +545,44 @@ RSpec.describe Gitlab::GitalyClient do
end
end
end
+
+ describe '.decode_detailed_error' do
+ let(:detailed_error) do
+ new_detailed_error(GRPC::Core::StatusCodes::INVALID_ARGUMENT,
+ "error message",
+ Gitaly::InvalidRefFormatError.new)
+ end
+
+ let(:error_without_details) do
+ error_code = GRPC::Core::StatusCodes::INVALID_ARGUMENT
+ error_message = "error message"
+
+ status_error = Google::Rpc::Status.new(
+ code: error_code,
+ message: error_message,
+ details: nil
+ )
+
+ GRPC::BadStatus.new(
+ error_code,
+ error_message,
+ { "grpc-status-details-bin" => Google::Rpc::Status.encode(status_error) })
+ end
+
+ context 'decodes a structured error' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:error, :result) do
+ detailed_error | Gitaly::InvalidRefFormatError.new
+ error_without_details | nil
+ StandardError.new | nil
+ end
+
+ with_them do
+ it 'returns correct detailed error' do
+ expect(described_class.decode_detailed_error(error)).to eq(result)
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
new file mode 100644
index 00000000000..b773598853d
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do
+ subject(:importer) { described_class.new(project, user.id) }
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ let(:issue) { create(:issue, project: project) }
+ let!(:label) { create(:label, project: project) }
+
+ let(:issue_event) do
+ Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
+ 'id' => 6501124486,
+ 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'event' => event_type,
+ 'commit_id' => nil,
+ 'label_title' => label.title,
+ 'issue_db_id' => issue.id,
+ 'created_at' => '2022-04-26 18:30:53 UTC'
+ )
+ end
+
+ let(:event_attrs) do
+ {
+ user_id: user.id,
+ issue_id: issue.id,
+ label_id: label.id,
+ created_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ shared_examples 'new event' do
+ it 'creates a new label event' do
+ expect { importer.execute(issue_event) }.to change { issue.resource_label_events.count }
+ .from(0).to(1)
+ expect(issue.resource_label_events.last)
+ .to have_attributes(expected_event_attrs)
+ end
+ end
+
+ before do
+ allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id)
+ end
+
+ context 'when importing a labeled event' do
+ let(:event_type) { 'labeled' }
+ let(:expected_event_attrs) { event_attrs.merge(action: 'add') }
+
+ it_behaves_like 'new event'
+ end
+
+ context 'when importing an unlabeled event' do
+ let(:event_type) { 'unlabeled' }
+ let(:expected_event_attrs) { event_attrs.merge(action: 'remove') }
+
+ it_behaves_like 'new event'
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
new file mode 100644
index 00000000000..116917d3e06
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
+ subject(:importer) { described_class.new(project, user.id) }
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ let(:issue) { create(:issue, project: project) }
+ let(:commit_id) { nil }
+
+ let(:issue_event) do
+ Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
+ 'id' => 6501124486,
+ 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
+ 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
+ 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'event' => 'closed',
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'commit_id' => commit_id,
+ 'issue_db_id' => issue.id
+ )
+ end
+
+ let(:expected_event_attrs) do
+ {
+ project_id: project.id,
+ author_id: user.id,
+ target_id: issue.id,
+ target_type: Issue.name,
+ action: 'closed',
+ created_at: issue_event.created_at,
+ updated_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ let(:expected_state_event_attrs) do
+ {
+ user_id: user.id,
+ issue_id: issue.id,
+ state: 'closed',
+ created_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ it 'creates expected event and state event' do
+ importer.execute(issue_event)
+
+ expect(issue.events.count).to eq 1
+ expect(issue.events[0].attributes)
+ .to include expected_event_attrs
+
+ expect(issue.resource_state_events.count).to eq 1
+ expect(issue.resource_state_events[0].attributes)
+ .to include expected_state_event_attrs
+ end
+
+ context 'when closed by commit' do
+ let!(:closing_commit) { create(:commit, project: project) }
+ let(:commit_id) { closing_commit.id }
+
+ it 'creates expected event and state event' do
+ importer.execute(issue_event)
+
+ expect(issue.events.count).to eq 1
+ state_event = issue.resource_state_events.last
+ expect(state_event.source_commit).to eq commit_id[0..40]
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb b/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
new file mode 100644
index 00000000000..118c482a7d9
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_gitlab_redis_cache do
+ subject(:importer) { described_class.new(project, user.id) }
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ let(:sawyer_stub) { Struct.new(:iid, :issuable_type, keyword_init: true) }
+
+ let(:issue) { create(:issue, project: project) }
+ let(:referenced_in) { build_stubbed(:issue, project: project) }
+ let(:commit_id) { nil }
+
+ let(:issue_event) do
+ Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
+ 'id' => 6501124486,
+ 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
+ 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
+ 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'event' => 'cross-referenced',
+ 'source' => {
+ 'type' => 'issue',
+ 'issue' => {
+ 'number' => referenced_in.iid,
+ 'pull_request' => pull_request_resource
+ }
+ },
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'issue_db_id' => issue.id
+ )
+ end
+
+ let(:pull_request_resource) { nil }
+ let(:expected_note_attrs) do
+ {
+ system: true,
+ noteable_type: Issue.name,
+ noteable_id: issue_event.issue_db_id,
+ project_id: project.id,
+ author_id: user.id,
+ note: expected_note_body,
+ created_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ context 'when referenced in other issue' do
+ let(:expected_note_body) { "mentioned in issue ##{issue.iid}" }
+
+ before do
+ other_issue_resource = sawyer_stub.new(iid: referenced_in.iid, issuable_type: 'Issue')
+ Gitlab::GithubImport::IssuableFinder.new(project, other_issue_resource)
+ .cache_database_id(referenced_in.iid)
+ end
+
+ it 'creates expected note' do
+ importer.execute(issue_event)
+
+ expect(issue.notes.count).to eq 1
+ expect(issue.notes[0]).to have_attributes expected_note_attrs
+ expect(issue.notes[0].system_note_metadata.action).to eq 'cross_reference'
+ end
+ end
+
+ context 'when referenced in pull request' do
+ let(:referenced_in) { build_stubbed(:merge_request, project: project) }
+ let(:pull_request_resource) { { 'id' => referenced_in.iid } }
+
+ let(:expected_note_body) { "mentioned in merge request !#{referenced_in.iid}" }
+
+ before do
+ other_issue_resource =
+ sawyer_stub.new(iid: referenced_in.iid, issuable_type: 'MergeRequest')
+ Gitlab::GithubImport::IssuableFinder.new(project, other_issue_resource)
+ .cache_database_id(referenced_in.iid)
+ end
+
+ it 'creates expected note' do
+ importer.execute(issue_event)
+
+ expect(issue.notes.count).to eq 1
+ expect(issue.notes[0]).to have_attributes expected_note_attrs
+ expect(issue.notes[0].system_note_metadata.action).to eq 'cross_reference'
+ end
+ end
+
+ context 'when referenced in out of project issue/pull_request' do
+ it 'creates expected note' do
+ importer.execute(issue_event)
+
+ expect(issue.notes.count).to eq 0
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb b/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
new file mode 100644
index 00000000000..a8c3fbcb05d
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
+ subject(:importer) { described_class.new(project, user.id) }
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ let(:issue) { create(:issue, project: project) }
+
+ let(:issue_event) do
+ Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
+ 'id' => 6501124486,
+ 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'event' => 'renamed',
+ 'commit_id' => nil,
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'old_title' => 'old title',
+ 'new_title' => 'new title',
+ 'issue_db_id' => issue.id
+ )
+ end
+
+ let(:expected_note_attrs) do
+ {
+ noteable_id: issue.id,
+ noteable_type: Issue.name,
+ project_id: project.id,
+ author_id: user.id,
+ note: "changed title from **{-old-} title** to **{+new+} title**",
+ system: true,
+ created_at: issue_event.created_at,
+ updated_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ let(:expected_system_note_metadata_attrs) do
+ {
+ action: "title",
+ created_at: issue_event.created_at,
+ updated_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ describe '#execute' do
+ it 'creates expected note' do
+ expect { importer.execute(issue_event) }.to change { issue.notes.count }
+ .from(0).to(1)
+
+ expect(issue.notes.last)
+ .to have_attributes(expected_note_attrs)
+ end
+
+ it 'creates expected system note metadata' do
+ expect { importer.execute(issue_event) }.to change { SystemNoteMetadata.count }
+ .from(0).to(1)
+
+ expect(SystemNoteMetadata.last)
+ .to have_attributes(
+ expected_system_note_metadata_attrs.merge(
+ note_id: Note.last.id
+ )
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb b/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
new file mode 100644
index 00000000000..81653b0ecdc
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_failures do
+ subject(:importer) { described_class.new(project, user.id) }
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ let(:issue) { create(:issue, project: project) }
+
+ let(:issue_event) do
+ Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
+ 'id' => 6501124486,
+ 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
+ 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
+ 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'event' => 'reopened',
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'issue_db_id' => issue.id
+ )
+ end
+
+ let(:expected_event_attrs) do
+ {
+ project_id: project.id,
+ author_id: user.id,
+ target_id: issue.id,
+ target_type: Issue.name,
+ action: 'reopened',
+ created_at: issue_event.created_at,
+ updated_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ let(:expected_state_event_attrs) do
+ {
+ user_id: user.id,
+ state: 'reopened',
+ created_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ it 'creates expected event and state event' do
+ importer.execute(issue_event)
+
+ expect(issue.events.count).to eq 1
+ expect(issue.events[0].attributes)
+ .to include expected_event_attrs
+
+ expect(issue.resource_state_events.count).to eq 1
+ expect(issue.resource_state_events[0].attributes)
+ .to include expected_state_event_attrs
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
new file mode 100644
index 00000000000..da32a3b3766
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab_redis_cache do
+ let(:importer) { described_class.new(issue_event, project, client) }
+
+ let(:project) { create(:project) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, project: project) }
+
+ let(:issue_event) do
+ Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
+ 'id' => 6501124486,
+ 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
+ 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
+ 'actor' => { 'id' => actor_id, 'login' => 'alice' },
+ 'event' => event_name,
+ 'commit_id' => '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ 'commit_url' =>
+ 'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'performed_via_github_app' => nil
+ )
+ end
+
+ let(:actor_id) { user.id }
+ let(:event_name) { 'closed' }
+
+ shared_examples 'triggers specific event importer' do |importer_class|
+ it importer_class.name do
+ specific_importer = double(importer_class.name) # rubocop:disable RSpec/VerifiedDoubles
+
+ expect(importer_class)
+ .to receive(:new).with(project, user.id)
+ .and_return(specific_importer)
+ expect(specific_importer).to receive(:execute).with(issue_event)
+
+ importer.execute
+ end
+ end
+
+ describe '#execute' do
+ before do
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:author_id_for)
+ .with(issue_event, author_key: :actor)
+ .and_return(user.id, true)
+ end
+
+ issue_event.attributes[:issue_db_id] = issue.id
+ end
+
+ context "when it's closed issue event" do
+ let(:event_name) { 'closed' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::Closed
+ end
+
+ context "when it's reopened issue event" do
+ let(:event_name) { 'reopened' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::Reopened
+ end
+
+ context "when it's labeled issue event" do
+ let(:event_name) { 'labeled' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::ChangedLabel
+ end
+
+ context "when it's unlabeled issue event" do
+ let(:event_name) { 'unlabeled' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::ChangedLabel
+ end
+
+ context "when it's renamed issue event" do
+ let(:event_name) { 'renamed' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::Renamed
+ end
+
+ context "when it's cross-referenced issue event" do
+ let(:event_name) { 'cross-referenced' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::CrossReferenced
+ end
+
+ context "when it's unknown issue event" do
+ let(:event_name) { 'fake' }
+
+ it 'logs warning and skips' do
+ expect(Gitlab::GithubImport::Logger).to receive(:debug)
+ .with(
+ message: 'UNSUPPORTED_EVENT_TYPE',
+ event_type: issue_event.event,
+ event_github_id: issue_event.id
+ )
+
+ importer.execute
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
index 2a06983417d..570d26cdf2d 100644
--- a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
@@ -131,6 +131,7 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
title: 'My Issue',
author_id: user.id,
project_id: project.id,
+ namespace_id: project.project_namespace_id,
description: 'This is my issue',
milestone_id: milestone.id,
state_id: 1,
@@ -160,6 +161,7 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
title: 'My Issue',
author_id: project.creator_id,
project_id: project.id,
+ namespace_id: project.project_namespace_id,
description: "*Created by: alice*\n\nThis is my issue",
milestone_id: milestone.id,
state_id: 1,
diff --git a/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb b/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
new file mode 100644
index 00000000000..087faeffe02
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter do
+ let(:client) { double }
+
+ let_it_be(:project) { create(:project, :import_started, import_source: 'http://somegithub.com') }
+ let_it_be(:issue) { create(:issue, project: project) }
+
+ subject { described_class.new(project, client, parallel: parallel) }
+
+ let(:parallel) { true }
+
+ it { is_expected.to include_module(Gitlab::GithubImport::ParallelScheduling) }
+
+ describe '#importer_class' do
+ it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::IssueEventImporter) }
+ end
+
+ describe '#representation_class' do
+ it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::IssueEvent) }
+ end
+
+ describe '#sidekiq_worker_class' do
+ it { expect(subject.sidekiq_worker_class).to eq(Gitlab::GithubImport::ImportIssueEventWorker) }
+ end
+
+ describe '#object_type' do
+ it { expect(subject.object_type).to eq(:issue_event) }
+ end
+
+ describe '#collection_method' do
+ it { expect(subject.collection_method).to eq(:issue_timeline) }
+ end
+
+ describe '#page_counter_id' do
+ it { expect(subject.page_counter_id(issue)).to eq("issues/#{issue.iid}/issue_timeline") }
+ end
+
+ describe '#id_for_already_imported_cache' do
+ let(:event) { instance_double('Event', id: 1) }
+
+ it { expect(subject.id_for_already_imported_cache(event)).to eq(1) }
+ end
+
+ describe '#collection_options' do
+ it do
+ expect(subject.collection_options)
+ .to eq({ state: 'all', sort: 'created', direction: 'asc' })
+ end
+ end
+
+ describe '#each_object_to_import', :clean_gitlab_redis_cache do
+ let(:issue_event) do
+ struct = Struct.new(:id, :event, :created_at, :issue_db_id, keyword_init: true)
+ struct.new(id: rand(10), event: 'closed', created_at: '2022-04-26 18:30:53 UTC')
+ end
+
+ let(:page) do
+ instance_double(
+ Gitlab::GithubImport::Client::Page,
+ number: 1, objects: [issue_event]
+ )
+ end
+
+ let(:page_counter) { instance_double(Gitlab::GithubImport::PageCounter) }
+
+ before do
+ allow(client).to receive(:each_page)
+ .once
+ .with(
+ :issue_timeline,
+ project.import_source,
+ issue.iid,
+ { state: 'all', sort: 'created', direction: 'asc', page: 1 }
+ ).and_yield(page)
+ end
+
+ it 'imports each issue event page by page' do
+ counter = 0
+ subject.each_object_to_import do |object|
+ expect(object).to eq issue_event
+ expect(issue_event.issue_db_id).to eq issue.id
+ counter += 1
+ end
+ expect(counter).to eq 1
+ end
+
+ it 'triggers page number increment' do
+ expect(Gitlab::GithubImport::PageCounter)
+ .to receive(:new).with(project, 'issues/1/issue_timeline')
+ .and_return(page_counter)
+ expect(page_counter).to receive(:current).and_return(1)
+ expect(page_counter)
+ .to receive(:set).with(page.number).and_return(true)
+
+ counter = 0
+ subject.each_object_to_import { counter += 1 }
+ expect(counter).to eq 1
+ end
+
+ context 'when page is already processed' do
+ before do
+ page_counter = Gitlab::GithubImport::PageCounter.new(
+ project, subject.page_counter_id(issue)
+ )
+ page_counter.set(page.number)
+ end
+
+ it "doesn't process this page" do
+ counter = 0
+ subject.each_object_to_import { counter += 1 }
+ expect(counter).to eq 0
+ end
+ end
+
+ context 'when event is already processed' do
+ it "doesn't process this event" do
+ subject.mark_as_imported(issue_event)
+
+ counter = 0
+ subject.each_object_to_import { counter += 1 }
+ expect(counter).to eq 0
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/markdown_text_spec.rb b/spec/lib/gitlab/github_import/markdown_text_spec.rb
index 2d159580b5f..ad45469a4c3 100644
--- a/spec/lib/gitlab/github_import/markdown_text_spec.rb
+++ b/spec/lib/gitlab/github_import/markdown_text_spec.rb
@@ -12,6 +12,54 @@ RSpec.describe Gitlab::GithubImport::MarkdownText do
end
end
+ describe '.convert_ref_links' do
+ let_it_be(:project) { create(:project) }
+
+ let(:paragraph) { FFaker::Lorem.paragraph }
+ let(:sentence) { FFaker::Lorem.sentence }
+ let(:issue_id) { rand(100) }
+ let(:pull_id) { rand(100) }
+
+ let(:text_in) do
+ <<-TEXT
+ #{paragraph}
+ https://github.com/#{project.import_source}/issues/#{issue_id}
+ #{sentence}
+ https://github.com/#{project.import_source}/pull/#{pull_id}
+ TEXT
+ end
+
+ let(:text_out) do
+ <<-TEXT
+ #{paragraph}
+ http://localhost/#{project.full_path}/-/issues/#{issue_id}
+ #{sentence}
+ http://localhost/#{project.full_path}/-/merge_requests/#{pull_id}
+ TEXT
+ end
+
+ it { expect(described_class.convert_ref_links(text_in, project)).to eq text_out }
+
+ context 'when Github EE with custom domain name' do
+ let(:github_domain) { 'https://custom.github.com/' }
+ let(:text_in) do
+ <<-TEXT
+ #{paragraph}
+ #{github_domain}#{project.import_source}/issues/#{issue_id}
+ #{sentence}
+ #{github_domain}#{project.import_source}/pull/#{pull_id}
+ TEXT
+ end
+
+ before do
+ allow(Gitlab::Auth::OAuth::Provider)
+ .to receive(:config_for).with('github').and_return({ 'url' => github_domain })
+ end
+
+ it { expect(described_class.convert_ref_links(text_in, project)).to eq text_out }
+ end
+ end
+
describe '#to_s' do
it 'returns the text when the author was found' do
author = double(:author, login: 'Alice')
diff --git a/spec/lib/gitlab/github_import/representation/issue_event_spec.rb b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
new file mode 100644
index 00000000000..23da8276f64
--- /dev/null
+++ b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
@@ -0,0 +1,156 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
+ shared_examples 'an IssueEvent' do
+ it 'returns an instance of IssueEvent' do
+ expect(issue_event).to be_an_instance_of(described_class)
+ end
+
+ context 'the returned IssueEvent' do
+ it 'includes the issue event id' do
+ expect(issue_event.id).to eq(6501124486)
+ end
+
+ it 'includes the issue event "event"' do
+ expect(issue_event.event).to eq('closed')
+ end
+
+ it 'includes the issue event commit_id' do
+ expect(issue_event.commit_id).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
+ end
+
+ it 'includes the issue event source' do
+ expect(issue_event.source).to eq({ type: 'issue', id: 123456 })
+ end
+
+ it 'includes the issue_db_id' do
+ expect(issue_event.issue_db_id).to eq(100500)
+ end
+
+ context 'when actor data present' do
+ it 'includes the actor details' do
+ expect(issue_event.actor)
+ .to be_an_instance_of(Gitlab::GithubImport::Representation::User)
+
+ expect(issue_event.actor.id).to eq(4)
+ expect(issue_event.actor.login).to eq('alice')
+ end
+ end
+
+ context 'when actor data is empty' do
+ let(:with_actor) { false }
+
+ it 'does not return such info' do
+ expect(issue_event.actor).to eq nil
+ end
+ end
+
+ context 'when label data is present' do
+ it 'includes the label_title' do
+ expect(issue_event.label_title).to eq('label title')
+ end
+ end
+
+ context 'when label data is empty' do
+ let(:with_label) { false }
+
+ it 'does not return such info' do
+ expect(issue_event.label_title).to eq nil
+ end
+ end
+
+ context 'when rename field is present' do
+ it 'includes the old_title and new_title fields' do
+ expect(issue_event.old_title).to eq('old title')
+ expect(issue_event.new_title).to eq('new title')
+ end
+ end
+
+ context 'when rename field is empty' do
+ let(:with_rename) { false }
+
+ it 'does not return such info' do
+ expect(issue_event.old_title).to eq nil
+ expect(issue_event.new_title).to eq nil
+ end
+ end
+
+ it 'includes the created timestamp' do
+ expect(issue_event.created_at).to eq('2022-04-26 18:30:53 UTC')
+ end
+ end
+
+ describe '#github_identifiers' do
+ it 'returns a hash with needed identifiers' do
+ expect(issue_event.github_identifiers).to eq({ id: 6501124486 })
+ end
+ end
+ end
+
+ describe '.from_api_response' do
+ let(:response) do
+ event_resource = Struct.new(
+ :id, :node_id, :url, :actor, :event, :commit_id, :commit_url, :label,
+ :rename, :issue_db_id, :created_at, :performed_via_github_app, :source,
+ keyword_init: true
+ )
+ user_resource = Struct.new(:id, :login, keyword_init: true)
+ event_resource.new(
+ id: 6501124486,
+ node_id: 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
+ url: 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
+ actor: with_actor ? user_resource.new(id: 4, login: 'alice') : nil,
+ event: 'closed',
+ commit_id: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ commit_url: 'https://api.github.com/repos/octocat/Hello-World/commits'\
+ '/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ rename: with_rename ? { from: 'old title', to: 'new title' } : nil,
+ source: { type: 'issue', id: 123456 },
+ issue_db_id: 100500,
+ label: with_label ? { name: 'label title' } : nil,
+ created_at: '2022-04-26 18:30:53 UTC',
+ performed_via_github_app: nil
+ )
+ end
+
+ let(:with_actor) { true }
+ let(:with_label) { true }
+ let(:with_rename) { true }
+
+ it_behaves_like 'an IssueEvent' do
+ let(:issue_event) { described_class.from_api_response(response) }
+ end
+ end
+
+ describe '.from_json_hash' do
+ it_behaves_like 'an IssueEvent' do
+ let(:hash) do
+ {
+ 'id' => 6501124486,
+ 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
+ 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
+ 'actor' => (with_actor ? { 'id' => 4, 'login' => 'alice' } : nil),
+ 'event' => 'closed',
+ 'commit_id' => '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ 'commit_url' =>
+ 'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ 'label_title' => (with_label ? 'label title' : nil),
+ 'old_title' => with_rename ? 'old title' : nil,
+ 'new_title' => with_rename ? 'new title' : nil,
+ 'source' => { 'type' => 'issue', 'id' => 123456 },
+ "issue_db_id" => 100500,
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'performed_via_github_app' => nil
+ }
+ end
+
+ let(:with_actor) { true }
+ let(:with_label) { true }
+ let(:with_rename) { true }
+
+ let(:issue_event) { described_class.from_json_hash(hash) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/single_endpoint_notes_importing_spec.rb b/spec/lib/gitlab/github_import/single_endpoint_notes_importing_spec.rb
new file mode 100644
index 00000000000..64dbc939348
--- /dev/null
+++ b/spec/lib/gitlab/github_import/single_endpoint_notes_importing_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::SingleEndpointNotesImporting do
+ let(:importer_class) do
+ Class.new do
+ def self.name
+ 'MyImporter'
+ end
+
+ include(Gitlab::GithubImport::SingleEndpointNotesImporting)
+ end
+ end
+
+ let(:importer_instance) { importer_class.new }
+
+ describe '#parent_collection' do
+ it { expect { importer_instance.parent_collection }.to raise_error(NotImplementedError) }
+ end
+
+ describe '#parent_imported_cache_key' do
+ it { expect { importer_instance.parent_imported_cache_key }.to raise_error(NotImplementedError) }
+ end
+
+ describe '#page_counter_id' do
+ it { expect { importer_instance.page_counter_id(build(:merge_request)) }.to raise_error(NotImplementedError) }
+ end
+end
diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb
index eb4c404e454..984c690add6 100644
--- a/spec/lib/gitlab/gitlab_import/importer_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb
@@ -21,8 +21,8 @@ RSpec.describe Gitlab::GitlabImport::Importer do
'name' => 'John Doe'
}
}
- ])
- stub_request('issues/3/notes', [])
+ ].to_json)
+ stub_request('issues/3/notes', [].to_json)
end
it 'persists issues' do
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index 9c399e78d80..919335bc9fa 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -3,6 +3,34 @@
require 'spec_helper'
RSpec.describe Gitlab::Gpg::Commit do
+ let_it_be(:project) { create(:project, :repository, path: 'sample-project') }
+
+ let(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
+ let(:committer_email) { GpgHelpers::User1.emails.first }
+ let(:user_email) { committer_email }
+ let(:public_key) { GpgHelpers::User1.public_key }
+ let(:user) { create(:user, email: user_email) }
+ let(:commit) { create(:commit, project: project, sha: commit_sha, committer_email: committer_email) }
+ let(:crypto) { instance_double(GPGME::Crypto) }
+ let(:mock_signature_data?) { true }
+ # gpg_keys must be pre-loaded so that they can be found during signature verification.
+ let!(:gpg_key) { create(:gpg_key, key: public_key, user: user) }
+
+ let(:signature_data) do
+ [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ]
+ end
+
+ before do
+ if mock_signature_data?
+ allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
+ .with(Gitlab::Git::Repository, commit_sha)
+ .and_return(signature_data)
+ end
+ end
+
describe '#signature' do
shared_examples 'returns the cached signature on second call' do
it 'returns the cached signature on second call' do
@@ -17,11 +45,8 @@ RSpec.describe Gitlab::Gpg::Commit do
end
end
- let!(:project) { create :project, :repository, path: 'sample-project' }
- let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
-
context 'unsigned commit' do
- let!(:commit) { create :commit, project: project, sha: commit_sha }
+ let(:signature_data) { nil }
it 'returns nil' do
expect(described_class.new(commit).signature).to be_nil
@@ -29,20 +54,12 @@ RSpec.describe Gitlab::Gpg::Commit do
end
context 'invalid signature' do
- let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first }
-
- let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
-
- before do
- allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
- .with(Gitlab::Git::Repository, commit_sha)
- .and_return(
- [
- # Corrupt the key
- GpgHelpers::User1.signed_commit_signature.tr('=', 'a'),
- GpgHelpers::User1.signed_commit_base_data
- ]
- )
+ let(:signature_data) do
+ [
+ # Corrupt the key
+ GpgHelpers::User1.signed_commit_signature.tr('=', 'a'),
+ GpgHelpers::User1.signed_commit_base_data
+ ]
end
it 'returns nil' do
@@ -53,25 +70,6 @@ RSpec.describe Gitlab::Gpg::Commit do
context 'known key' do
context 'user matches the key uid' do
context 'user email matches the email committer' do
- let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first }
-
- let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
-
- let!(:gpg_key) do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: user
- end
-
- before do
- allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
- .with(Gitlab::Git::Repository, commit_sha)
- .and_return(
- [
- GpgHelpers::User1.signed_commit_signature,
- GpgHelpers::User1.signed_commit_base_data
- ]
- )
- end
-
it 'returns a valid signature' do
signature = described_class.new(commit).signature
@@ -112,32 +110,13 @@ RSpec.describe Gitlab::Gpg::Commit do
end
context 'valid key signed using recent version of Gnupg' do
- let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first }
-
- let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
-
- let!(:gpg_key) do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: user
- end
-
- let!(:crypto) { instance_double(GPGME::Crypto) }
-
before do
- fake_signature = [
- GpgHelpers::User1.signed_commit_signature,
- GpgHelpers::User1.signed_commit_base_data
- ]
-
- allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
- .with(Gitlab::Git::Repository, commit_sha)
- .and_return(fake_signature)
- end
-
- it 'returns a valid signature' do
verified_signature = double('verified-signature', fingerprint: GpgHelpers::User1.fingerprint, valid?: true)
allow(GPGME::Crypto).to receive(:new).and_return(crypto)
allow(crypto).to receive(:verify).and_yield(verified_signature)
+ end
+ it 'returns a valid signature' do
signature = described_class.new(commit).signature
expect(signature).to have_attributes(
@@ -153,33 +132,14 @@ RSpec.describe Gitlab::Gpg::Commit do
end
context 'valid key signed using older version of Gnupg' do
- let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first }
-
- let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
-
- let!(:gpg_key) do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: user
- end
-
- let!(:crypto) { instance_double(GPGME::Crypto) }
-
before do
- fake_signature = [
- GpgHelpers::User1.signed_commit_signature,
- GpgHelpers::User1.signed_commit_base_data
- ]
-
- allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
- .with(Gitlab::Git::Repository, commit_sha)
- .and_return(fake_signature)
- end
-
- it 'returns a valid signature' do
keyid = GpgHelpers::User1.fingerprint.last(16)
verified_signature = double('verified-signature', fingerprint: keyid, valid?: true)
allow(GPGME::Crypto).to receive(:new).and_return(crypto)
allow(crypto).to receive(:verify).and_yield(verified_signature)
+ end
+ it 'returns a valid signature' do
signature = described_class.new(commit).signature
expect(signature).to have_attributes(
@@ -195,32 +155,13 @@ RSpec.describe Gitlab::Gpg::Commit do
end
context 'commit with multiple signatures' do
- let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first }
-
- let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
-
- let!(:gpg_key) do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: user
- end
-
- let!(:crypto) { instance_double(GPGME::Crypto) }
-
before do
- fake_signature = [
- GpgHelpers::User1.signed_commit_signature,
- GpgHelpers::User1.signed_commit_base_data
- ]
-
- allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
- .with(Gitlab::Git::Repository, commit_sha)
- .and_return(fake_signature)
- end
-
- it 'returns an invalid signatures error' do
verified_signature = double('verified-signature', fingerprint: GpgHelpers::User1.fingerprint, valid?: true)
allow(GPGME::Crypto).to receive(:new).and_return(crypto)
allow(crypto).to receive(:verify).and_yield(verified_signature).and_yield(verified_signature)
+ end
+ it 'returns an invalid signatures error' do
signature = described_class.new(commit).signature
expect(signature).to have_attributes(
@@ -236,27 +177,18 @@ RSpec.describe Gitlab::Gpg::Commit do
end
context 'commit signed with a subkey' do
- let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User3.emails.first }
-
- let!(:user) { create(:user, email: GpgHelpers::User3.emails.first) }
-
- let!(:gpg_key) do
- create :gpg_key, key: GpgHelpers::User3.public_key, user: user
- end
+ let(:committer_email) { GpgHelpers::User3.emails.first }
+ let(:public_key) { GpgHelpers::User3.public_key }
let(:gpg_key_subkey) do
gpg_key.subkeys.find_by(fingerprint: GpgHelpers::User3.subkey_fingerprints.last)
end
- before do
- allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
- .with(Gitlab::Git::Repository, commit_sha)
- .and_return(
- [
- GpgHelpers::User3.signed_commit_signature,
- GpgHelpers::User3.signed_commit_base_data
- ]
- )
+ let(:signature_data) do
+ [
+ GpgHelpers::User3.signed_commit_signature,
+ GpgHelpers::User3.signed_commit_base_data
+ ]
end
it 'returns a valid signature' do
@@ -275,7 +207,7 @@ RSpec.describe Gitlab::Gpg::Commit do
end
context 'user email does not match the committer email, but is the same user' do
- let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first }
+ let(:committer_email) { GpgHelpers::User2.emails.first }
let(:user) do
create(:user, email: GpgHelpers::User1.emails.first).tap do |user|
@@ -283,21 +215,6 @@ RSpec.describe Gitlab::Gpg::Commit do
end
end
- let!(:gpg_key) do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: user
- end
-
- before do
- allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
- .with(Gitlab::Git::Repository, commit_sha)
- .and_return(
- [
- GpgHelpers::User1.signed_commit_signature,
- GpgHelpers::User1.signed_commit_base_data
- ]
- )
- end
-
it 'returns an invalid signature' do
expect(described_class.new(commit).signature).to have_attributes(
commit_sha: commit_sha,
@@ -314,24 +231,8 @@ RSpec.describe Gitlab::Gpg::Commit do
end
context 'user email does not match the committer email' do
- let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first }
-
- let(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
-
- let!(:gpg_key) do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: user
- end
-
- before do
- allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
- .with(Gitlab::Git::Repository, commit_sha)
- .and_return(
- [
- GpgHelpers::User1.signed_commit_signature,
- GpgHelpers::User1.signed_commit_base_data
- ]
- )
- end
+ let(:committer_email) { GpgHelpers::User2.emails.first }
+ let(:user_email) { GpgHelpers::User1.emails.first }
it 'returns an invalid signature' do
expect(described_class.new(commit).signature).to have_attributes(
@@ -350,24 +251,8 @@ RSpec.describe Gitlab::Gpg::Commit do
end
context 'user does not match the key uid' do
- let!(:commit) { create :commit, project: project, sha: commit_sha }
-
- let(:user) { create(:user, email: GpgHelpers::User2.emails.first) }
-
- let!(:gpg_key) do
- create :gpg_key, key: GpgHelpers::User1.public_key, user: user
- end
-
- before do
- allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
- .with(Gitlab::Git::Repository, commit_sha)
- .and_return(
- [
- GpgHelpers::User1.signed_commit_signature,
- GpgHelpers::User1.signed_commit_base_data
- ]
- )
- end
+ let(:user_email) { GpgHelpers::User2.emails.first }
+ let(:public_key) { GpgHelpers::User1.public_key }
it 'returns an invalid signature' do
expect(described_class.new(commit).signature).to have_attributes(
@@ -386,18 +271,7 @@ RSpec.describe Gitlab::Gpg::Commit do
end
context 'unknown key' do
- let!(:commit) { create :commit, project: project, sha: commit_sha }
-
- before do
- allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
- .with(Gitlab::Git::Repository, commit_sha)
- .and_return(
- [
- GpgHelpers::User1.signed_commit_signature,
- GpgHelpers::User1.signed_commit_base_data
- ]
- )
- end
+ let(:gpg_key) { nil }
it 'returns an invalid signature' do
expect(described_class.new(commit).signature).to have_attributes(
@@ -415,15 +289,15 @@ RSpec.describe Gitlab::Gpg::Commit do
end
context 'multiple commits with signatures' do
- let(:first_signature) { create(:gpg_signature) }
-
- let(:gpg_key) { create(:gpg_key, key: GpgHelpers::User2.public_key) }
- let(:second_signature) { create(:gpg_signature, gpg_key: gpg_key) }
+ let(:mock_signature_data?) { false }
+ let!(:first_signature) { create(:gpg_signature) }
+ let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User2.public_key) }
+ let!(:second_signature) { create(:gpg_signature, gpg_key: gpg_key) }
let!(:first_commit) { create(:commit, project: project, sha: first_signature.commit_sha) }
let!(:second_commit) { create(:commit, project: project, sha: second_signature.commit_sha) }
- let(:commits) do
+ let!(:commits) do
[first_commit, second_commit].map do |commit|
gpg_commit = described_class.new(commit)
@@ -442,4 +316,21 @@ RSpec.describe Gitlab::Gpg::Commit do
end
end
end
+
+ describe '#update_signature!' do
+ let!(:gpg_key) { nil }
+
+ let(:signature) { described_class.new(commit).signature }
+
+ it 'updates signature record' do
+ signature
+
+ create(:gpg_key, key: public_key, user: user)
+
+ stored_signature = CommitSignatures::GpgSignature.find_by_commit_sha(commit_sha)
+ expect { described_class.new(commit).update_signature!(stored_signature) }.to(
+ change { signature.reload.verification_status }.from('unknown_key').to('verified')
+ )
+ end
+ end
end
diff --git a/spec/lib/gitlab/grape_logging/loggers/response_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/response_logger_spec.rb
new file mode 100644
index 00000000000..94e880d979d
--- /dev/null
+++ b/spec/lib/gitlab/grape_logging/loggers/response_logger_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GrapeLogging::Loggers::ResponseLogger do
+ let(:logger) { described_class.new }
+
+ describe '#parameters' do
+ let(:response1) { 'response1' }
+ let(:response) { [response1] }
+
+ subject { logger.parameters(nil, response) }
+
+ it { expect(subject).to eq({ response_bytes: response1.bytesize }) }
+
+ context 'with multiple response parts' do
+ let(:response2) { 'response2' }
+ let(:response) { [response1, response2] }
+
+ it { expect(subject).to eq({ response_bytes: response1.bytesize + response2.bytesize }) }
+ end
+
+ context 'with log_response_length disabled' do
+ before do
+ stub_feature_flags(log_response_length: false)
+ end
+
+ it { expect(subject).to eq({}) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/connection_generic_keyset_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/connection_generic_keyset_spec.rb
index 97613edee5e..8a2b5ae0d38 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/connection_generic_keyset_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/connection_generic_keyset_spec.rb
@@ -79,7 +79,7 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
let(:nodes) { Project.all.order(Gitlab::Pagination::Keyset::Order.build([column_order_updated_at, column_order_created_at, column_order_id])) }
it 'returns the encoded value of the order' do
- expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.strftime('%Y-%m-%d %H:%M:%S.%N %Z'))
+ expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s(:inspect))
end
end
end
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
index 61a79d90546..6574b3e3131 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
let(:nodes) { Project.order(:updated_at) }
it 'returns the encoded value of the order' do
- expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.strftime('%Y-%m-%d %H:%M:%S.%N %Z'))
+ expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s(:inspect))
end
it 'includes the :id even when not specified in the order' do
@@ -104,7 +104,7 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
let(:nodes) { Project.order(:updated_at).order(:created_at) }
it 'returns the encoded value of the order' do
- expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.strftime('%Y-%m-%d %H:%M:%S.%N %Z'))
+ expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s(:inspect))
end
end
@@ -112,7 +112,7 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
let(:nodes) { Project.order(Arel.sql('projects.updated_at IS NULL')).order(:updated_at).order(:id) }
it 'returns the encoded value of the order' do
- expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.strftime('%Y-%m-%d %H:%M:%S.%N %Z'))
+ expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s(:inspect))
end
end
end
diff --git a/spec/lib/gitlab/harbor/client_spec.rb b/spec/lib/gitlab/harbor/client_spec.rb
index bc5b593370a..4e80b8b53e3 100644
--- a/spec/lib/gitlab/harbor/client_spec.rb
+++ b/spec/lib/gitlab/harbor/client_spec.rb
@@ -3,12 +3,277 @@
require 'spec_helper'
RSpec.describe Gitlab::Harbor::Client do
- let(:harbor_integration) { build(:harbor_integration) }
+ let_it_be(:harbor_integration) { create(:harbor_integration) }
subject(:client) { described_class.new(harbor_integration) }
+ describe '#initialize' do
+ context 'if integration is nil' do
+ let(:harbor_integration) { nil }
+
+ it 'raises ConfigError' do
+ expect { client }.to raise_error(described_class::ConfigError)
+ end
+ end
+
+ context 'integration is provided' do
+ it 'is initialized successfully' do
+ expect { client }.not_to raise_error
+ end
+ end
+ end
+
+ describe '#get_repositories' do
+ context 'with valid params' do
+ let(:mock_response) do
+ [
+ {
+ "artifact_count": 1,
+ "creation_time": "2022-03-13T09:36:43.240Z",
+ "id": 1,
+ "name": "jihuprivate/busybox",
+ "project_id": 4,
+ "pull_count": 0,
+ "update_time": "2022-03-13T09:36:43.240Z"
+ }
+ ]
+ end
+
+ let(:mock_repositories) do
+ {
+ body: mock_response,
+ total_count: 2
+ }
+ end
+
+ before do
+ stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ })
+ .to_return(status: 200, body: mock_response.to_json, headers: { "x-total-count": 2 })
+ end
+
+ it 'get repositories' do
+ expect(client.get_repositories({}).deep_stringify_keys).to eq(mock_repositories.deep_stringify_keys)
+ end
+ end
+
+ context 'when harbor project does not exist' do
+ before do
+ stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ })
+ .to_return(status: 404, body: {}.to_json)
+ end
+
+ it 'raises Gitlab::Harbor::Client::Error' do
+ expect do
+ client.get_repositories({})
+ end.to raise_error(Gitlab::Harbor::Client::Error, 'request error')
+ end
+ end
+
+ context 'with invalid response' do
+ before do
+ stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ })
+ .to_return(status: 200, body: '[not json}')
+ end
+
+ it 'raises Gitlab::Harbor::Client::Error' do
+ expect do
+ client.get_repositories({})
+ end.to raise_error(Gitlab::Harbor::Client::Error, 'invalid response format')
+ end
+ end
+ end
+
+ describe '#get_artifacts' do
+ context 'with valid params' do
+ let(:mock_response) do
+ [
+ {
+ "digest": "sha256:661e8e44e5d7290fbd42d0495ab4ff6fdf1ad251a9f358969b3264a22107c14d",
+ "icon": "sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06",
+ "id": 1,
+ "project_id": 1,
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-04-23T08:04:08.901Z",
+ "repository_id": 1,
+ "size": 126745886,
+ "tags": [
+ {
+ "artifact_id": 1,
+ "id": 1,
+ "immutable": false,
+ "name": "2",
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-04-23T08:04:08.920Z",
+ "repository_id": 1,
+ "signed": false
+ }
+ ],
+ "type": "IMAGE"
+ }
+ ]
+ end
+
+ let(:mock_artifacts) do
+ {
+ body: mock_response,
+ total_count: 1
+ }
+ end
+
+ before do
+ stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ })
+ .to_return(status: 200, body: mock_response.to_json, headers: { "x-total-count": 1 })
+ end
+
+ it 'get artifacts' do
+ expect(client.get_artifacts({ repository_name: 'test' })
+ .deep_stringify_keys).to eq(mock_artifacts.deep_stringify_keys)
+ end
+ end
+
+ context 'when harbor repository does not exist' do
+ before do
+ stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ })
+ .to_return(status: 404, body: {}.to_json)
+ end
+
+ it 'raises Gitlab::Harbor::Client::Error' do
+ expect do
+ client.get_artifacts({ repository_name: 'test' })
+ end.to raise_error(Gitlab::Harbor::Client::Error, 'request error')
+ end
+ end
+
+ context 'with invalid response' do
+ before do
+ stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ })
+ .to_return(status: 200, body: '[not json}')
+ end
+
+ it 'raises Gitlab::Harbor::Client::Error' do
+ expect do
+ client.get_artifacts({ repository_name: 'test' })
+ end.to raise_error(Gitlab::Harbor::Client::Error, 'invalid response format')
+ end
+ end
+ end
+
+ describe '#get_tags' do
+ context 'with valid params' do
+ let(:mock_response) do
+ [
+ {
+ "artifact_id": 1,
+ "id": 1,
+ "immutable": false,
+ "name": "2",
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-04-23T08:04:08.920Z",
+ "repository_id": 1,
+ "signed": false
+ }
+ ]
+ end
+
+ let(:mock_tags) do
+ {
+ body: mock_response,
+ total_count: 1
+ }
+ end
+
+ before do
+ stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts/1/tags")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ })
+ .to_return(status: 200, body: mock_response.to_json, headers: { "x-total-count": 1 })
+ end
+
+ it 'get tags' do
+ expect(client.get_tags({ repository_name: 'test', artifact_name: '1' })
+ .deep_stringify_keys).to eq(mock_tags.deep_stringify_keys)
+ end
+ end
+
+ context 'when harbor artifact does not exist' do
+ before do
+ stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts/1/tags")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ })
+ .to_return(status: 404, body: {}.to_json)
+ end
+
+ it 'raises Gitlab::Harbor::Client::Error' do
+ expect do
+ client.get_tags({ repository_name: 'test', artifact_name: '1' })
+ end.to raise_error(Gitlab::Harbor::Client::Error, 'request error')
+ end
+ end
+
+ context 'with invalid response' do
+ before do
+ stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts/1/tags")
+ .with(
+ headers: {
+ 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=',
+ 'Content-Type': 'application/json'
+ })
+ .to_return(status: 200, body: '[not json}')
+ end
+
+ it 'raises Gitlab::Harbor::Client::Error' do
+ expect do
+ client.get_tags({ repository_name: 'test', artifact_name: '1' })
+ end.to raise_error(Gitlab::Harbor::Client::Error, 'invalid response format')
+ end
+ end
+ end
+
describe '#ping' do
- let!(:harbor_ping_request) { stub_harbor_request("https://demo.goharbor.io/api/v2.0/ping") }
+ before do
+ stub_request(:get, "https://demo.goharbor.io/api/v2.0/ping")
+ .with(
+ headers: {
+ 'Content-Type': 'application/json'
+ })
+ .to_return(status: 200, body: 'pong')
+ end
it "calls api/v2.0/ping successfully" do
expect(client.ping).to eq(success: true)
diff --git a/spec/lib/gitlab/harbor/query_spec.rb b/spec/lib/gitlab/harbor/query_spec.rb
new file mode 100644
index 00000000000..dcb9a16b27b
--- /dev/null
+++ b/spec/lib/gitlab/harbor/query_spec.rb
@@ -0,0 +1,375 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Harbor::Query do
+ let_it_be(:harbor_integration) { create(:harbor_integration) }
+
+ let(:params) { {} }
+
+ subject(:query) { described_class.new(harbor_integration, ActionController::Parameters.new(params)) }
+
+ describe 'Validations' do
+ context 'page' do
+ context 'with valid page' do
+ let(:params) { { page: 1 } }
+
+ it 'initialize successfully' do
+ expect(query.valid?).to eq(true)
+ end
+ end
+
+ context 'with invalid page' do
+ let(:params) { { page: -1 } }
+
+ it 'initialize failed' do
+ expect(query.valid?).to eq(false)
+ end
+ end
+ end
+
+ context 'limit' do
+ context 'with valid limit' do
+ let(:params) { { limit: 1 } }
+
+ it 'initialize successfully' do
+ expect(query.valid?).to eq(true)
+ end
+ end
+
+ context 'with invalid limit' do
+ context 'with limit less than 0' do
+ let(:params) { { limit: -1 } }
+
+ it 'initialize failed' do
+ expect(query.valid?).to eq(false)
+ end
+ end
+
+ context 'with limit greater than 25' do
+ let(:params) { { limit: 26 } }
+
+ it 'initialize failed' do
+ expect(query.valid?).to eq(false)
+ end
+ end
+ end
+ end
+
+ context 'repository_id' do
+ context 'with valid repository_id' do
+ let(:params) { { repository_id: 'test' } }
+
+ it 'initialize successfully' do
+ expect(query.valid?).to eq(true)
+ end
+ end
+
+ context 'with invalid repository_id' do
+ let(:params) { { repository_id: 'test@@' } }
+
+ it 'initialize failed' do
+ expect(query.valid?).to eq(false)
+ end
+ end
+ end
+
+ context 'artifact_id' do
+ context 'with valid artifact_id' do
+ let(:params) { { artifact_id: 'test' } }
+
+ it 'initialize successfully' do
+ expect(query.valid?).to eq(true)
+ end
+ end
+
+ context 'with invalid artifact_id' do
+ let(:params) { { artifact_id: 'test@@' } }
+
+ it 'initialize failed' do
+ expect(query.valid?).to eq(false)
+ end
+ end
+ end
+
+ context 'sort' do
+ context 'with valid sort' do
+ let(:params) { { sort: 'creation_time desc' } }
+
+ it 'initialize successfully' do
+ expect(query.valid?).to eq(true)
+ end
+ end
+
+ context 'with invalid sort' do
+ let(:params) { { sort: 'blabla desc' } }
+
+ it 'initialize failed' do
+ expect(query.valid?).to eq(false)
+ end
+ end
+ end
+
+ context 'search' do
+ context 'with valid search' do
+ let(:params) { { search: 'name=desc' } }
+
+ it 'initialize successfully' do
+ expect(query.valid?).to eq(true)
+ end
+ end
+
+ context 'with invalid search' do
+ let(:params) { { search: 'blabla' } }
+
+ it 'initialize failed' do
+ expect(query.valid?).to eq(false)
+ end
+ end
+ end
+ end
+
+ describe '#repositories' do
+ let(:response) { { total_count: 0, repositories: [] } }
+
+ def expect_query_option_include(expected_params)
+ expect_next_instance_of(Gitlab::Harbor::Client) do |client|
+ expect(client).to receive(:get_repositories)
+ .with(hash_including(expected_params))
+ .and_return(response)
+ end
+
+ query.repositories
+ end
+
+ context 'when params is {}' do
+ it 'fills default params' do
+ expect_query_option_include(page_size: 10, page: 1)
+ end
+ end
+
+ context 'when params contains options' do
+ let(:params) { { search: 'name=bu', sort: 'creation_time desc', limit: 20, page: 3 } }
+
+ it 'fills params with standard of Harbor' do
+ expect_query_option_include(q: 'name=~bu', sort: '-creation_time', page_size: 20, page: 3)
+ end
+ end
+
+ context 'when params contains invalid sort option' do
+ let(:params) { { search: 'name=bu', sort: 'blabla desc', limit: 20, page: 3 } }
+
+ it 'ignores invalid sort params' do
+ expect(query.valid?).to eq(false)
+ end
+ end
+
+ context 'when client.get_repositories returns data' do
+ let(:response_with_data) do
+ {
+ total_count: 1,
+ body:
+ [
+ {
+ "id": 3,
+ "name": "testproject/thirdbusybox",
+ "artifact_count": 1,
+ "creation_time": "2022-03-15T07:12:14.479Z",
+ "update_time": "2022-03-15T07:12:14.479Z",
+ "project_id": 3,
+ "pull_count": 0
+ }.with_indifferent_access
+ ]
+ }
+ end
+
+ it 'returns the right repositories data' do
+ expect_next_instance_of(Gitlab::Harbor::Client) do |client|
+ expect(client).to receive(:get_repositories)
+ .with(hash_including(page_size: 10, page: 1))
+ .and_return(response_with_data)
+ end
+
+ expect(query.repositories.first).to include(
+ "name": "testproject/thirdbusybox",
+ "artifact_count": 1
+ )
+ end
+ end
+ end
+
+ describe '#artifacts' do
+ let(:response) { { total_count: 0, artifacts: [] } }
+
+ def expect_query_option_include(expected_params)
+ expect_next_instance_of(Gitlab::Harbor::Client) do |client|
+ expect(client).to receive(:get_artifacts)
+ .with(hash_including(expected_params))
+ .and_return(response)
+ end
+
+ query.artifacts
+ end
+
+ context 'when params is {}' do
+ it 'fills default params' do
+ expect_query_option_include(page_size: 10, page: 1)
+ end
+ end
+
+ context 'when params contains options' do
+ let(:params) do
+ { search: 'tags=1', repository_id: 'jihuprivate', sort: 'creation_time desc', limit: 20, page: 3 }
+ end
+
+ it 'fills params with standard of Harbor' do
+ expect_query_option_include(q: 'tags=~1', sort: '-creation_time', page_size: 20, page: 3)
+ end
+ end
+
+ context 'when params contains invalid sort option' do
+ let(:params) { { search: 'tags=1', repository_id: 'jihuprivate', sort: 'blabla desc', limit: 20, page: 3 } }
+
+ it 'ignores invalid sort params' do
+ expect(query.valid?).to eq(false)
+ end
+ end
+
+ context 'when client.get_artifacts returns data' do
+ let(:response_with_data) do
+ {
+ total_count: 1,
+ body:
+ [
+ {
+ "digest": "sha256:14d4f50961544fdb669075c442509f194bdc4c0e344bde06e35dbd55af842a38",
+ "icon": "sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06",
+ "id": 5,
+ "project_id": 14,
+ "push_time": "2022-03-22T09:04:56.170Z",
+ "repository_id": 5,
+ "size": 774790,
+ "tags": [
+ {
+ "artifact_id": 5,
+ "id": 7,
+ "immutable": false,
+ "name": "2",
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-03-22T09:05:04.844Z",
+ "repository_id": 5
+ },
+ {
+ "artifact_id": 5,
+ "id": 6,
+ "immutable": false,
+ "name": "1",
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-03-22T09:04:56.186Z",
+ "repository_id": 5
+ }
+ ],
+ "type": "IMAGE"
+ }.with_indifferent_access
+ ]
+ }
+ end
+
+ it 'returns the right artifacts data' do
+ expect_next_instance_of(Gitlab::Harbor::Client) do |client|
+ expect(client).to receive(:get_artifacts)
+ .with(hash_including(page_size: 10, page: 1))
+ .and_return(response_with_data)
+ end
+
+ artifact = query.artifacts.first
+
+ expect(artifact).to include(
+ "digest": "sha256:14d4f50961544fdb669075c442509f194bdc4c0e344bde06e35dbd55af842a38",
+ "push_time": "2022-03-22T09:04:56.170Z"
+ )
+ expect(artifact["tags"].size).to eq(2)
+ end
+ end
+ end
+
+ describe '#tags' do
+ let(:response) { { total_count: 0, tags: [] } }
+
+ def expect_query_option_include(expected_params)
+ expect_next_instance_of(Gitlab::Harbor::Client) do |client|
+ expect(client).to receive(:get_tags)
+ .with(hash_including(expected_params))
+ .and_return(response)
+ end
+
+ query.tags
+ end
+
+ context 'when params is {}' do
+ it 'fills default params' do
+ expect_query_option_include(page_size: 10, page: 1)
+ end
+ end
+
+ context 'when params contains options' do
+ let(:params) { { repository_id: 'jihuprivate', sort: 'creation_time desc', limit: 20, page: 3 } }
+
+ it 'fills params with standard of Harbor' do
+ expect_query_option_include(sort: '-creation_time', page_size: 20, page: 3)
+ end
+ end
+
+ context 'when params contains invalid sort option' do
+ let(:params) { { repository_id: 'jihuprivate', artifact_id: 'test', sort: 'blabla desc', limit: 20, page: 3 } }
+
+ it 'ignores invalid sort params' do
+ expect(query.valid?).to eq(false)
+ end
+ end
+
+ context 'when client.get_tags returns data' do
+ let(:response_with_data) do
+ {
+ total_count: 2,
+ body:
+ [
+ {
+ "artifact_id": 5,
+ "id": 7,
+ "immutable": false,
+ "name": "2",
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-03-22T09:05:04.844Z",
+ "repository_id": 5
+ },
+ {
+ "artifact_id": 5,
+ "id": 6,
+ "immutable": false,
+ "name": "1",
+ "pull_time": "0001-01-01T00:00:00.000Z",
+ "push_time": "2022-03-22T09:04:56.186Z",
+ "repository_id": 5
+ }.with_indifferent_access
+ ]
+ }
+ end
+
+ it 'returns the right tags data' do
+ expect_next_instance_of(Gitlab::Harbor::Client) do |client|
+ expect(client).to receive(:get_tags)
+ .with(hash_including(page_size: 10, page: 1))
+ .and_return(response_with_data)
+ end
+
+ tag = query.tags.first
+
+ expect(tag).to include(
+ "immutable": false,
+ "push_time": "2022-03-22T09:05:04.844Z"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/hash_digest/facade_spec.rb b/spec/lib/gitlab/hash_digest/facade_spec.rb
deleted file mode 100644
index b352744513e..00000000000
--- a/spec/lib/gitlab/hash_digest/facade_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::HashDigest::Facade do
- describe '.hexdigest' do
- let(:plaintext) { 'something that is plaintext' }
-
- let(:sha256_hash) { OpenSSL::Digest::SHA256.hexdigest(plaintext) }
- let(:md5_hash) { Digest::MD5.hexdigest(plaintext) } # rubocop:disable Fips/MD5
-
- it 'uses SHA256' do
- expect(described_class.hexdigest(plaintext)).to eq(sha256_hash)
- end
-
- context 'when feature flags is not available' do
- before do
- allow(Feature).to receive(:feature_flags_available?).and_return(false)
- end
-
- it 'uses MD5' do
- expect(described_class.hexdigest(plaintext)).to eq(md5_hash)
- end
- end
-
- context 'when active_support_hash_digest_sha256 FF is disabled' do
- before do
- stub_feature_flags(active_support_hash_digest_sha256: false)
- end
-
- it 'uses MD5' do
- expect(described_class.hexdigest(plaintext)).to eq(md5_hash)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
index 771fc0218e2..25b84a67ab2 100644
--- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
@@ -7,13 +7,12 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
let(:builder) { described_class.new(merge_request) }
- describe '#build' do
- let(:data) { builder.build }
+ describe '.safe_hook_attributes' do
+ let(:safe_attribute_keys) { described_class.safe_hook_attributes }
it 'includes safe attribute' do
- %w[
+ expected_safe_attribute_keys = %i[
assignee_id
- assignee_ids
author_id
blocking_discussions_resolved
created_at
@@ -32,17 +31,21 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
milestone_id
source_branch
source_project_id
- state
+ state_id
target_branch
target_project_id
time_estimate
title
updated_at
updated_by_id
- ].each do |key|
- expect(data).to include(key)
- end
+ ].freeze
+
+ expect(safe_attribute_keys).to match_array(expected_safe_attribute_keys)
end
+ end
+
+ describe '#build' do
+ let(:data) { builder.build }
%i[source target].each do |key|
describe "#{key} key" do
@@ -52,17 +55,30 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
end
end
+ it 'includes safe attributes' do
+ expect(data).to include(*described_class.safe_hook_attributes)
+ end
+
it 'includes additional attrs' do
- expect(data).to include(:source)
- expect(data).to include(:target)
- expect(data).to include(:last_commit)
- expect(data).to include(:work_in_progress)
- expect(data).to include(:total_time_spent)
- expect(data).to include(:time_change)
- expect(data).to include(:human_time_estimate)
- expect(data).to include(:human_total_time_spent)
- expect(data).to include(:human_time_change)
- expect(data).to include(:labels)
+ expected_additional_attributes = %w[
+ description
+ url
+ last_commit
+ work_in_progress
+ total_time_spent
+ time_change
+ human_total_time_spent
+ human_time_change
+ human_time_estimate
+ assignee_ids
+ assignee_id
+ labels
+ state
+ blocking_discussions_resolved
+ first_contribution
+ ].freeze
+
+ expect(data).to include(*expected_additional_attributes)
end
context 'when the MR has an image in the description' do
diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb
index cde8376febd..a241a4b6490 100644
--- a/spec/lib/gitlab/http_connection_adapter_spec.rb
+++ b/spec/lib/gitlab/http_connection_adapter_spec.rb
@@ -15,18 +15,6 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do
stub_all_dns('https://example.org', ip_address: '93.184.216.34')
end
- context 'with use_read_total_timeout option' do
- let(:options) { { use_read_total_timeout: true } }
-
- it 'sets up the connection using the Gitlab::NetHttpAdapter' do
- expect(connection).to be_a(Gitlab::NetHttpAdapter)
- expect(connection.address).to eq('93.184.216.34')
- expect(connection.hostname_override).to eq('example.org')
- expect(connection.addr_port).to eq('example.org')
- expect(connection.port).to eq(443)
- end
- end
-
context 'when local requests are allowed' do
let(:options) { { allow_local_requests: true } }
diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb
index c2fb987d195..929fd37ee40 100644
--- a/spec/lib/gitlab/http_spec.rb
+++ b/spec/lib/gitlab/http_spec.rb
@@ -83,67 +83,25 @@ RSpec.describe Gitlab::HTTP do
subject(:request_slow_responder) { described_class.post('http://example.org', **options) }
- shared_examples 'tracks the timeout but does not raise an error' do
- specify :aggregate_failures do
- expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
- an_instance_of(Gitlab::HTTP::ReadTotalTimeout)
- ).once
-
- expect { request_slow_responder }.not_to raise_error
- end
-
- it 'still calls the block' do
- expect { |b| described_class.post('http://example.org', **options, &b) }.to yield_successive_args('a', 'b')
- end
- end
-
- shared_examples 'does not track or raise timeout error' do
- specify :aggregate_failures do
- expect(Gitlab::ErrorTracking).not_to receive(:track_exception)
-
- expect { request_slow_responder }.not_to raise_error
- end
- end
-
- it_behaves_like 'tracks the timeout but does not raise an error'
-
- context 'and use_read_total_timeout option is truthy' do
- let(:options) { { use_read_total_timeout: true } }
-
- 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
+ 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_behaves_like 'does not track or raise timeout error'
+ 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_behaves_like 'does not track or raise timeout error'
-
- context 'but skip_read_total_timeout option is falsey' do
- let(:options) { { stream_body: true, skip_read_total_timeout: false } }
-
- it_behaves_like 'tracks the timeout but does not raise an error'
+ it 'does not raise an error' do
+ expect { request_slow_responder }.not_to raise_error
end
end
-
- context 'and skip_read_total_timeout option is truthy' do
- let(:options) { { skip_read_total_timeout: true } }
-
- it_behaves_like 'does not track or raise timeout error'
- end
-
- context 'and skip_read_total_timeout option is falsely' do
- let(:options) { { skip_read_total_timeout: false } }
-
- it_behaves_like 'tracks the timeout but does not raise an error'
- end
end
it 'calls a block' do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 9d516c8d7ac..af910b08fae 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -420,6 +420,8 @@ project:
- zentao_integration
# dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417
- dingtalk_integration
+# dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/640
+- feishu_integration
- redmine_integration
- youtrack_integration
- custom_issue_tracker_integration
@@ -557,7 +559,6 @@ project:
- packages
- package_files
- packages_cleanup_policy
-- tracing_setting
- alerting_setting
- project_setting
- webide_pipelines
@@ -604,6 +605,7 @@ project:
- incident_management_oncall_schedules
- incident_management_oncall_rotations
- incident_management_escalation_policies
+- incident_management_issuable_escalation_statuses
- debian_distributions
- merge_request_metrics
- security_orchestration_policy_configuration
@@ -695,8 +697,6 @@ epic_issues:
feature_flag_issues:
- issue
- feature_flag
-tracing_setting:
-- project
reviews:
- project
- merge_request
diff --git a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
index 03f522ae490..3f73a730744 100644
--- a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
@@ -171,4 +171,27 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do
expect(described_class.batch_size(exportable)).to eq(described_class::BATCH_SIZE)
end
end
+
+ describe '#serialize_relation' do
+ context 'when record is a merge request' do
+ let(:json_writer) do
+ Class.new do
+ def write_relation_array(_, _, enumerator)
+ enumerator.each { _1 }
+ end
+ end.new
+ end
+
+ it 'removes cached external diff' do
+ merge_request = create(:merge_request, source_project: exportable, target_project: exportable)
+ cache_dir = merge_request.merge_request_diff.send(:external_diff_cache_dir)
+
+ expect(subject).to receive(:remove_cached_external_diff).with(merge_request).twice
+
+ subject.serialize_relation({ merge_requests: { include: [] } })
+
+ expect(Dir.exist?(cache_dir)).to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 87ca899a87d..d7ad34255c1 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -258,7 +258,7 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
end
before do
- group.add_users([user, user2], GroupMember::DEVELOPER)
+ group.add_members([user, user2], GroupMember::DEVELOPER)
end
it 'maps the project member' do
@@ -281,7 +281,7 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
end
before do
- group.add_users([user, user2], GroupMember::DEVELOPER)
+ group.add_members([user, user2], GroupMember::DEVELOPER)
end
it 'maps the importer' do
@@ -315,7 +315,7 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
shared_examples_for 'it fetches the access level from parent group' do
before do
- group.add_users([user], group_access_level)
+ group.add_members([user], group_access_level)
end
it "and resolves it correctly" do
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 d3397e89f1f..157cd408da9 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -383,21 +383,52 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
end
end
- it 'restores releases with links & milestones' do
- release = @project.releases.last
- link = release.links.last
+ context 'restores releases' do
+ it 'with links & milestones' do
+ release = @project.releases.last
+ link = release.links.last
+
+ aggregate_failures do
+ expect(release.tag).to eq('release-1.2')
+ expect(release.description).to eq('Some release notes')
+ expect(release.name).to eq('release-1.2')
+ expect(release.sha).to eq('903de3a8bd5573f4a049b1457d28bc1592ba6bf9')
+ expect(release.released_at).to eq('2019-12-27T10:17:14.615Z')
+ expect(release.milestone_releases.count).to eq(1)
+ expect(release.milestone_releases.first.milestone.title).to eq('test milestone')
+
+ expect(link.url).to eq('http://localhost/namespace6/project6/-/jobs/140463678/artifacts/download')
+ expect(link.name).to eq('release-1.2.dmg')
+ end
+ end
- aggregate_failures do
- expect(release.tag).to eq('release-1.1')
- expect(release.description).to eq('Some release notes')
- expect(release.name).to eq('release-1.1')
- expect(release.sha).to eq('901de3a8bd5573f4a049b1457d28bc1592ba6bf9')
- expect(release.released_at).to eq('2019-12-26T10:17:14.615Z')
- expect(release.milestone_releases.count).to eq(1)
- expect(release.milestone_releases.first.milestone.title).to eq('test milestone')
-
- expect(link.url).to eq('http://localhost/namespace6/project6/-/jobs/140463678/artifacts/download')
- expect(link.name).to eq('release-1.1.dmg')
+ context 'with author' do
+ it 'as ghost user when imported release author is empty' do
+ release = @project.releases.first
+
+ aggregate_failures do
+ expect(release.tag).to eq('release-1.0')
+ expect(release.author_id).to eq(User.select(:id).ghost.id)
+ end
+ end
+
+ it 'as existing member when imported release author is matched with existing user' do
+ release = @project.releases.second
+
+ aggregate_failures do
+ expect(release.tag).to eq('release-1.1')
+ expect(release.author_id).to eq(@existing_members.first.id)
+ end
+ end
+
+ it 'as import user when imported release author cannot be matched' do
+ release = @project.releases.last
+
+ aggregate_failures do
+ expect(release.tag).to eq('release-1.2')
+ expect(release.author_id).to eq(@user.id)
+ end
+ end
end
end
@@ -441,7 +472,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
end
it 'has a new CI build token' do
- expect(Ci::Build.where(token: 'abcd')).to be_empty
+ expect(Ci::Build.find_by_token('abcd')).to be_nil
end
end
@@ -568,20 +599,10 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
context 'when there is an existing build with build token' do
before do
- create(:ci_build, token: 'abcd')
- end
-
- it_behaves_like 'restores project successfully',
- issues: 1,
- labels: 2,
- label_with_priorities: 'A project label',
- milestones: 1,
- first_issue_labels: 1
- end
-
- context 'when there is an existing build with build token' do
- before do
- create(:ci_build, token: 'abcd')
+ create(:ci_build).tap do |job|
+ job.set_token('abcd')
+ job.save!
+ end
end
it_behaves_like 'restores project successfully',
@@ -885,7 +906,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
context 'with group visibility' do
before do
group = create(:group, visibility_level: group_visibility)
- group.add_users([user], GroupMember::MAINTAINER)
+ group.add_members([user], GroupMember::MAINTAINER)
project.update!(group: group)
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index d7f07a1eadf..bd60bb53d49 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -564,8 +564,6 @@ Project:
- suggestion_commit_message
- merge_commit_template
- squash_commit_template
-ProjectTracingSetting:
-- external_url
Author:
- name
ProjectFeature:
diff --git a/spec/lib/gitlab/issuable/clone/attributes_rewriter_spec.rb b/spec/lib/gitlab/issuable/clone/attributes_rewriter_spec.rb
new file mode 100644
index 00000000000..dbb753d5b9f
--- /dev/null
+++ b/spec/lib/gitlab/issuable/clone/attributes_rewriter_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Issuable::Clone::AttributesRewriter do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project1) { create(:project, :public, group: group) }
+ let_it_be(:project2) { create(:project, :public, group: group) }
+ let_it_be(:original_issue) { create(:issue, project: project1) }
+
+ let(:new_attributes) { described_class.new(user, original_issue, project2).execute }
+
+ context 'with missing target parent' do
+ it 'raises an ArgumentError' do
+ expect { described_class.new(user, original_issue, nil) }.to raise_error ArgumentError
+ end
+ end
+
+ context 'setting labels' do
+ it 'sets labels present in the new project and group labels' do
+ project1_label_1 = create(:label, title: 'label1', project: project1)
+ project1_label_2 = create(:label, title: 'label2', project: project1)
+ project2_label_1 = create(:label, title: 'label1', project: project2)
+ group_label = create(:group_label, title: 'group_label', group: group)
+ create(:label, title: 'label3', project: project2)
+
+ original_issue.update!(labels: [project1_label_1, project1_label_2, group_label])
+
+ expect(new_attributes[:label_ids]).to match_array([project2_label_1.id, group_label.id])
+ end
+
+ it 'does not set any labels when not used on the original issue' do
+ expect(new_attributes[:label_ids]).to be_empty
+ end
+ end
+
+ context 'setting milestones' do
+ it 'sets milestone to nil when old issue milestone is not in the new project' do
+ milestone = create(:milestone, title: 'milestone', project: project1)
+
+ original_issue.update!(milestone: milestone)
+
+ expect(new_attributes[:milestone_id]).to be_nil
+ end
+
+ it 'copies the milestone when old issue milestone title is in the new project' do
+ milestone_project1 = create(:milestone, title: 'milestone', project: project1)
+ milestone_project2 = create(:milestone, title: 'milestone', project: project2)
+
+ original_issue.update!(milestone: milestone_project1)
+
+ expect(new_attributes[:milestone_id]).to eq(milestone_project2.id)
+ end
+
+ it 'copies the milestone when old issue milestone is a group milestone' do
+ milestone = create(:milestone, title: 'milestone', group: group)
+
+ original_issue.update!(milestone: milestone)
+
+ expect(new_attributes[:milestone_id]).to eq(milestone.id)
+ end
+
+ context 'when include_milestone is false' do
+ let(:new_attributes) { described_class.new(user, original_issue, project2).execute(include_milestone: false) }
+
+ it 'does not return any milestone' do
+ milestone = create(:milestone, title: 'milestone', group: group)
+
+ original_issue.update!(milestone: milestone)
+
+ expect(new_attributes[:milestone_id]).to be_nil
+ end
+ end
+ end
+
+ context 'when target parent is a group' do
+ let(:new_attributes) { described_class.new(user, original_issue, group).execute }
+
+ context 'setting labels' do
+ let(:project_label1) { create(:label, title: 'label1', project: project1) }
+ let!(:project_label2) { create(:label, title: 'label2', project: project1) }
+ let(:group_label1) { create(:group_label, title: 'group_label', group: group) }
+ let!(:group_label2) { create(:group_label, title: 'label2', group: group) }
+
+ it 'keeps group labels and merges project labels where possible' do
+ original_issue.update!(labels: [project_label1, project_label2, group_label1])
+
+ expect(new_attributes[:label_ids]).to match_array([group_label1.id, group_label2.id])
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/issuable/clone/copy_resource_events_service_spec.rb b/spec/lib/gitlab/issuable/clone/copy_resource_events_service_spec.rb
new file mode 100644
index 00000000000..1700939f49e
--- /dev/null
+++ b/spec/lib/gitlab/issuable/clone/copy_resource_events_service_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Issuable::Clone::CopyResourceEventsService do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project1) { create(:project, :public, group: group) }
+ let_it_be(:project2) { create(:project, :public, group: group) }
+ let_it_be(:new_issue) { create(:issue, project: project2) }
+ let_it_be_with_reload(:original_issue) { create(:issue, project: project1) }
+
+ subject { described_class.new(user, original_issue, new_issue) }
+
+ it 'copies the resource label events' do
+ resource_label_events = create_list(:resource_label_event, 2, issue: original_issue)
+
+ subject.execute
+
+ expected = resource_label_events.map(&:label_id)
+
+ expect(new_issue.resource_label_events.map(&:label_id)).to match_array(expected)
+ end
+
+ context 'with existing milestone events' do
+ let!(:milestone1_project1) { create(:milestone, title: 'milestone1', project: project1) }
+ let!(:milestone2_project1) { create(:milestone, title: 'milestone2', project: project1) }
+ let!(:milestone3_project1) { create(:milestone, title: 'milestone3', project: project1) }
+
+ let!(:milestone1_project2) { create(:milestone, title: 'milestone1', project: project2) }
+ let!(:milestone2_project2) { create(:milestone, title: 'milestone2', project: project2) }
+
+ before do
+ original_issue.update!(milestone: milestone2_project1)
+
+ create_event(milestone1_project1)
+ create_event(milestone2_project1)
+ create_event(nil, 'remove')
+ create_event(milestone3_project1)
+ end
+
+ it 'copies existing resource milestone events' do
+ subject.execute
+
+ new_issue_milestone_events = new_issue.reload.resource_milestone_events
+ expect(new_issue_milestone_events.count).to eq(3)
+
+ expect_milestone_event(
+ new_issue_milestone_events.first, milestone: milestone1_project2, action: 'add', state: 'opened'
+ )
+ expect_milestone_event(
+ new_issue_milestone_events.second, milestone: milestone2_project2, action: 'add', state: 'opened'
+ )
+ expect_milestone_event(
+ new_issue_milestone_events.third, milestone: nil, action: 'remove', state: 'opened'
+ )
+ end
+
+ def create_event(milestone, action = 'add')
+ create(:resource_milestone_event, issue: original_issue, milestone: milestone, action: action)
+ end
+
+ def expect_milestone_event(event, expected_attrs)
+ expect(event.milestone_id).to eq(expected_attrs[:milestone]&.id)
+ expect(event.action).to eq(expected_attrs[:action])
+ expect(event.state).to eq(expected_attrs[:state])
+ end
+ end
+
+ context 'with existing state events' do
+ let!(:event1) { create(:resource_state_event, issue: original_issue, state: 'opened') }
+ let!(:event2) { create(:resource_state_event, issue: original_issue, state: 'closed') }
+ let!(:event3) { create(:resource_state_event, issue: original_issue, state: 'reopened') }
+
+ it 'copies existing state events as expected' do
+ subject.execute
+
+ state_events = new_issue.reload.resource_state_events
+ expect(state_events.size).to eq(3)
+
+ expect_state_event(state_events.first, issue: new_issue, state: 'opened')
+ expect_state_event(state_events.second, issue: new_issue, state: 'closed')
+ expect_state_event(state_events.third, issue: new_issue, state: 'reopened')
+ end
+
+ def expect_state_event(event, expected_attrs)
+ expect(event.issue_id).to eq(expected_attrs[:issue]&.id)
+ expect(event.state).to eq(expected_attrs[:state])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/jira_import/issue_serializer_spec.rb b/spec/lib/gitlab/jira_import/issue_serializer_spec.rb
index 198d2db234c..30ad24472b4 100644
--- a/spec/lib/gitlab/jira_import/issue_serializer_spec.rb
+++ b/spec/lib/gitlab/jira_import/issue_serializer_spec.rb
@@ -11,6 +11,7 @@ RSpec.describe Gitlab::JiraImport::IssueSerializer do
let_it_be(:group_label) { create(:group_label, group: group, title: 'dev') }
let_it_be(:current_user) { create(:user) }
let_it_be(:user) { create(:user) }
+ let_it_be(:issue_type_id) { WorkItems::Type.default_issue_type.id }
let(:iid) { 5 }
let(:key) { 'PROJECT-5' }
@@ -54,7 +55,7 @@ RSpec.describe Gitlab::JiraImport::IssueSerializer do
let(:params) { { iid: iid } }
- subject { described_class.new(project, jira_issue, current_user.id, params).execute }
+ subject { described_class.new(project, jira_issue, current_user.id, issue_type_id, params).execute }
let(:expected_description) do
<<~MD
@@ -74,6 +75,7 @@ RSpec.describe Gitlab::JiraImport::IssueSerializer do
expect(subject).to eq(
iid: iid,
project_id: project.id,
+ namespace_id: project.project_namespace_id,
description: expected_description.strip,
title: "[#{key}] #{summary}",
state_id: 1,
@@ -81,7 +83,8 @@ RSpec.describe Gitlab::JiraImport::IssueSerializer do
created_at: created_at,
author_id: current_user.id,
assignee_ids: nil,
- label_ids: [project_label.id, group_label.id] + Label.reorder(id: :asc).last(2).pluck(:id)
+ label_ids: [project_label.id, group_label.id] + Label.reorder(id: :asc).last(2).pluck(:id),
+ work_item_type_id: issue_type_id
)
end
diff --git a/spec/lib/gitlab/jira_import/issues_importer_spec.rb b/spec/lib/gitlab/jira_import/issues_importer_spec.rb
index 565a9ad17e1..1bc052ee0b6 100644
--- a/spec/lib/gitlab/jira_import/issues_importer_spec.rb
+++ b/spec/lib/gitlab/jira_import/issues_importer_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe Gitlab::JiraImport::IssuesImporter do
let_it_be(:project) { create(:project) }
let_it_be(:jira_import) { create(:jira_import_state, project: project, user: current_user) }
let_it_be(:jira_integration) { create(:jira_integration, project: project) }
+ let_it_be(:default_issue_type_id) { WorkItems::Type.default_issue_type.id }
subject { described_class.new(project) }
@@ -47,12 +48,22 @@ RSpec.describe Gitlab::JiraImport::IssuesImporter do
count.times do |i|
if raise_exception_on_even_mocks && i.even?
- expect(Gitlab::JiraImport::IssueSerializer).to receive(:new)
- .with(project, jira_issues[i], current_user.id, { iid: next_iid + 1 }).and_raise('Some error')
+ expect(Gitlab::JiraImport::IssueSerializer).to receive(:new).with(
+ project,
+ jira_issues[i],
+ current_user.id,
+ default_issue_type_id,
+ { iid: next_iid + 1 }
+ ).and_raise('Some error')
else
next_iid += 1
- expect(Gitlab::JiraImport::IssueSerializer).to receive(:new)
- .with(project, jira_issues[i], current_user.id, { iid: next_iid }).and_return(serializer)
+ expect(Gitlab::JiraImport::IssueSerializer).to receive(:new).with(
+ project,
+ jira_issues[i],
+ current_user.id,
+ default_issue_type_id,
+ { iid: next_iid }
+ ).and_return(serializer)
end
end
end
diff --git a/spec/lib/gitlab/lograge/custom_options_spec.rb b/spec/lib/gitlab/lograge/custom_options_spec.rb
index 58b05be6ff9..090b79c5d3c 100644
--- a/spec/lib/gitlab/lograge/custom_options_spec.rb
+++ b/spec/lib/gitlab/lograge/custom_options_spec.rb
@@ -25,7 +25,8 @@ RSpec.describe Gitlab::Lograge::CustomOptions do
remote_ip: '192.168.1.2',
ua: 'Nyxt',
queue_duration_s: 0.2,
- etag_route: '/etag'
+ etag_route: '/etag',
+ response_bytes: 1234
}
end
@@ -55,6 +56,20 @@ RSpec.describe Gitlab::Lograge::CustomOptions do
expect(subject[:user_id]).to eq('test')
end
+ it 'adds the response length' do
+ expect(subject[:response_bytes]).to eq(1234)
+ end
+
+ context 'with log_response_length disabled' do
+ before do
+ stub_feature_flags(log_response_length: false)
+ end
+
+ it 'does not add the response length' do
+ expect(subject).not_to include(:response_bytes)
+ end
+ end
+
it 'adds Cloudflare headers' do
expect(subject[:cf_ray]).to eq(event.payload[:cf_ray])
expect(subject[:cf_request_id]).to eq(event.payload[:cf_request_id])
diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
index d22bef5bda9..81910773dfa 100644
--- a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
@@ -11,6 +11,8 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
attribute :author
attribute :project
+
+ before_validation -> { self.work_item_type_id = ::WorkItems::Type.default_issue_type.id }
end
end
diff --git a/spec/lib/gitlab/memory/watchdog_spec.rb b/spec/lib/gitlab/memory/watchdog_spec.rb
new file mode 100644
index 00000000000..8b82078bcb9
--- /dev/null
+++ b/spec/lib/gitlab/memory/watchdog_spec.rb
@@ -0,0 +1,308 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
+ context 'watchdog' do
+ let(:logger) { instance_double(::Logger) }
+ let(:handler) { instance_double(described_class::NullHandler) }
+
+ let(:heap_frag_limit_gauge) { instance_double(::Prometheus::Client::Gauge) }
+ let(:heap_frag_violations_counter) { instance_double(::Prometheus::Client::Counter) }
+ let(:heap_frag_violations_handled_counter) { instance_double(::Prometheus::Client::Counter) }
+
+ let(:sleep_time) { 0.1 }
+ let(:max_heap_fragmentation) { 0.2 }
+
+ subject(:watchdog) do
+ described_class.new(handler: handler, logger: logger, sleep_time_seconds: sleep_time,
+ max_strikes: max_strikes, max_heap_fragmentation: max_heap_fragmentation)
+ end
+
+ before do
+ allow(handler).to receive(:on_high_heap_fragmentation).and_return(true)
+
+ allow(logger).to receive(:warn)
+ allow(logger).to receive(:info)
+
+ allow(Gitlab::Metrics::Memory).to receive(:gc_heap_fragmentation).and_return(fragmentation)
+ end
+
+ after do
+ watchdog.stop
+ end
+
+ context 'when starting up' do
+ let(:fragmentation) { 0 }
+ let(:max_strikes) { 0 }
+
+ it 'sets the heap fragmentation limit gauge' do
+ allow(Gitlab::Metrics).to receive(:gauge).and_return(heap_frag_limit_gauge)
+
+ expect(heap_frag_limit_gauge).to receive(:set).with({}, max_heap_fragmentation)
+ end
+
+ context 'when no settings are set in the environment' do
+ it 'initializes with defaults' do
+ watchdog = described_class.new(handler: handler, logger: logger)
+
+ expect(watchdog.max_heap_fragmentation).to eq(described_class::DEFAULT_HEAP_FRAG_THRESHOLD)
+ expect(watchdog.max_strikes).to eq(described_class::DEFAULT_MAX_STRIKES)
+ expect(watchdog.sleep_time_seconds).to eq(described_class::DEFAULT_SLEEP_TIME_SECONDS)
+ end
+ end
+
+ context 'when settings are passed through the environment' do
+ before do
+ stub_env('GITLAB_MEMWD_MAX_HEAP_FRAG', 1)
+ stub_env('GITLAB_MEMWD_MAX_STRIKES', 2)
+ stub_env('GITLAB_MEMWD_SLEEP_TIME_SEC', 3)
+ end
+
+ it 'initializes with these settings' do
+ watchdog = described_class.new(handler: handler, logger: logger)
+
+ expect(watchdog.max_heap_fragmentation).to eq(1)
+ expect(watchdog.max_strikes).to eq(2)
+ expect(watchdog.sleep_time_seconds).to eq(3)
+ end
+ end
+ end
+
+ context 'when process does not exceed heap fragmentation threshold' do
+ let(:fragmentation) { max_heap_fragmentation - 0.1 }
+ let(:max_strikes) { 0 } # To rule out that we were granting too many strikes.
+
+ it 'does not signal the handler' do
+ expect(handler).not_to receive(:on_high_heap_fragmentation)
+
+ watchdog.start
+
+ sleep sleep_time * 3
+ end
+ end
+
+ context 'when process exceeds heap fragmentation threshold permanently' do
+ let(:fragmentation) { max_heap_fragmentation + 0.1 }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:counter)
+ .with(:gitlab_memwd_heap_frag_violations_total, anything, anything)
+ .and_return(heap_frag_violations_counter)
+ allow(Gitlab::Metrics).to receive(:counter)
+ .with(:gitlab_memwd_heap_frag_violations_handled_total, anything, anything)
+ .and_return(heap_frag_violations_handled_counter)
+ allow(heap_frag_violations_counter).to receive(:increment)
+ allow(heap_frag_violations_handled_counter).to receive(:increment)
+ end
+
+ context 'when process has not exceeded allowed number of strikes' do
+ let(:max_strikes) { 10 }
+
+ it 'does not signal the handler' do
+ expect(handler).not_to receive(:on_high_heap_fragmentation)
+
+ watchdog.start
+
+ sleep sleep_time * 3
+ end
+
+ it 'does not log any events' do
+ expect(logger).not_to receive(:warn)
+
+ watchdog.start
+
+ sleep sleep_time * 3
+ end
+
+ it 'increments the violations counter' do
+ expect(heap_frag_violations_counter).to receive(:increment)
+
+ watchdog.start
+
+ sleep sleep_time * 3
+ end
+
+ it 'does not increment violations handled counter' do
+ expect(heap_frag_violations_handled_counter).not_to receive(:increment)
+
+ watchdog.start
+
+ sleep sleep_time * 3
+ end
+ end
+
+ context 'when process exceeds the allowed number of strikes' do
+ let(:max_strikes) { 1 }
+
+ it 'signals the handler and resets strike counter' do
+ expect(handler).to receive(:on_high_heap_fragmentation).and_return(true)
+
+ watchdog.start
+
+ sleep sleep_time * 3
+
+ expect(watchdog.strikes).to eq(0)
+ end
+
+ it 'logs the event' do
+ expect(::Prometheus::PidProvider).to receive(:worker_id).at_least(:once).and_return('worker_1')
+ expect(Gitlab::Metrics::System).to receive(:memory_usage_rss).at_least(:once).and_return(1024)
+ expect(logger).to receive(:warn).with({
+ message: 'heap fragmentation limit exceeded',
+ pid: Process.pid,
+ worker_id: 'worker_1',
+ memwd_handler_class: 'RSpec::Mocks::InstanceVerifyingDouble',
+ memwd_sleep_time_s: sleep_time,
+ memwd_max_heap_frag: max_heap_fragmentation,
+ memwd_cur_heap_frag: fragmentation,
+ memwd_max_strikes: max_strikes,
+ memwd_cur_strikes: max_strikes + 1,
+ memwd_rss_bytes: 1024
+ })
+
+ watchdog.start
+
+ sleep sleep_time * 3
+ end
+
+ it 'increments both the violations and violations handled counters' do
+ expect(heap_frag_violations_counter).to receive(:increment)
+ expect(heap_frag_violations_handled_counter).to receive(:increment)
+
+ watchdog.start
+
+ sleep sleep_time * 3
+ end
+
+ context 'when enforce_memory_watchdog ops toggle is off' do
+ before do
+ stub_feature_flags(enforce_memory_watchdog: false)
+ end
+
+ it 'always uses the NullHandler' do
+ expect(handler).not_to receive(:on_high_heap_fragmentation)
+ expect(described_class::NullHandler.instance).to(
+ receive(:on_high_heap_fragmentation).with(fragmentation).and_return(true)
+ )
+
+ watchdog.start
+
+ sleep sleep_time * 3
+ end
+ end
+ end
+
+ context 'when handler result is true' do
+ let(:max_strikes) { 1 }
+
+ it 'considers the event handled and stops itself' do
+ expect(handler).to receive(:on_high_heap_fragmentation).once.and_return(true)
+
+ watchdog.start
+
+ sleep sleep_time * 3
+ end
+ end
+
+ context 'when handler result is false' do
+ let(:max_strikes) { 1 }
+
+ it 'keeps running' do
+ # Return true the third time to terminate the daemon.
+ expect(handler).to receive(:on_high_heap_fragmentation).and_return(false, false, true)
+
+ watchdog.start
+
+ sleep sleep_time * 4
+ end
+ end
+ end
+
+ context 'when process exceeds heap fragmentation threshold temporarily' do
+ let(:fragmentation) { max_heap_fragmentation }
+ let(:max_strikes) { 1 }
+
+ before do
+ allow(Gitlab::Metrics::Memory).to receive(:gc_heap_fragmentation).and_return(
+ fragmentation - 0.1,
+ fragmentation + 0.2,
+ fragmentation - 0.1,
+ fragmentation + 0.1
+ )
+ end
+
+ it 'does not signal the handler' do
+ expect(handler).not_to receive(:on_high_heap_fragmentation)
+
+ watchdog.start
+
+ sleep sleep_time * 4
+ end
+ end
+
+ context 'when gitlab_memory_watchdog ops toggle is off' do
+ let(:fragmentation) { 0 }
+ let(:max_strikes) { 0 }
+
+ before do
+ stub_feature_flags(gitlab_memory_watchdog: false)
+ end
+
+ it 'does not monitor heap fragmentation' do
+ expect(Gitlab::Metrics::Memory).not_to receive(:gc_heap_fragmentation)
+
+ watchdog.start
+
+ sleep sleep_time * 3
+ end
+ end
+ end
+
+ context 'handlers' do
+ context 'NullHandler' do
+ subject(:handler) { described_class::NullHandler.instance }
+
+ describe '#on_high_heap_fragmentation' do
+ it 'does nothing' do
+ expect(handler.on_high_heap_fragmentation(1.0)).to be(false)
+ end
+ end
+ end
+
+ context 'TermProcessHandler' do
+ subject(:handler) { described_class::TermProcessHandler.new(42) }
+
+ describe '#on_high_heap_fragmentation' do
+ it 'sends SIGTERM to the current process' do
+ expect(Process).to receive(:kill).with(:TERM, 42)
+
+ expect(handler.on_high_heap_fragmentation(1.0)).to be(true)
+ end
+ end
+ end
+
+ context 'PumaHandler' do
+ # rubocop: disable RSpec/VerifiedDoubles
+ # In tests, the Puma constant is not loaded so we cannot make this an instance_double.
+ let(:puma_worker_handle_class) { double('Puma::Cluster::WorkerHandle') }
+ let(:puma_worker_handle) { double('worker') }
+ # rubocop: enable RSpec/VerifiedDoubles
+
+ subject(:handler) { described_class::PumaHandler.new({}) }
+
+ before do
+ stub_const('::Puma::Cluster::WorkerHandle', puma_worker_handle_class)
+ end
+
+ describe '#on_high_heap_fragmentation' do
+ it 'invokes orderly termination via Puma API' do
+ expect(puma_worker_handle_class).to receive(:new).and_return(puma_worker_handle)
+ expect(puma_worker_handle).to receive(:term)
+
+ expect(handler.on_high_heap_fragmentation(1.0)).to be(true)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb
index 66fba7ab683..dc5c7eb2e55 100644
--- a/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb
+++ b/spec/lib/gitlab/metrics/exporter/base_exporter_spec.rb
@@ -19,6 +19,7 @@ RSpec.describe Gitlab::Metrics::Exporter::BaseExporter do
allow(settings).to receive(:enabled).and_return(true)
allow(settings).to receive(:port).and_return(0)
allow(settings).to receive(:address).and_return('127.0.0.1')
+ allow(settings).to receive(:[]).with('tls_enabled').and_return(false)
end
after do
@@ -88,6 +89,51 @@ RSpec.describe Gitlab::Metrics::Exporter::BaseExporter do
exporter
end
end
+
+ context 'with TLS enabled' do
+ let(:test_cert) { Rails.root.join('spec/fixtures/x509_certificate.crt').to_s }
+ let(:test_key) { Rails.root.join('spec/fixtures/x509_certificate_pk.key').to_s }
+
+ before do
+ allow(settings).to receive(:[]).with('tls_enabled').and_return(true)
+ allow(settings).to receive(:[]).with('tls_cert_path').and_return(test_cert)
+ allow(settings).to receive(:[]).with('tls_key_path').and_return(test_key)
+ end
+
+ it 'injects the necessary OpenSSL config for WEBrick' do
+ expect(::WEBrick::HTTPServer).to receive(:new).with(
+ a_hash_including(
+ SSLEnable: true,
+ SSLCertificate: an_instance_of(OpenSSL::X509::Certificate),
+ SSLPrivateKey: an_instance_of(OpenSSL::PKey::RSA),
+ SSLStartImmediately: true,
+ SSLExtraChainCert: []
+ ))
+
+ exporter.start
+ end
+
+ context 'with intermediate certificates' do
+ let(:test_cert) { Rails.root.join('spec/fixtures/clusters/chain_certificates.pem').to_s }
+ let(:test_key) { Rails.root.join('spec/fixtures/clusters/sample_key.key').to_s }
+
+ it 'injects them in the extra chain' do
+ expect(::WEBrick::HTTPServer).to receive(:new).with(
+ a_hash_including(
+ SSLEnable: true,
+ SSLCertificate: an_instance_of(OpenSSL::X509::Certificate),
+ SSLPrivateKey: an_instance_of(OpenSSL::PKey::RSA),
+ SSLStartImmediately: true,
+ SSLExtraChainCert: [
+ an_instance_of(OpenSSL::X509::Certificate),
+ an_instance_of(OpenSSL::X509::Certificate)
+ ]
+ ))
+
+ exporter.start
+ end
+ end
+ end
end
describe 'when thread is not alive' do
@@ -159,6 +205,7 @@ RSpec.describe Gitlab::Metrics::Exporter::BaseExporter do
allow(settings).to receive(:enabled).and_return(true)
allow(settings).to receive(:port).and_return(0)
allow(settings).to receive(:address).and_return('127.0.0.1')
+ allow(settings).to receive(:[]).with('tls_enabled').and_return(false)
stub_const('Gitlab::Metrics::Exporter::MetricsMiddleware', fake_collector)
diff --git a/spec/lib/gitlab/metrics/memory_spec.rb b/spec/lib/gitlab/metrics/memory_spec.rb
new file mode 100644
index 00000000000..fd8ca3b37c6
--- /dev/null
+++ b/spec/lib/gitlab/metrics/memory_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Metrics::Memory do
+ describe '.gc_heap_fragmentation' do
+ subject(:call) do
+ described_class.gc_heap_fragmentation(
+ heap_live_slots: gc_stat_heap_live_slots,
+ heap_eden_pages: gc_stat_heap_eden_pages
+ )
+ end
+
+ context 'when the Ruby heap is perfectly utilized' do
+ # All objects are located in a single heap page.
+ let(:gc_stat_heap_live_slots) { described_class::HEAP_SLOTS_PER_PAGE }
+ let(:gc_stat_heap_eden_pages) { 1 }
+
+ it { is_expected.to eq(0) }
+ end
+
+ context 'when the Ruby heap is greatly fragmented' do
+ # There is one object per heap page.
+ let(:gc_stat_heap_live_slots) { described_class::HEAP_SLOTS_PER_PAGE }
+ let(:gc_stat_heap_eden_pages) { described_class::HEAP_SLOTS_PER_PAGE }
+
+ # The heap can never be "perfectly fragmented" because that would require
+ # zero objects per page.
+ it { is_expected.to be > 0.99 }
+ end
+
+ context 'when the Ruby heap is semi-fragmented' do
+ # All objects are spread over two pages i.e. each page is 50% utilized.
+ let(:gc_stat_heap_live_slots) { described_class::HEAP_SLOTS_PER_PAGE }
+ let(:gc_stat_heap_eden_pages) { 2 }
+
+ it { is_expected.to eq(0.5) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index dfae5aa6784..b1566ffa7b4 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -125,5 +125,11 @@ RSpec.describe Gitlab::Metrics::Samplers::RubySampler do
sampler.sample
end
+
+ it 'adds a heap fragmentation metric' do
+ expect(sampler.metrics[:heap_fragmentation]).to receive(:set).with({}, anything)
+
+ sampler.sample
+ end
end
end
diff --git a/spec/lib/gitlab/metrics/sli_spec.rb b/spec/lib/gitlab/metrics/sli_spec.rb
index 102ea442b3a..d100f66be19 100644
--- a/spec/lib/gitlab/metrics/sli_spec.rb
+++ b/spec/lib/gitlab/metrics/sli_spec.rb
@@ -172,11 +172,11 @@ RSpec.describe Gitlab::Metrics::Sli do
fake_counter
end
- def fake_total_counter(name)
- fake_prometheus_counter("gitlab_sli:#{name}:total")
+ def fake_total_counter(name, separator = '_')
+ fake_prometheus_counter(['gitlab_sli', name, 'total'].join(separator))
end
- def fake_numerator_counter(name, numerator_name)
- fake_prometheus_counter("gitlab_sli:#{name}:#{numerator_name}_total")
+ def fake_numerator_counter(name, numerator_name, separator = '_')
+ fake_prometheus_counter(["gitlab_sli", name, "#{numerator_name}_total"].join(separator))
end
end
diff --git a/spec/lib/gitlab/pages/cache_control_spec.rb b/spec/lib/gitlab/pages/cache_control_spec.rb
new file mode 100644
index 00000000000..6ed823427fb
--- /dev/null
+++ b/spec/lib/gitlab/pages/cache_control_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Pages::CacheControl do
+ it 'fails with invalid type' do
+ expect { described_class.new(type: :unknown, id: nil) }
+ .to raise_error(ArgumentError, "type must be :namespace or :project")
+ end
+
+ describe '.for_namespace' do
+ let(:subject) { described_class.for_namespace(1) }
+
+ it { expect(subject.cache_key).to eq('pages_domain_for_namespace_1') }
+
+ describe '#clear_cache' do
+ it 'clears the cache' do
+ expect(Rails.cache)
+ .to receive(:delete)
+ .with('pages_domain_for_namespace_1')
+
+ subject.clear_cache
+ end
+ end
+ end
+
+ describe '.for_project' do
+ let(:subject) { described_class.for_project(1) }
+
+ it { expect(subject.cache_key).to eq('pages_domain_for_project_1') }
+
+ describe '#clear_cache' do
+ it 'clears the cache' do
+ expect(Rails.cache)
+ .to receive(:delete)
+ .with('pages_domain_for_project_1')
+
+ subject.clear_cache
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/pages/deployment_update_spec.rb b/spec/lib/gitlab/pages/deployment_update_spec.rb
new file mode 100644
index 00000000000..cf109248f36
--- /dev/null
+++ b/spec/lib/gitlab/pages/deployment_update_spec.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Pages::DeploymentUpdate do
+ let_it_be(:project, refind: true) { create(:project, :repository) }
+
+ let_it_be(:old_pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) }
+
+ let(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD') }
+ let(:invalid_file) { fixture_file_upload('spec/fixtures/dk.png') }
+
+ let(:file) { fixture_file_upload("spec/fixtures/pages.zip") }
+ let(:empty_file) { fixture_file_upload("spec/fixtures/pages_empty.zip") }
+ let(:empty_metadata_filename) { "spec/fixtures/pages_empty.zip.meta" }
+ let(:metadata_filename) { "spec/fixtures/pages.zip.meta" }
+ let(:metadata) { fixture_file_upload(metadata_filename) if File.exist?(metadata_filename) }
+
+ subject(:pages_deployment_update) { described_class.new(project, build) }
+
+ context 'for new artifacts' do
+ context 'for a valid job' do
+ let!(:artifacts_archive) { create(:ci_job_artifact, :correct_checksum, file: file, job: build) }
+
+ before do
+ create(:ci_job_artifact, file_type: :metadata, file_format: :gzip, file: metadata, job: build)
+
+ build.reload
+ end
+
+ it 'is valid' do
+ expect(pages_deployment_update).to be_valid
+ end
+
+ context 'when missing artifacts metadata' do
+ before do
+ expect(build).to receive(:artifacts_metadata?).and_return(false)
+ end
+
+ it 'is invalid' do
+ expect(pages_deployment_update).not_to be_valid
+ expect(pages_deployment_update.errors.full_messages).to include('missing artifacts metadata')
+ end
+ end
+ end
+
+ it 'is invalid for invalid archive' do
+ create(:ci_job_artifact, :archive, file: invalid_file, job: build)
+
+ expect(pages_deployment_update).not_to be_valid
+ expect(pages_deployment_update.errors.full_messages).to include('missing artifacts metadata')
+ end
+ end
+
+ describe 'maximum pages artifacts size' do
+ let(:metadata) { spy('metadata') } # rubocop: disable RSpec/VerifiedDoubles
+
+ before do
+ file = fixture_file_upload('spec/fixtures/pages.zip')
+ metafile = fixture_file_upload('spec/fixtures/pages.zip.meta')
+
+ create(:ci_job_artifact, :archive, :correct_checksum, file: file, job: build)
+ create(:ci_job_artifact, :metadata, file: metafile, job: build)
+
+ allow(build).to receive(:artifacts_metadata_entry)
+ .and_return(metadata)
+ end
+
+ context 'when maximum pages size is set to zero' do
+ before do
+ stub_application_setting(max_pages_size: 0)
+ end
+
+ context "when size is above the limit" do
+ before do
+ allow(metadata).to receive(:total_size).and_return(1.megabyte)
+ allow(metadata).to receive(:entries).and_return([])
+ end
+
+ it 'is valid' do
+ expect(pages_deployment_update).to be_valid
+ end
+ end
+ end
+
+ context 'when size is limited on the instance level' do
+ before do
+ stub_application_setting(max_pages_size: 100)
+ end
+
+ context "when size is below the limit" do
+ before do
+ allow(metadata).to receive(:total_size).and_return(1.megabyte)
+ allow(metadata).to receive(:entries).and_return([])
+ end
+
+ it 'is valid' do
+ expect(pages_deployment_update).to be_valid
+ end
+ end
+
+ context "when size is above the limit" do
+ before do
+ allow(metadata).to receive(:total_size).and_return(101.megabyte)
+ allow(metadata).to receive(:entries).and_return([])
+ end
+
+ it 'is invalid' do
+ expect(pages_deployment_update).not_to be_valid
+ expect(pages_deployment_update.errors.full_messages)
+ .to include('artifacts for pages are too large: 105906176')
+ end
+ end
+ end
+ end
+
+ context 'when retrying the job' do
+ let!(:older_deploy_job) do
+ create(
+ :generic_commit_status,
+ :failed,
+ pipeline: pipeline,
+ ref: build.ref,
+ stage: 'deploy',
+ name: 'pages:deploy'
+ )
+ end
+
+ before do
+ create(:ci_job_artifact, :correct_checksum, file: file, job: build)
+ create(:ci_job_artifact, file_type: :metadata, file_format: :gzip, file: metadata, job: build)
+ build.reload
+ end
+
+ it 'marks older pages:deploy jobs retried' do
+ expect(pages_deployment_update).to be_valid
+ 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 ac2695977c4..879c874b134 100644
--- a/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb
+++ b/spec/lib/gitlab/pagination/cursor_based_keyset_spec.rb
@@ -15,6 +15,22 @@ RSpec.describe Gitlab::Pagination::CursorBasedKeyset do
end
end
+ describe '.enforced_for_type?' do
+ subject { described_class.enforced_for_type?(relation) }
+
+ context 'when relation is Group' do
+ let(:relation) { Group.all }
+
+ it { is_expected.to be true }
+ end
+
+ context 'when relation is AuditEvent' do
+ let(:relation) { AuditEvent.all }
+
+ it { is_expected.to be false }
+ end
+ end
+
describe '.available?' do
let(:request_context) { double('request_context', params: { order_by: order_by, sort: sort }) }
let(:cursor_based_request_context) { Gitlab::Pagination::Keyset::CursorBasedRequestContext.new(request_context) }
diff --git a/spec/lib/gitlab/pagination/keyset/order_spec.rb b/spec/lib/gitlab/pagination/keyset/order_spec.rb
index abbb3a21cd4..c1fc73603d6 100644
--- a/spec/lib/gitlab/pagination/keyset/order_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/order_spec.rb
@@ -680,4 +680,28 @@ RSpec.describe Gitlab::Pagination::Keyset::Order do
end
end
end
+
+ describe '#attribute_names' do
+ let(:expected_attribute_names) { %w(id name) }
+ let(:order) do
+ Gitlab::Pagination::Keyset::Order.build([
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'id',
+ order_expression: Project.arel_table['id'].desc,
+ nullable: :not_nullable,
+ distinct: true
+ ),
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'name',
+ order_expression: Project.arel_table['name'].desc,
+ nullable: :not_nullable,
+ distinct: true
+ )
+ ])
+ end
+
+ subject { order.attribute_names }
+
+ it { is_expected.to match_array(expected_attribute_names) }
+ end
end
diff --git a/spec/lib/gitlab/quick_actions/users_extractor_spec.rb b/spec/lib/gitlab/quick_actions/users_extractor_spec.rb
new file mode 100644
index 00000000000..d00f52bb056
--- /dev/null
+++ b/spec/lib/gitlab/quick_actions/users_extractor_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::QuickActions::UsersExtractor do
+ subject(:extractor) { described_class.new(current_user, project: project, group: group, target: target, text: text) }
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:target) { create(:issue, project: project) }
+
+ let_it_be(:pancakes) { create(:user, username: 'pancakes') }
+ let_it_be(:waffles) { create(:user, username: 'waffles') }
+ let_it_be(:syrup) { create(:user, username: 'syrup') }
+
+ before do
+ allow(target).to receive(:allows_multiple_assignees?).and_return(false)
+ end
+
+ context 'when the text is nil' do
+ let(:text) { nil }
+
+ it 'returns an empty array' do
+ expect(extractor.execute).to be_empty
+ end
+ end
+
+ context 'when the text is blank' do
+ let(:text) { ' ' }
+
+ it 'returns an empty array' do
+ expect(extractor.execute).to be_empty
+ end
+ end
+
+ context 'when there are users to be found' do
+ context 'when using usernames' do
+ let(:text) { 'me, pancakes waffles and syrup' }
+
+ it 'finds the users' do
+ expect(extractor.execute).to contain_exactly(current_user, pancakes, waffles, syrup)
+ end
+ end
+
+ context 'when there are too many users' do
+ let(:text) { 'me, pancakes waffles and syrup' }
+
+ before do
+ stub_const("#{described_class}::MAX_QUICK_ACTION_USERS", 2)
+ end
+
+ it 'complains' do
+ expect { extractor.execute }.to raise_error(described_class::TooManyError)
+ end
+ end
+
+ context 'when using references' do
+ let(:text) { 'me, @pancakes @waffles and @syrup' }
+
+ it 'finds the users' do
+ expect(extractor.execute).to contain_exactly(current_user, pancakes, waffles, syrup)
+ end
+ end
+
+ context 'when using a mixture of usernames and references' do
+ let(:text) { 'me, @pancakes waffles and @syrup' }
+
+ it 'finds the users' do
+ expect(extractor.execute).to contain_exactly(current_user, pancakes, waffles, syrup)
+ end
+ end
+
+ context 'when one or more users cannot be found' do
+ let(:text) { 'me, @bacon @pancakes, chicken waffles and @syrup' }
+
+ it 'reports an error' do
+ expect { extractor.execute }.to raise_error(described_class::MissingError, include('bacon', 'chicken'))
+ end
+ end
+
+ context 'when trying to find group members' do
+ let(:group) { create(:group, path: 'breakfast-foods') }
+ let(:text) { group.to_reference }
+
+ it 'reports an error' do
+ [pancakes, waffles].each { group.add_developer(_1) }
+
+ expect { extractor.execute }.to raise_error(described_class::MissingError, include('breakfast-foods'))
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/redis/multi_store_spec.rb b/spec/lib/gitlab/redis/multi_store_spec.rb
index e127c89c303..50ebf43a05e 100644
--- a/spec/lib/gitlab/redis/multi_store_spec.rb
+++ b/spec/lib/gitlab/redis/multi_store_spec.rb
@@ -507,7 +507,7 @@ RSpec.describe Gitlab::Redis::MultiStore do
secondary_store.flushdb
end
- describe "command execution in a transaction" do
+ describe "command execution in a pipelined command" do
let(:counter) { Gitlab::Metrics::NullMetric.instance }
before do
@@ -557,7 +557,15 @@ RSpec.describe Gitlab::Redis::MultiStore do
include_examples 'verify that store contains values', :secondary_store
end
- describe 'return values from a transaction' do
+ describe 'return values from a pipelined command' do
+ RSpec::Matchers.define :pipeline_diff_error_with_stacktrace do |message|
+ match do |object|
+ expect(object).to be_a(Gitlab::Redis::MultiStore::PipelinedDiffError)
+ expect(object.backtrace).not_to be_nil
+ expect(object.message).to eq(message)
+ end
+ end
+
subject do
multi_store.send(name) do |redis|
redis.get(key1)
@@ -585,7 +593,10 @@ RSpec.describe Gitlab::Redis::MultiStore do
it 'returns the value from the secondary store, logging an error' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
- an_instance_of(Gitlab::Redis::MultiStore::PipelinedDiffError),
+ pipeline_diff_error_with_stacktrace(
+ 'Pipelined command executed on both stores successfully but results differ between them. ' \
+ "Result from the primary: [#{value1.inspect}]. Result from the secondary: [#{value2.inspect}]."
+ ),
hash_including(command_name: name, instance_name: instance_name)
).and_call_original
expect(counter).to receive(:increment).with(command: name, instance_name: instance_name)
@@ -601,7 +612,10 @@ RSpec.describe Gitlab::Redis::MultiStore do
it 'returns the value from the secondary store, logging an error' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
- an_instance_of(Gitlab::Redis::MultiStore::PipelinedDiffError),
+ pipeline_diff_error_with_stacktrace(
+ 'Pipelined command executed on both stores successfully but results differ between them. ' \
+ "Result from the primary: [nil]. Result from the secondary: [#{value2.inspect}]."
+ ),
hash_including(command_name: name, instance_name: instance_name)
)
expect(counter).to receive(:increment).with(command: name, instance_name: instance_name)
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index d48e8183650..a3afbed18e2 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -968,4 +968,18 @@ RSpec.describe Gitlab::Regex do
it { is_expected.not_to match('abc!abc') }
it { is_expected.not_to match((['abc'] * 100).join('.') + '!') }
end
+
+ describe '.x509_subject_key_identifier_regex' do
+ subject { described_class.x509_subject_key_identifier_regex }
+
+ it { is_expected.to match('AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB') }
+ it { is_expected.to match('CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD') }
+ it { is_expected.to match('79:FB:C1:E5:6B:53:8B:0A') }
+ it { is_expected.to match('79:fb:c1:e5:6b:53:8b:0a') }
+
+ it { is_expected.not_to match('') }
+ it { is_expected.not_to match('CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:GG') }
+ it { is_expected.not_to match('random string') }
+ it { is_expected.not_to match('12321342545356434523412341245452345623453542345234523453245') }
+ end
end
diff --git a/spec/lib/gitlab/security/scan_configuration_spec.rb b/spec/lib/gitlab/security/scan_configuration_spec.rb
index 1760796c5a0..774a362617a 100644
--- a/spec/lib/gitlab/security/scan_configuration_spec.rb
+++ b/spec/lib/gitlab/security/scan_configuration_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe ::Gitlab::Security::ScanConfiguration do
let(:configured) { true }
context 'with a core scanner' do
- where(type: %i(sast sast_iac secret_detection))
+ where(type: %i(sast sast_iac secret_detection container_scanning))
with_them do
it { is_expected.to be_truthy }
diff --git a/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
index 4a952a2040a..01b7270d761 100644
--- a/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
@@ -129,7 +129,7 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
allow(Sidekiq).to receive(:options).and_return(timeout: 9)
end
- it 'return true when everything is within limit' do
+ it 'return true when everything is within limit', :aggregate_failures do
expect(memory_killer).to receive(:get_rss).and_return(100)
expect(memory_killer).to receive(:get_soft_limit_rss).and_return(200)
expect(memory_killer).to receive(:get_hard_limit_rss).and_return(300)
@@ -144,7 +144,7 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
expect(subject).to be true
end
- it 'return false when rss exceeds hard_limit_rss' do
+ it 'return false when rss exceeds hard_limit_rss', :aggregate_failures do
expect(memory_killer).to receive(:get_rss).at_least(:once).and_return(400)
expect(memory_killer).to receive(:get_soft_limit_rss).at_least(:once).and_return(200)
expect(memory_killer).to receive(:get_hard_limit_rss).at_least(:once).and_return(300)
@@ -159,12 +159,12 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
expect(Gitlab::Metrics::System).to receive(:monotonic_time).and_call_original
- expect(memory_killer).to receive(:log_rss_out_of_range).with(400, 300, 200)
+ expect(memory_killer).to receive(:out_of_range_description).with(400, 300, 200, true)
expect(subject).to be false
end
- it 'return false when rss exceed hard_limit_rss after a while' do
+ it 'return false when rss exceed hard_limit_rss after a while', :aggregate_failures do
expect(memory_killer).to receive(:get_rss).and_return(250, 400, 400)
expect(memory_killer).to receive(:get_soft_limit_rss).at_least(:once).and_return(200)
expect(memory_killer).to receive(:get_hard_limit_rss).at_least(:once).and_return(300)
@@ -180,12 +180,13 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
expect(Gitlab::Metrics::System).to receive(:monotonic_time).twice.and_call_original
expect(memory_killer).to receive(:sleep).with(check_interval_seconds)
- expect(memory_killer).to receive(:log_rss_out_of_range).with(400, 300, 200)
+ expect(memory_killer).to receive(:out_of_range_description).with(400, 300, 200, false)
+ expect(memory_killer).to receive(:out_of_range_description).with(400, 300, 200, true)
expect(subject).to be false
end
- it 'return true when rss below soft_limit_rss after a while within GRACE_BALLOON_SECONDS' do
+ it 'return true when rss below soft_limit_rss after a while within GRACE_BALLOON_SECONDS', :aggregate_failures do
expect(memory_killer).to receive(:get_rss).and_return(250, 100)
expect(memory_killer).to receive(:get_soft_limit_rss).and_return(200, 200)
expect(memory_killer).to receive(:get_hard_limit_rss).and_return(300, 300)
@@ -201,15 +202,15 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
expect(Gitlab::Metrics::System).to receive(:monotonic_time).twice.and_call_original
expect(memory_killer).to receive(:sleep).with(check_interval_seconds)
- expect(memory_killer).not_to receive(:log_rss_out_of_range)
+ expect(memory_killer).to receive(:out_of_range_description).with(100, 300, 200, false)
expect(subject).to be true
end
- context 'when exceeding GRACE_BALLOON_SECONDS' do
+ context 'when exceeds GRACE_BALLOON_SECONDS' do
let(:grace_balloon_seconds) { 0 }
- it 'return false when rss exceed soft_limit_rss' do
+ it 'return false when rss exceed soft_limit_rss', :aggregate_failures do
allow(memory_killer).to receive(:get_rss).and_return(250)
allow(memory_killer).to receive(:get_soft_limit_rss).and_return(200)
allow(memory_killer).to receive(:get_hard_limit_rss).and_return(300)
@@ -222,8 +223,7 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
.with(:above_soft_limit)
.and_call_original
- expect(memory_killer).to receive(:log_rss_out_of_range)
- .with(250, 300, 200)
+ expect(memory_killer).to receive(:out_of_range_description).with(250, 300, 200, true)
expect(subject).to be false
end
@@ -318,7 +318,7 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
subject { memory_killer.send(:signal_pgroup, signal, explanation) }
- it 'send signal to this proces if it is not group leader' do
+ it 'send signal to this process if it is not group leader' do
expect(Process).to receive(:getpgrp).and_return(pid + 1)
expect(Sidekiq.logger).to receive(:warn).once
@@ -351,12 +351,34 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
let(:current_rss) { 100 }
let(:soft_limit_rss) { 200 }
let(:hard_limit_rss) { 300 }
+ let(:jid) { 1 }
let(:reason) { 'rss out of range reason description' }
+ let(:queue) { 'default' }
+ let(:running_jobs) { [{ jid: jid, worker_class: 'DummyWorker' }] }
+ let(:worker) do
+ Class.new do
+ def self.name
+ 'DummyWorker'
+ end
+
+ include ApplicationWorker
+ end
+ end
+
+ before do
+ stub_const("DummyWorker", worker)
+
+ allow(memory_killer).to receive(:get_rss).and_return(*current_rss)
+ allow(memory_killer).to receive(:get_soft_limit_rss).and_return(soft_limit_rss)
+ allow(memory_killer).to receive(:get_hard_limit_rss).and_return(hard_limit_rss)
+
+ memory_killer.send(:refresh_state, :running)
+ end
- subject { memory_killer.send(:log_rss_out_of_range, current_rss, hard_limit_rss, soft_limit_rss) }
+ subject { memory_killer.send(:log_rss_out_of_range) }
it 'invoke sidekiq logger warn' do
- expect(memory_killer).to receive(:out_of_range_description).with(current_rss, hard_limit_rss, soft_limit_rss).and_return(reason)
+ expect(memory_killer).to receive(:out_of_range_description).with(current_rss, hard_limit_rss, soft_limit_rss, true).and_return(reason)
expect(Sidekiq.logger).to receive(:warn)
.with(
class: described_class.to_s,
@@ -365,9 +387,12 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
current_rss: current_rss,
hard_limit_rss: hard_limit_rss,
soft_limit_rss: soft_limit_rss,
- reason: reason)
+ reason: reason,
+ running_jobs: running_jobs)
- subject
+ Gitlab::SidekiqDaemon::Monitor.instance.within_job(DummyWorker, jid, queue) do
+ subject
+ end
end
end
@@ -375,8 +400,9 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
let(:hard_limit) { 300 }
let(:soft_limit) { 200 }
let(:grace_balloon_seconds) { 12 }
+ let(:deadline_exceeded) { true }
- subject { memory_killer.send(:out_of_range_description, rss, hard_limit, soft_limit) }
+ subject { memory_killer.send(:out_of_range_description, rss, hard_limit, soft_limit, deadline_exceeded) }
context 'when rss > hard_limit' do
let(:rss) { 400 }
@@ -389,9 +415,20 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
context 'when rss <= hard_limit' do
let(:rss) { 300 }
- it 'tells reason' do
- stub_const("#{described_class}::GRACE_BALLOON_SECONDS", grace_balloon_seconds)
- expect(subject).to eq("current_rss(#{rss}) > soft_limit_rss(#{soft_limit}) longer than GRACE_BALLOON_SECONDS(#{grace_balloon_seconds})")
+ context 'deadline exceeded' do
+ let(:deadline_exceeded) { true }
+
+ it 'tells reason' do
+ stub_const("#{described_class}::GRACE_BALLOON_SECONDS", grace_balloon_seconds)
+ expect(subject).to eq("current_rss(#{rss}) > soft_limit_rss(#{soft_limit}) longer than GRACE_BALLOON_SECONDS(#{grace_balloon_seconds})")
+ end
+ end
+ context 'deadline not exceeded' do
+ let(:deadline_exceeded) { false }
+
+ it 'tells reason' do
+ expect(subject).to eq("current_rss(#{rss}) > soft_limit_rss(#{soft_limit})")
+ end
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
index 7d31979a393..117b37ffda3 100644
--- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
@@ -169,6 +169,16 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
subject.call(worker, job, :test) { nil }
end
end
+
+ context 'when job is interrupted' do
+ let(:job) { { 'interrupted_count' => 1 } }
+
+ it 'sets sidekiq_jobs_interrupted_total metric' do
+ expect(interrupted_total_metric).to receive(:increment)
+
+ subject.call(worker, job, :test) { nil }
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/tracking/destinations/snowplow_micro_spec.rb b/spec/lib/gitlab/tracking/destinations/snowplow_micro_spec.rb
index 2b94eaa2db9..2554a15d97e 100644
--- a/spec/lib/gitlab/tracking/destinations/snowplow_micro_spec.rb
+++ b/spec/lib/gitlab/tracking/destinations/snowplow_micro_spec.rb
@@ -5,46 +5,83 @@ require 'spec_helper'
RSpec.describe Gitlab::Tracking::Destinations::SnowplowMicro do
include StubENV
+ let(:snowplow_micro_settings) do
+ {
+ enabled: true,
+ address: address
+ }
+ end
+
+ let(:address) { "gdk.test:9091" }
+
before do
- stub_application_setting(snowplow_enabled: true)
- stub_env('SNOWPLOW_MICRO_ENABLE', '1')
allow(Rails.env).to receive(:development?).and_return(true)
end
describe '#hostname' do
- context 'when SNOWPLOW_MICRO_URI is set' do
+ context 'when snowplow_micro config is set' do
+ let(:address) { '127.0.0.1:9091' }
+
before do
- stub_env('SNOWPLOW_MICRO_URI', 'http://gdk.test:9091')
+ stub_config(snowplow_micro: snowplow_micro_settings)
end
- it 'returns hostname URI part' do
- expect(subject.hostname).to eq('gdk.test:9091')
+ it 'returns proper URI' do
+ expect(subject.hostname).to eq('127.0.0.1:9091')
+ expect(subject.uri.scheme).to eq('http')
+ end
+
+ context 'when gitlab config has https scheme' do
+ before do
+ stub_config_setting(https: true)
+ end
+
+ it 'returns proper URI' do
+ expect(subject.hostname).to eq('127.0.0.1:9091')
+ expect(subject.uri.scheme).to eq('https')
+ end
end
end
- context 'when SNOWPLOW_MICRO_URI is without protocol' do
+ context 'when snowplow_micro config is not set' do
before do
- stub_env('SNOWPLOW_MICRO_URI', 'gdk.test:9091')
+ allow(Gitlab.config).to receive(:snowplow_micro).and_raise(Settingslogic::MissingSetting)
end
- it 'returns hostname URI part' do
- expect(subject.hostname).to eq('gdk.test:9091')
+ context 'when SNOWPLOW_MICRO_URI has scheme and port' do
+ before do
+ stub_env('SNOWPLOW_MICRO_URI', 'http://gdk.test:9091')
+ end
+
+ it 'returns hostname URI part' do
+ expect(subject.hostname).to eq('gdk.test:9091')
+ end
end
- end
- context 'when SNOWPLOW_MICRO_URI is hostname only' do
- before do
- stub_env('SNOWPLOW_MICRO_URI', 'uriwithoutport')
+ context 'when SNOWPLOW_MICRO_URI is without protocol' do
+ before do
+ stub_env('SNOWPLOW_MICRO_URI', 'gdk.test:9091')
+ end
+
+ it 'returns hostname URI part' do
+ expect(subject.hostname).to eq('gdk.test:9091')
+ end
end
- it 'returns hostname URI with default HTTP port' do
- expect(subject.hostname).to eq('uriwithoutport:80')
+ context 'when SNOWPLOW_MICRO_URI is hostname only' do
+ before do
+ stub_env('SNOWPLOW_MICRO_URI', 'uriwithoutport')
+ end
+
+ it 'returns hostname URI with default HTTP port' do
+ expect(subject.hostname).to eq('uriwithoutport:80')
+ end
end
- end
- context 'when SNOWPLOW_MICRO_URI is not set' do
- it 'returns localhost hostname' do
- expect(subject.hostname).to eq('localhost:9090')
+ context 'when SNOWPLOW_MICRO_URI is not set' do
+ it 'returns localhost hostname' do
+ expect(subject.hostname).to eq('localhost:9090')
+ end
end
end
end
@@ -53,7 +90,7 @@ RSpec.describe Gitlab::Tracking::Destinations::SnowplowMicro do
let_it_be(:group) { create :group }
before do
- stub_env('SNOWPLOW_MICRO_URI', 'http://gdk.test:9091')
+ stub_config(snowplow_micro: snowplow_micro_settings)
end
it 'includes protocol with the correct value' do
diff --git a/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb b/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb
index 06cc2d3800c..1d4725cf405 100644
--- a/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb
+++ b/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Tracking::Destinations::Snowplow do
+RSpec.describe Gitlab::Tracking::Destinations::Snowplow, :do_not_stub_snowplow_by_default do
let(:emitter) { SnowplowTracker::Emitter.new('localhost', buffer_size: 1) }
let(:tracker) { SnowplowTracker::Tracker.new(emitter, SnowplowTracker::Subject.new, 'namespace', 'app_id') }
diff --git a/spec/lib/gitlab/tracking/incident_management_spec.rb b/spec/lib/gitlab/tracking/incident_management_spec.rb
index fbcb9bf3e4c..ef7816aa0db 100644
--- a/spec/lib/gitlab/tracking/incident_management_spec.rb
+++ b/spec/lib/gitlab/tracking/incident_management_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Gitlab::Tracking::IncidentManagement do
described_class.track_from_params(params)
end
- context 'known params' do
+ context 'known params', :do_not_stub_snowplow_by_default do
known_params = described_class.tracking_keys
known_params.each do |key, values|
diff --git a/spec/lib/gitlab/tracking/standard_context_spec.rb b/spec/lib/gitlab/tracking/standard_context_spec.rb
index 508b33949a8..cfb83bc0528 100644
--- a/spec/lib/gitlab/tracking/standard_context_spec.rb
+++ b/spec/lib/gitlab/tracking/standard_context_spec.rb
@@ -93,30 +93,11 @@ RSpec.describe Gitlab::Tracking::StandardContext do
end
context 'with incorrect argument type' do
- context 'when standard_context_type_check FF is disabled' do
- before do
- stub_feature_flags(standard_context_type_check: false)
- end
-
- subject { described_class.new(project: create(:group)) }
-
- it 'does not call `track_and_raise_for_dev_exception`' do
- expect(Gitlab::ErrorTracking).not_to receive(:track_and_raise_for_dev_exception)
- snowplow_context
- end
- end
+ subject { described_class.new(project: create(:group)) }
- context 'when standard_context_type_check FF is enabled' do
- before do
- stub_feature_flags(standard_context_type_check: true)
- end
-
- subject { described_class.new(project: create(:group)) }
-
- it 'does call `track_and_raise_for_dev_exception`' do
- expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
- snowplow_context
- end
+ it 'does call `track_and_raise_for_dev_exception`' do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
+ snowplow_context
end
end
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index cc973be8be9..dd62c832f6f 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -10,11 +10,11 @@ RSpec.describe Gitlab::Tracking do
stub_application_setting(snowplow_cookie_domain: '.gitfoo.com')
stub_application_setting(snowplow_app_id: '_abc123_')
- described_class.instance_variable_set("@snowplow", nil)
+ described_class.instance_variable_set("@tracker", nil)
end
after do
- described_class.instance_variable_set("@snowplow", nil)
+ described_class.instance_variable_set("@tracker", nil)
end
describe '.options' do
@@ -34,6 +34,26 @@ RSpec.describe Gitlab::Tracking do
end
end
+ shared_examples 'delegates to SnowplowMicro destination with proper options' do
+ it_behaves_like 'delegates to destination', Gitlab::Tracking::Destinations::SnowplowMicro
+
+ it 'returns useful client options' do
+ expected_fields = {
+ namespace: 'gl',
+ hostname: 'localhost:9090',
+ cookieDomain: '.gitlab.com',
+ appId: '_abc123_',
+ protocol: 'http',
+ port: 9090,
+ forceSecureTracker: false,
+ formTracking: true,
+ linkClickTracking: true
+ }
+
+ expect(subject.options(nil)).to match(expected_fields)
+ end
+ end
+
context 'when destination is Snowplow' do
it_behaves_like 'delegates to destination', Gitlab::Tracking::Destinations::Snowplow
@@ -53,26 +73,31 @@ RSpec.describe Gitlab::Tracking do
context 'when destination is SnowplowMicro' do
before do
- stub_env('SNOWPLOW_MICRO_ENABLE', '1')
allow(Rails.env).to receive(:development?).and_return(true)
end
- it_behaves_like 'delegates to destination', Gitlab::Tracking::Destinations::SnowplowMicro
+ context "enabled with yml config" do
+ let(:snowplow_micro_settings) do
+ {
+ enabled: true,
+ address: "localhost:9090"
+ }
+ end
- it 'returns useful client options' do
- expected_fields = {
- namespace: 'gl',
- hostname: 'localhost:9090',
- cookieDomain: '.gitlab.com',
- appId: '_abc123_',
- protocol: 'http',
- port: 9090,
- forceSecureTracker: false,
- formTracking: true,
- linkClickTracking: true
- }
+ before do
+ stub_config(snowplow_micro: snowplow_micro_settings)
+ end
- expect(subject.options(nil)).to match(expected_fields)
+ it_behaves_like 'delegates to SnowplowMicro destination with proper options'
+ end
+
+ context "enabled with env variable" do
+ before do
+ allow(Gitlab.config).to receive(:snowplow_micro).and_raise(Settingslogic::MissingSetting)
+ stub_env('SNOWPLOW_MICRO_ENABLE', '1')
+ end
+
+ it_behaves_like 'delegates to SnowplowMicro destination with proper options'
end
end
diff --git a/spec/lib/gitlab/tree_summary_spec.rb b/spec/lib/gitlab/tree_summary_spec.rb
index 3021d92244e..f45005fcc9b 100644
--- a/spec/lib/gitlab/tree_summary_spec.rb
+++ b/spec/lib/gitlab/tree_summary_spec.rb
@@ -30,50 +30,31 @@ RSpec.describe Gitlab::TreeSummary do
describe '#summarize' do
let(:project) { create(:project, :custom_repo, files: { 'a.txt' => '' }) }
- subject(:summarized) { summary.summarize }
+ subject(:entries) { summary.summarize }
- it 'returns an array of entries, and an array of commits' do
- expect(summarized).to be_a(Array)
- expect(summarized.size).to eq(2)
+ it 'returns an array of entries' do
+ expect(entries).to be_a(Array)
+ expect(entries.size).to eq(1)
- entries, commits = *summarized
aggregate_failures do
expect(entries).to contain_exactly(
a_hash_including(file_name: 'a.txt', commit: have_attributes(id: commit.id))
)
- expect(commits).to match_array(entries.map { |entry| entry[:commit] })
- end
- end
-
- context 'when offset is over the limit' do
- let(:offset) { 100 }
-
- it 'returns an empty array' do
- expect(summarized).to eq([[], []])
+ expect(summary.resolved_commits.values).to match_array(entries.map { |entry| entry[:commit] })
end
end
context 'with caching', :use_clean_rails_memory_store_caching do
subject { Rails.cache.fetch(key) }
- context 'Repository tree cache' do
- let(:key) { ['projects', project.id, 'content', commit.id, path] }
-
- it 'creates a cache for repository content' do
- summarized
-
- is_expected.to eq([{ file_name: 'a.txt', type: :blob }])
- end
- end
-
context 'Commits list cache' do
let(:offset) { 0 }
let(:limit) { 25 }
- let(:key) { ['projects', project.id, 'last_commits', commit.id, path, offset, limit] }
+ let(:key) { ['projects', project.id, 'last_commits', commit.id, path, offset, limit + 1] }
it 'creates a cache for commits list' do
- summarized
+ entries
is_expected.to eq('a.txt' => commit.to_hash)
end
@@ -93,7 +74,7 @@ RSpec.describe Gitlab::TreeSummary do
let(:expected_message) { message[0...1021] + '...' }
it 'truncates commit message to 1 kilobyte' do
- summarized
+ entries
is_expected.to include('long.txt' => a_hash_including(message: expected_message))
end
@@ -102,7 +83,7 @@ RSpec.describe Gitlab::TreeSummary do
end
end
- describe '#summarize (entries)' do
+ describe '#fetch_logs' do
let(:limit) { 4 }
custom_files = {
@@ -116,33 +97,32 @@ RSpec.describe Gitlab::TreeSummary do
let!(:project) { create(:project, :custom_repo, files: custom_files) }
let(:commit) { repo.head_commit }
- subject(:entries) { summary.summarize.first }
+ subject(:entries) { summary.fetch_logs.first }
it 'summarizes the entries within the window' do
is_expected.to contain_exactly(
- a_hash_including(type: :tree, file_name: 'directory'),
- a_hash_including(type: :blob, file_name: 'a.txt'),
- a_hash_including(type: :blob, file_name: ':file'),
- a_hash_including(type: :tree, file_name: ':dir')
+ a_hash_including('file_name' => 'directory'),
+ a_hash_including('file_name' => 'a.txt'),
+ a_hash_including('file_name' => ':file'),
+ a_hash_including('file_name' => ':dir')
# b.txt is excluded by the limit
)
end
it 'references the commit and commit path in entries' do
# There are 2 trees and the summary is not ordered
- entry = entries.find { |entry| entry[:commit].id == commit.id }
+ entry = entries.find { |entry| entry['commit']['id'] == commit.id }
expected_commit_path = Gitlab::Routing.url_helpers.project_commit_path(project, commit)
- expect(entry[:commit]).to be_a(::Commit)
- expect(entry[:commit_path]).to eq(expected_commit_path)
- expect(entry[:commit_title_html]).to eq(commit.message)
+ expect(entry['commit_path']).to eq(expected_commit_path)
+ expect(entry['commit_title_html']).to eq(commit.message)
end
context 'in a good subdirectory' do
let(:path) { 'directory' }
it 'summarizes the entries in the subdirectory' do
- is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'c.txt'))
+ is_expected.to contain_exactly(a_hash_including('file_name' => 'c.txt'))
end
end
@@ -150,7 +130,7 @@ RSpec.describe Gitlab::TreeSummary do
let(:path) { ':dir' }
it 'summarizes the entries in the subdirectory' do
- is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'test.txt'))
+ is_expected.to contain_exactly(a_hash_including('file_name' => 'test.txt'))
end
end
@@ -164,7 +144,25 @@ RSpec.describe Gitlab::TreeSummary do
let(:offset) { 4 }
it 'returns entries from the offset' do
- is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'b.txt'))
+ is_expected.to contain_exactly(a_hash_including('file_name' => 'b.txt'))
+ end
+ end
+
+ context 'next offset' do
+ subject { summary.fetch_logs.last }
+
+ context 'when there are more entries to fetch' do
+ it 'returns next offset' do
+ is_expected.to eq(4)
+ end
+ end
+
+ context 'when there are no more entries to fetch' do
+ let(:limit) { 5 }
+
+ it 'returns next offset' do
+ is_expected.to be_nil
+ end
end
end
end
@@ -178,10 +176,11 @@ RSpec.describe Gitlab::TreeSummary do
let(:project) { create(:project, :repository) }
let(:commit) { repo.commit(test_commit_sha) }
let(:limit) { nil }
- let(:entries) { summary.summarize.first }
+ let(:entries) { summary.summarize }
subject(:commits) do
- summary.summarize.last
+ summary.summarize
+ summary.resolved_commits.values
end
it 'returns an Array of ::Commit objects' do
@@ -227,7 +226,7 @@ RSpec.describe Gitlab::TreeSummary do
let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:issue) { create(:issue, project: project) }
- let(:entries) { summary.summarize.first }
+ let(:entries) { summary.summarize }
let(:entry) { entries.find { |entry| entry[:file_name] == 'issue.txt' } }
before_all do
@@ -264,67 +263,6 @@ RSpec.describe Gitlab::TreeSummary do
end
end
- describe '#more?' do
- let(:path) { 'tmp/more' }
-
- where(:num_entries, :offset, :limit, :expected_result) do
- 0 | 0 | 0 | false
- 0 | 0 | 1 | false
-
- 1 | 0 | 0 | true
- 1 | 0 | 1 | false
- 1 | 1 | 0 | false
- 1 | 1 | 1 | false
-
- 2 | 0 | 0 | true
- 2 | 0 | 1 | true
- 2 | 0 | 2 | false
- 2 | 0 | 3 | false
- 2 | 1 | 0 | true
- 2 | 1 | 1 | false
- 2 | 2 | 0 | false
- 2 | 2 | 1 | false
- end
-
- with_them do
- before do
- create_file('dummy', path: 'other') if num_entries == 0
- 1.upto(num_entries) { |n| create_file(n, path: path) }
- end
-
- subject { summary.more? }
-
- it { is_expected.to eq(expected_result) }
- end
- end
-
- describe '#next_offset' do
- let(:path) { 'tmp/next_offset' }
-
- where(:num_entries, :offset, :limit, :expected_result) do
- 0 | 0 | 0 | 0
- 0 | 0 | 1 | 1
- 0 | 1 | 0 | 1
- 0 | 1 | 1 | 1
-
- 1 | 0 | 0 | 0
- 1 | 0 | 1 | 1
- 1 | 1 | 0 | 1
- 1 | 1 | 1 | 2
- end
-
- with_them do
- before do
- create_file('dummy', path: 'other') if num_entries == 0
- 1.upto(num_entries) { |n| create_file(n, path: path) }
- end
-
- subject { summary.next_offset }
-
- it { is_expected.to eq(expected_result) }
- end
- end
-
def create_file(unique, path:)
repo.create_file(
project.creator,
diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb
index 070586319a5..a1bddcb3a47 100644
--- a/spec/lib/gitlab/usage/metric_definition_spec.rb
+++ b/spec/lib/gitlab/usage/metric_definition_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
milestone: '14.1',
default_generation: 'generation_1',
key_path: 'uuid',
- product_group: 'group::product analytics',
+ product_group: 'product_analytics',
time_frame: 'none',
data_source: 'database',
distribution: %w(ee ce),
@@ -270,7 +270,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
milestone: '14.1',
default_generation: 'generation_1',
key_path: 'counter.category.event',
- product_group: 'group::product analytics',
+ product_group: 'product_analytics',
time_frame: 'none',
data_source: 'database',
distribution: %w(ee ce),
diff --git a/spec/lib/gitlab/usage/metric_spec.rb b/spec/lib/gitlab/usage/metric_spec.rb
index 10ae94e746b..8e0fce37e46 100644
--- a/spec/lib/gitlab/usage/metric_spec.rb
+++ b/spec/lib/gitlab/usage/metric_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Gitlab::Usage::Metric do
description: "Count of Issues created",
product_section: "dev",
product_stage: "plan",
- product_group: "group::plan",
+ product_group: "plan",
product_category: "issue_tracking",
value_type: "number",
status: "active",
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/unique_active_users_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/unique_active_users_metric_spec.rb
deleted file mode 100644
index 8a0ce61de74..00000000000
--- a/spec/lib/gitlab/usage/metrics/instrumentations/unique_active_users_metric_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Usage::Metrics::Instrumentations::UniqueActiveUsersMetric do
- let_it_be(:user1) { create(:user, last_activity_on: 1.day.ago) }
- let_it_be(:user2) { create(:user, last_activity_on: 5.days.ago) }
- let_it_be(:user3) { create(:user, last_activity_on: 50.days.ago) }
- let_it_be(:user4) { create(:user) }
- let_it_be(:user5) { create(:user, user_type: 1, last_activity_on: 5.days.ago ) } # support bot
- let_it_be(:user6) { create(:user, state: 'blocked') }
-
- context '28d' do
- let(:start) { 30.days.ago.to_date.to_s }
- let(:finish) { 2.days.ago.to_date.to_s }
- let(:expected_value) { 1 }
- let(:expected_query) do
- "SELECT COUNT(\"users\".\"id\") FROM \"users\" WHERE (\"users\".\"state\" IN ('active')) AND " \
- "(\"users\".\"user_type\" IS NULL OR \"users\".\"user_type\" IN (6, 4)) AND \"users\".\"last_activity_on\" " \
- "BETWEEN '#{start}' AND '#{finish}'"
- end
-
- it_behaves_like 'a correct instrumented metric value and query', { time_frame: '28d' }
- end
-
- context 'all' do
- let(:expected_value) { 4 }
-
- it_behaves_like 'a correct instrumented metric value', { time_frame: 'all' }
- end
-end
diff --git a/spec/lib/gitlab/usage/service_ping/instrumented_payload_spec.rb b/spec/lib/gitlab/usage/service_ping/instrumented_payload_spec.rb
index 76548483cfa..9d2711c49c6 100644
--- a/spec/lib/gitlab/usage/service_ping/instrumented_payload_spec.rb
+++ b/spec/lib/gitlab/usage/service_ping/instrumented_payload_spec.rb
@@ -46,4 +46,54 @@ RSpec.describe Gitlab::Usage::ServicePing::InstrumentedPayload do
expect(described_class.new(['counts.ci_builds'], :with_value).build).to eq({})
end
end
+
+ context 'with broken metric definition file' do
+ let(:key_path) { 'counts.broken_metric_definition_test' }
+ let(:definitions) { [Gitlab::Usage::MetricDefinition.new(key_path, key_path: key_path)] }
+
+ subject(:build_metric) { described_class.new([key_path], :with_value).build }
+
+ before do
+ allow(Gitlab::Usage::MetricDefinition).to receive(:with_instrumentation_class).and_return(definitions)
+ allow_next_instance_of(Gitlab::Usage::Metric) do |instance|
+ allow(instance).to receive(:with_value).and_raise(error)
+ end
+ end
+
+ context 'when instrumentation class name is incorrect' do
+ let(:error) { NameError.new("uninitialized constant Gitlab::Usage::Metrics::Instrumentations::IDontExists") }
+
+ it 'tracks error and return fallback', :aggregate_failures do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(error)
+ expect(build_metric).to eql(counts: { broken_metric_definition_test: -1 })
+ end
+ end
+
+ context 'when instrumentation class raises TypeError' do
+ let(:error) { TypeError.new("nil can't be coerced into BigDecimal") }
+
+ it 'tracks error and return fallback', :aggregate_failures do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(error)
+ expect(build_metric).to eql(counts: { broken_metric_definition_test: -1 })
+ end
+ end
+
+ context 'when instrumentation class raises ArgumentError' do
+ let(:error) { ArgumentError.new("wrong number of arguments (given 2, expected 0)") }
+
+ it 'tracks error and return fallback', :aggregate_failures do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(error)
+ expect(build_metric).to eql(counts: { broken_metric_definition_test: -1 })
+ end
+ end
+
+ context 'when instrumentation class raises StandardError' do
+ let(:error) { StandardError.new("something went very wrong") }
+
+ it 'tracks error and return fallback', :aggregate_failures do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(error)
+ expect(build_metric).to eql(counts: { broken_metric_definition_test: -1 })
+ end
+ end
+ 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 dbc34681660..bfbabd858f0 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
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
let(:user1) { build(:user, id: 1) }
let(:user2) { build(:user, id: 2) }
let(:user3) { build(:user, id: 3) }
+ let(:project) { build(:project) }
let(:time) { Time.zone.now }
shared_examples 'tracks and counts action' do
@@ -15,10 +16,9 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
specify do
aggregate_failures do
- expect(track_action(author: user1)).to be_truthy
- expect(track_action(author: user1)).to be_truthy
- expect(track_action(author: user2)).to be_truthy
- expect(track_action(author: user3, time: time - 3.days)).to be_truthy
+ expect(track_action(author: user1, project: project)).to be_truthy
+ expect(track_action(author: user2, project: project)).to be_truthy
+ expect(track_action(author: user3, time: time - 3.days, project: project)).to be_truthy
expect(count_unique(date_from: time, date_to: Date.today)).to eq(2)
expect(count_unique(date_from: time - 5.days, date_to: Date.tomorrow)).to eq(3)
@@ -26,7 +26,7 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
end
it 'does not track edit actions if author is not present' do
- expect(track_action(author: nil)).to be_nil
+ expect(track_action(author: nil, project: project)).to be_nil
end
end
@@ -67,16 +67,16 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
end
it 'can return the count of actions per user deduplicated' do
- described_class.track_web_ide_edit_action(author: user1)
- described_class.track_live_preview_edit_action(author: user1)
- described_class.track_snippet_editor_edit_action(author: user1)
- described_class.track_sfe_edit_action(author: user1)
- described_class.track_web_ide_edit_action(author: user2, time: time - 2.days)
- described_class.track_web_ide_edit_action(author: user3, time: time - 3.days)
- described_class.track_live_preview_edit_action(author: user2, time: time - 2.days)
- described_class.track_live_preview_edit_action(author: user3, time: time - 3.days)
- described_class.track_snippet_editor_edit_action(author: user3, time: time - 3.days)
- described_class.track_sfe_edit_action(author: user3, time: time - 3.days)
+ described_class.track_web_ide_edit_action(author: user1, project: project)
+ described_class.track_live_preview_edit_action(author: user1, project: project)
+ described_class.track_snippet_editor_edit_action(author: user1, project: project)
+ described_class.track_sfe_edit_action(author: user1, project: project)
+ described_class.track_web_ide_edit_action(author: user2, time: time - 2.days, project: project)
+ described_class.track_web_ide_edit_action(author: user3, time: time - 3.days, project: project)
+ described_class.track_live_preview_edit_action(author: user2, time: time - 2.days, project: project)
+ described_class.track_live_preview_edit_action(author: user3, time: time - 3.days, project: project)
+ described_class.track_snippet_editor_edit_action(author: user3, time: time - 3.days, project: project)
+ described_class.track_sfe_edit_action(author: user3, time: time - 3.days, project: project)
expect(described_class.count_edit_using_editor(date_from: time, date_to: Date.today)).to eq(1)
expect(described_class.count_edit_using_editor(date_from: time - 5.days, date_to: Date.tomorrow)).to eq(3)
diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
index 77cf94daa3f..54d49b432f4 100644
--- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
@@ -19,6 +19,82 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
# Monday 6th of June
reference_time = Time.utc(2020, 6, 1)
travel_to(reference_time) { example.run }
+ described_class.clear_memoization(:known_events)
+ end
+
+ context 'migration to instrumentation classes data collection' do
+ let_it_be(:instrumented_events) do
+ ::Gitlab::Usage::MetricDefinition.all.map do |definition|
+ next unless definition.attributes[:instrumentation_class] == 'RedisHLLMetric' && definition.available?
+
+ definition.attributes.dig(:options, :events)&.sort
+ end.compact.to_set
+ end
+
+ def not_instrumented_events(category)
+ described_class
+ .events_for_category(category)
+ .sort
+ .reject do |event|
+ instrumented_events.include?([event])
+ end
+ end
+
+ def not_instrumented_aggregate(category)
+ events = described_class.events_for_category(category).sort
+
+ return unless described_class::CATEGORIES_FOR_TOTALS.include?(category)
+ return unless described_class.send(:eligible_for_totals?, events)
+ return if instrumented_events.include?(events)
+
+ events
+ end
+
+ describe 'Gitlab::UsageDataCounters::HLLRedisCounter::CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS' do
+ it 'includes only fully migrated categories' do
+ wrong_skipped_events = described_class::CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS.map do |category|
+ next if not_instrumented_events(category).empty? && not_instrumented_aggregate(category).nil?
+
+ [category, [not_instrumented_events(category), not_instrumented_aggregate(category)].compact]
+ end.compact.to_h
+
+ expect(wrong_skipped_events).to be_empty
+ end
+
+ context 'with not instrumented category' do
+ let(:instrumented_events) { [] }
+
+ it 'can detect not migrated category' do
+ wrong_skipped_events = described_class::CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS.map do |category|
+ next if not_instrumented_events(category).empty? && not_instrumented_aggregate(category).nil?
+
+ [category, [not_instrumented_events(category), not_instrumented_aggregate(category)].compact]
+ end.compact.to_h
+
+ expect(wrong_skipped_events).not_to be_empty
+ end
+ end
+ end
+
+ describe '.unique_events_data' do
+ context 'with use_redis_hll_instrumentation_classes feature enabled' do
+ it 'does not include instrumented categories' do
+ stub_feature_flags(use_redis_hll_instrumentation_classes: true)
+
+ expect(described_class.unique_events_data.keys)
+ .not_to include(*described_class::CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS)
+ end
+ end
+
+ context 'with use_redis_hll_instrumentation_classes feature disabled' do
+ it 'includes instrumented categories' do
+ stub_feature_flags(use_redis_hll_instrumentation_classes: false)
+
+ expect(described_class.unique_events_data.keys)
+ .to include(*described_class::CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS)
+ end
+ end
+ end
end
describe '.categories' do
@@ -53,11 +129,40 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'growth',
'work_items',
'ci_users',
- 'error_tracking'
+ 'error_tracking',
+ 'manage'
)
end
end
+ describe '.known_events' do
+ let(:ce_temp_dir) { Dir.mktmpdir }
+ let(:ce_temp_file) { Tempfile.new(%w[common .yml], ce_temp_dir) }
+ let(:ce_event) do
+ {
+ "name" => "ce_event",
+ "redis_slot" => "analytics",
+ "category" => "analytics",
+ "expiry" => 84,
+ "aggregation" => "weekly"
+ }
+ end
+
+ before do
+ stub_const("#{described_class}::KNOWN_EVENTS_PATH", File.expand_path('*.yml', ce_temp_dir))
+ File.open(ce_temp_file.path, "w+b") { |f| f.write [ce_event].to_yaml }
+ end
+
+ it 'returns ce events' do
+ expect(described_class.known_events).to include(ce_event)
+ end
+
+ after do
+ ce_temp_file.unlink
+ FileUtils.remove_entry(ce_temp_dir) if Dir.exist?(ce_temp_dir)
+ end
+ end
+
describe 'known_events' do
let(:feature) { 'test_hll_redis_counter_ff_check' }
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 cd3388701fe..3f44cfdcf27 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
@@ -82,11 +82,43 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl
end
describe '.track_approve_mr_action' do
- subject { described_class.track_approve_mr_action(user: user) }
+ include ProjectForksHelper
+
+ let(:merge_request) { create(:merge_request, target_project: target_project, source_project: source_project) }
+ let(:source_project) { fork_project(target_project) }
+ let(:target_project) { create(:project) }
+
+ subject { described_class.track_approve_mr_action(user: user, merge_request: merge_request) }
it_behaves_like 'a tracked merge request unique event' do
let(:action) { described_class::MR_APPROVE_ACTION }
end
+
+ it 'records correct payload with Snowplow event', :snowplow do
+ stub_feature_flags(route_hll_to_snowplow_phase2: true)
+
+ subject
+
+ expect_snowplow_event(
+ category: 'merge_requests',
+ action: 'i_code_review_user_approve_mr',
+ namespace: target_project.namespace,
+ user: user,
+ project: target_project
+ )
+ end
+
+ context 'when FF is disabled' do
+ before do
+ stub_feature_flags(route_hll_to_snowplow_phase2: false)
+ end
+
+ it 'doesnt emit snowplow events', :snowplow do
+ subject
+
+ expect_no_snowplow_event
+ end
+ end
end
describe '.track_unapprove_mr_action' do
diff --git a/spec/lib/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb
index 4561d898479..0264236f087 100644
--- a/spec/lib/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb
@@ -5,46 +5,6 @@ require 'spec_helper'
RSpec.describe Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter, :clean_gitlab_redis_shared_state do
let(:user) { build(:user, id: 1) }
- shared_examples 'counter that does not track the event' do
- it 'does not track the event' do
- expect { 3.times { track_event } }.to not_change {
- Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(
- event_names: event_name,
- start_date: 2.weeks.ago,
- end_date: 2.weeks.from_now
- )
- }
- end
- end
-
- shared_examples 'work item unique counter' do
- context 'when track_work_items_activity FF is enabled' do
- it 'tracks a unique event only once' do
- expect { 3.times { track_event } }.to change {
- Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(
- event_names: event_name,
- start_date: 2.weeks.ago,
- end_date: 2.weeks.from_now
- )
- }.by(1)
- end
-
- context 'when author is nil' do
- let(:user) { nil }
-
- it_behaves_like 'counter that does not track the event'
- end
- end
-
- context 'when track_work_items_activity FF is disabled' do
- before do
- stub_feature_flags(track_work_items_activity: false)
- end
-
- it_behaves_like 'counter that does not track the event'
- end
- end
-
describe '.track_work_item_created_action' do
subject(:track_event) { described_class.track_work_item_created_action(author: user) }
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 790f5b638b9..6eb00053b17 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -249,7 +249,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
)
end
- it 'includes imports usage data' do
+ it 'includes imports usage data', :clean_gitlab_redis_cache do
for_defined_days_back do
user = create(:user)
@@ -347,7 +347,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
cluster = create(:cluster, user: user)
project = create(:project, creator: user)
create(:clusters_integrations_prometheus, cluster: cluster)
- create(:project_tracing_setting)
create(:project_error_tracking_setting)
create(:incident)
create(:incident, alert_management_alert: create(:alert_management_alert))
@@ -358,7 +357,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
clusters: 2,
clusters_integrations_prometheus: 2,
operations_dashboard_default_dashboard: 2,
- projects_with_tracing_enabled: 2,
projects_with_error_tracking_enabled: 2,
projects_with_incidents: 4,
projects_with_alert_incidents: 2,
@@ -370,7 +368,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
clusters: 1,
clusters_integrations_prometheus: 1,
operations_dashboard_default_dashboard: 1,
- projects_with_tracing_enabled: 1,
projects_with_error_tracking_enabled: 1,
projects_with_incidents: 2,
projects_with_alert_incidents: 1
@@ -535,7 +532,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(count_data[:groups_inheriting_slack_active]).to eq(1)
expect(count_data[:projects_with_repositories_enabled]).to eq(3)
expect(count_data[:projects_with_error_tracking_enabled]).to eq(1)
- expect(count_data[:projects_with_tracing_enabled]).to eq(1)
expect(count_data[:projects_with_enabled_alert_integrations]).to eq(1)
expect(count_data[:projects_with_terraform_reports]).to eq(2)
expect(count_data[:projects_with_terraform_states]).to eq(2)
@@ -564,7 +560,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(count_data[:clusters_platforms_eks]).to eq(1)
expect(count_data[:clusters_platforms_gke]).to eq(1)
expect(count_data[:clusters_platforms_user]).to eq(1)
- expect(count_data[:clusters_integrations_elastic_stack]).to eq(1)
expect(count_data[:clusters_integrations_prometheus]).to eq(1)
expect(count_data[:grafana_integrated_projects]).to eq(2)
expect(count_data[:clusters_management_project]).to eq(1)
@@ -1157,35 +1152,36 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
let(:user2) { build(:user, id: 2) }
let(:user3) { build(:user, id: 3) }
let(:user4) { build(:user, id: 4) }
+ let(:project) { build(:project) }
before do
counter = Gitlab::UsageDataCounters::TrackUniqueEvents
- project = Event::TARGET_TYPES[:project]
+ project_type = Event::TARGET_TYPES[:project]
wiki = Event::TARGET_TYPES[:wiki]
design = Event::TARGET_TYPES[:design]
- counter.track_event(event_action: :pushed, event_target: project, author_id: 1)
- counter.track_event(event_action: :pushed, event_target: project, author_id: 1)
- counter.track_event(event_action: :pushed, event_target: project, author_id: 2)
- counter.track_event(event_action: :pushed, event_target: project, author_id: 3)
- counter.track_event(event_action: :pushed, event_target: project, author_id: 4, time: time - 3.days)
+ counter.track_event(event_action: :pushed, event_target: project_type, author_id: 1)
+ counter.track_event(event_action: :pushed, event_target: project_type, author_id: 1)
+ counter.track_event(event_action: :pushed, event_target: project_type, author_id: 2)
+ counter.track_event(event_action: :pushed, event_target: project_type, author_id: 3)
+ counter.track_event(event_action: :pushed, event_target: project_type, author_id: 4, time: time - 3.days)
counter.track_event(event_action: :created, event_target: wiki, author_id: 3)
counter.track_event(event_action: :created, event_target: design, author_id: 3)
counter.track_event(event_action: :created, event_target: design, author_id: 4)
counter = Gitlab::UsageDataCounters::EditorUniqueCounter
- counter.track_web_ide_edit_action(author: user1)
- counter.track_web_ide_edit_action(author: user1)
- counter.track_sfe_edit_action(author: user1)
- counter.track_snippet_editor_edit_action(author: user1)
- counter.track_snippet_editor_edit_action(author: user1, time: time - 3.days)
+ counter.track_web_ide_edit_action(author: user1, project: project)
+ counter.track_web_ide_edit_action(author: user1, project: project)
+ counter.track_sfe_edit_action(author: user1, project: project)
+ counter.track_snippet_editor_edit_action(author: user1, project: project)
+ counter.track_snippet_editor_edit_action(author: user1, time: time - 3.days, project: project)
- counter.track_web_ide_edit_action(author: user2)
- counter.track_sfe_edit_action(author: user2)
+ counter.track_web_ide_edit_action(author: user2, project: project)
+ counter.track_sfe_edit_action(author: user2, project: project)
- counter.track_web_ide_edit_action(author: user3, time: time - 3.days)
- counter.track_snippet_editor_edit_action(author: user3)
+ counter.track_web_ide_edit_action(author: user3, time: time - 3.days, project: project)
+ counter.track_snippet_editor_edit_action(author: user3, project: project)
end
it 'returns the distinct count of user actions within the specified time period' do
@@ -1212,6 +1208,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
let(:ignored_metrics) { ["i_package_composer_deploy_token_weekly"] }
it 'has all known_events' do
+ stub_feature_flags(use_redis_hll_instrumentation_classes: false)
expect(subject).to have_key(:redis_hll_counters)
expect(subject[:redis_hll_counters].keys).to match_array(categories)
@@ -1312,8 +1309,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
"in_product_marketing_email_team_1_sent" => -1,
"in_product_marketing_email_team_1_cta_clicked" => -1,
"in_product_marketing_email_team_2_sent" => -1,
- "in_product_marketing_email_team_2_cta_clicked" => -1,
- "in_product_marketing_email_experience_0_sent" => -1
+ "in_product_marketing_email_team_2_cta_clicked" => -1
}
expect(subject).to eq(expected_data)
@@ -1358,8 +1354,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
"in_product_marketing_email_team_1_sent" => 0,
"in_product_marketing_email_team_1_cta_clicked" => 0,
"in_product_marketing_email_team_2_sent" => 0,
- "in_product_marketing_email_team_2_cta_clicked" => 0,
- "in_product_marketing_email_experience_0_sent" => 0
+ "in_product_marketing_email_team_2_cta_clicked" => 0
}
expect(subject).to eq(expected_data)
@@ -1368,29 +1363,11 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
describe ".with_duration" do
- context 'with feature flag measure_service_ping_metric_collection turned off' do
- before do
- stub_feature_flags(measure_service_ping_metric_collection: false)
- end
-
- it 'does NOT record duration and return block response' do
- expect(::Gitlab::Usage::ServicePing::LegacyMetricTimingDecorator).not_to receive(:new)
-
- expect(described_class.with_duration { 1 + 1 }).to be 2
- end
- end
+ it 'records duration' do
+ expect(::Gitlab::Usage::ServicePing::LegacyMetricTimingDecorator)
+ .to receive(:new).with(2, kind_of(Float))
- context 'with feature flag measure_service_ping_metric_collection turned off' do
- before do
- stub_feature_flags(measure_service_ping_metric_collection: true)
- end
-
- it 'records duration' do
- expect(::Gitlab::Usage::ServicePing::LegacyMetricTimingDecorator)
- .to receive(:new).with(2, kind_of(Float))
-
- described_class.with_duration { 1 + 1 }
- end
+ described_class.with_duration { 1 + 1 }
end
end
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index b1de3e21b77..1ae45d41f2d 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -219,19 +219,19 @@ RSpec.describe Gitlab::UserAccess do
describe '#can_create_tag?' do
describe 'push to none protected tag' do
it 'returns true if user is a maintainer' do
- project.add_user(user, :maintainer)
+ project.add_member(user, :maintainer)
expect(access.can_create_tag?('random_tag')).to be_truthy
end
it 'returns true if user is a developer' do
- project.add_user(user, :developer)
+ project.add_member(user, :developer)
expect(access.can_create_tag?('random_tag')).to be_truthy
end
it 'returns false if user is a reporter' do
- project.add_user(user, :reporter)
+ project.add_member(user, :reporter)
expect(access.can_create_tag?('random_tag')).to be_falsey
end
@@ -242,19 +242,19 @@ RSpec.describe Gitlab::UserAccess do
let(:not_existing_tag) { create :protected_tag, project: project }
it 'returns true if user is a maintainer' do
- project.add_user(user, :maintainer)
+ project.add_member(user, :maintainer)
expect(access.can_create_tag?(tag.name)).to be_truthy
end
it 'returns false if user is a developer' do
- project.add_user(user, :developer)
+ project.add_member(user, :developer)
expect(access.can_create_tag?(tag.name)).to be_falsey
end
it 'returns false if user is a reporter' do
- project.add_user(user, :reporter)
+ project.add_member(user, :reporter)
expect(access.can_create_tag?(tag.name)).to be_falsey
end
@@ -266,19 +266,19 @@ RSpec.describe Gitlab::UserAccess do
end
it 'returns true if user is a maintainer' do
- project.add_user(user, :maintainer)
+ project.add_member(user, :maintainer)
expect(access.can_create_tag?(@tag.name)).to be_truthy
end
it 'returns true if user is a developer' do
- project.add_user(user, :developer)
+ project.add_member(user, :developer)
expect(access.can_create_tag?(@tag.name)).to be_truthy
end
it 'returns false if user is a reporter' do
- project.add_user(user, :reporter)
+ project.add_member(user, :reporter)
expect(access.can_create_tag?(@tag.name)).to be_falsey
end
@@ -288,19 +288,19 @@ RSpec.describe Gitlab::UserAccess do
describe '#can_delete_branch?' do
describe 'delete unprotected branch' do
it 'returns true if user is a maintainer' do
- project.add_user(user, :maintainer)
+ project.add_member(user, :maintainer)
expect(access.can_delete_branch?('random_branch')).to be_truthy
end
it 'returns true if user is a developer' do
- project.add_user(user, :developer)
+ project.add_member(user, :developer)
expect(access.can_delete_branch?('random_branch')).to be_truthy
end
it 'returns false if user is a reporter' do
- project.add_user(user, :reporter)
+ project.add_member(user, :reporter)
expect(access.can_delete_branch?('random_branch')).to be_falsey
end
@@ -310,19 +310,19 @@ RSpec.describe Gitlab::UserAccess do
let(:branch) { create(:protected_branch, project: project, name: "test") }
it 'returns true if user is a maintainer' do
- project.add_user(user, :maintainer)
+ project.add_member(user, :maintainer)
expect(access.can_delete_branch?(branch.name)).to be_truthy
end
it 'returns false if user is a developer' do
- project.add_user(user, :developer)
+ project.add_member(user, :developer)
expect(access.can_delete_branch?(branch.name)).to be_falsey
end
it 'returns false if user is a reporter' do
- project.add_user(user, :reporter)
+ project.add_member(user, :reporter)
expect(access.can_delete_branch?(branch.name)).to be_falsey
end
@@ -334,7 +334,7 @@ RSpec.describe Gitlab::UserAccess do
context 'when user cannot push_code to a project repository (eg. as a guest)' do
it 'is false' do
- project.add_user(user, :guest)
+ project.add_member(user, :guest)
expect(access.can_push_for_ref?(ref)).to be_falsey
end
@@ -342,7 +342,7 @@ RSpec.describe Gitlab::UserAccess do
context 'when user can push_code to a project repository (eg. as a developer)' do
it 'is true' do
- project.add_user(user, :developer)
+ project.add_member(user, :developer)
expect(access.can_push_for_ref?(ref)).to be_truthy
end
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index f81e3aa070a..6ed094f11c8 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -1,73 +1,170 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
-RSpec.describe 'Gitlab::VersionInfo' do
+RSpec.describe Gitlab::VersionInfo do
before do
- @unknown = Gitlab::VersionInfo.new
- @v0_0_1 = Gitlab::VersionInfo.new(0, 0, 1)
- @v0_1_0 = Gitlab::VersionInfo.new(0, 1, 0)
- @v1_0_0 = Gitlab::VersionInfo.new(1, 0, 0)
- @v1_0_1 = Gitlab::VersionInfo.new(1, 0, 1)
- @v1_1_0 = Gitlab::VersionInfo.new(1, 1, 0)
- @v2_0_0 = Gitlab::VersionInfo.new(2, 0, 0)
+ @unknown = described_class.new
+ @v0_0_1 = described_class.new(0, 0, 1)
+ @v0_1_0 = described_class.new(0, 1, 0)
+ @v1_0_0 = described_class.new(1, 0, 0)
+ @v1_0_1 = described_class.new(1, 0, 1)
+ @v1_0_1_b1 = described_class.new(1, 0, 1, '-b1')
+ @v1_0_1_rc1 = described_class.new(1, 0, 1, '-rc1')
+ @v1_0_1_rc2 = described_class.new(1, 0, 1, '-rc2')
+ @v1_1_0 = described_class.new(1, 1, 0)
+ @v1_1_0_beta1 = described_class.new(1, 1, 0, '-beta1')
+ @v2_0_0 = described_class.new(2, 0, 0)
+ @v13_10_1_1574_89 = described_class.parse("v13.10.1~beta.1574.gf6ea9389", parse_suffix: true)
+ @v13_10_1_1575_89 = described_class.parse("v13.10.1~beta.1575.gf6ea9389", parse_suffix: true)
+ @v13_10_1_1575_90 = described_class.parse("v13.10.1~beta.1575.gf6ea9390", parse_suffix: true)
end
- context '>' do
+ describe '>' do
it { expect(@v2_0_0).to be > @v1_1_0 }
it { expect(@v1_1_0).to be > @v1_0_1 }
+ it { expect(@v1_0_1_b1).to be > @v1_0_0 }
+ it { expect(@v1_0_1_rc1).to be > @v1_0_0 }
+ it { expect(@v1_0_1_rc1).to be > @v1_0_1_b1 }
+ it { expect(@v1_0_1_rc2).to be > @v1_0_1_rc1 }
+ it { expect(@v1_0_1).to be > @v1_0_1_rc1 }
+ it { expect(@v1_0_1).to be > @v1_0_1_rc2 }
it { expect(@v1_0_1).to be > @v1_0_0 }
it { expect(@v1_0_0).to be > @v0_1_0 }
+ it { expect(@v1_1_0_beta1).to be > @v1_0_1_rc2 }
+ it { expect(@v1_1_0).to be > @v1_1_0_beta1 }
it { expect(@v0_1_0).to be > @v0_0_1 }
end
- context '>=' do
- it { expect(@v2_0_0).to be >= Gitlab::VersionInfo.new(2, 0, 0) }
+ describe '>=' do
+ it { expect(@v2_0_0).to be >= described_class.new(2, 0, 0) }
it { expect(@v2_0_0).to be >= @v1_1_0 }
+ it { expect(@v1_0_1_rc2).to be >= @v1_0_1_rc1 }
end
- context '<' do
+ describe '<' do
it { expect(@v0_0_1).to be < @v0_1_0 }
it { expect(@v0_1_0).to be < @v1_0_0 }
it { expect(@v1_0_0).to be < @v1_0_1 }
it { expect(@v1_0_1).to be < @v1_1_0 }
+ it { expect(@v1_0_0).to be < @v1_0_1_rc2 }
+ it { expect(@v1_0_1_rc1).to be < @v1_0_1 }
+ it { expect(@v1_0_1_rc1).to be < @v1_0_1_rc2 }
+ it { expect(@v1_0_1_rc2).to be < @v1_0_1 }
it { expect(@v1_1_0).to be < @v2_0_0 }
+ it { expect(@v13_10_1_1574_89).to be < @v13_10_1_1575_89 }
+ it { expect(@v13_10_1_1575_89).to be < @v13_10_1_1575_90 }
end
- context '<=' do
- it { expect(@v0_0_1).to be <= Gitlab::VersionInfo.new(0, 0, 1) }
+ describe '<=' do
+ it { expect(@v0_0_1).to be <= described_class.new(0, 0, 1) }
it { expect(@v0_0_1).to be <= @v0_1_0 }
+ it { expect(@v1_0_1_b1).to be <= @v1_0_1_rc1 }
+ it { expect(@v1_0_1_rc1).to be <= @v1_0_1_rc2 }
+ it { expect(@v1_1_0_beta1).to be <= @v1_1_0 }
end
- context '==' do
- it { expect(@v0_0_1).to eq(Gitlab::VersionInfo.new(0, 0, 1)) }
- it { expect(@v0_1_0).to eq(Gitlab::VersionInfo.new(0, 1, 0)) }
- it { expect(@v1_0_0).to eq(Gitlab::VersionInfo.new(1, 0, 0)) }
+ describe '==' do
+ it { expect(@v0_0_1).to eq(described_class.new(0, 0, 1)) }
+ it { expect(@v0_1_0).to eq(described_class.new(0, 1, 0)) }
+ it { expect(@v1_0_0).to eq(described_class.new(1, 0, 0)) }
+ it { expect(@v1_0_1_rc1).to eq(described_class.new(1, 0, 1, '-rc1')) }
end
- context '!=' do
+ describe '!=' do
it { expect(@v0_0_1).not_to eq(@v0_1_0) }
+ it { expect(@v1_0_1_rc1).not_to eq(@v1_0_1_rc2) }
end
- context 'unknown' do
+ describe '.unknown' do
it { expect(@unknown).not_to be @v0_0_1 }
- it { expect(@unknown).not_to be Gitlab::VersionInfo.new }
+ it { expect(@unknown).not_to be described_class.new }
it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) }
it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) }
end
- context 'parse' do
- it { expect(Gitlab::VersionInfo.parse("1.0.0")).to eq(@v1_0_0) }
- it { expect(Gitlab::VersionInfo.parse("1.0.0.1")).to eq(@v1_0_0) }
- it { expect(Gitlab::VersionInfo.parse("1.0.0-ee")).to eq(@v1_0_0) }
- it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1")).to eq(@v1_0_0) }
- it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) }
- it { expect(Gitlab::VersionInfo.parse("git 1.0.0b1")).to eq(@v1_0_0) }
- it { expect(Gitlab::VersionInfo.parse("git 1.0b1")).not_to be_valid }
+ describe '.parse' do
+ it { expect(described_class.parse("1.0.0")).to eq(@v1_0_0) }
+ it { expect(described_class.parse("1.0.0.1")).to eq(@v1_0_0) }
+ it { expect(described_class.parse("1.0.0-ee")).to eq(@v1_0_0) }
+ it { expect(described_class.parse("1.0.0-rc1")).to eq(@v1_0_0) }
+ it { expect(described_class.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) }
+ it { expect(described_class.parse("git 1.0.0b1")).to eq(@v1_0_0) }
+ it { expect(described_class.parse("git 1.0b1")).not_to be_valid }
+
+ context 'with parse_suffix: true' do
+ let(:versions) do
+ <<-VERSIONS.lines
+ 0.0.1
+ 0.1.0
+ 1.0.0
+ 1.0.1-b1
+ 1.0.1-rc1
+ 1.0.1-rc2
+ 1.0.1
+ 1.1.0-beta1
+ 1.1.0
+ 2.0.0
+ v13.10.0-pre
+ v13.10.0-rc1
+ v13.10.0-rc2
+ v13.10.0
+ v13.10.1~beta.1574.gf6ea9389
+ v13.10.1~beta.1575.gf6ea9389
+ v13.10.1-rc1
+ v13.10.1-rc2
+ v13.10.1
+ VERSIONS
+ end
+
+ let(:parsed_versions) do
+ versions.map(&:strip).map { |version| described_class.parse(version, parse_suffix: true) }
+ end
+
+ it 'versions are returned in a correct order' do
+ expect(parsed_versions.shuffle.sort).to eq(parsed_versions)
+ end
+ end
end
- context 'to_s' do
+ describe '.to_s' do
it { expect(@v1_0_0.to_s).to eq("1.0.0") }
+ it { expect(@v1_0_1_rc1.to_s).to eq("1.0.1-rc1") }
it { expect(@unknown.to_s).to eq("Unknown") }
end
+
+ describe '.hash' do
+ it { expect(described_class.parse("1.0.0").hash).to eq(@v1_0_0.hash) }
+ it { expect(described_class.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) }
+ it { expect(described_class.parse("1.0.1b1").hash).to eq(@v1_0_1.hash) }
+ it { expect(described_class.parse("1.0.1-rc1", parse_suffix: true).hash).to eq(@v1_0_1_rc1.hash) }
+ end
+
+ describe '.eql?' do
+ it { expect(described_class.parse("1.0.0").eql?(@v1_0_0)).to be_truthy }
+ it { expect(described_class.parse("1.0.0.1").eql?(@v1_0_0)).to be_truthy }
+ it { expect(@v1_0_1_rc1.eql?(@v1_0_1_rc1)).to be_truthy }
+ it { expect(@v1_0_1_rc1.eql?(@v1_0_1_rc2)).to be_falsey }
+ it { expect(@v1_0_1_rc1.eql?(@v1_0_1)).to be_falsey }
+ it { expect(@v1_0_1.eql?(@v1_0_0)).to be_falsey }
+ it { expect(@v1_1_0.eql?(@v1_0_0)).to be_falsey }
+ it { expect(@v1_0_0.eql?(@v1_0_0)).to be_truthy }
+ it { expect([@v1_0_0, @v1_1_0, @v1_0_0, @v1_0_1_rc1, @v1_0_1_rc1].uniq).to eq [@v1_0_0, @v1_1_0, @v1_0_1_rc1] }
+ end
+
+ describe '.same_minor_version?' do
+ it { expect(@v0_1_0.same_minor_version?(@v0_0_1)).to be_falsey }
+ it { expect(@v1_0_1.same_minor_version?(@v1_0_0)).to be_truthy }
+ it { expect(@v1_0_1_rc1.same_minor_version?(@v1_0_0)).to be_truthy }
+ it { expect(@v1_0_0.same_minor_version?(@v1_0_1)).to be_truthy }
+ it { expect(@v1_1_0.same_minor_version?(@v1_0_0)).to be_falsey }
+ it { expect(@v2_0_0.same_minor_version?(@v1_0_0)).to be_falsey }
+ end
+
+ describe '.without_patch' do
+ it { expect(@v0_1_0.without_patch).to eq(@v0_1_0) }
+ it { expect(@v1_0_0.without_patch).to eq(@v1_0_0) }
+ it { expect(@v1_0_1.without_patch).to eq(@v1_0_0) }
+ it { expect(@v1_0_1_rc1.without_patch).to eq(@v1_0_0) }
+ end
end
diff --git a/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb b/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb
index c0629c8d795..3152dc2ad2f 100644
--- a/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb
+++ b/spec/lib/gitlab/wiki_pages/front_matter_parser_spec.rb
@@ -3,10 +3,11 @@
require 'spec_helper'
RSpec.describe Gitlab::WikiPages::FrontMatterParser do
- subject(:parser) { described_class.new(raw_content) }
+ subject(:parser) { described_class.new(raw_content, gate) }
let(:content) { 'This is the content' }
let(:end_divider) { '---' }
+ let(:gate) { stub_feature_flag_gate('Gate') }
let(:with_front_matter) do
<<~MD
@@ -61,6 +62,32 @@ RSpec.describe Gitlab::WikiPages::FrontMatterParser do
it { is_expected.to have_attributes(reason: :no_match) }
end
+ context 'the feature flag is disabled' do
+ let(:raw_content) { with_front_matter }
+
+ before do
+ stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => false)
+ end
+
+ it { is_expected.to have_attributes(front_matter: be_empty, content: raw_content) }
+ end
+
+ context 'the feature flag is enabled for the gated object' do
+ let(:raw_content) { with_front_matter }
+
+ before do
+ stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => gate)
+ end
+
+ it do
+ is_expected.to have_attributes(
+ front_matter: have_correct_front_matter,
+ content: content + "\n",
+ reason: be_nil
+ )
+ end
+ end
+
context 'the end divider is ...' do
let(:end_divider) { '...' }
let(:raw_content) { with_front_matter }
diff --git a/spec/lib/gitlab/x509/certificate_spec.rb b/spec/lib/gitlab/x509/certificate_spec.rb
index 2dc30cc871d..d919b99de2a 100644
--- a/spec/lib/gitlab/x509/certificate_spec.rb
+++ b/spec/lib/gitlab/x509/certificate_spec.rb
@@ -116,9 +116,69 @@ RSpec.describe Gitlab::X509::Certificate do
end
end
+ describe '.default_cert_dir' do
+ before do
+ described_class.reset_default_cert_paths
+ end
+
+ after(:context) do
+ described_class.reset_default_cert_paths
+ end
+
+ context 'when SSL_CERT_DIR env variable is not set' do
+ before do
+ stub_env('SSL_CERT_DIR', nil)
+ end
+
+ it 'returns default directory from OpenSSL' do
+ expect(described_class.default_cert_dir).to eq(OpenSSL::X509::DEFAULT_CERT_DIR)
+ end
+ end
+
+ context 'when SSL_CERT_DIR env variable is set' do
+ before do
+ stub_env('SSL_CERT_DIR', '/tmp/foo/certs')
+ end
+
+ it 'returns specified directory' do
+ expect(described_class.default_cert_dir).to eq('/tmp/foo/certs')
+ end
+ end
+ end
+
+ describe '.default_cert_file' do
+ before do
+ described_class.reset_default_cert_paths
+ end
+
+ after(:context) do
+ described_class.reset_default_cert_paths
+ end
+
+ context 'when SSL_CERT_FILE env variable is not set' do
+ before do
+ stub_env('SSL_CERT_FILE', nil)
+ end
+
+ it 'returns default file from OpenSSL' do
+ expect(described_class.default_cert_file).to eq(OpenSSL::X509::DEFAULT_CERT_FILE)
+ end
+ end
+
+ context 'when SSL_CERT_FILE env variable is set' do
+ before do
+ stub_env('SSL_CERT_FILE', '/tmp/foo/cert.pem')
+ end
+
+ it 'returns specified file' do
+ expect(described_class.default_cert_file).to eq('/tmp/foo/cert.pem')
+ end
+ end
+ end
+
describe '.ca_certs_paths' do
it 'returns all files specified by OpenSSL defaults' do
- cert_paths = Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*"]
+ cert_paths = Dir["#{described_class.default_cert_dir}/*"]
expect(described_class.ca_certs_paths).to match_array(cert_paths + [sample_cert])
end
diff --git a/spec/lib/gitlab/x509/commit_spec.rb b/spec/lib/gitlab/x509/commit_spec.rb
index a81955b995e..c7d56e49fab 100644
--- a/spec/lib/gitlab/x509/commit_spec.rb
+++ b/spec/lib/gitlab/x509/commit_spec.rb
@@ -2,14 +2,21 @@
require 'spec_helper'
RSpec.describe Gitlab::X509::Commit do
- describe '#signature' do
- let(:signature) { described_class.new(commit).signature }
+ let(:commit_sha) { '189a6c924013fc3fe40d6f1ec1dc20214183bc97' }
+ let(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
+ let(:project) { create(:project, :repository, path: X509Helpers::User1.path, creator: user) }
+ let(:commit) { project.commit_by(oid: commit_sha ) }
+ let(:signature) { Gitlab::X509::Commit.new(commit).signature }
+ let(:store) { OpenSSL::X509::Store.new }
+ let(:certificate) { OpenSSL::X509::Certificate.new(X509Helpers::User1.trust_cert) }
- context 'returns the cached signature' do
- let(:commit_sha) { '189a6c924013fc3fe40d6f1ec1dc20214183bc97' }
- let(:project) { create(:project, :public, :repository) }
- let(:commit) { create(:commit, project: project, sha: commit_sha) }
+ before do
+ store.add_cert(certificate) if certificate
+ allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
+ end
+ describe '#signature' do
+ context 'returns the cached signature' do
it 'on second call' do
allow_any_instance_of(described_class).to receive(:new).and_call_original
expect_any_instance_of(described_class).to receive(:create_cached_signature!).and_call_original
@@ -23,13 +30,29 @@ RSpec.describe Gitlab::X509::Commit do
end
context 'unsigned commit' do
- let!(:project) { create :project, :repository, path: X509Helpers::User1.path }
- let!(:commit_sha) { X509Helpers::User1.commit }
- let!(:commit) { create :commit, project: project, sha: commit_sha }
+ let(:project) { create :project, :repository, path: X509Helpers::User1.path }
+ let(:commit_sha) { X509Helpers::User1.commit }
+ let(:commit) { create :commit, project: project, sha: commit_sha }
it 'returns nil' do
expect(signature).to be_nil
end
end
end
+
+ describe '#update_signature!' do
+ let(:certificate) { nil }
+
+ it 'updates verification status' do
+ signature
+
+ cert = OpenSSL::X509::Certificate.new(X509Helpers::User1.trust_cert)
+ store.add_cert(cert)
+
+ stored_signature = CommitSignatures::X509CommitSignature.find_by_commit_sha(commit_sha)
+ expect { described_class.new(commit).update_signature!(stored_signature) }.to(
+ change { signature.reload.verification_status }.from('unverified').to('verified')
+ )
+ end
+ end
end
diff --git a/spec/lib/gitlab/x509/signature_spec.rb b/spec/lib/gitlab/x509/signature_spec.rb
index 0e34d5393d6..5626e49bfe1 100644
--- a/spec/lib/gitlab/x509/signature_spec.rb
+++ b/spec/lib/gitlab/x509/signature_spec.rb
@@ -107,7 +107,7 @@ RSpec.describe Gitlab::X509::Signature do
f.print certificate.to_pem
end
- stub_const("OpenSSL::X509::DEFAULT_CERT_FILE", file_path)
+ allow(Gitlab::X509::Certificate).to receive(:default_cert_file).and_return(file_path)
allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
end
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index a81ed38382b..ba49c00245e 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
+require 'google/apis/sqladmin_v1beta4'
RSpec.describe GoogleApi::CloudPlatform::Client do
let(:token) { 'token' }
@@ -8,6 +9,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
let(:user_agent_options) { client.instance_eval { user_agent_header } }
let(:gcp_project_id) { String('gcp_proj_id') }
let(:operation) { true }
+ let(:database_instance) { Google::Apis::SqladminV1beta4::DatabaseInstance.new(state: 'RUNNABLE') }
describe '.session_key_for_redirect_uri' do
let(:state) { 'random_string' }
@@ -350,4 +352,40 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
.to_return(status: 200, body: "", headers: {})
end
end
+
+ describe '#create_cloudsql_database' do
+ subject { client.create_cloudsql_database(:gcp_project_id, :instance_name, :database_name) }
+
+ it 'calls Google Api SQLAdminService#insert_database' do
+ expect_any_instance_of(Google::Apis::SqladminV1beta4::SQLAdminService)
+ .to receive(:insert_database)
+ .with(any_args)
+ .and_return(operation)
+ is_expected.to eq(operation)
+ end
+ end
+
+ describe '#create_cloudsql_user' do
+ subject { client.create_cloudsql_user(:gcp_project_id, :instance_name, :database_name, :user_name) }
+
+ it 'calls Google Api SQLAdminService#insert_user' do
+ expect_any_instance_of(Google::Apis::SqladminV1beta4::SQLAdminService)
+ .to receive(:insert_user)
+ .with(any_args)
+ .and_return(operation)
+ is_expected.to eq(operation)
+ end
+ end
+
+ describe '#get_cloudsql_instance' do
+ subject { client.get_cloudsql_instance(:gcp_project_id, :instance_name) }
+
+ it 'calls Google Api SQLAdminService#get_instance' do
+ expect_any_instance_of(Google::Apis::SqladminV1beta4::SQLAdminService)
+ .to receive(:get_instance)
+ .with(any_args)
+ .and_return(database_instance)
+ is_expected.to eq(database_instance)
+ end
+ end
end
diff --git a/spec/lib/initializer_connections_spec.rb b/spec/lib/initializer_connections_spec.rb
new file mode 100644
index 00000000000..4ca283c4f22
--- /dev/null
+++ b/spec/lib/initializer_connections_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe InitializerConnections do
+ describe '.with_disabled_database_connections', :reestablished_active_record_base do
+ def block_with_database_call
+ described_class.with_disabled_database_connections do
+ Project.first
+ end
+ end
+
+ def block_with_error
+ described_class.with_disabled_database_connections do
+ raise "oops, an error"
+ end
+ end
+
+ it 'prevents any database connection within the block' do
+ expect { block_with_database_call }.to raise_error(/Database connection should not be called during initializer/)
+ end
+
+ it 'does not prevent database connection if SKIP_RAISE_ON_INITIALIZE_CONNECTIONS is set' do
+ stub_env('SKIP_RAISE_ON_INITIALIZE_CONNECTIONS', '1')
+
+ expect { block_with_database_call }.not_to raise_error
+ end
+
+ it 'prevents any database connection if SKIP_RAISE_ON_INITIALIZE_CONNECTIONS is false' do
+ stub_env('SKIP_RAISE_ON_INITIALIZE_CONNECTIONS', 'false')
+
+ expect { block_with_database_call }.to raise_error(/Database connection should not be called during initializer/)
+ end
+
+ it 'restores original connection handler' do
+ # rubocop:disable Database/MultipleDatabases
+ original_handler = ActiveRecord::Base.connection_handler
+
+ expect { block_with_database_call }.to raise_error(/Database connection should not be called during initializer/)
+
+ expect(ActiveRecord::Base.connection_handler).to eq(original_handler)
+ # rubocop:enabled Database/MultipleDatabases
+ end
+
+ it 'restores original connection handler even there is an error' do
+ # rubocop:disable Database/MultipleDatabases
+ original_handler = ActiveRecord::Base.connection_handler
+
+ expect { block_with_error }.to raise_error(/an error/)
+
+ expect(ActiveRecord::Base.connection_handler).to eq(original_handler)
+ # rubocop:enabled Database/MultipleDatabases
+ end
+
+ it 'raises if any new connection_pools are established in the block' do
+ expect do
+ described_class.with_disabled_database_connections do
+ ApplicationRecord.connects_to database: { writing: :main, reading: :main }
+ end
+ end.to raise_error(/Unxpected connection_pools/)
+ end
+ end
+end
diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb
index 8f0d62d8f0c..6d2026752d6 100644
--- a/spec/lib/json_web_token/rsa_token_spec.rb
+++ b/spec/lib/json_web_token/rsa_token_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe JSONWebToken::RSAToken do
end
context 'for invalid key to raise an exception' do
- let(:new_key) { OpenSSL::PKey::RSA.generate(512) }
+ let(:new_key) { OpenSSL::PKey::RSA.generate(3072) }
subject { JWT.decode(rsa_encoded, new_key, true, { algorithm: 'RS256' }) }
diff --git a/spec/lib/learn_gitlab/onboarding_spec.rb b/spec/lib/learn_gitlab/onboarding_spec.rb
index 8c7284ed7f5..3e22ce59091 100644
--- a/spec/lib/learn_gitlab/onboarding_spec.rb
+++ b/spec/lib/learn_gitlab/onboarding_spec.rb
@@ -6,11 +6,14 @@ RSpec.describe LearnGitlab::Onboarding do
describe '#completed_percentage' do
let(:completed_actions) { {} }
let(:onboarding_progress) { build(:onboarding_progress, namespace: namespace, **completed_actions) }
- let(:namespace) { build(:namespace) }
+ let(:namespace) { create(:namespace) }
let_it_be(:tracked_action_columns) do
- tracked_actions = described_class::ACTION_ISSUE_IDS.keys + described_class::ACTION_PATHS
- tracked_actions.map { |key| OnboardingProgress.column_name(key) }
+ [
+ *described_class::ACTION_ISSUE_IDS.keys,
+ *described_class::ACTION_PATHS,
+ :security_scan_enabled
+ ].map { |key| OnboardingProgress.column_name(key) }
end
before do
@@ -29,12 +32,6 @@ RSpec.describe LearnGitlab::Onboarding do
it { is_expected.to eq(0) }
end
- context 'when one action has been completed' do
- let(:completed_actions) { Hash[tracked_action_columns.first, Time.current] }
-
- it { is_expected.to eq(11) }
- end
-
context 'when all tracked actions have been completed' do
let(:completed_actions) do
tracked_action_columns.to_h { |action| [action, Time.current] }
@@ -42,5 +39,25 @@ RSpec.describe LearnGitlab::Onboarding do
it { is_expected.to eq(100) }
end
+
+ describe 'security_actions_continuous_onboarding experiment' do
+ let(:completed_actions) { Hash[tracked_action_columns.first, Time.current] }
+
+ context 'when control' do
+ before do
+ stub_experiments(security_actions_continuous_onboarding: :control)
+ end
+
+ it { is_expected.to eq(11) }
+ end
+
+ context 'when candidate' do
+ before do
+ stub_experiments(security_actions_continuous_onboarding: :candidate)
+ end
+
+ it { is_expected.to eq(9) }
+ end
+ end
end
end
diff --git a/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb b/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb
index b11c9db4e46..ba5137e2b92 100644
--- a/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb
@@ -68,34 +68,6 @@ RSpec.describe Sidebars::Projects::Menus::MonitorMenu do
it_behaves_like 'access rights checks'
end
- describe 'Logs' do
- let(:item_id) { :logs }
-
- it_behaves_like 'access rights checks'
-
- context 'when feature disabled' do
- before do
- stub_feature_flags(monitor_logging: false)
- end
-
- specify { is_expected.to be_nil }
- end
- end
-
- describe 'Tracing' do
- let(:item_id) { :tracing }
-
- it_behaves_like 'access rights checks'
-
- context 'when feature disabled' do
- before do
- stub_feature_flags(monitor_tracing: false)
- end
-
- specify { is_expected.to be_nil }
- end
- end
-
describe 'Error Tracking' do
let(:item_id) { :error_tracking }
diff --git a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb
index d6136dddf40..f41f7a01d88 100644
--- a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb
@@ -135,18 +135,20 @@ RSpec.describe Sidebars::Projects::Menus::SettingsMenu do
describe 'Packages & Registries' do
let(:item_id) { :packages_and_registries }
+ let(:packages_enabled) { false }
before do
stub_container_registry_config(enabled: container_enabled)
+ stub_config(packages: { enabled: packages_enabled })
end
- describe 'when config registry setting is disabled' do
+ describe 'when container registry setting is disabled' do
let(:container_enabled) { false }
specify { is_expected.to be_nil }
end
- describe 'when config registry setting is enabled' do
+ describe 'when container registry setting is enabled' do
let(:container_enabled) { true }
specify { is_expected.not_to be_nil }
@@ -157,6 +159,19 @@ RSpec.describe Sidebars::Projects::Menus::SettingsMenu do
specify { is_expected.to be_nil }
end
end
+
+ describe 'when package registry setting is enabled' do
+ let(:container_enabled) { false }
+ let(:packages_enabled) { true }
+
+ specify { is_expected.not_to be_nil }
+
+ describe 'when the user does not have access' do
+ let(:user) { nil }
+
+ specify { is_expected.to be_nil }
+ end
+ end
end
describe 'Usage Quotas' do
diff --git a/spec/lib/unnested_in_filters/dsl_spec.rb b/spec/lib/unnested_in_filters/dsl_spec.rb
new file mode 100644
index 00000000000..bce4c88f94c
--- /dev/null
+++ b/spec/lib/unnested_in_filters/dsl_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe UnnestedInFilters::Dsl do
+ let(:test_model) do
+ Class.new(ApplicationRecord) do
+ include UnnestedInFilters::Dsl
+
+ self.table_name = 'users'
+ end
+ end
+
+ describe '#exists?' do
+ let(:states) { %w(active banned) }
+
+ subject { test_model.where(state: states).use_unnested_filters.exists? }
+
+ context 'when there is no record in the database with given filters' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when there is a record in the database with given filters' do
+ before do
+ create(:user, state: :active)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+end
diff --git a/spec/lib/unnested_in_filters/rewriter_spec.rb b/spec/lib/unnested_in_filters/rewriter_spec.rb
new file mode 100644
index 00000000000..e2ccbd92504
--- /dev/null
+++ b/spec/lib/unnested_in_filters/rewriter_spec.rb
@@ -0,0 +1,157 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe UnnestedInFilters::Rewriter do
+ let(:rewriter) { described_class.new(relation) }
+
+ before(:all) do
+ User.include(UnnestedInFilters::Dsl)
+ end
+
+ describe '#rewrite?' do
+ subject(:rewrite?) { rewriter.rewrite? }
+
+ context 'when the given relation does not have an `IN` predicate' do
+ let(:relation) { User.where(username: 'user') }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when the given relation has an `IN` predicate' do
+ context 'when there is no index coverage for the used columns' do
+ let(:relation) { User.where(username: %w(user_1 user_2), state: :active) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when there is an index coverage for the used columns' do
+ let(:relation) { User.where(state: :active, user_type: [:support_bot, :alert_bot]) }
+
+ it { is_expected.to be_truthy }
+
+ context 'when there is an ordering' do
+ let(:relation) { User.where(state: %w(active blocked banned)).order(order).limit(2) }
+
+ context 'when the order is an Arel node' do
+ let(:order) { { user_type: :desc } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when the order is a Keyset order' do
+ let(:order) do
+ Gitlab::Pagination::Keyset::Order.build([
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'user_type',
+ order_expression: User.arel_table['user_type'].desc,
+ nullable: :not_nullable,
+ distinct: false
+ )
+ ])
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+ end
+ end
+
+ describe '#rewrite' do
+ let(:recorded_queries) { ActiveRecord::QueryRecorder.new { rewriter.rewrite.load } }
+ let(:relation) { User.where(state: :active, user_type: %i(support_bot alert_bot)).limit(2) }
+
+ let(:expected_query) do
+ <<~SQL
+ SELECT
+ "users".*
+ FROM
+ unnest('{1,2}'::smallint[]) AS "user_types"("user_type"),
+ LATERAL (
+ SELECT
+ "users".*
+ FROM
+ "users"
+ WHERE
+ "users"."state" = 'active' AND
+ (users."user_type" = "user_types"."user_type")
+ LIMIT 2
+ ) AS users
+ LIMIT 2
+ SQL
+ end
+
+ subject(:issued_query) { recorded_queries.occurrences.each_key.first }
+
+ it 'changes the query' do
+ expect(issued_query.gsub(/\s/, '')).to start_with(expected_query.gsub(/\s/, ''))
+ end
+
+ context 'when there is an order' do
+ let(:relation) { User.where(state: %w(active blocked banned)).order(order).limit(2) }
+ let(:expected_query) do
+ <<~SQL
+ SELECT
+ "users".*
+ FROM
+ unnest('{active,blocked,banned}'::charactervarying[]) AS "states"("state"),
+ LATERAL (
+ SELECT
+ "users".*
+ FROM
+ "users"
+ WHERE
+ (users."state" = "states"."state")
+ ORDER BY
+ "users"."user_type" DESC
+ LIMIT 2
+ ) AS users
+ ORDER BY
+ "users"."user_type" DESC
+ LIMIT 2
+ SQL
+ end
+
+ context 'when the order is an Arel node' do
+ let(:order) { { user_type: :desc } }
+
+ it 'changes the query' do
+ expect(issued_query.gsub(/\s/, '')).to start_with(expected_query.gsub(/\s/, ''))
+ end
+ end
+
+ context 'when the order is a Keyset order' do
+ let(:order) do
+ Gitlab::Pagination::Keyset::Order.build([
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'user_type',
+ order_expression: User.arel_table['user_type'].desc,
+ nullable: :not_nullable,
+ distinct: false
+ )
+ ])
+ end
+
+ it 'changes the query' do
+ expect(issued_query.gsub(/\s/, '')).to start_with(expected_query.gsub(/\s/, ''))
+ end
+ end
+ end
+
+ describe 'logging' do
+ subject(:load_reload) { rewriter.rewrite }
+
+ before do
+ allow(::Gitlab::AppLogger).to receive(:info)
+ end
+
+ it 'logs the call' do
+ load_reload
+
+ expect(::Gitlab::AppLogger)
+ .to have_received(:info).with(message: 'Query is being rewritten by `UnnestedInFilters`', model: 'User')
+ end
+ end
+ end
+end