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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab')
-rw-r--r--spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb153
-rw-r--r--spec/lib/gitlab/alert_management/payload_spec.rb12
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb2
-rw-r--r--spec/lib/gitlab/audit/auditor_spec.rb30
-rw-r--r--spec/lib/gitlab/auth/auth_finders_spec.rb18
-rw-r--r--spec/lib/gitlab/auth/saml/auth_hash_spec.rb26
-rw-r--r--spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb59
-rw-r--r--spec/lib/gitlab/avatar_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_default_branch_protection_namespace_setting_spec.rb65
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_pipeline_artifacts_size_job_spec.rb113
-rw-r--r--spec/lib/gitlab/background_migration/fix_allow_descendants_override_disabled_shared_runners_spec.rb53
-rw-r--r--spec/lib/gitlab/background_migration/redis/backfill_project_pipeline_status_ttl_spec.rb11
-rw-r--r--spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb3
-rw-r--r--spec/lib/gitlab/blame_spec.rb6
-rw-r--r--spec/lib/gitlab/cache/json_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/cache/json_caches/json_keyed_spec.rb2
-rw-r--r--spec/lib/gitlab/cache/json_caches/redis_keyed_spec.rb4
-rw-r--r--spec/lib/gitlab/checks/branch_check_spec.rb12
-rw-r--r--spec/lib/gitlab/checks/file_size_check/allow_existing_oversized_blobs_spec.rb86
-rw-r--r--spec/lib/gitlab/checks/file_size_check/hook_environment_aware_any_oversized_blobs_spec.rb75
-rw-r--r--spec/lib/gitlab/checks/global_file_size_check_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/artifacts/decompressed_artifact_size_validator_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/components/instance_path_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb131
-rw-r--r--spec/lib/gitlab/ci/config/entry/include/rules_spec.rb71
-rw-r--r--spec/lib/gitlab/ci/config/entry/need_spec.rb77
-rw-r--r--spec/lib/gitlab/ci/config/entry/needs_spec.rb137
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/config/external/context_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/config/external/file/component_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb64
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb72
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/config/external/rules_spec.rb96
-rw-r--r--spec/lib/gitlab/ci/config/header/input_spec.rb19
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/access_spec.rb (renamed from spec/lib/gitlab/ci/interpolation/access_spec.rb)11
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/block_spec.rb112
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/config_spec.rb86
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/context_spec.rb (renamed from spec/lib/gitlab/ci/interpolation/context_spec.rb)16
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb35
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb38
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb137
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb (renamed from spec/lib/gitlab/ci/config/yaml/interpolator_spec.rb)56
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/template_spec.rb (renamed from spec/lib/gitlab/ci/interpolation/template_spec.rb)6
-rw-r--r--spec/lib/gitlab/ci/config/normalizer_spec.rb105
-rw-r--r--spec/lib/gitlab/ci/config/yaml/loader_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/config/yaml/result_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/config/yaml_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/decompressed_gzip_size_validator_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/input/arguments/base_spec.rb19
-rw-r--r--spec/lib/gitlab/ci/input/arguments/default_spec.rb53
-rw-r--r--spec/lib/gitlab/ci/input/arguments/options_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/input/arguments/required_spec.rb45
-rw-r--r--spec/lib/gitlab/ci/input/arguments/unknown_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/input/inputs_spec.rb126
-rw-r--r--spec/lib/gitlab/ci/interpolation/block_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/interpolation/config_spec.rb49
-rw-r--r--spec/lib/gitlab/ci/jwt_v2/claim_mapper/repository_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/jwt_v2/claim_mapper_spec.rb40
-rw-r--r--spec/lib/gitlab/ci/jwt_v2_spec.rb78
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/project_config/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/project_config_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/queue/metrics_spec.rb71
-rw-r--r--spec/lib/gitlab/ci/reports/sbom/component_spec.rb148
-rw-r--r--spec/lib/gitlab/ci/status/stage/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/stage/play_manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/tags/bulk_insert_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/builder_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb59
-rw-r--r--spec/lib/gitlab/ci/variables/downstream/generator_spec.rb57
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb36
-rw-r--r--spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb22
-rw-r--r--spec/lib/gitlab/config/entry/validators_spec.rb33
-rw-r--r--spec/lib/gitlab/container_repository/tags/cache_spec.rb4
-rw-r--r--spec/lib/gitlab/content_security_policy/config_loader_spec.rb338
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb32
-rw-r--r--spec/lib/gitlab/data_builder/deployment_spec.rb4
-rw-r--r--spec/lib/gitlab/data_builder/issuable_spec.rb27
-rw-r--r--spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb10
-rw-r--r--spec/lib/gitlab/database/batch_count_spec.rb4
-rw-r--r--spec/lib/gitlab/database/bump_sequences_spec.rb83
-rw-r--r--spec/lib/gitlab/database/click_house_client_spec.rb13
-rw-r--r--spec/lib/gitlab/database/gitlab_schema_spec.rb2
-rw-r--r--spec/lib/gitlab/database/health_status/indicators/patroni_apdex_spec.rb151
-rw-r--r--spec/lib/gitlab/database/health_status/indicators/prometheus_alert_indicator_spec.rb66
-rw-r--r--spec/lib/gitlab/database/health_status/indicators/wal_rate_spec.rb29
-rw-r--r--spec/lib/gitlab/database/health_status_spec.rb16
-rw-r--r--spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb53
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb39
-rw-r--r--spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/database/migrations/squasher_spec.rb65
-rw-r--r--spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb27
-rw-r--r--spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb19
-rw-r--r--spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb6
-rw-r--r--spec/lib/gitlab/database/query_analyzers/query_recorder_spec.rb114
-rw-r--r--spec/lib/gitlab/database/schema_validation/schema_inconsistency_spec.rb41
-rw-r--r--spec/lib/gitlab/database/tables_sorted_by_foreign_keys_spec.rb20
-rw-r--r--spec/lib/gitlab/database/tables_truncate_spec.rb1
-rw-r--r--spec/lib/gitlab/database_spec.rb27
-rw-r--r--spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb38
-rw-r--r--spec/lib/gitlab/exclusive_lease_spec.rb328
-rw-r--r--spec/lib/gitlab/git/blame_spec.rb8
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb108
-rw-r--r--spec/lib/gitlab/git/diff_tree_spec.rb30
-rw-r--r--spec/lib/gitlab/git/object_pool_spec.rb25
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb93
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb2
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb39
-rw-r--r--spec/lib/gitlab/git_access_snippet_spec.rb4
-rw-r--r--spec/lib/gitlab/gitaly_client/blob_service_spec.rb19
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb56
-rw-r--r--spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb26
-rw-r--r--spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb34
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb10
-rw-r--r--spec/lib/gitlab/github_import_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/connections_spec.rb2
-rw-r--r--spec/lib/gitlab/group_search_results_spec.rb11
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb11
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/http_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml15
-rw-r--r--spec/lib/gitlab/import_export/command_line_util_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb21
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/project/relation_tree_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/internal_events/event_definitions_spec.rb4
-rw-r--r--spec/lib/gitlab/internal_events_spec.rb2
-rw-r--r--spec/lib/gitlab/jwt_authenticatable_spec.rb36
-rw-r--r--spec/lib/gitlab/kas_spec.rb79
-rw-r--r--spec/lib/gitlab/merge_requests/message_generator_spec.rb20
-rw-r--r--spec/lib/gitlab/metrics/dashboard/defaults_spec.rb7
-rw-r--r--spec/lib/gitlab/metrics/dashboard/finder_spec.rb178
-rw-r--r--spec/lib/gitlab/metrics/dashboard/importer_spec.rb55
-rw-r--r--spec/lib/gitlab/metrics/dashboard/importers/prometheus_metrics_spec.rb97
-rw-r--r--spec/lib/gitlab/metrics/dashboard/processor_spec.rb178
-rw-r--r--spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb148
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb58
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/metric_endpoint_inserter_spec.rb59
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb88
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/track_panel_type_spec.rb26
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter_spec.rb77
-rw-r--r--spec/lib/gitlab/metrics/dashboard/transformers/yml/v1/prometheus_metrics_spec.rb99
-rw-r--r--spec/lib/gitlab/metrics/dashboard/validator/client_spec.rb29
-rw-r--r--spec/lib/gitlab/metrics/dashboard/validator/custom_formats_spec.rb15
-rw-r--r--spec/lib/gitlab/metrics/dashboard/validator/errors_spec.rb149
-rw-r--r--spec/lib/gitlab/metrics/dashboard/validator/post_schema_validator_spec.rb78
-rw-r--r--spec/lib/gitlab/metrics/dashboard/validator_spec.rb146
-rw-r--r--spec/lib/gitlab/metrics/global_search_slis_spec.rb3
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/webhook_recursion_detection_spec.rb2
-rw-r--r--spec/lib/gitlab/null_request_store_spec.rb75
-rw-r--r--spec/lib/gitlab/pages/url_builder_spec.rb72
-rw-r--r--spec/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy_spec.rb8
-rw-r--r--spec/lib/gitlab/plantuml_spec.rb2
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/cache_spec.rb4
-rw-r--r--spec/lib/gitlab/redis/cluster_shared_state_spec.rb (renamed from spec/lib/gitlab/redis/cluster_cache_spec.rb)4
-rw-r--r--spec/lib/gitlab/redis/etag_cache_spec.rb56
-rw-r--r--spec/lib/gitlab/regex_requires_app_spec.rb2
-rw-r--r--spec/lib/gitlab/regex_spec.rb17
-rw-r--r--spec/lib/gitlab/repository_size_checker_spec.rb5
-rw-r--r--spec/lib/gitlab/safe_request_store_spec.rb257
-rw-r--r--spec/lib/gitlab/search_results_spec.rb26
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb5
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/pause_control/client_spec.rb53
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/pause_control/pause_control_service_spec.rb178
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/pause_control/server_spec.rb76
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/pause_control/strategy_handler_spec.rb68
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/pause_control_spec.rb19
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb192
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb2
-rw-r--r--spec/lib/gitlab/time_tracking_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/tracking/standard_context_spec.rb48
-rw-r--r--spec/lib/gitlab/usage/metric_definition_spec.rb2
-rw-r--r--spec/lib/gitlab/usage/metric_spec.rb8
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/batched_background_migration_failed_jobs_metric_spec.rb40
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb2
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_deployments_metric_spec.rb2
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric_spec.rb72
-rw-r--r--spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb113
-rw-r--r--spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb97
-rw-r--r--spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/having_constraints_spec.rb19
-rw-r--r--spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins_spec.rb45
-rw-r--r--spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/where_constraints_spec.rb18
-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.rb165
-rw-r--r--spec/lib/gitlab/usage_data_counters/neovim_plugin_activity_unique_counter_spec.rb19
-rw-r--r--spec/lib/gitlab/usage_data_counters/visual_studio_extension_activity_unique_counter_spec.rb19
-rw-r--r--spec/lib/gitlab/with_request_store_spec.rb30
-rw-r--r--spec/lib/gitlab/x509/signature_spec.rb44
197 files changed, 4737 insertions, 4092 deletions
diff --git a/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb b/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb
deleted file mode 100644
index d7184c89933..00000000000
--- a/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb
+++ /dev/null
@@ -1,153 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::AlertManagement::Payload::ManagedPrometheus do
- let_it_be(:project) { create(:project) }
-
- let(:raw_payload) { {} }
-
- let(:parsed_payload) { described_class.new(project: project, payload: raw_payload) }
-
- it_behaves_like 'subclass has expected api'
-
- shared_context 'with gitlab alert' do
- let_it_be(:gitlab_alert) { create(:prometheus_alert, project: project) }
- let(:metric_id) { gitlab_alert.prometheus_metric_id.to_s }
- let(:alert_id) { gitlab_alert.id.to_s }
- end
-
- describe '#metric_id' do
- subject { parsed_payload.metric_id }
-
- it { is_expected.to be_nil }
-
- context 'with gitlab_alert_id' do
- let(:raw_payload) { { 'labels' => { 'gitlab_alert_id' => '12' } } }
-
- it { is_expected.to eq(12) }
- end
- end
-
- describe '#gitlab_prometheus_alert_id' do
- subject { parsed_payload.gitlab_prometheus_alert_id }
-
- it { is_expected.to be_nil }
-
- context 'with gitlab_alert_id' do
- let(:raw_payload) { { 'labels' => { 'gitlab_prometheus_alert_id' => '12' } } }
-
- it { is_expected.to eq(12) }
- end
- end
-
- describe '#gitlab_alert' do
- subject { parsed_payload.gitlab_alert }
-
- context 'without alert info in payload' do
- it { is_expected.to be_nil }
- end
-
- context 'with metric id in payload' do
- let(:raw_payload) { { 'labels' => { 'gitlab_alert_id' => metric_id } } }
- let(:metric_id) { '-1' }
-
- context 'without matching alert' do
- it { is_expected.to be_nil }
- end
-
- context 'with matching alert' do
- include_context 'with gitlab alert'
-
- it { is_expected.to eq(gitlab_alert) }
-
- context 'when unclear which alert applies' do
- # With multiple alerts for different environments,
- # we can't be sure which prometheus alert the payload
- # belongs to
- let_it_be(:another_alert) do
- create(:prometheus_alert,
- prometheus_metric: gitlab_alert.prometheus_metric,
- project: project)
- end
-
- it { is_expected.to be_nil }
- end
- end
- end
-
- context 'with alert id' do
- # gitlab_prometheus_alert_id is a stronger identifier,
- # but was added after gitlab_alert_id; we won't
- # see it without gitlab_alert_id also present
- let(:raw_payload) do
- {
- 'labels' => {
- 'gitlab_alert_id' => metric_id,
- 'gitlab_prometheus_alert_id' => alert_id
- }
- }
- end
-
- context 'without matching alert' do
- let(:alert_id) { '-1' }
- let(:metric_id) { '-1' }
-
- it { is_expected.to be_nil }
- end
-
- context 'with matching alerts' do
- include_context 'with gitlab alert'
-
- it { is_expected.to eq(gitlab_alert) }
- end
- end
- end
-
- describe '#full_query' do
- subject { parsed_payload.full_query }
-
- it { is_expected.to be_nil }
-
- context 'with gitlab alert' do
- include_context 'with gitlab alert'
-
- let(:raw_payload) { { 'labels' => { 'gitlab_alert_id' => metric_id } } }
-
- it { is_expected.to eq(gitlab_alert.full_query) }
- end
-
- context 'with sufficient fallback info' do
- let(:raw_payload) { { 'generatorURL' => 'http://localhost:9090/graph?g0.expr=vector%281%29' } }
-
- it { is_expected.to eq('vector(1)') }
- end
- end
-
- describe '#environment' do
- subject { parsed_payload.environment }
-
- context 'with gitlab alert' do
- include_context 'with gitlab alert'
-
- let(:raw_payload) { { 'labels' => { 'gitlab_alert_id' => metric_id } } }
-
- it { is_expected.to eq(gitlab_alert.environment) }
- end
-
- context 'with sufficient fallback info' do
- let_it_be(:environment) { create(:environment, project: project, name: 'production') }
-
- let(:raw_payload) do
- {
- 'labels' => {
- 'gitlab_alert_id' => '-1',
- 'gitlab_environment_name' => 'production'
- }
- }
- end
-
- it { is_expected.to eq(environment) }
- end
- end
-end
diff --git a/spec/lib/gitlab/alert_management/payload_spec.rb b/spec/lib/gitlab/alert_management/payload_spec.rb
index efde7ed3772..fe14e6ae53c 100644
--- a/spec/lib/gitlab/alert_management/payload_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload_spec.rb
@@ -19,12 +19,6 @@ RSpec.describe Gitlab::AlertManagement::Payload do
let(:payload) { { 'monitoring_tool' => 'Prometheus' } }
it { is_expected.to be_a Gitlab::AlertManagement::Payload::Prometheus }
-
- context 'with gitlab-managed attributes' do
- let(:payload) { { 'monitoring_tool' => 'Prometheus', 'labels' => { 'gitlab_alert_id' => '12' } } }
-
- it { is_expected.to be_a Gitlab::AlertManagement::Payload::ManagedPrometheus }
- end
end
context 'with the payload specifying an unknown tool' do
@@ -43,12 +37,6 @@ RSpec.describe Gitlab::AlertManagement::Payload do
context 'with an externally managed prometheus payload' do
it { is_expected.to be_a Gitlab::AlertManagement::Payload::Prometheus }
end
-
- context 'with a self-managed prometheus payload' do
- let(:payload) { { 'labels' => { 'gitlab_alert_id' => '14' } } }
-
- it { is_expected.to be_a Gitlab::AlertManagement::Payload::ManagedPrometheus }
- end
end
context 'as an unknown tool' do
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 e9a9dfeca82..276f797536b 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
@@ -117,7 +117,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
})
end
- before(:all) do
+ before_all do
issue1.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
issue2.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
issue3.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
index 24248c557bd..df0e4fb92a0 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::StageEvent, feature_category: :product_analytics do
+RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::StageEvent, feature_category: :product_analytics_data_management do
let(:instance) { described_class.new({}) }
it { expect(described_class).to respond_to(:name) }
diff --git a/spec/lib/gitlab/audit/auditor_spec.rb b/spec/lib/gitlab/audit/auditor_spec.rb
index 386d4157e90..bde72a656b8 100644
--- a/spec/lib/gitlab/audit/auditor_spec.rb
+++ b/spec/lib/gitlab/audit/auditor_spec.rb
@@ -25,35 +25,33 @@ RSpec.describe Gitlab::Audit::Auditor, feature_category: :audit_events do
describe '.audit' do
let(:audit!) { auditor.audit(context) }
+ before do
+ allow(Gitlab::Audit::Type::Definition).to receive(:defined?).and_call_original
+ allow(Gitlab::Audit::Type::Definition).to receive(:defined?).with(name).and_return(true)
+ end
+
context 'when yaml definition is not defined' do
before do
- allow(Gitlab::Audit::Type::Definition).to receive(:defined?).and_return(false)
- allow(Gitlab::AppLogger).to receive(:warn).and_return(app_logger)
+ allow(Gitlab::Audit::Type::Definition).to receive(:defined?).and_call_original
+ allow(Gitlab::Audit::Type::Definition).to receive(:defined?).with(name).and_return(false)
end
- it 'logs a warning when YAML is not defined' do
- expected_warning = {
- message: 'Logging audit events without an event type definition will be deprecated soon ' \
- '(https://docs.gitlab.com/ee/development/audit_event_guide/#event-type-definitions)',
- event_type: name
- }
-
- audit!
+ it 'raises an error' do
+ expected_error = "Audit event type YML file is not defined for audit_operation. " \
+ "Please read https://docs.gitlab.com/ee/development/audit_event_guide/" \
+ "#how-to-instrument-new-audit-events for adding a new audit event"
- expect(Gitlab::AppLogger).to have_received(:warn).with(expected_warning)
+ expect { audit! }.to raise_error(StandardError, expected_error)
end
end
context 'when yaml definition is defined' do
before do
allow(Gitlab::Audit::Type::Definition).to receive(:defined?).and_return(true)
- allow(Gitlab::AppLogger).to receive(:warn).and_return(app_logger)
end
- it 'does not log a warning when YAML is defined' do
- audit!
-
- expect(Gitlab::AppLogger).not_to have_received(:warn)
+ it 'does not raise an error' do
+ expect { audit! }.not_to raise_error
end
end
diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb
index 1a1e165c50a..b0ec46a3a0e 100644
--- a/spec/lib/gitlab/auth/auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/auth_finders_spec.rb
@@ -516,17 +516,23 @@ RSpec.describe Gitlab::Auth::AuthFinders, feature_category: :system_access do
set_bearer_token(token_3.token)
end
- it 'revokes the latest rotated token' do
- expect(token_1).not_to be_revoked
+ context 'with url related to access tokens' do
+ before do
+ set_header('SCRIPT_NAME', "/personal_access_tokens/#{token_3.id}/rotate")
+ end
+
+ it 'revokes the latest rotated token' do
+ expect(token_1).not_to be_revoked
- expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::RevokedError)
+ expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::RevokedError)
- expect(token_1.reload).to be_revoked
+ expect(token_1.reload).to be_revoked
+ end
end
- context 'when the feature flag is disabled' do
+ context 'with url not related to access tokens' do
before do
- stub_feature_flags(pat_reuse_detection: false)
+ set_header('SCRIPT_NAME', '/epics/1')
end
it 'does not revoke the latest rotated token' do
diff --git a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
index f1fad946f35..5286e22abc9 100644
--- a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
@@ -40,6 +40,32 @@ RSpec.describe Gitlab::Auth::Saml::AuthHash do
end
end
+ describe '#azure_group_overage_claim?' do
+ context 'when the claim is not present' do
+ let(:raw_info_attr) { {} }
+
+ it 'is false' do
+ expect(saml_auth_hash.azure_group_overage_claim?).to eq(false)
+ end
+ end
+
+ context 'when the claim is present' do
+ # The value of the claim is irrelevant, but it's still included
+ # in the test response to keep tests as real-world as possible.
+ # https://learn.microsoft.com/en-us/security/zero-trust/develop/configure-tokens-group-claims-app-roles#group-overages
+ let(:raw_info_attr) do
+ {
+ 'http://schemas.microsoft.com/claims/groups.link' =>
+ ['https://graph.windows.net/8c750e43/users/e631c82c/getMemberObjects']
+ }
+ end
+
+ it 'is true' do
+ expect(saml_auth_hash.azure_group_overage_claim?).to eq(true)
+ end
+ end
+ end
+
describe '#authn_context' do
let(:auth_hash_data) do
{
diff --git a/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb b/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb
index 876c23a91bd..e0ef45d5621 100644
--- a/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb
+++ b/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb
@@ -5,10 +5,13 @@ require 'spec_helper'
RSpec.describe Gitlab::Auth::TwoFactorAuthVerifier do
using RSpec::Parameterized::TableSyntax
- subject(:verifier) { described_class.new(user) }
+ let(:request) { instance_double(ActionDispatch::Request, session: session) }
+ let(:session) { {} }
let(:user) { build_stubbed(:user, otp_grace_period_started_at: Time.zone.now) }
+ subject(:verifier) { described_class.new(user, request) }
+
describe '#two_factor_authentication_enforced?' do
subject { verifier.two_factor_authentication_enforced? }
@@ -34,25 +37,69 @@ RSpec.describe Gitlab::Auth::TwoFactorAuthVerifier do
describe '#two_factor_authentication_required?' do
subject { verifier.two_factor_authentication_required? }
- where(:instance_level_enabled, :group_level_enabled, :should_be_required) do
- true | false | true
- false | true | true
- false | false | false
+ where(:instance_level_enabled, :group_level_enabled, :should_be_required, :provider_2FA) do
+ true | false | true | false
+ false | true | false | true
+ false | true | true | false
+ false | false | false | true
end
with_them do
before do
stub_application_setting(require_two_factor_authentication: instance_level_enabled)
allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(group_level_enabled)
+ session[:provider_2FA] = provider_2FA
end
it { is_expected.to eq(should_be_required) }
end
+
+ context 'when feature by_pass_two_factor_for_current_session is disabled' do
+ where(:instance_level_enabled, :group_level_enabled, :should_be_required, :provider_2FA) do
+ true | false | true | false
+ false | true | true | true
+ false | false | false | true
+ end
+
+ with_them do
+ before do
+ allow(request).to receive(:session).and_return(session)
+ stub_feature_flags(by_pass_two_factor_for_current_session: false)
+ stub_application_setting(require_two_factor_authentication: instance_level_enabled)
+ allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(group_level_enabled)
+ session[:provider_2FA] = provider_2FA
+ end
+
+ it { is_expected.to eq(should_be_required) }
+ end
+ end
+
+ context 'when request is nil' do
+ let(:request) { nil }
+
+ where(:instance_level_enabled, :group_level_enabled, :should_be_required, :provider_2FA) do
+ true | false | true | false
+ false | true | true | true
+ false | false | false | true
+ end
+
+ with_them do
+ before do
+ allow(request).to receive(:session).and_return(session)
+ stub_feature_flags(bypass_two_factor: false)
+ stub_application_setting(require_two_factor_authentication: instance_level_enabled)
+ allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(group_level_enabled)
+ session[:provider_2FA] = provider_2FA
+ end
+
+ it { is_expected.to eq(should_be_required) }
+ end
+ end
end
describe '#current_user_needs_to_setup_two_factor?' do
it 'returns false when current_user is nil' do
- expect(described_class.new(nil).current_user_needs_to_setup_two_factor?).to be_falsey
+ expect(described_class.new(nil, request).current_user_needs_to_setup_two_factor?).to be_falsey
end
it 'returns false when current_user does not have temp email' do
diff --git a/spec/lib/gitlab/avatar_cache_spec.rb b/spec/lib/gitlab/avatar_cache_spec.rb
index c959c5d80b2..65cde195a61 100644
--- a/spec/lib/gitlab/avatar_cache_spec.rb
+++ b/spec/lib/gitlab/avatar_cache_spec.rb
@@ -47,7 +47,7 @@ RSpec.describe Gitlab::AvatarCache, :clean_gitlab_redis_cache do
it "finds the cached value in the request store and doesn't execute the block" do
expect(thing).to receive(:avatar_path).once
- Gitlab::WithRequestStore.with_request_store do
+ Gitlab::SafeRequestStore.ensure_request_store do
described_class.by_email("foo@bar.com", 20, 2, true) do
thing.avatar_path
end
diff --git a/spec/lib/gitlab/background_migration/backfill_default_branch_protection_namespace_setting_spec.rb b/spec/lib/gitlab/background_migration/backfill_default_branch_protection_namespace_setting_spec.rb
new file mode 100644
index 00000000000..62c9e240b7a
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_default_branch_protection_namespace_setting_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillDefaultBranchProtectionNamespaceSetting,
+ schema: 20230724071541,
+ feature_category: :database 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',
+ default_branch_protection: 0)
+ namespaces_table.create!(id: 2, name: 'user_namespace', path: 'path-2', type: 'User', default_branch_protection: 1)
+ namespaces_table.create!(id: 3, name: 'user_three_namespace', path: 'path-3', type: 'User',
+ default_branch_protection: 2)
+ namespaces_table.create!(id: 4, name: 'group_four_namespace', path: 'path-4', type: 'Group',
+ default_branch_protection: 3)
+ namespaces_table.create!(id: 5, name: 'group_five_namespace', path: 'path-5', type: 'Group',
+ default_branch_protection: 4)
+
+ namespace_settings_table.create!(namespace_id: 1, default_branch_protection_defaults: {})
+ namespace_settings_table.create!(namespace_id: 2, default_branch_protection_defaults: {})
+ namespace_settings_table.create!(namespace_id: 3, default_branch_protection_defaults: {})
+ namespace_settings_table.create!(namespace_id: 4, default_branch_protection_defaults: {})
+ namespace_settings_table.create!(namespace_id: 5, default_branch_protection_defaults: {})
+ end
+
+ it 'updates default_branch_protection_defaults to a correct value', :aggregate_failures do
+ expect(ActiveRecord::QueryRecorder.new { perform_migration }.count).to eq(16)
+
+ expect(migrated_attribute(1)).to eq({ "allow_force_push" => true,
+ "allowed_to_merge" => [{ "access_level" => 30 }],
+ "allowed_to_push" => [{ "access_level" => 30 }] })
+ expect(migrated_attribute(2)).to eq({ "allow_force_push" => false,
+ "allowed_to_merge" => [{ "access_level" => 30 }],
+ "allowed_to_push" => [{ "access_level" => 30 }] })
+ expect(migrated_attribute(3)).to eq({ "allow_force_push" => false,
+ "allowed_to_merge" => [{ "access_level" => 40 }],
+ "allowed_to_push" => [{ "access_level" => 40 }] })
+ expect(migrated_attribute(4)).to eq({ "allow_force_push" => true,
+ "allowed_to_merge" => [{ "access_level" => 30 }],
+ "allowed_to_push" => [{ "access_level" => 40 }] })
+ expect(migrated_attribute(5)).to eq({ "allow_force_push" => true,
+ "allowed_to_merge" => [{ "access_level" => 30 }],
+ "allowed_to_push" => [{ "access_level" => 40 }],
+ "developer_can_initial_push" => true })
+ end
+
+ def migrated_attribute(namespace_id)
+ namespace_settings_table.find(namespace_id).default_branch_protection_defaults
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_pipeline_artifacts_size_job_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_pipeline_artifacts_size_job_spec.rb
new file mode 100644
index 00000000000..c85636f4998
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_pipeline_artifacts_size_job_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillProjectStatisticsStorageSizeWithoutPipelineArtifactsSizeJob,
+ schema: 20230719083202,
+ feature_category: :consumables_cost_management do
+ include MigrationHelpers::ProjectStatisticsHelper
+
+ include_context 'when backfilling project statistics'
+
+ let(:default_pipeline_artifacts_size) { 5 }
+ let(:default_stats) do
+ {
+ repository_size: 1,
+ wiki_size: 1,
+ lfs_objects_size: 1,
+ build_artifacts_size: 1,
+ packages_size: 1,
+ snippets_size: 1,
+ uploads_size: 1,
+ pipeline_artifacts_size: default_pipeline_artifacts_size,
+ storage_size: default_storage_size
+ }
+ end
+
+ describe '#filter_batch' do
+ it 'filters out project_statistics with no artifacts size' do
+ project_statistics = generate_records(default_projects, project_statistics_table, default_stats)
+ project_statistics_table.create!(
+ project_id: proj5.id,
+ namespace_id: proj5.namespace_id,
+ repository_size: 1,
+ wiki_size: 1,
+ lfs_objects_size: 1,
+ build_artifacts_size: 1,
+ packages_size: 1,
+ snippets_size: 1,
+ pipeline_artifacts_size: 0,
+ uploads_size: 1,
+ storage_size: 7
+ )
+
+ expected = project_statistics.map(&:id)
+ actual = migration.filter_batch(project_statistics_table).pluck(:id)
+
+ expect(actual).to match_array(expected)
+ end
+ end
+
+ describe '#perform' do
+ subject(:perform_migration) { migration.perform }
+
+ context 'when project_statistics backfill runs' do
+ before do
+ generate_records(default_projects, project_statistics_table, default_stats)
+ end
+
+ context 'when storage_size includes pipeline_artifacts_size' do
+ it 'removes pipeline_artifacts_size from storage_size' do
+ allow(::Namespaces::ScheduleAggregationWorker).to receive(:perform_async)
+ expect(project_statistics_table.pluck(:storage_size).uniq).to match_array([default_storage_size])
+
+ perform_migration
+
+ expect(project_statistics_table.pluck(:storage_size).uniq).to match_array(
+ [default_storage_size - default_pipeline_artifacts_size]
+ )
+ expect(::Namespaces::ScheduleAggregationWorker).to have_received(:perform_async).exactly(4).times
+ end
+ end
+
+ context 'when storage_size does not include default_pipeline_artifacts_size' do
+ it 'does not update the record' do
+ allow(::Namespaces::ScheduleAggregationWorker).to receive(:perform_async)
+ proj_stat = project_statistics_table.last
+ expect(proj_stat.storage_size).to eq(default_storage_size)
+ proj_stat.storage_size = default_storage_size - default_pipeline_artifacts_size
+ proj_stat.save!
+
+ perform_migration
+
+ expect(project_statistics_table.pluck(:storage_size).uniq).to match_array(
+ [default_storage_size - default_pipeline_artifacts_size]
+ )
+ expect(::Namespaces::ScheduleAggregationWorker).to have_received(:perform_async).exactly(3).times
+ end
+ end
+ end
+
+ it 'coerces a null wiki_size to 0' do
+ project_statistics = create_project_stats(projects, namespaces, default_stats, { wiki_size: nil })
+ allow(::Namespaces::ScheduleAggregationWorker).to receive(:perform_async)
+ migration = create_migration(end_id: project_statistics.project_id)
+
+ migration.perform
+
+ project_statistics.reload
+ expect(project_statistics.storage_size).to eq(6)
+ end
+
+ it 'coerces a null snippets_size to 0' do
+ project_statistics = create_project_stats(projects, namespaces, default_stats, { snippets_size: nil })
+ allow(::Namespaces::ScheduleAggregationWorker).to receive(:perform_async)
+ migration = create_migration(end_id: project_statistics.project_id)
+
+ migration.perform
+
+ project_statistics.reload
+ expect(project_statistics.storage_size).to eq(6)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/fix_allow_descendants_override_disabled_shared_runners_spec.rb b/spec/lib/gitlab/background_migration/fix_allow_descendants_override_disabled_shared_runners_spec.rb
new file mode 100644
index 00000000000..5f5dcb35836
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/fix_allow_descendants_override_disabled_shared_runners_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::FixAllowDescendantsOverrideDisabledSharedRunners, schema: 20230802085923, feature_category: :runner_fleet do # rubocop:disable Layout/LineLength
+ let(:namespaces) { table(:namespaces) }
+
+ let!(:valid_enabled) do
+ namespaces.create!(name: 'valid_enabled', path: 'valid_enabled',
+ shared_runners_enabled: true,
+ allow_descendants_override_disabled_shared_runners: false)
+ end
+
+ let!(:invalid_enabled) do
+ namespaces.create!(name: 'invalid_enabled', path: 'invalid_enabled',
+ shared_runners_enabled: true,
+ allow_descendants_override_disabled_shared_runners: true)
+ end
+
+ let!(:disabled_and_overridable) do
+ namespaces.create!(name: 'disabled_and_overridable', path: 'disabled_and_overridable',
+ shared_runners_enabled: false,
+ allow_descendants_override_disabled_shared_runners: true)
+ end
+
+ let!(:disabled_and_unoverridable) do
+ namespaces.create!(name: 'disabled_and_unoverridable', path: 'disabled_and_unoverridable',
+ shared_runners_enabled: false,
+ allow_descendants_override_disabled_shared_runners: false)
+ end
+
+ let(:migration_attrs) do
+ {
+ start_id: namespaces.minimum(:id),
+ end_id: namespaces.maximum(:id),
+ batch_table: :namespaces,
+ batch_column: :id,
+ sub_batch_size: 2,
+ pause_ms: 0,
+ connection: ApplicationRecord.connection
+ }
+ end
+
+ it 'fixes invalid allow_descendants_override_disabled_shared_runners and does not affect others' do
+ expect do
+ described_class.new(**migration_attrs).perform
+ end.to change { invalid_enabled.reload.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
+ .and not_change { valid_enabled.reload.allow_descendants_override_disabled_shared_runners }.from(false)
+ .and not_change { disabled_and_overridable.reload.allow_descendants_override_disabled_shared_runners }.from(true)
+ .and not_change { disabled_and_unoverridable.reload.allow_descendants_override_disabled_shared_runners }
+ .from(false)
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/redis/backfill_project_pipeline_status_ttl_spec.rb b/spec/lib/gitlab/background_migration/redis/backfill_project_pipeline_status_ttl_spec.rb
index e3b1b67cb40..c52d1b4c9f2 100644
--- a/spec/lib/gitlab/background_migration/redis/backfill_project_pipeline_status_ttl_spec.rb
+++ b/spec/lib/gitlab/background_migration/redis/backfill_project_pipeline_status_ttl_spec.rb
@@ -26,7 +26,16 @@ RSpec.describe Gitlab::BackgroundMigration::Redis::BackfillProjectPipelineStatus
describe '#scan_match_pattern' do
it "finds all the required keys only" do
- expect(redis.scan('0').second).to match_array(keys + invalid_keys)
+ cursor = '0'
+ scanned = []
+ loop do
+ # multiple scans are performed if it is a Redis cluster
+ cursor, result = redis.scan(cursor)
+ scanned.concat(result)
+ break if cursor == '0'
+ end
+
+ expect(scanned).to match_array(keys + invalid_keys)
expect(subject.redis.scan_each(match: subject.scan_match_pattern).to_a).to contain_exactly(*keys)
end
end
diff --git a/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb b/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb
index 582c0fe1b1b..af8b5240e40 100644
--- a/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb
+++ b/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveBackfilledJobArtifactsExpireAt
describe '#perform' do
let(:job_artifact) { table(:ci_job_artifacts, database: :ci) }
+ let(:jobs) { table(:ci_builds, database: :ci) { |model| model.primary_key = :id } }
let(:test_worker) do
described_class.new(
@@ -85,7 +86,7 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveBackfilledJobArtifactsExpireAt
private
def create_job_artifact(id:, file_type:, expire_at:)
- job = table(:ci_builds, database: :ci).create!(id: id, partition_id: 100)
+ job = jobs.create!(partition_id: 100)
job_artifact.create!(
id: id, job_id: job.id, expire_at: expire_at, project_id: project.id,
file_type: file_type, partition_id: 100
diff --git a/spec/lib/gitlab/blame_spec.rb b/spec/lib/gitlab/blame_spec.rb
index f636ce283ae..bfe2b7d1360 100644
--- a/spec/lib/gitlab/blame_spec.rb
+++ b/spec/lib/gitlab/blame_spec.rb
@@ -33,12 +33,18 @@ RSpec.describe Gitlab::Blame do
expect(subject.count).to eq(18)
expect(subject[0][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
expect(subject[0][:lines]).to eq(["require 'fileutils'", "require 'open3'", ""])
+ expect(subject[0][:span]).to eq(3)
+ expect(subject[0][:lineno]).to eq(1)
expect(subject[1][:commit].sha).to eq('874797c3a73b60d2187ed6e2fcabd289ff75171e')
expect(subject[1][:lines]).to eq(["module Popen", " extend self"])
+ expect(subject[1][:span]).to eq(2)
+ expect(subject[1][:lineno]).to eq(4)
expect(subject[-1][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
expect(subject[-1][:lines]).to eq([" end", "end"])
+ expect(subject[-1][:span]).to eq(2)
+ expect(subject[-1][:lineno]).to eq(36)
end
context 'with a range 1..5' do
diff --git a/spec/lib/gitlab/cache/json_cache_spec.rb b/spec/lib/gitlab/cache/json_cache_spec.rb
index 05126319ef9..1904e42f937 100644
--- a/spec/lib/gitlab/cache/json_cache_spec.rb
+++ b/spec/lib/gitlab/cache/json_cache_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::Cache::JsonCache, feature_category: :shared do
describe '#active?' do
context 'when backend respond to active? method' do
it 'delegates to the underlying cache implementation' do
- backend = instance_double(Gitlab::NullRequestStore, active?: false)
+ backend = instance_double(Gitlab::SafeRequestStore::NullStore, active?: false)
cache = described_class.new(namespace: namespace, backend: backend)
diff --git a/spec/lib/gitlab/cache/json_caches/json_keyed_spec.rb b/spec/lib/gitlab/cache/json_caches/json_keyed_spec.rb
index c4ec393c3ac..8afd5c2bfcd 100644
--- a/spec/lib/gitlab/cache/json_caches/json_keyed_spec.rb
+++ b/spec/lib/gitlab/cache/json_caches/json_keyed_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe Gitlab::Cache::JsonCaches::JsonKeyed, feature_category: :shared d
current_cache = { '_other_revision_' => '_other_value_' }.merge(nested_cache_result).to_json
allow(backend).to receive(:read).with(expanded_key).and_return(current_cache)
- expect(cache.read(key, BroadcastMessage)).to eq(broadcast_message)
+ expect(cache.read(key, System::BroadcastMessage)).to eq(broadcast_message)
end
end
end
diff --git a/spec/lib/gitlab/cache/json_caches/redis_keyed_spec.rb b/spec/lib/gitlab/cache/json_caches/redis_keyed_spec.rb
index 6e98cdd74ce..f408bbf8d25 100644
--- a/spec/lib/gitlab/cache/json_caches/redis_keyed_spec.rb
+++ b/spec/lib/gitlab/cache/json_caches/redis_keyed_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe Gitlab::Cache::JsonCaches::RedisKeyed, feature_category: :shared
allow(backend).to receive(:read).with(expanded_key).and_return(true)
expect(Gitlab::Json).to receive(:parse).with("true").and_call_original
- expect(cache.read(key, BroadcastMessage)).to eq(true)
+ expect(cache.read(key, System::BroadcastMessage)).to eq(true)
end
end
@@ -30,7 +30,7 @@ RSpec.describe Gitlab::Cache::JsonCaches::RedisKeyed, feature_category: :shared
allow(backend).to receive(:read).with(expanded_key).and_return(false)
expect(Gitlab::Json).to receive(:parse).with("false").and_call_original
- expect(cache.read(key, BroadcastMessage)).to eq(false)
+ expect(cache.read(key, System::BroadcastMessage)).to eq(false)
end
end
end
diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb
index 9950d4dbd12..c3d6b9510e5 100644
--- a/spec/lib/gitlab/checks/branch_check_spec.rb
+++ b/spec/lib/gitlab/checks/branch_check_spec.rb
@@ -38,6 +38,18 @@ RSpec.describe Gitlab::Checks::BranchCheck, feature_category: :source_code_manag
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
end
+ it "prohibits 64-character hexadecimal branch names" do
+ allow(subject).to receive(:branch_name).and_return("09b9fd3ea68e9b95a51b693a29568c898e27d1476bbd83c825664f18467fc175")
+
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
+ end
+
+ it "prohibits 64-character hexadecimal branch names as the start of a path" do
+ allow(subject).to receive(:branch_name).and_return("09b9fd3ea68e9b95a51b693a29568c898e27d1476bbd83c825664f18467fc175/test")
+
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
+ end
+
it "doesn't prohibit a nested hexadecimal in a branch name" do
allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e-fix")
diff --git a/spec/lib/gitlab/checks/file_size_check/allow_existing_oversized_blobs_spec.rb b/spec/lib/gitlab/checks/file_size_check/allow_existing_oversized_blobs_spec.rb
deleted file mode 100644
index 3b52d2e1364..00000000000
--- a/spec/lib/gitlab/checks/file_size_check/allow_existing_oversized_blobs_spec.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Checks::FileSizeCheck::AllowExistingOversizedBlobs, feature_category: :source_code_management do
- subject { checker.find }
-
- let_it_be(:project) { create(:project, :public, :repository) }
- let(:checker) do
- described_class.new(
- project: project,
- changes: changes,
- file_size_limit_megabytes: 1)
- end
-
- describe '#find' do
- let(:branch_name) { SecureRandom.uuid }
- let(:other_branch_name) { SecureRandom.uuid }
- let(:filename) { 'log.log' }
- let(:create_file) do
- project.repository.create_file(
- project.owner,
- filename,
- initial_contents,
- branch_name: branch_name,
- message: 'whatever'
- )
- end
-
- let(:changed_ref) do
- project.repository.update_file(
- project.owner,
- filename,
- changed_contents,
- branch_name: other_branch_name,
- start_branch_name: branch_name,
- message: 'whatever'
- )
- end
-
- let(:changes) { [oldrev: create_file, newrev: changed_ref] }
-
- before do
- # set up a branch
- create_file
-
- # branch off that branch
- changed_ref
-
- # delete stuff so it can be picked up by new_blobs
- project.repository.delete_branch(other_branch_name)
- end
-
- context 'when changing from valid to oversized' do
- let(:initial_contents) { 'a' }
- let(:changed_contents) { 'a' * ((2**20) + 1) } # 1 MB + 1 byte
-
- it 'returns an array with blobs that became oversized' do
- blob = subject.first
- expect(blob.path).to eq(filename)
- expect(subject).to contain_exactly(blob)
- end
- end
-
- context 'when changing from oversized to oversized' do
- let(:initial_contents) { 'a' * ((2**20) + 1) } # 1 MB + 1 byte
- let(:changed_contents) { 'a' * ((2**20) + 2) } # 1 MB + 1 byte
-
- it { is_expected.to be_blank }
- end
-
- context 'when changing from oversized to valid' do
- let(:initial_contents) { 'a' * ((2**20) + 1) } # 1 MB + 1 byte
- let(:changed_contents) { 'aa' }
-
- it { is_expected.to be_blank }
- end
-
- context 'when changing from valid to valid' do
- let(:initial_contents) { 'abc' }
- let(:changed_contents) { 'def' }
-
- it { is_expected.to be_blank }
- end
- end
-end
diff --git a/spec/lib/gitlab/checks/file_size_check/hook_environment_aware_any_oversized_blobs_spec.rb b/spec/lib/gitlab/checks/file_size_check/hook_environment_aware_any_oversized_blobs_spec.rb
new file mode 100644
index 00000000000..bea0c02cfb8
--- /dev/null
+++ b/spec/lib/gitlab/checks/file_size_check/hook_environment_aware_any_oversized_blobs_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Checks::FileSizeCheck::HookEnvironmentAwareAnyOversizedBlobs, feature_category: :source_code_management do
+ let_it_be(:project) { create(:project, :small_repo) }
+ let(:repository) { project.repository }
+ let(:file_size_limit) { 1 }
+ let(:any_quarantined_blobs) do
+ described_class.new(
+ project: project,
+ changes: changes,
+ file_size_limit_megabytes: file_size_limit)
+ end
+
+ let(:changes) { [{ newrev: 'master' }] }
+
+ describe '#find' do
+ subject { any_quarantined_blobs.find }
+
+ let(:stubbed_result) { 'stubbed' }
+
+ it 'returns the result from AnyOversizedBlobs' do
+ expect_next_instance_of(Gitlab::Checks::FileSizeCheck::AnyOversizedBlobs) do |instance|
+ expect(instance).to receive(:find).and_return(stubbed_result)
+ end
+
+ expect(subject).to eq(stubbed_result)
+ end
+
+ context 'with hook env' do
+ context 'with hook environment' do
+ let(:git_env) do
+ {
+ 'GIT_OBJECT_DIRECTORY_RELATIVE' => "objects",
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['/dir/one', '/dir/two']
+ }
+ end
+
+ before do
+ allow(Gitlab::Git::HookEnv).to receive(:all).with(repository.gl_repository).and_return(git_env)
+ end
+
+ it 'returns an emtpy array' do
+ expect(subject).to eq([])
+ end
+
+ context 'when the file is over the limit' do
+ let(:file_size_limit) { 0 }
+
+ context 'when the blob does not exist in the repo' do
+ before do
+ allow(repository.gitaly_commit_client).to receive(:object_existence_map).and_return(Hash.new { false })
+ end
+
+ it 'returns an array with the blobs that are over the limit' do
+ expect(subject.size).to eq(1)
+ expect(subject.first).to be_kind_of(Gitlab::Git::Blob)
+ end
+ end
+
+ context 'when the blob exists in the repo' do
+ before do
+ allow(repository.gitaly_commit_client).to receive(:object_existence_map).and_return(Hash.new { true })
+ end
+
+ it 'filters out the blobs in the repo' do
+ expect(subject).to eq([])
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/checks/global_file_size_check_spec.rb b/spec/lib/gitlab/checks/global_file_size_check_spec.rb
index 9ea0c73b1c7..a2b3ee0f761 100644
--- a/spec/lib/gitlab/checks/global_file_size_check_spec.rb
+++ b/spec/lib/gitlab/checks/global_file_size_check_spec.rb
@@ -14,13 +14,13 @@ RSpec.describe Gitlab::Checks::GlobalFileSizeCheck, feature_category: :source_co
it 'does not log' do
expect(subject).not_to receive(:log_timed)
expect(Gitlab::AppJsonLogger).not_to receive(:info)
- expect(Gitlab::Checks::FileSizeCheck::AllowExistingOversizedBlobs).not_to receive(:new)
+ expect(Gitlab::Checks::FileSizeCheck::HookEnvironmentAwareAnyOversizedBlobs).not_to receive(:new)
subject.validate!
end
end
it 'checks for file sizes' do
- expect_next_instance_of(Gitlab::Checks::FileSizeCheck::AllowExistingOversizedBlobs,
+ expect_next_instance_of(Gitlab::Checks::FileSizeCheck::HookEnvironmentAwareAnyOversizedBlobs,
project: project,
changes: changes,
file_size_limit_megabytes: 100
@@ -32,5 +32,35 @@ RSpec.describe Gitlab::Checks::GlobalFileSizeCheck, feature_category: :source_co
expect(Gitlab::AppJsonLogger).to receive(:info).with('Checking for blobs over the file size limit')
subject.validate!
end
+
+ context 'when there are oversized blobs' do
+ let(:blob_double) { instance_double(Gitlab::Git::Blob, size: 10) }
+
+ before do
+ allow_next_instance_of(Gitlab::Checks::FileSizeCheck::HookEnvironmentAwareAnyOversizedBlobs,
+ project: project,
+ changes: changes,
+ file_size_limit_megabytes: 100
+ ) do |check|
+ allow(check).to receive(:find).and_return([blob_double])
+ end
+ end
+
+ it 'logs a message with blob size and raises an exception' do
+ expect(Gitlab::AppJsonLogger).to receive(:info).with('Checking for blobs over the file size limit')
+ expect(Gitlab::AppJsonLogger).to receive(:info).with(message: 'Found blob over global limit', blob_sizes: [10])
+ expect { subject.validate! }.to raise_exception(Gitlab::GitAccess::ForbiddenError)
+ end
+
+ context 'when the enforce_global_file_size_limit feature flag is disabled' do
+ before do
+ stub_feature_flags(enforce_global_file_size_limit: false)
+ end
+
+ it 'does not raise an exception' do
+ expect { subject.validate! }.not_to raise_error
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/artifacts/decompressed_artifact_size_validator_spec.rb b/spec/lib/gitlab/ci/artifacts/decompressed_artifact_size_validator_spec.rb
index ef39a431d63..47d91e2478e 100644
--- a/spec/lib/gitlab/ci/artifacts/decompressed_artifact_size_validator_spec.rb
+++ b/spec/lib/gitlab/ci/artifacts/decompressed_artifact_size_validator_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Gitlab::Ci::Artifacts::DecompressedArtifactSizeValidator, feature
let(:gzip_valid?) { true }
let(:validator) { instance_double(::Gitlab::Ci::DecompressedGzipSizeValidator, valid?: gzip_valid?) }
- before(:all) do
+ before_all do
Zlib::GzipWriter.open(file_path) do |gz|
gz.write('Hello World!')
end
diff --git a/spec/lib/gitlab/ci/components/instance_path_spec.rb b/spec/lib/gitlab/ci/components/instance_path_spec.rb
index 511036efd37..f4bc706f9b4 100644
--- a/spec/lib/gitlab/ci/components/instance_path_spec.rb
+++ b/spec/lib/gitlab/ci/components/instance_path_spec.rb
@@ -106,7 +106,7 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
create(:release, project: existing_project, sha: 'sha-1', released_at: Time.zone.now)
end
- before(:all) do
+ before_all do
# Previous release
create(:release, project: existing_project, sha: 'sha-2', released_at: Time.zone.now - 1.day)
end
diff --git a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
index 10c1d92e209..dd15b049b9b 100644
--- a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
@@ -1,117 +1,132 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper' # Change this to fast spec helper when FF `ci_refactor_external_rules` is removed
require_dependency 'active_model'
RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule, feature_category: :pipeline_composition do
let(:factory) do
- Gitlab::Config::Entry::Factory.new(described_class)
- .value(config)
+ Gitlab::Config::Entry::Factory.new(described_class).value(config)
end
subject(:entry) { factory.create! }
- describe '.new' do
- shared_examples 'an invalid config' do |error_message|
- it { is_expected.not_to be_valid }
+ before do
+ entry.compose!
+ end
+
+ shared_examples 'a valid config' do
+ it { is_expected.to be_valid }
+
+ it 'returns the expected value' do
+ expect(entry.value).to eq(config.compact)
+ end
- it 'has errors' do
- expect(entry.errors).to include(error_message)
+ context 'when FF `ci_refactor_external_rules` is disabled' do
+ before do
+ stub_feature_flags(ci_refactor_external_rules: false)
end
+
+ it 'returns the expected value' do
+ expect(entry.value).to eq(config)
+ end
+ end
+ end
+
+ shared_examples 'an invalid config' do |error_message|
+ it { is_expected.not_to be_valid }
+
+ it 'has errors' do
+ expect(entry.errors).to include(error_message)
end
+ end
- context 'when specifying an if: clause' do
- let(:config) { { if: '$THIS || $THAT' } }
+ context 'when specifying an if: clause' do
+ let(:config) { { if: '$THIS || $THAT' } }
- it { is_expected.to be_valid }
+ it_behaves_like 'a valid config'
- context 'with when:' do
- let(:config) { { if: '$THIS || $THAT', when: 'never' } }
+ context 'with when:' do
+ let(:config) { { if: '$THIS || $THAT', when: 'never' } }
- it { is_expected.to be_valid }
- end
+ it_behaves_like 'a valid config'
end
- context 'when specifying an exists: clause' do
- let(:config) { { exists: './this.md' } }
+ context 'with when: <invalid string>' do
+ let(:config) { { if: '$THIS || $THAT', when: 'on_success' } }
- it { is_expected.to be_valid }
+ it_behaves_like 'an invalid config', /when unknown value: on_success/
end
- context 'using a list of multiple expressions' do
- let(:config) { { if: ['$MY_VAR == "this"', '$YOUR_VAR == "that"'] } }
+ context 'with when: null' do
+ let(:config) { { if: '$THIS || $THAT', when: nil } }
- it_behaves_like 'an invalid config', /invalid expression syntax/
+ it_behaves_like 'a valid config'
end
- context 'when specifying an invalid if: clause expression' do
- let(:config) { { if: ['$MY_VAR =='] } }
+ context 'when if: clause is invalid' do
+ let(:config) { { if: '$MY_VAR ==' } }
it_behaves_like 'an invalid config', /invalid expression syntax/
end
- context 'when specifying an if: clause expression with an invalid token' do
- let(:config) { { if: ['$MY_VAR == 123'] } }
+ context 'when if: clause has an integer operand' do
+ let(:config) { { if: '$MY_VAR == 123' } }
it_behaves_like 'an invalid config', /invalid expression syntax/
end
- context 'when using invalid regex in an if: clause' do
- let(:config) { { if: ['$MY_VAR =~ /some ( thing/'] } }
+ context 'when if: clause has invalid regex' do
+ let(:config) { { if: '$MY_VAR =~ /some ( thing/' } }
it_behaves_like 'an invalid config', /invalid expression syntax/
end
- context 'when using an if: clause with lookahead regex character "?"' do
+ context 'when if: clause has lookahead regex character "?"' do
let(:config) { { if: '$CI_COMMIT_REF =~ /^(?!master).+/' } }
it_behaves_like 'an invalid config', /invalid expression syntax/
end
- context 'when specifying unknown policy' do
- let(:config) { { invalid: :something } }
+ context 'when if: clause has array of expressions' do
+ let(:config) { { if: ['$MY_VAR == "this"', '$YOUR_VAR == "that"'] } }
- it_behaves_like 'an invalid config', /unknown keys: invalid/
+ it_behaves_like 'an invalid config', /invalid expression syntax/
end
+ end
+
+ context 'when specifying an exists: clause' do
+ let(:config) { { exists: './this.md' } }
- context 'when clause is empty' do
- let(:config) { {} }
+ it_behaves_like 'a valid config'
- it_behaves_like 'an invalid config', /can't be blank/
+ context 'when array' do
+ let(:config) { { exists: ['./this.md', './that.md'] } }
+
+ it_behaves_like 'a valid config'
end
- context 'when policy strategy does not match' do
- let(:config) { 'string strategy' }
+ context 'when null' do
+ let(:config) { { exists: nil } }
- it_behaves_like 'an invalid config', /should be a hash/
+ it_behaves_like 'a valid config'
end
end
- describe '#value' do
- subject(:value) { entry.value }
-
- context 'when specifying an if: clause' do
- let(:config) { { if: '$THIS || $THAT' } }
+ context 'when specifying an unknown keyword' do
+ let(:config) { { invalid: :something } }
- it 'returns the config' do
- expect(subject).to eq(if: '$THIS || $THAT')
- end
+ it_behaves_like 'an invalid config', /unknown keys: invalid/
+ end
- context 'with when:' do
- let(:config) { { if: '$THIS || $THAT', when: 'never' } }
+ context 'when config is blank' do
+ let(:config) { {} }
- it 'returns the config' do
- expect(subject).to eq(if: '$THIS || $THAT', when: 'never')
- end
- end
- end
+ it_behaves_like 'an invalid config', /can't be blank/
+ end
- context 'when specifying an exists: clause' do
- let(:config) { { exists: './test.md' } }
+ context 'when config type is invalid' do
+ let(:config) { 'invalid' }
- it 'returns the config' do
- expect(subject).to eq(exists: './test.md')
- end
- end
+ it_behaves_like 'an invalid config', /should be a hash/
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb
index d5988dbbb58..05db81abfc1 100644
--- a/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/include/rules_spec.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper' # Change this to fast spec helper when FF `ci_refactor_external_rules` is removed
require_dependency 'active_model'
-RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules do
+RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules, feature_category: :pipeline_composition do
let(:factory) do
Gitlab::Config::Entry::Factory.new(described_class)
.value(config)
@@ -77,23 +77,68 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules do
describe '#value' do
subject(:value) { entry.value }
- context 'with an "if"' do
- let(:config) do
- [{ if: '$THIS == "that"' }]
+ let(:config) do
+ [
+ { if: '$THIS == "that"' },
+ { if: '$SKIP', when: 'never' }
+ ]
+ end
+
+ it { is_expected.to eq([]) }
+
+ context 'when composed' do
+ before do
+ entry.compose!
end
- it { is_expected.to eq(config) }
+ it 'returns the composed entries value' do
+ expect(entry).to be_valid
+ is_expected.to eq(
+ [
+ { if: '$THIS == "that"' },
+ { if: '$SKIP', when: 'never' }
+ ]
+ )
+ end
+
+ context 'when invalid' do
+ let(:config) do
+ [
+ { if: '$THIS == "that"' },
+ { if: '$SKIP', invalid: 'invalid' }
+ ]
+ end
+
+ it 'returns the invalid config' do
+ expect(entry).not_to be_valid
+ is_expected.to eq(config)
+ end
+ end
end
- context 'with a list of two rules' do
- let(:config) do
- [
- { if: '$THIS == "that"' },
- { if: '$SKIP' }
- ]
+ context 'when FF `ci_refactor_external_rules` is disabled' do
+ before do
+ stub_feature_flags(ci_refactor_external_rules: false)
+ end
+
+ context 'with an "if"' do
+ let(:config) do
+ [{ if: '$THIS == "that"' }]
+ end
+
+ it { is_expected.to eq(config) }
end
- it { is_expected.to eq(config) }
+ context 'with a list of two rules' do
+ let(:config) do
+ [
+ { if: '$THIS == "that"' },
+ { if: '$SKIP' }
+ ]
+ end
+
+ it { is_expected.to eq(config) }
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/need_spec.rb b/spec/lib/gitlab/ci/config/entry/need_spec.rb
index ab2e8d4db78..eba9411560e 100644
--- a/spec/lib/gitlab/ci/config/entry/need_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/need_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Need, feature_category: :pipeline_composition do
subject(:need) { described_class.new(config) }
shared_examples 'job type' do
@@ -219,6 +219,81 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
it_behaves_like 'job type'
end
+
+ context 'when parallel:matrix has a value' do
+ before do
+ need.compose!
+ end
+
+ context 'and it is a string value' do
+ let(:config) do
+ { job: 'job_name', parallel: { matrix: [{ platform: 'p1', stack: 's1' }] } }
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+
+ describe '#value' do
+ it 'returns job needs configuration' do
+ expect(need.value).to eq(
+ name: 'job_name',
+ artifacts: true,
+ optional: false,
+ parallel: { matrix: [{ "platform" => ['p1'], "stack" => ['s1'] }] }
+ )
+ end
+ end
+
+ it_behaves_like 'job type'
+ end
+
+ context 'and it is an array value' do
+ let(:config) do
+ { job: 'job_name', parallel: { matrix: [{ platform: %w[p1 p2], stack: %w[s1 s2] }] } }
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+
+ describe '#value' do
+ it 'returns job needs configuration' do
+ expect(need.value).to eq(
+ name: 'job_name',
+ artifacts: true,
+ optional: false,
+ parallel: { matrix: [{ 'platform' => %w[p1 p2], 'stack' => %w[s1 s2] }] }
+ )
+ end
+ end
+
+ it_behaves_like 'job type'
+ end
+
+ context 'and it is a both an array and string value' do
+ let(:config) do
+ { job: 'job_name', parallel: { matrix: [{ platform: %w[p1 p2], stack: 's1' }] } }
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+
+ describe '#value' do
+ it 'returns job needs configuration' do
+ expect(need.value).to eq(
+ name: 'job_name',
+ artifacts: true,
+ optional: false,
+ parallel: { matrix: [{ 'platform' => %w[p1 p2], 'stack' => ['s1'] }] }
+ )
+ end
+ end
+
+ it_behaves_like 'job type'
+ end
+ end
end
context 'with cross pipeline artifacts needs' do
diff --git a/spec/lib/gitlab/ci/config/entry/needs_spec.rb b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
index 489fbac68b2..d1a8a74ac06 100644
--- a/spec/lib/gitlab/ci/config/entry/needs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
+RSpec.describe ::Gitlab::Ci::Config::Entry::Needs, feature_category: :pipeline_composition do
subject(:needs) { described_class.new(config) }
before do
@@ -67,6 +67,141 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
end
end
+ context 'when needs value is a hash' do
+ context 'with a job value' do
+ let(:config) do
+ { job: 'job_name' }
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'with a parallel value that is a numeric value' do
+ let(:config) do
+ { job: 'job_name', parallel: 2 }
+ end
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns errors about number values being invalid for needs:parallel' do
+ expect(needs.errors).to match_array(["needs config cannot use \"parallel: <number>\"."])
+ end
+ end
+ end
+ end
+
+ context 'when needs:parallel value is incorrect' do
+ context 'with a keyword that is not "matrix"' do
+ let(:config) do
+ [
+ { job: 'job_name', parallel: { not_matrix: [{ one: 'aaa', two: 'bbb' }] } }
+ ]
+ end
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns errors about incorrect matrix keyword' do
+ expect(needs.errors).to match_array([
+ 'need:parallel config contains unknown keys: not_matrix',
+ 'need:parallel config missing required keys: matrix'
+ ])
+ end
+ end
+ end
+
+ context 'with a number value' do
+ let(:config) { [{ job: 'job_name', parallel: 2 }] }
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns errors about number values being invalid for needs:parallel' do
+ expect(needs.errors).to match_array(["needs config cannot use \"parallel: <number>\"."])
+ end
+ end
+ end
+ end
+
+ context 'when needs:parallel:matrix value is empty' do
+ let(:config) { [{ job: 'job_name', parallel: { matrix: {} } }] }
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns error about incorrect type' do
+ expect(needs.errors).to contain_exactly(
+ 'need:parallel:matrix config should be an array of hashes')
+ end
+ end
+ end
+
+ context 'when needs:parallel:matrix value is incorrect' do
+ let(:config) { [{ job: 'job_name', parallel: { matrix: 'aaa' } }] }
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns error about incorrect type' do
+ expect(needs.errors).to contain_exactly(
+ 'need:parallel:matrix config should be an array of hashes')
+ end
+ end
+ end
+
+ context 'when needs:parallel:matrix value is correct' do
+ context 'with a simple config' do
+ let(:config) do
+ [
+ { job: 'job_name', parallel: { matrix: [{ A: 'a1', B: 'b1' }] } }
+ ]
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'with a complex config' do
+ let(:config) do
+ [
+ {
+ job: 'job_name1',
+ artifacts: true,
+ parallel: { matrix: [{ A: %w[a1 a2], B: %w[b1 b2 b3], C: %w[c1 c2] }] }
+ },
+ {
+ job: 'job_name2',
+ parallel: {
+ matrix: [
+ { A: %w[a1 a2], D: %w[d1 d2] },
+ { E: %w[e1 e2], F: ['f1'] },
+ { C: %w[c1 c2 c3], G: %w[g1 g2], H: ['h1'] }
+ ]
+ }
+ }
+ ]
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+ end
+ end
+
context 'with too many cross pipeline dependencies' do
let(:limit) { described_class::NEEDS_CROSS_PIPELINE_DEPENDENCIES_LIMIT }
diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
index 73bf2d422b7..d610c3ce2f6 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -48,6 +48,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports, feature_category: :pipeline_c
:terraform | 'tfplan.json'
:accessibility | 'gl-accessibility.json'
:cyclonedx | 'gl-sbom.cdx.zip'
+ :annotations | 'gl-annotations.json'
end
with_them do
diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb
index d917924f257..d8bd578be94 100644
--- a/spec/lib/gitlab/ci/config/external/context_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/context_spec.rb
@@ -57,6 +57,24 @@ RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipelin
end
end
end
+
+ describe 'max_total_yaml_size_bytes' do
+ context 'when application setting `max_total_yaml_size_bytes` is requsted and was never updated by the admin' do
+ it 'returns the default value `max_total_yaml_size_bytes`' do
+ expect(subject.max_total_yaml_size_bytes).to eq(157286400)
+ end
+ end
+
+ context 'when `max_total_yaml_size_bytes` was adjusted by the admin' do
+ before do
+ stub_application_setting(ci_max_total_yaml_size_bytes: 200000000)
+ end
+
+ it 'returns the updated value of application setting `max_total_yaml_size_bytes`' do
+ expect(subject.max_total_yaml_size_bytes).to eq(200000000)
+ end
+ end
+ end
end
describe '#set_deadline' do
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
index d6dd75f4b10..1415dbeb532 100644
--- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -254,7 +254,12 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
describe '#load_and_validate_expanded_hash!' do
let(:location) { 'some/file/config.yml' }
let(:logger) { instance_double(::Gitlab::Ci::Pipeline::Logger, :instrument) }
- let(:context_params) { { sha: 'HEAD', variables: variables, project: project, logger: logger } }
+ let(:context_params) { { sha: 'HEAD', variables: variables, project: project, logger: logger, user: user } }
+ let(:user) { instance_double(User, id: 'test-user-id') }
+
+ before do
+ allow(logger).to receive(:instrument).and_yield
+ end
it 'includes instrumentation for loading and expanding the content' do
expect(logger).to receive(:instrument).once.ordered.with(:config_file_fetch_content_hash).and_yield
@@ -262,5 +267,26 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipe
file.load_and_validate_expanded_hash!
end
+
+ context 'when the content is interpolated' do
+ let(:content) { "spec:\n inputs:\n website:\n---\nkey: value" }
+
+ subject(:file) { test_class.new({ inputs: { website: 'test' }, location: location, content: content }, ctx) }
+
+ it 'increments the ci_interpolation_users usage counter' do
+ expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
+ .with('ci_interpolation_users', values: 'test-user-id')
+
+ file.load_and_validate_expanded_hash!
+ end
+ end
+
+ context 'when the content is not interpolated' do
+ it 'does not increment the ci_interpolation_users usage counter' do
+ expect(::Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+
+ file.load_and_validate_expanded_hash!
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/component_spec.rb b/spec/lib/gitlab/ci/config/external/file/component_spec.rb
index 7e3406413d0..487690296b5 100644
--- a/spec/lib/gitlab/ci/config/external/file/component_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/component_spec.rb
@@ -41,14 +41,6 @@ RSpec.describe Gitlab::Ci::Config::External::File::Component, feature_category:
let(:params) { { component: 'some-value' } }
it { is_expected.to be_truthy }
-
- context 'when feature flag ci_include_components is disabled' do
- before do
- stub_feature_flags(ci_include_components: false)
- end
-
- it { is_expected.to be_falsey }
- end
end
context 'when component is not specified' do
diff --git a/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb
index 719c75dca80..cea65faccd7 100644
--- a/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb
@@ -18,54 +18,26 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Matcher, feature_category:
describe '#process' do
subject(:process) { matcher.process(locations) }
- context 'with ci_include_components FF disabled' do
- before do
- stub_feature_flags(ci_include_components: false)
- end
-
- let(:locations) do
- [
- { local: 'file.yml' },
- { file: 'file.yml', project: 'namespace/project' },
- { remote: 'https://example.com/.gitlab-ci.yml' },
- { template: 'file.yml' },
- { artifact: 'generated.yml', job: 'test' }
- ]
- end
-
- it 'returns an array of file objects' do
- is_expected.to contain_exactly(
- an_instance_of(Gitlab::Ci::Config::External::File::Local),
- an_instance_of(Gitlab::Ci::Config::External::File::Project),
- an_instance_of(Gitlab::Ci::Config::External::File::Remote),
- an_instance_of(Gitlab::Ci::Config::External::File::Template),
- an_instance_of(Gitlab::Ci::Config::External::File::Artifact)
- )
- end
+ let(:locations) do
+ [
+ { local: 'file.yml' },
+ { file: 'file.yml', project: 'namespace/project' },
+ { component: 'gitlab.com/org/component@1.0' },
+ { remote: 'https://example.com/.gitlab-ci.yml' },
+ { template: 'file.yml' },
+ { artifact: 'generated.yml', job: 'test' }
+ ]
end
- context 'with ci_include_components FF enabled' do
- let(:locations) do
- [
- { local: 'file.yml' },
- { file: 'file.yml', project: 'namespace/project' },
- { component: 'gitlab.com/org/component@1.0' },
- { remote: 'https://example.com/.gitlab-ci.yml' },
- { template: 'file.yml' },
- { artifact: 'generated.yml', job: 'test' }
- ]
- end
-
- it 'returns an array of file objects' do
- is_expected.to contain_exactly(
- an_instance_of(Gitlab::Ci::Config::External::File::Local),
- an_instance_of(Gitlab::Ci::Config::External::File::Project),
- an_instance_of(Gitlab::Ci::Config::External::File::Component),
- an_instance_of(Gitlab::Ci::Config::External::File::Remote),
- an_instance_of(Gitlab::Ci::Config::External::File::Template),
- an_instance_of(Gitlab::Ci::Config::External::File::Artifact)
- )
- end
+ it 'returns an array of file objects' do
+ is_expected.to contain_exactly(
+ an_instance_of(Gitlab::Ci::Config::External::File::Local),
+ an_instance_of(Gitlab::Ci::Config::External::File::Project),
+ an_instance_of(Gitlab::Ci::Config::External::File::Component),
+ an_instance_of(Gitlab::Ci::Config::External::File::Remote),
+ an_instance_of(Gitlab::Ci::Config::External::File::Template),
+ an_instance_of(Gitlab::Ci::Config::External::File::Artifact)
+ )
end
context 'when a location is not valid' do
diff --git a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
index e7dd5bd5079..69b0524be9e 100644
--- a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
@@ -364,5 +364,77 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category:
end
end
end
+
+ describe '#verify_max_total_pipeline_size' do
+ let(:files) do
+ [
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context),
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file2.yml' }, context)
+ ]
+ end
+
+ let(:project_files) do
+ {
+ 'myfolder/file1.yml' => <<~YAML,
+ build:
+ script: echo Hello World
+ YAML
+ 'myfolder/file2.yml' => <<~YAML
+ include:
+ - local: myfolder/file1.yml
+ build:
+ script: echo Hello from the other file
+ YAML
+ }
+ end
+
+ context 'when pipeline tree size is within the limit' do
+ before do
+ stub_application_setting(ci_max_total_yaml_size_bytes: 10000)
+ end
+
+ it 'passes the verification' do
+ expect(process.all?(&:valid?)).to be_truthy
+ end
+ end
+
+ context 'when pipeline tree size is larger then the limit' do
+ before do
+ stub_application_setting(ci_max_total_yaml_size_bytes: 50)
+ end
+
+ let(:expected_error_class) { Gitlab::Ci::Config::External::Mapper::TooMuchDataInPipelineTreeError }
+
+ it 'raises a limit error' do
+ expect { process }.to raise_error(expected_error_class)
+ end
+ end
+
+ context 'when introduce_ci_max_total_yaml_size_bytes is disabled' do
+ before do
+ stub_feature_flags(introduce_ci_max_total_yaml_size_bytes: false)
+ end
+
+ context 'when pipeline tree size is within the limit' do
+ before do
+ stub_application_setting(ci_max_total_yaml_size_bytes: 10000)
+ end
+
+ it 'passes the verification' do
+ expect(process.all?(&:valid?)).to be_truthy
+ end
+ end
+
+ context 'when pipeline tree size is larger then the limit' do
+ before do
+ stub_application_setting(ci_max_total_yaml_size_bytes: 100)
+ end
+
+ it 'passes the verification' do
+ expect(process.all?(&:valid?)).to be_truthy
+ end
+ end
+ end
+ end
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 935b6989dd7..19113ce6a4e 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -425,17 +425,6 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
output = processor.perform
expect(output.keys).to match_array([:image, :component_x_job])
end
-
- context 'when feature flag ci_include_components is disabled' do
- before do
- stub_feature_flags(ci_include_components: false)
- end
-
- it 'returns an error' do
- expect { processor.perform }
- .to raise_error(described_class::IncludeError, /does not have a valid subkey for include./)
- end
- end
end
context 'when a valid project file is defined' do
@@ -572,7 +561,17 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
end
it 'raises IncludeError' do
- expect { subject }.to raise_error(described_class::IncludeError, /invalid include rule/)
+ expect { subject }.to raise_error(described_class::IncludeError, /contains unknown keys: changes/)
+ end
+
+ context 'when FF `ci_refactor_external_rules` is disabled' do
+ before do
+ stub_feature_flags(ci_refactor_external_rules: false)
+ end
+
+ it 'raises IncludeError' do
+ expect { subject }.to raise_error(described_class::IncludeError, /invalid include rule/)
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/rules_spec.rb b/spec/lib/gitlab/ci/config/external/rules_spec.rb
index 25b7998ef5e..8674af7ab65 100644
--- a/spec/lib/gitlab/ci/config/external/rules_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/rules_spec.rb
@@ -76,8 +76,7 @@ RSpec.describe Gitlab::Ci::Config::External::Rules, feature_category: :pipeline_
let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] }
it 'raises an error' do
- expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
- 'invalid include rule: {:if=>"$MY_VAR == \"hello\"", :when=>"on_success"}')
+ expect { result }.to raise_error(described_class::InvalidIncludeRulesError, /when unknown value: on_success/)
end
end
@@ -105,8 +104,7 @@ RSpec.describe Gitlab::Ci::Config::External::Rules, feature_category: :pipeline_
let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'on_success' }] }
it 'raises an error' do
- expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
- 'invalid include rule: {:exists=>"Dockerfile", :when=>"on_success"}')
+ expect { result }.to raise_error(described_class::InvalidIncludeRulesError, /when unknown value: on_success/)
end
end
@@ -121,8 +119,94 @@ RSpec.describe Gitlab::Ci::Config::External::Rules, feature_category: :pipeline_
let(:rule_hashes) { [{ changes: ['$MY_VAR'] }] }
it 'raises an error' do
- expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
- 'invalid include rule: {:changes=>["$MY_VAR"]}')
+ expect { result }.to raise_error(described_class::InvalidIncludeRulesError, /contains unknown keys: changes/)
+ end
+ end
+
+ context 'when FF `ci_refactor_external_rules` is disabled' do
+ before do
+ stub_feature_flags(ci_refactor_external_rules: false)
+ end
+
+ context 'when there is no rule' do
+ let(:rule_hashes) {}
+
+ it { is_expected.to eq(true) }
+ end
+
+ it_behaves_like 'when there is a rule with if'
+
+ context 'when there is a rule with exists' do
+ let(:rule_hashes) { [{ exists: 'Dockerfile' }] }
+
+ it_behaves_like 'when there is a rule with exists'
+ end
+
+ context 'when there is a rule with if and when' do
+ context 'with when: never' do
+ let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'never' }] }
+
+ it_behaves_like 'when there is a rule with if', false, false
+ end
+
+ context 'with when: always' do
+ let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'always' }] }
+
+ it_behaves_like 'when there is a rule with if'
+ end
+
+ context 'with when: <invalid string>' do
+ let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] }
+
+ it 'raises an error' do
+ expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
+ 'invalid include rule: {:if=>"$MY_VAR == \"hello\"", :when=>"on_success"}')
+ end
+ end
+
+ context 'with when: null' do
+ let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: nil }] }
+
+ it_behaves_like 'when there is a rule with if'
+ end
+ end
+
+ context 'when there is a rule with exists and when' do
+ context 'with when: never' do
+ let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'never' }] }
+
+ it_behaves_like 'when there is a rule with exists', false, false
+ end
+
+ context 'with when: always' do
+ let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'always' }] }
+
+ it_behaves_like 'when there is a rule with exists'
+ end
+
+ context 'with when: <invalid string>' do
+ let(:rule_hashes) { [{ exists: 'Dockerfile', when: 'on_success' }] }
+
+ it 'raises an error' do
+ expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
+ 'invalid include rule: {:exists=>"Dockerfile", :when=>"on_success"}')
+ end
+ end
+
+ context 'with when: null' do
+ let(:rule_hashes) { [{ exists: 'Dockerfile', when: nil }] }
+
+ it_behaves_like 'when there is a rule with exists'
+ end
+ end
+
+ context 'when there is a rule with changes' do
+ let(:rule_hashes) { [{ changes: ['$MY_VAR'] }] }
+
+ it 'raises an error' do
+ expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
+ 'invalid include rule: {:changes=>["$MY_VAR"]}')
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/header/input_spec.rb b/spec/lib/gitlab/ci/config/header/input_spec.rb
index 73b5b8f9497..b5155dff6e8 100644
--- a/spec/lib/gitlab/ci/config/header/input_spec.rb
+++ b/spec/lib/gitlab/ci/config/header/input_spec.rb
@@ -46,12 +46,29 @@ RSpec.describe Gitlab::Ci::Config::Header::Input, feature_category: :pipeline_co
it_behaves_like 'a valid input'
end
- context 'when is a required required input' do
+ context 'when is a required input' do
let(:input_hash) { nil }
it_behaves_like 'a valid input'
end
+ context 'when given a valid type' do
+ where(:input_type) { ::Gitlab::Ci::Config::Interpolation::Inputs.input_types }
+
+ with_them do
+ let(:input_hash) { { type: input_type } }
+
+ it_behaves_like 'a valid input'
+ end
+ end
+
+ context 'when given an invalid type' do
+ let(:input_hash) { { type: 'datetime' } }
+ let(:expected_errors) { ['foo input type unknown value: datetime'] }
+
+ it_behaves_like 'an invalid input'
+ end
+
context 'when contains unknown keywords' do
let(:input_hash) { { test: 123 } }
let(:expected_errors) { ['foo config contains unknown keys: test'] }
diff --git a/spec/lib/gitlab/ci/interpolation/access_spec.rb b/spec/lib/gitlab/ci/config/interpolation/access_spec.rb
index f327377b7e3..ee414c209f7 100644
--- a/spec/lib/gitlab/ci/interpolation/access_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/access_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-RSpec.describe Gitlab::Ci::Interpolation::Access, feature_category: :pipeline_composition do
+RSpec.describe Gitlab::Ci::Config::Interpolation::Access, feature_category: :pipeline_composition do
subject { described_class.new(access, ctx) }
let(:access) do
@@ -46,4 +46,13 @@ RSpec.describe Gitlab::Ci::Interpolation::Access, feature_category: :pipeline_co
.to eq 'invalid interpolation access pattern'
end
end
+
+ context 'when a non-existent key is accessed' do
+ let(:access) { 'inputs.nonexistent' }
+
+ it 'returns an error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to eq('unknown interpolation key: `nonexistent`')
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/interpolation/block_spec.rb b/spec/lib/gitlab/ci/config/interpolation/block_spec.rb
new file mode 100644
index 00000000000..bfaa4eb3e05
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/block_spec.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Block, feature_category: :pipeline_composition do
+ subject { described_class.new(block, data, ctx) }
+
+ let(:data) do
+ 'inputs.data'
+ end
+
+ let(:block) do
+ "$[[ #{data} ]]"
+ end
+
+ let(:ctx) do
+ { inputs: { data: 'abcdef' }, env: { 'ENV' => 'dev' } }
+ end
+
+ it 'knows its content' do
+ expect(subject.content).to eq 'inputs.data'
+ end
+
+ it 'properly evaluates the access pattern' do
+ expect(subject.value).to eq 'abcdef'
+ end
+
+ describe '.match' do
+ it 'matches each block in a string' do
+ expect { |b| described_class.match('$[[ access1 ]] $[[ access2 ]]', &b) }
+ .to yield_successive_args(['$[[ access1 ]]', 'access1'], ['$[[ access2 ]]', 'access2'])
+ end
+
+ it 'matches an empty block' do
+ expect { |b| described_class.match('$[[]]', &b) }
+ .to yield_with_args('$[[]]', '')
+ end
+
+ context 'when functions are specified in the block' do
+ it 'matches each block in a string' do
+ expect { |b| described_class.match('$[[ access1 | func1 ]] $[[ access2 | func1 | func2(0,1) ]]', &b) }
+ .to yield_successive_args(['$[[ access1 | func1 ]]', 'access1 | func1'],
+ ['$[[ access2 | func1 | func2(0,1) ]]', 'access2 | func1 | func2(0,1)'])
+ end
+ end
+ end
+
+ describe 'when functions are specified in the block' do
+ let(:function_string1) { 'truncate(1,5)' }
+ let(:data) { "inputs.data | #{function_string1}" }
+ let(:access_value) { 'abcdef' }
+
+ it 'returns the modified value' do
+ expect(subject).to be_valid
+ expect(subject.value).to eq('bcdef')
+ end
+
+ context 'when there is an access error' do
+ let(:data) { "inputs.undefined | #{function_string1}" }
+
+ it 'returns the access error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to eq('unknown interpolation key: `undefined`')
+ end
+ end
+
+ context 'when there is a function error' do
+ let(:data) { 'inputs.data | undefined' }
+
+ it 'returns the function error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to match(/no function matching `undefined`/)
+ end
+ end
+
+ context 'when multiple functions are specified' do
+ let(:function_string2) { 'truncate(2,2)' }
+ let(:data) { "inputs.data | #{function_string1} | #{function_string2}" }
+
+ it 'executes each function in the specified order' do
+ expect(subject.value).to eq('de')
+ end
+
+ context 'when the data has inconsistent spacing' do
+ let(:data) { "inputs.data|#{function_string1} | #{function_string2} " }
+
+ it 'executes each function in the specified order' do
+ expect(subject.value).to eq('de')
+ end
+ end
+
+ context 'when a stack of functions errors in the middle' do
+ let(:function_string2) { 'truncate(2)' }
+
+ it 'does not modify the value' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to match(/no function matching `truncate\(2\)`/)
+ expect(subject.instance_variable_get(:@value)).to be_nil
+ end
+ end
+
+ context 'when too many functions are specified' do
+ it 'returns error' do
+ stub_const('Gitlab::Ci::Config::Interpolation::Block::MAX_FUNCTIONS', 1)
+
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to eq('too many functions in interpolation block')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/config_spec.rb b/spec/lib/gitlab/ci/config/interpolation/config_spec.rb
new file mode 100644
index 00000000000..1731e954906
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/config_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Config, feature_category: :pipeline_composition do
+ subject { described_class.new(YAML.safe_load(config)) }
+
+ let(:config) do
+ <<~CFG
+ test:
+ spec:
+ env: $[[ inputs.env ]]
+
+ $[[ inputs.key ]]:
+ name: $[[ inputs.key ]]
+ script: my-value
+ CFG
+ end
+
+ describe '.fabricate' do
+ subject { described_class.fabricate(config) }
+
+ context 'when given an Interpolation::Config' do
+ let(:config) { described_class.new(YAML.safe_load('yaml:')) }
+
+ it 'returns the given config' do
+ is_expected.to be(config)
+ end
+ end
+
+ context 'when given an unknown object' do
+ let(:config) { [] }
+
+ it 'raises an ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError, 'unknown interpolation config')
+ end
+ end
+ end
+
+ describe '#replace!' do
+ it 'replaces each of the nodes with a block return value' do
+ result = subject.replace! { |node| "abc#{node}cde" }
+
+ expect(result).to eq({
+ 'abctestcde' => { 'abcspeccde' => { 'abcenvcde' => 'abc$[[ inputs.env ]]cde' } },
+ 'abc$[[ inputs.key ]]cde' => {
+ 'abcnamecde' => 'abc$[[ inputs.key ]]cde',
+ 'abcscriptcde' => 'abcmy-valuecde'
+ }
+ })
+ expect(subject.to_h).to eq({
+ '$[[ inputs.key ]]' => { 'name' => '$[[ inputs.key ]]', 'script' => 'my-value' },
+ 'test' => { 'spec' => { 'env' => '$[[ inputs.env ]]' } }
+ })
+ end
+
+ context 'when config size is exceeded' do
+ before do
+ stub_const("#{described_class}::MAX_NODES", 7)
+ end
+
+ it 'returns a config size error' do
+ replaced = 0
+
+ subject.replace! { replaced += 1 }
+
+ expect(replaced).to eq 4
+ expect(subject.errors.size).to eq 1
+ expect(subject.errors.first).to eq 'config too large'
+ end
+ end
+
+ context 'when node size is exceeded' do
+ before do
+ stub_const("#{described_class}::MAX_NODE_SIZE", 1)
+ end
+
+ it 'returns a config size error' do
+ subject.replace! { |node| "abc#{node}cde" }
+
+ expect(subject.errors.size).to eq 1
+ expect(subject.errors.first).to eq 'config node too large'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/interpolation/context_spec.rb b/spec/lib/gitlab/ci/config/interpolation/context_spec.rb
index 2b126f4a8b3..c90866c986a 100644
--- a/spec/lib/gitlab/ci/interpolation/context_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/context_spec.rb
@@ -2,13 +2,27 @@
require 'fast_spec_helper'
-RSpec.describe Gitlab::Ci::Interpolation::Context, feature_category: :pipeline_composition do
+RSpec.describe Gitlab::Ci::Config::Interpolation::Context, feature_category: :pipeline_composition do
subject { described_class.new(ctx) }
let(:ctx) do
{ inputs: { key: 'abc' } }
end
+ describe '.fabricate' do
+ context 'when given an unexpected object' do
+ it 'raises an ArgumentError' do
+ expect { described_class.fabricate([]) }.to raise_error(ArgumentError, 'unknown interpolation context')
+ end
+ end
+ end
+
+ describe '#to_h' do
+ it 'returns the context hash' do
+ expect(subject.to_h).to eq(ctx)
+ end
+ end
+
describe '#depth' do
it 'returns a max depth of the hash' do
expect(subject.depth).to eq 2
diff --git a/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb
new file mode 100644
index 00000000000..c193e88dbe2
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Functions::Base, feature_category: :pipeline_composition do
+ let(:custom_function_klass) do
+ Class.new(described_class) do
+ def self.function_expression_pattern
+ /.*/
+ end
+
+ def self.name
+ 'test_function'
+ end
+ end
+ end
+
+ it 'defines an expected interface for child classes' do
+ expect { described_class.function_expression_pattern }.to raise_error(NotImplementedError)
+ expect { described_class.name }.to raise_error(NotImplementedError)
+ expect { custom_function_klass.new('test').execute('input') }.to raise_error(NotImplementedError)
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb
new file mode 100644
index 00000000000..c521eff9811
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Functions::Truncate, feature_category: :pipeline_composition do
+ it 'matches exactly the truncate function with 2 numeric arguments' do
+ expect(described_class.matches?('truncate(1,2)')).to be_truthy
+ expect(described_class.matches?('truncate( 11 , 222 )')).to be_truthy
+ expect(described_class.matches?('truncate( string , 222 )')).to be_falsey
+ expect(described_class.matches?('truncate(222)')).to be_falsey
+ expect(described_class.matches?('unknown(1,2)')).to be_falsey
+ end
+
+ it 'truncates the given input' do
+ function = described_class.new('truncate(1,2)')
+
+ output = function.execute('test')
+
+ expect(function).to be_valid
+ expect(output).to eq('es')
+ end
+
+ context 'when given a non-string input' do
+ it 'returns an error' do
+ function = described_class.new('truncate(1,2)')
+
+ function.execute(100)
+
+ expect(function).not_to be_valid
+ expect(function.errors).to contain_exactly(
+ 'error in `truncate` function: invalid input type: truncate can only be used with string inputs'
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb
new file mode 100644
index 00000000000..881f092c440
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::FunctionsStack, feature_category: :pipeline_composition do
+ let(:functions) { ['truncate(0,4)', 'truncate(1,2)'] }
+ let(:input_value) { 'test_input_value' }
+
+ subject { described_class.new(functions).evaluate(input_value) }
+
+ it 'modifies the given input value according to the function expressions' do
+ expect(subject).to be_success
+ expect(subject.value).to eq('es')
+ end
+
+ context 'when applying a function fails' do
+ let(:input_value) { 666 }
+
+ it 'returns the error given by the failure' do
+ expect(subject).not_to be_success
+ expect(subject.errors).to contain_exactly(
+ 'error in `truncate` function: invalid input type: truncate can only be used with string inputs'
+ )
+ end
+ end
+
+ context 'when function expressions do not match any function' do
+ let(:functions) { ['truncate(0)', 'unknown'] }
+
+ it 'returns an error' do
+ expect(subject).not_to be_success
+ expect(subject.errors).to contain_exactly(
+ 'no function matching `truncate(0)`: check that the function name, arguments, and types are correct',
+ 'no function matching `unknown`: check that the function name, arguments, and types are correct'
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb b/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb
new file mode 100644
index 00000000000..30036ee68ed
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Inputs::BaseInput, feature_category: :pipeline_composition do
+ describe '.matches?' do
+ it 'is not implemented' do
+ expect { described_class.matches?(double) }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '.type_name' do
+ it 'is not implemented' do
+ expect { described_class.type_name }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#valid_value?' do
+ it 'is not implemented' do
+ expect do
+ described_class.new(
+ name: 'website', spec: { website: nil }, value: { website: 'example.com' }
+ ).valid_value?('test')
+ end.to raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb b/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb
new file mode 100644
index 00000000000..ea06f181fa4
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/inputs_spec.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::Inputs, feature_category: :pipeline_composition do
+ let(:inputs) { described_class.new(specs, args) }
+ let(:specs) { { foo: { default: 'bar' } } }
+ let(:args) { {} }
+
+ context 'when inputs are valid' do
+ where(:specs, :args, :merged) do
+ [
+ [
+ { foo: { default: 'bar' } }, {},
+ { foo: 'bar' }
+ ],
+ [
+ { foo: { default: 'bar' } }, { foo: 'test' },
+ { foo: 'test' }
+ ],
+ [
+ { foo: nil }, { foo: 'bar' },
+ { foo: 'bar' }
+ ],
+ [
+ { foo: { type: 'string' } }, { foo: 'bar' },
+ { foo: 'bar' }
+ ],
+ [
+ { foo: { type: 'string', default: 'bar' } }, { foo: 'test' },
+ { foo: 'test' }
+ ],
+ [
+ { foo: { type: 'string', default: 'bar' } }, {},
+ { foo: 'bar' }
+ ],
+ [
+ { foo: { default: 'bar' }, baz: nil }, { baz: 'test' },
+ { foo: 'bar', baz: 'test' }
+ ],
+ [
+ { number_input: { type: 'number' } },
+ { number_input: 8 },
+ { number_input: 8 }
+ ],
+ [
+ { default_number_input: { default: 9, type: 'number' } },
+ {},
+ { default_number_input: 9 }
+ ],
+ [
+ { true_input: { type: 'boolean' }, false_input: { type: 'boolean' } },
+ { true_input: true, false_input: false },
+ { true_input: true, false_input: false }
+ ],
+ [
+ { default_boolean_input: { default: true, type: 'boolean' } },
+ {},
+ { default_boolean_input: true }
+ ]
+ ]
+ end
+
+ with_them do
+ it 'contains the merged inputs' do
+ expect(inputs).to be_valid
+ expect(inputs.to_hash).to eq(merged)
+ end
+ end
+ end
+
+ context 'when inputs are invalid' do
+ where(:specs, :args, :errors) do
+ [
+ [
+ { foo: nil }, { foo: 'bar', test: 'bar' },
+ ['unknown input arguments: test']
+ ],
+ [
+ { foo: nil }, { test: 'bar', gitlab: '1' },
+ ['unknown input arguments: test, gitlab', '`foo` input: required value has not been provided']
+ ],
+ [
+ { foo: 123 }, {},
+ ['unknown input specification for `foo` (valid types: boolean, number, string)']
+ ],
+ [
+ { a: nil, foo: 123 }, { a: '123' },
+ ['unknown input specification for `foo` (valid types: boolean, number, string)']
+ ],
+ [
+ { foo: nil }, {},
+ ['`foo` input: required value has not been provided']
+ ],
+ [
+ { foo: { default: 123 } }, { foo: 'test' },
+ ['`foo` input: default value is not a string']
+ ],
+ [
+ { foo: { default: 'test' } }, { foo: 123 },
+ ['`foo` input: provided value is not a string']
+ ],
+ [
+ { foo: nil }, { foo: 123 },
+ ['`foo` input: provided value is not a string']
+ ],
+ [
+ { number_input: { type: 'number' } },
+ { number_input: 'NaN' },
+ ['`number_input` input: provided value is not a number']
+ ],
+ [
+ { default_number_input: { default: 'NaN', type: 'number' } },
+ {},
+ ['`default_number_input` input: default value is not a number']
+ ],
+ [
+ { boolean_input: { type: 'boolean' } },
+ { boolean_input: 'string' },
+ ['`boolean_input` input: provided value is not a boolean']
+ ],
+ [
+ { default_boolean_input: { default: 'string', type: 'boolean' } },
+ {},
+ ['`default_boolean_input` input: default value is not a boolean']
+ ]
+ ]
+ end
+
+ with_them do
+ it 'contains the merged inputs', :aggregate_failures do
+ expect(inputs).not_to be_valid
+ expect(inputs.errors).to contain_exactly(*errors)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/yaml/interpolator_spec.rb b/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb
index 888756a3eb1..7bb09d35064 100644
--- a/spec/lib/gitlab/ci/config/yaml/interpolator_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb
@@ -2,13 +2,12 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeline_composition do
+RSpec.describe Gitlab::Ci::Config::Interpolation::Interpolator, feature_category: :pipeline_composition do
let_it_be(:project) { create(:project) }
- let(:current_user) { build(:user, id: 1234) }
let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(config: [header, content]) }
- subject { described_class.new(result, arguments, current_user: current_user) }
+ subject { described_class.new(result, arguments) }
context 'when input data is valid' do
let(:header) do
@@ -26,16 +25,10 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeli
it 'correctly interpolates the config' do
subject.interpolate!
+ expect(subject).to be_interpolated
expect(subject).to be_valid
expect(subject.to_hash).to eq({ test: 'deploy gitlab.com' })
end
-
- it 'tracks the event' do
- expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
- .with('ci_interpolation_users', { values: 1234 })
-
- subject.interpolate!
- end
end
context 'when config has a syntax error' do
@@ -54,6 +47,20 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeli
end
end
+ context 'when spec header is missing but inputs are specified' do
+ let(:header) { nil }
+ let(:content) { { test: 'echo' } }
+ let(:arguments) { { foo: 'bar' } }
+
+ it 'surfaces an error about invalid inputs' do
+ subject.interpolate!
+
+ expect(subject).not_to be_valid
+ expect(subject.error_message).to eq subject.errors.first
+ expect(subject.errors).to include('unknown input arguments')
+ end
+ end
+
context 'when spec header is invalid' do
let(:header) do
{ spec: { arguments: { website: nil } } }
@@ -76,47 +83,47 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeli
end
end
- context 'when interpolation block is invalid' do
+ context 'when provided interpolation argument is invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
- { test: 'deploy $[[ inputs.abc ]]' }
+ { test: 'deploy $[[ inputs.website ]]' }
end
let(:arguments) do
- { website: 'gitlab.com' }
+ { website: ['gitlab.com'] }
end
- it 'correctly interpolates the config' do
+ it 'returns an error' do
subject.interpolate!
expect(subject).not_to be_valid
- expect(subject.errors).to include 'unknown interpolation key: `abc`'
- expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `abc`'
+ expect(subject.error_message).to eq subject.errors.first
+ expect(subject.errors).to include '`website` input: provided value is not a string'
end
end
- context 'when provided interpolation argument is invalid' do
+ context 'when interpolation block is invalid' do
let(:header) do
{ spec: { inputs: { website: nil } } }
end
let(:content) do
- { test: 'deploy $[[ inputs.website ]]' }
+ { test: 'deploy $[[ inputs.abc ]]' }
end
let(:arguments) do
- { website: ['gitlab.com'] }
+ { website: 'gitlab.com' }
end
- it 'correctly interpolates the config' do
+ it 'returns an error' do
subject.interpolate!
expect(subject).not_to be_valid
- expect(subject.error_message).to eq subject.errors.first
- expect(subject.errors).to include 'unsupported value in input argument `website`'
+ expect(subject.errors).to include 'unknown interpolation key: `abc`'
+ expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `abc`'
end
end
@@ -133,11 +140,12 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Interpolator, feature_category: :pipeli
{ website: 'gitlab.com' }
end
- it 'correctly interpolates the config' do
+ it 'returns an error' do
subject.interpolate!
expect(subject).not_to be_valid
- expect(subject.error_message).to eq 'interpolation interrupted by errors, unknown interpolation key: `something`'
+ expect(subject.error_message)
+ .to eq 'interpolation interrupted by errors, unknown interpolation key: `something`'
end
end
diff --git a/spec/lib/gitlab/ci/interpolation/template_spec.rb b/spec/lib/gitlab/ci/config/interpolation/template_spec.rb
index a3ef1bb4445..c7d88822558 100644
--- a/spec/lib/gitlab/ci/interpolation/template_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/template_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-RSpec.describe Gitlab::Ci::Interpolation::Template, feature_category: :pipeline_composition do
+RSpec.describe Gitlab::Ci::Config::Interpolation::Template, feature_category: :pipeline_composition do
subject { described_class.new(YAML.safe_load(config), ctx) }
let(:config) do
@@ -67,7 +67,7 @@ RSpec.describe Gitlab::Ci::Interpolation::Template, feature_category: :pipeline_
context 'when template contains symbols that need interpolation' do
subject do
- described_class.new({ '$[[ inputs.key ]]'.to_sym => 'cde' }, ctx)
+ described_class.new({ '$[[ inputs.key ]]': 'cde' }, ctx)
end
it 'performs a valid interpolation' do
@@ -78,7 +78,7 @@ RSpec.describe Gitlab::Ci::Interpolation::Template, feature_category: :pipeline_
context 'when template is too large' do
before do
- stub_const('Gitlab::Ci::Interpolation::Config::MAX_NODES', 1)
+ stub_const('Gitlab::Ci::Config::Interpolation::Config::MAX_NODES', 1)
end
it 'returns an error' do
diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb
index 96ca5d98a6e..cc549b38dc3 100644
--- a/spec/lib/gitlab/ci/config/normalizer_spec.rb
+++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Normalizer do
let(:job_name) { :rspec }
@@ -103,6 +103,34 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
end
end
+ shared_examples 'needs:parallel:matrix' do
+ let(:expanded_needs_parallel_job_attributes) do
+ expanded_needs_parallel_job_names.map do |job_name|
+ { name: job_name }
+ end
+ end
+
+ context 'when job has needs:parallel:matrix on parallelized jobs' do
+ let(:config) do
+ {
+ job_name => job_config,
+ other_job: {
+ script: 'echo 1',
+ needs: {
+ job: [
+ { name: job_name.to_s, parallel: needs_parallel_config }
+ ]
+ }
+ }
+ }
+ end
+
+ it 'parallelizes and only keeps needs specified by needs:parallel:matrix' do
+ expect(subject.dig(:other_job, :needs, :job)).to eq(expanded_needs_parallel_job_attributes)
+ end
+ end
+ end
+
context 'with parallel config as integer' do
let(:variables_config) { {} }
let(:parallel_config) { 5 }
@@ -167,7 +195,7 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
it_behaves_like 'parallel needs'
end
- context 'with parallel matrix config' do
+ context 'with a simple parallel matrix config' do
let(:variables_config) do
{
USER_VARIABLE: 'user value'
@@ -192,6 +220,19 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
]
end
+ let(:needs_parallel_config) do
+ {
+ matrix: [
+ {
+ VAR_1: ['A'],
+ VAR_2: ['C']
+ }
+ ]
+ }
+ end
+
+ let(:expanded_needs_parallel_job_names) { ['rspec: [A, C]'] }
+
it 'does not have original job' do
is_expected.not_to include(job_name)
end
@@ -228,6 +269,66 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
it_behaves_like 'parallel dependencies'
it_behaves_like 'parallel needs'
+ it_behaves_like 'needs:parallel:matrix'
+ end
+
+ context 'with a complex parallel matrix config' do
+ let(:variables_config) { {} }
+ let(:parallel_config) do
+ {
+ matrix: [
+ {
+ PLATFORM: ['centos'],
+ STACK: %w[ruby python java],
+ DB: %w[postgresql mysql]
+ },
+ {
+ PLATFORM: ['ubuntu'],
+ PROVIDER: %w[aws gcp]
+ }
+ ]
+ }
+ end
+
+ let(:needs_parallel_config) do
+ {
+ matrix: [
+ {
+ PLATFORM: ['centos'],
+ STACK: %w[ruby python],
+ DB: ['postgresql']
+ },
+ {
+ PLATFORM: ['ubuntu'],
+ PROVIDER: ['aws']
+ }
+ ]
+ }
+ end
+
+ let(:expanded_needs_parallel_job_names) do
+ [
+ 'rspec: [centos, ruby, postgresql]',
+ 'rspec: [centos, python, postgresql]',
+ 'rspec: [ubuntu, aws]'
+ ]
+ end
+
+ let(:expanded_job_names) do
+ [
+ 'rspec: [centos, ruby, postgresql]',
+ 'rspec: [centos, ruby, mysql]',
+ 'rspec: [centos, python, postgresql]',
+ 'rspec: [centos, python, mysql]',
+ 'rspec: [centos, java, postgresql]',
+ 'rspec: [centos, java, mysql]',
+ 'rspec: [ubuntu, aws]',
+ 'rspec: [ubuntu, gcp]'
+ ]
+ end
+
+ it_behaves_like 'parallel needs'
+ it_behaves_like 'needs:parallel:matrix'
end
context 'when parallel config does not matches a factory' do
diff --git a/spec/lib/gitlab/ci/config/yaml/loader_spec.rb b/spec/lib/gitlab/ci/config/yaml/loader_spec.rb
index 4e6151677e6..57a9a47d699 100644
--- a/spec/lib/gitlab/ci/config/yaml/loader_spec.rb
+++ b/spec/lib/gitlab/ci/config/yaml/loader_spec.rb
@@ -21,12 +21,13 @@ RSpec.describe ::Gitlab::Ci::Config::Yaml::Loader, feature_category: :pipeline_c
YAML
end
- subject(:result) { described_class.new(yaml, inputs: inputs, current_user: project.creator).load }
+ subject(:result) { described_class.new(yaml, inputs: inputs).load }
it 'loads and interpolates CI config YAML' do
expected_config = { test_job: { script: ['echo "hello test"'] } }
expect(result).to be_valid
+ expect(result).to be_interpolated
expect(result.content).to eq(expected_config)
end
diff --git a/spec/lib/gitlab/ci/config/yaml/result_spec.rb b/spec/lib/gitlab/ci/config/yaml/result_spec.rb
index d17e0609ef6..a66c630dfc9 100644
--- a/spec/lib/gitlab/ci/config/yaml/result_spec.rb
+++ b/spec/lib/gitlab/ci/config/yaml/result_spec.rb
@@ -51,4 +51,14 @@ RSpec.describe Gitlab::Ci::Config::Yaml::Result, feature_category: :pipeline_com
expect(result).not_to be_valid
expect(result.error).to be_a ArgumentError
end
+
+ describe '#interpolated?' do
+ it 'defaults to false' do
+ expect(described_class.new).not_to be_interpolated
+ end
+
+ it 'returns the value passed to the initializer' do
+ expect(described_class.new(interpolated: true)).to be_interpolated
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/yaml_spec.rb b/spec/lib/gitlab/ci/config/yaml_spec.rb
index 27d93d555f1..e30ddbb8033 100644
--- a/spec/lib/gitlab/ci/config/yaml_spec.rb
+++ b/spec/lib/gitlab/ci/config/yaml_spec.rb
@@ -36,17 +36,5 @@ RSpec.describe Gitlab::Ci::Config::Yaml, feature_category: :pipeline_composition
.to raise_error ::Gitlab::Config::Loader::FormatError, /mapping values are not allowed in this context/
end
end
-
- context 'when given a user' do
- let(:user) { instance_double(User) }
-
- subject(:config) { described_class.load!(yaml, current_user: user) }
-
- it 'passes it to Loader' do
- expect(::Gitlab::Ci::Config::Yaml::Loader).to receive(:new).with(yaml, current_user: user).and_call_original
-
- config
- end
- end
end
end
diff --git a/spec/lib/gitlab/ci/decompressed_gzip_size_validator_spec.rb b/spec/lib/gitlab/ci/decompressed_gzip_size_validator_spec.rb
index dad5bd2548b..f1b10648f51 100644
--- a/spec/lib/gitlab/ci/decompressed_gzip_size_validator_spec.rb
+++ b/spec/lib/gitlab/ci/decompressed_gzip_size_validator_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::DecompressedGzipSizeValidator, feature_category: :importers do
let_it_be(:filepath) { File.join(Dir.tmpdir, 'decompressed_gzip_size_validator_spec.gz') }
- before(:all) do
+ before_all do
create_compressed_file
end
diff --git a/spec/lib/gitlab/ci/input/arguments/base_spec.rb b/spec/lib/gitlab/ci/input/arguments/base_spec.rb
deleted file mode 100644
index ed8e99b7257..00000000000
--- a/spec/lib/gitlab/ci/input/arguments/base_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Input::Arguments::Base, feature_category: :pipeline_composition do
- subject do
- Class.new(described_class) do
- def validate!; end
- def to_value; end
- end
- end
-
- it 'fabricates an invalid input argument if unknown value is provided' do
- argument = subject.new(:something, { spec: 123 }, [:a, :b])
-
- expect(argument).not_to be_valid
- expect(argument.errors.first).to eq 'unsupported value in input argument `something`'
- end
-end
diff --git a/spec/lib/gitlab/ci/input/arguments/default_spec.rb b/spec/lib/gitlab/ci/input/arguments/default_spec.rb
deleted file mode 100644
index bc0cee6ac4e..00000000000
--- a/spec/lib/gitlab/ci/input/arguments/default_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Input::Arguments::Default, feature_category: :pipeline_composition do
- it 'returns a user-provided value if it is present' do
- argument = described_class.new(:website, { default: 'https://gitlab.com' }, 'https://example.gitlab.com')
-
- expect(argument).to be_valid
- expect(argument.to_value).to eq 'https://example.gitlab.com'
- expect(argument.to_hash).to eq({ website: 'https://example.gitlab.com' })
- end
-
- it 'returns an empty value if user-provider input is empty' do
- argument = described_class.new(:website, { default: 'https://gitlab.com' }, '')
-
- expect(argument).to be_valid
- expect(argument.to_value).to eq ''
- expect(argument.to_hash).to eq({ website: '' })
- end
-
- it 'returns a default value if user-provider one is unknown' do
- argument = described_class.new(:website, { default: 'https://gitlab.com' }, nil)
-
- expect(argument).to be_valid
- expect(argument.to_value).to eq 'https://gitlab.com'
- expect(argument.to_hash).to eq({ website: 'https://gitlab.com' })
- end
-
- it 'returns an error if the default argument has not been recognized' do
- argument = described_class.new(:website, { default: ['gitlab.com'] }, 'abc')
-
- expect(argument).not_to be_valid
- end
-
- it 'returns an error if the argument has not been fabricated correctly' do
- argument = described_class.new(:website, { required: 'https://gitlab.com' }, 'https://example.gitlab.com')
-
- expect(argument).not_to be_valid
- end
-
- describe '.matches?' do
- it 'matches specs with default configuration' do
- expect(described_class.matches?({ default: 'abc' })).to be true
- end
-
- it 'does not match specs different configuration keyword' do
- expect(described_class.matches?({ options: %w[a b] })).to be false
- expect(described_class.matches?('a b c')).to be false
- expect(described_class.matches?(%w[default a])).to be false
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/input/arguments/options_spec.rb b/spec/lib/gitlab/ci/input/arguments/options_spec.rb
deleted file mode 100644
index 17e3469b294..00000000000
--- a/spec/lib/gitlab/ci/input/arguments/options_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Input::Arguments::Options, feature_category: :pipeline_composition do
- it 'returns a user-provided value if it is an allowed one' do
- argument = described_class.new(:run, { options: %w[opt1 opt2] }, 'opt1')
-
- expect(argument).to be_valid
- expect(argument.to_value).to eq 'opt1'
- expect(argument.to_hash).to eq({ run: 'opt1' })
- end
-
- it 'returns an error if user-provided value is not allowlisted' do
- argument = described_class.new(:run, { options: %w[opt1 opt2] }, 'opt3')
-
- expect(argument).not_to be_valid
- expect(argument.errors.first).to eq '`run` input: argument value opt3 not allowlisted'
- end
-
- it 'returns an error if specification is not correct' do
- argument = described_class.new(:website, { options: nil }, 'opt1')
-
- expect(argument).not_to be_valid
- expect(argument.errors.first).to eq '`website` input: argument specification invalid'
- end
-
- it 'returns an error if specification is using a hash' do
- argument = described_class.new(:website, { options: { a: 1 } }, 'opt1')
-
- expect(argument).not_to be_valid
- expect(argument.errors.first).to eq '`website` input: argument specification invalid'
- end
-
- it 'returns an empty value if it is allowlisted' do
- argument = described_class.new(:run, { options: ['opt1', ''] }, '')
-
- expect(argument).to be_valid
- expect(argument.to_value).to be_empty
- expect(argument.to_hash).to eq({ run: '' })
- end
-
- describe '.matches?' do
- it 'matches specs with options configuration' do
- expect(described_class.matches?({ options: %w[a b] })).to be true
- end
-
- it 'does not match specs different configuration keyword' do
- expect(described_class.matches?({ default: 'abc' })).to be false
- expect(described_class.matches?(['options'])).to be false
- expect(described_class.matches?('options')).to be false
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/input/arguments/required_spec.rb b/spec/lib/gitlab/ci/input/arguments/required_spec.rb
deleted file mode 100644
index 847272998c2..00000000000
--- a/spec/lib/gitlab/ci/input/arguments/required_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Input::Arguments::Required, feature_category: :pipeline_composition do
- it 'returns a user-provided value if it is present' do
- argument = described_class.new(:website, nil, 'https://example.gitlab.com')
-
- expect(argument).to be_valid
- expect(argument.to_value).to eq 'https://example.gitlab.com'
- expect(argument.to_hash).to eq({ website: 'https://example.gitlab.com' })
- end
-
- it 'returns an empty value if user-provider value is empty' do
- argument = described_class.new(:website, nil, '')
-
- expect(argument).to be_valid
- expect(argument.to_hash).to eq(website: '')
- end
-
- it 'returns an error if user-provided value is unspecified' do
- argument = described_class.new(:website, nil, nil)
-
- expect(argument).not_to be_valid
- expect(argument.errors.first).to eq '`website` input: required value has not been provided'
- end
-
- describe '.matches?' do
- it 'matches specs without configuration' do
- expect(described_class.matches?(nil)).to be true
- end
-
- it 'matches specs with empty configuration' do
- expect(described_class.matches?('')).to be true
- end
-
- it 'matches specs with an empty hash configuration' do
- expect(described_class.matches?({})).to be true
- end
-
- it 'does not match specs with configuration' do
- expect(described_class.matches?({ options: %w[a b] })).to be false
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/input/arguments/unknown_spec.rb b/spec/lib/gitlab/ci/input/arguments/unknown_spec.rb
deleted file mode 100644
index 1270423ac72..00000000000
--- a/spec/lib/gitlab/ci/input/arguments/unknown_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Input::Arguments::Unknown, feature_category: :pipeline_composition do
- it 'raises an error when someone tries to evaluate the value' do
- argument = described_class.new(:website, nil, 'https://example.gitlab.com')
-
- expect(argument).not_to be_valid
- expect { argument.to_value }.to raise_error ArgumentError
- end
-
- describe '.matches?' do
- it 'always matches' do
- expect(described_class.matches?('abc')).to be true
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/input/inputs_spec.rb b/spec/lib/gitlab/ci/input/inputs_spec.rb
deleted file mode 100644
index 5d2d5192299..00000000000
--- a/spec/lib/gitlab/ci/input/inputs_spec.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Input::Inputs, feature_category: :pipeline_composition do
- describe '#valid?' do
- let(:spec) { { website: nil } }
-
- it 'describes user-provided inputs' do
- inputs = described_class.new(spec, { website: 'http://example.gitlab.com' })
-
- expect(inputs).to be_valid
- end
- end
-
- context 'when proper specification has been provided' do
- let(:spec) do
- {
- website: nil,
- env: { default: 'development' },
- run: { options: %w[tests spec e2e] }
- }
- end
-
- let(:args) { { website: 'https://gitlab.com', run: 'tests' } }
-
- it 'fabricates desired input arguments' do
- inputs = described_class.new(spec, args)
-
- expect(inputs).to be_valid
- expect(inputs.count).to eq 3
- expect(inputs.to_hash).to eq(args.merge(env: 'development'))
- end
- end
-
- context 'when inputs and args are empty' do
- it 'is a valid use-case' do
- inputs = described_class.new({}, {})
-
- expect(inputs).to be_valid
- expect(inputs.to_hash).to be_empty
- end
- end
-
- context 'when there are arguments recoincilation errors present' do
- context 'when required argument is missing' do
- let(:spec) { { website: nil } }
-
- it 'returns an error' do
- inputs = described_class.new(spec, {})
-
- expect(inputs).not_to be_valid
- expect(inputs.errors.first).to eq '`website` input: required value has not been provided'
- end
- end
-
- context 'when argument is not present but configured as allowlist' do
- let(:spec) do
- { run: { options: %w[opt1 opt2] } }
- end
-
- it 'returns an error' do
- inputs = described_class.new(spec, {})
-
- expect(inputs).not_to be_valid
- expect(inputs.errors.first).to eq '`run` input: argument not provided'
- end
- end
- end
-
- context 'when unknown specification argument has been used' do
- let(:spec) do
- {
- website: nil,
- env: { default: 'development' },
- run: { options: %w[tests spec e2e] },
- test: { unknown: 'something' }
- }
- end
-
- let(:args) { { website: 'https://gitlab.com', run: 'tests' } }
-
- it 'fabricates an unknown argument entry and returns an error' do
- inputs = described_class.new(spec, args)
-
- expect(inputs).not_to be_valid
- expect(inputs.count).to eq 4
- expect(inputs.errors.first).to eq '`test` input: unrecognized input argument specification: `unknown`'
- end
- end
-
- context 'when unknown arguments are being passed by a user' do
- let(:spec) do
- { env: { default: 'development' } }
- end
-
- let(:args) { { website: 'https://gitlab.com', run: 'tests' } }
-
- it 'returns an error with a list of unknown arguments' do
- inputs = described_class.new(spec, args)
-
- expect(inputs).not_to be_valid
- expect(inputs.errors.first).to eq 'unknown input arguments: [:website, :run]'
- end
- end
-
- context 'when composite specification is being used' do
- let(:spec) do
- {
- env: {
- default: 'dev',
- options: %w[test dev prod]
- }
- }
- end
-
- let(:args) { { env: 'dev' } }
-
- it 'returns an error describing an unknown specification' do
- inputs = described_class.new(spec, args)
-
- expect(inputs).not_to be_valid
- expect(inputs.errors.first).to eq '`env` input: unrecognized input argument definition'
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/interpolation/block_spec.rb b/spec/lib/gitlab/ci/interpolation/block_spec.rb
deleted file mode 100644
index 4a8709df3dc..00000000000
--- a/spec/lib/gitlab/ci/interpolation/block_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Interpolation::Block, feature_category: :pipeline_composition do
- subject { described_class.new(block, data, ctx) }
-
- let(:data) do
- 'inputs.data'
- end
-
- let(:block) do
- "$[[ #{data} ]]"
- end
-
- let(:ctx) do
- { inputs: { data: 'abc' }, env: { 'ENV' => 'dev' } }
- end
-
- it 'knows its content' do
- expect(subject.content).to eq 'inputs.data'
- end
-
- it 'properly evaluates the access pattern' do
- expect(subject.value).to eq 'abc'
- end
-
- describe '.match' do
- it 'matches each block in a string' do
- expect { |b| described_class.match('$[[ access1 ]] $[[ access2 ]]', &b) }
- .to yield_successive_args(['$[[ access1 ]]', 'access1'], ['$[[ access2 ]]', 'access2'])
- end
-
- it 'matches an empty block' do
- expect { |b| described_class.match('$[[]]', &b) }
- .to yield_with_args('$[[]]', '')
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/interpolation/config_spec.rb b/spec/lib/gitlab/ci/interpolation/config_spec.rb
deleted file mode 100644
index e745269d8c0..00000000000
--- a/spec/lib/gitlab/ci/interpolation/config_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Interpolation::Config, feature_category: :pipeline_composition do
- subject { described_class.new(YAML.safe_load(config)) }
-
- let(:config) do
- <<~CFG
- test:
- spec:
- env: $[[ inputs.env ]]
-
- $[[ inputs.key ]]:
- name: $[[ inputs.key ]]
- script: my-value
- CFG
- end
-
- describe '#replace!' do
- it 'replaces each od the nodes with a block return value' do
- result = subject.replace! { |node| "abc#{node}cde" }
-
- expect(result).to eq({
- 'abctestcde' => { 'abcspeccde' => { 'abcenvcde' => 'abc$[[ inputs.env ]]cde' } },
- 'abc$[[ inputs.key ]]cde' => {
- 'abcnamecde' => 'abc$[[ inputs.key ]]cde',
- 'abcscriptcde' => 'abcmy-valuecde'
- }
- })
- end
- end
-
- context 'when config size is exceeded' do
- before do
- stub_const("#{described_class}::MAX_NODES", 7)
- end
-
- it 'returns a config size error' do
- replaced = 0
-
- subject.replace! { replaced += 1 }
-
- expect(replaced).to eq 4
- expect(subject.errors.size).to eq 1
- expect(subject.errors.first).to eq 'config too large'
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/jwt_v2/claim_mapper/repository_spec.rb b/spec/lib/gitlab/ci/jwt_v2/claim_mapper/repository_spec.rb
new file mode 100644
index 00000000000..0dd0d2fcf0d
--- /dev/null
+++ b/spec/lib/gitlab/ci/jwt_v2/claim_mapper/repository_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::JwtV2::ClaimMapper::Repository, feature_category: :continuous_integration do
+ let_it_be(:sha) { '35fa264414ee3ed7d0b8a6f5da40751c8600a772' }
+ let_it_be(:pipeline) { build_stubbed(:ci_pipeline, ref: 'test-branch-for-claim-mapper', sha: sha) }
+
+ let(:url) { 'gitlab.com/gitlab-org/gitlab//.gitlab-ci.yml' }
+ let(:project_config) { instance_double(Gitlab::Ci::ProjectConfig, url: url) }
+
+ subject(:mapper) { described_class.new(project_config, pipeline) }
+
+ describe '#to_h' do
+ it 'returns expected claims' do
+ expect(mapper.to_h).to eq({
+ ci_config_ref_uri: 'gitlab.com/gitlab-org/gitlab//.gitlab-ci.yml@refs/heads/test-branch-for-claim-mapper',
+ ci_config_sha: sha
+ })
+ end
+
+ context 'when ref is a tag' do
+ let_it_be(:tag) { 'test-tag-for-claim-mapper' }
+ let_it_be(:pipeline) { build_stubbed(:ci_pipeline, tag: tag, ref: tag, sha: sha) }
+
+ it 'returns expected claims' do
+ expect(mapper.to_h).to eq({
+ ci_config_ref_uri: 'gitlab.com/gitlab-org/gitlab//.gitlab-ci.yml@refs/tags/test-tag-for-claim-mapper',
+ ci_config_sha: sha
+ })
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/jwt_v2/claim_mapper_spec.rb b/spec/lib/gitlab/ci/jwt_v2/claim_mapper_spec.rb
new file mode 100644
index 00000000000..b7a73c938a3
--- /dev/null
+++ b/spec/lib/gitlab/ci/jwt_v2/claim_mapper_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::JwtV2::ClaimMapper, feature_category: :continuous_integration do
+ let_it_be(:pipeline) { build_stubbed(:ci_pipeline) }
+
+ let(:source) { :unknown_source }
+ let(:url) { 'gitlab.com/gitlab-org/gitlab//.gitlab-ci.yml' }
+ let(:project_config) { instance_double(Gitlab::Ci::ProjectConfig, url: url, source: source) }
+
+ subject(:mapper) { described_class.new(project_config, pipeline) }
+
+ describe '#to_h' do
+ it 'returns an empty hash when source is not implemented' do
+ expect(mapper.to_h).to eq({})
+ end
+
+ context 'when mapper for source is implemented' do
+ where(:source) { described_class::MAPPER_FOR_CONFIG_SOURCE.keys }
+ let(:result) do
+ {
+ ci_config_ref_uri: 'ci_config_ref_uri',
+ ci_config_sha: 'ci_config_sha'
+ }
+ end
+
+ with_them do
+ it 'uses mapper' do
+ mapper_class = described_class::MAPPER_FOR_CONFIG_SOURCE[source]
+ expect_next_instance_of(mapper_class, project_config, pipeline) do |instance|
+ expect(instance).to receive(:to_h).and_return(result)
+ end
+
+ expect(mapper.to_h).to eq(result)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/jwt_v2_spec.rb b/spec/lib/gitlab/ci/jwt_v2_spec.rb
index 575f174f737..d45d8cacb88 100644
--- a/spec/lib/gitlab/ci/jwt_v2_spec.rb
+++ b/spec/lib/gitlab/ci/jwt_v2_spec.rb
@@ -129,75 +129,39 @@ RSpec.describe Gitlab::Ci::JwtV2, feature_category: :continuous_integration do
end
end
- describe 'ci_config_ref_uri' do
- it 'joins project_config.url and pipeline.source_ref_path with @' do
- expect(payload[:ci_config_ref_uri]).to eq('gitlab.com/gitlab-org/gitlab//.gitlab-ci.yml' \
- '@refs/heads/auto-deploy-2020-03-19')
- end
-
- context 'when project config is nil' do
- before do
- allow(Gitlab::Ci::ProjectConfig).to receive(:new).and_return(nil)
- end
-
- it 'is nil' do
- expect(payload[:ci_config_ref_uri]).to be_nil
- end
- end
-
- context 'when ProjectConfig#url raises an error' do
- before do
- allow(project_config).to receive(:url).and_raise(RuntimeError)
- end
+ describe 'claims delegated to mapper' do
+ let(:ci_config_ref_uri) { 'ci_config_ref_uri' }
+ let(:ci_config_sha) { 'ci_config_sha' }
- it 'raises the same error' do
- expect { payload }.to raise_error(RuntimeError)
+ it 'delegates claims to Gitlab::Ci::JwtV2::ClaimMapper' do
+ expect_next_instance_of(Gitlab::Ci::JwtV2::ClaimMapper, project_config, pipeline) do |mapper|
+ expect(mapper).to receive(:to_h).and_return({
+ ci_config_ref_uri: ci_config_ref_uri,
+ ci_config_sha: ci_config_sha
+ })
end
- context 'in production' do
- before do
- stub_rails_env('production')
- end
-
- it 'is nil' do
- expect(payload[:ci_config_ref_uri]).to be_nil
- end
- end
- end
-
- context 'when config source is not repository' do
- before do
- allow(project_config).to receive(:source).and_return(:auto_devops_source)
- end
-
- it 'is nil' do
- expect(payload[:ci_config_ref_uri]).to be_nil
- end
+ expect(payload[:ci_config_ref_uri]).to eq(ci_config_ref_uri)
+ expect(payload[:ci_config_sha]).to eq(ci_config_sha)
end
end
- describe 'ci_config_sha' do
- it 'is the SHA of the pipeline' do
- expect(payload[:ci_config_sha]).to eq(pipeline.sha)
- end
+ describe 'project_visibility' do
+ using RSpec::Parameterized::TableSyntax
- context 'when project config is nil' do
- before do
- allow(Gitlab::Ci::ProjectConfig).to receive(:new).and_return(nil)
- end
-
- it 'is nil' do
- expect(payload[:ci_config_sha]).to be_nil
- end
+ where(:visibility_level, :visibility_level_string) do
+ Project::PUBLIC | 'public'
+ Project::INTERNAL | 'internal'
+ Project::PRIVATE | 'private'
end
- context 'when config source is not repository' do
+ with_them do
before do
- allow(project_config).to receive(:source).and_return(:auto_devops_source)
+ project.visibility_level = visibility_level
end
- it 'is nil' do
- expect(payload[:ci_config_sha]).to be_nil
+ it 'is a string representation of the project visibility_level' do
+ expect(payload[:project_visibility]).to eq(visibility_level_string)
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
index 9c268d9039e..66e4b987ac1 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
@@ -42,9 +42,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: :
before do
expect(project.repository)
- .to receive(:gitlab_ci_yml_for)
+ .to receive(:blob_at)
.with(pipeline.sha, ci_config_path)
- .and_return('the-content')
+ .and_return(instance_double(Blob, empty?: false))
end
it 'builds root config including the local custom file' do
@@ -132,9 +132,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: :
before do
expect(project.repository)
- .to receive(:gitlab_ci_yml_for)
+ .to receive(:blob_at)
.with(pipeline.sha, '.gitlab-ci.yml')
- .and_return('the-content')
+ .and_return(instance_double(Blob, empty?: false))
end
it 'builds root config including the canonical CI config file' do
diff --git a/spec/lib/gitlab/ci/project_config/repository_spec.rb b/spec/lib/gitlab/ci/project_config/repository_spec.rb
index e8a997a7e43..bd95eefe821 100644
--- a/spec/lib/gitlab/ci/project_config/repository_spec.rb
+++ b/spec/lib/gitlab/ci/project_config/repository_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Gitlab::Ci::ProjectConfig::Repository, feature_category: :continu
context 'when Gitaly raises error' do
before do
- allow(project.repository).to receive(:gitlab_ci_yml_for).and_raise(GRPC::Internal)
+ allow(project.repository).to receive(:blob_at).and_raise(GRPC::Internal)
end
it { is_expected.to be_nil }
diff --git a/spec/lib/gitlab/ci/project_config_spec.rb b/spec/lib/gitlab/ci/project_config_spec.rb
index 13ef0939ddd..6a4af3c61bf 100644
--- a/spec/lib/gitlab/ci/project_config_spec.rb
+++ b/spec/lib/gitlab/ci/project_config_spec.rb
@@ -45,9 +45,9 @@ RSpec.describe Gitlab::Ci::ProjectConfig, feature_category: :pipeline_compositio
before do
allow(project.repository)
- .to receive(:gitlab_ci_yml_for)
+ .to receive(:blob_at)
.with(sha, ci_config_path)
- .and_return('the-content')
+ .and_return(instance_double(Blob, empty?: false))
end
it 'returns root config including the local custom file' do
@@ -122,9 +122,9 @@ RSpec.describe Gitlab::Ci::ProjectConfig, feature_category: :pipeline_compositio
before do
allow(project.repository)
- .to receive(:gitlab_ci_yml_for)
+ .to receive(:blob_at)
.with(sha, '.gitlab-ci.yml')
- .and_return('the-content')
+ .and_return(instance_double(Blob, empty?: false))
end
it 'returns root config including the canonical CI config file' do
diff --git a/spec/lib/gitlab/ci/queue/metrics_spec.rb b/spec/lib/gitlab/ci/queue/metrics_spec.rb
new file mode 100644
index 00000000000..2fb4226ba5a
--- /dev/null
+++ b/spec/lib/gitlab/ci/queue/metrics_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Queue::Metrics, feature_category: :continuous_integration do
+ let(:metrics) { described_class.new(build(:ci_runner)) }
+
+ describe '#observe_queue_depth' do
+ subject { metrics.observe_queue_depth(:found, 1) }
+
+ it { is_expected.not_to be_nil }
+
+ context 'with feature flag gitlab_ci_builds_queueing_metrics disabled' do
+ before do
+ stub_feature_flags(gitlab_ci_builds_queuing_metrics: false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#observe_queue_size' do
+ subject { metrics.observe_queue_size(-> { 0 }, :some_runner_type) }
+
+ it { is_expected.not_to be_nil }
+
+ context 'with feature flag gitlab_ci_builds_queueing_metrics disabled' do
+ before do
+ stub_feature_flags(gitlab_ci_builds_queuing_metrics: false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#observe_queue_time' do
+ subject { metrics.observe_queue_time(:process, :some_runner_type) { 1 } }
+
+ specify do
+ expect(described_class).to receive(:queue_iteration_duration_seconds).and_call_original
+
+ subject
+ end
+
+ context 'with feature flag gitlab_ci_builds_queueing_metrics disabled' do
+ before do
+ stub_feature_flags(gitlab_ci_builds_queuing_metrics: false)
+ end
+
+ specify do
+ expect(described_class).not_to receive(:queue_iteration_duration_seconds)
+
+ subject
+ end
+ end
+
+ describe '.observe_active_runners' do
+ subject { described_class.observe_active_runners(-> { 0 }) }
+
+ it { is_expected.not_to be_nil }
+
+ context 'with feature flag gitlab_ci_builds_queueing_metrics disabled' do
+ before do
+ stub_feature_flags(gitlab_ci_builds_queuing_metrics: false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/sbom/component_spec.rb b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb
index 5dbcc1991d4..d62d25aeefe 100644
--- a/spec/lib/gitlab/ci/reports/sbom/component_spec.rb
+++ b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb
@@ -27,6 +27,154 @@ RSpec.describe Gitlab::Ci::Reports::Sbom::Component, feature_category: :dependen
)
end
+ describe '#name' do
+ subject { component.name }
+
+ it { is_expected.to eq(name) }
+
+ context 'with namespace' do
+ let(:purl) do
+ 'pkg:maven/org.NameSpace/Name@v0.0.1'
+ end
+
+ it { is_expected.to eq('org.NameSpace/Name') }
+
+ context 'when needing normalization' do
+ let(:purl) do
+ 'pkg:pypi/org.NameSpace/Name@v0.0.1'
+ end
+
+ it { is_expected.to eq('org.namespace/name') }
+ end
+ end
+ end
+
+ describe '#<=>' do
+ where do
+ {
+ 'equal' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-a@1.0.0',
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: '1.0.0',
+ b_version: '1.0.0',
+ expected: 0
+ },
+ 'name lesser' => {
+ a_name: 'component-a',
+ b_name: 'component-b',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-a@1.0.0',
+ b_purl: 'pkg:npm/component-b@1.0.0',
+ a_version: '1.0.0',
+ b_version: '1.0.0',
+ expected: -1
+ },
+ 'name greater' => {
+ a_name: 'component-b',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-b@1.0.0',
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: '1.0.0',
+ b_version: '1.0.0',
+ expected: 1
+ },
+ 'purl type lesser' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:composer/component-a@1.0.0',
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: '1.0.0',
+ b_version: '1.0.0',
+ expected: -1
+ },
+ 'purl type greater' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-a@1.0.0',
+ b_purl: 'pkg:composer/component-a@1.0.0',
+ a_version: '1.0.0',
+ b_version: '1.0.0',
+ expected: 1
+ },
+ 'purl type nulls first' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: nil,
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: '1.0.0',
+ b_version: '1.0.0',
+ expected: -1
+ },
+ 'version lesser' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-a@1.0.0',
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: '1.0.0',
+ b_version: '2.0.0',
+ expected: -1
+ },
+ 'version greater' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-a@1.0.0',
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: '2.0.0',
+ b_version: '1.0.0',
+ expected: 1
+ },
+ 'version nulls first' => {
+ a_name: 'component-a',
+ b_name: 'component-a',
+ a_type: 'library',
+ b_type: 'library',
+ a_purl: 'pkg:npm/component-a@1.0.0',
+ b_purl: 'pkg:npm/component-a@1.0.0',
+ a_version: nil,
+ b_version: '1.0.0',
+ expected: -1
+ }
+ }
+ end
+
+ with_them do
+ specify do
+ a = described_class.new(
+ name: a_name,
+ type: a_type,
+ purl: a_purl,
+ version: a_version
+ )
+
+ b = described_class.new(
+ name: b_name,
+ type: b_type,
+ purl: b_purl,
+ version: b_version
+ )
+
+ expect(a <=> b).to eq(expected)
+ end
+ end
+ end
+
describe '#ingestible?' do
subject { component.ingestible? }
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index 702341a7ea7..34e430202c9 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -62,7 +62,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory, feature_category: :continuous
end
context 'when stage has manual builds' do
- Ci::HasStatus::BLOCKED_STATUS.each do |core_status|
+ (Ci::HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status|
context "when status is #{core_status}" do
let(:stage) { create(:ci_stage, pipeline: pipeline, status: core_status) }
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 e23645c106b..fc52b7bf9d4 100644
--- a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb
@@ -48,7 +48,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::PlayManual, feature_category: :continu
context 'when stage is skipped' do
let(:stage) { create(:ci_stage, status: :skipped) }
- it { is_expected.to be_falsy }
+ it { is_expected.to be_truthy }
end
context 'when stage is manual' do
diff --git a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb
index b72a818c16c..460ecbb05d0 100644
--- a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb
+++ b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do
subject(:service) { described_class.new(statuses) }
describe 'gem version' do
- let(:acceptable_version) { '9.0.0' }
+ let(:acceptable_version) { '9.0.1' }
let(:error_message) do
<<~MESSAGE
diff --git a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
index e5324560944..0880c556523 100644
--- a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
CI_PIPELINE_IID
CI_PIPELINE_SOURCE
CI_PIPELINE_CREATED_AT
+ CI_PIPELINE_NAME
CI_COMMIT_SHA
CI_COMMIT_SHORT_SHA
CI_COMMIT_BEFORE_SHA
@@ -43,6 +44,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
CI_PIPELINE_IID
CI_PIPELINE_SOURCE
CI_PIPELINE_CREATED_AT
+ CI_PIPELINE_NAME
CI_COMMIT_SHA
CI_COMMIT_SHORT_SHA
CI_COMMIT_BEFORE_SHA
diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb
index 28c9bdc4c4b..3411426fcdb 100644
--- a/spec/lib/gitlab/ci/variables/builder_spec.rb
+++ b/spec/lib/gitlab/ci/variables/builder_spec.rb
@@ -111,6 +111,8 @@ RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache, featur
value: pipeline.source },
{ key: 'CI_PIPELINE_CREATED_AT',
value: pipeline.created_at.iso8601 },
+ { key: 'CI_PIPELINE_NAME',
+ value: pipeline.name },
{ key: 'CI_COMMIT_SHA',
value: job.sha },
{ key: 'CI_COMMIT_SHORT_SHA',
diff --git a/spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb b/spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb
index 5b33527e06c..95d0f089f6d 100644
--- a/spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb
+++ b/spec/lib/gitlab/ci/variables/downstream/expandable_variable_generator_spec.rb
@@ -7,13 +7,19 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::ExpandableVariableGenerator, f
Gitlab::Ci::Variables::Collection.fabricate(
[
{ key: 'REF1', value: 'ref 1' },
- { key: 'REF2', value: 'ref 2' }
+ { key: 'REF2', value: 'ref 2' },
+ { key: 'NESTED_REF1', value: 'nested $REF1' }
]
)
end
+ let(:expand_file_refs) { false }
+
let(:context) do
- Gitlab::Ci::Variables::Downstream::Generator::Context.new(all_bridge_variables: all_bridge_variables)
+ Gitlab::Ci::Variables::Downstream::Generator::Context.new(
+ all_bridge_variables: all_bridge_variables,
+ expand_file_refs: expand_file_refs
+ )
end
subject(:generator) { described_class.new(context) }
@@ -34,5 +40,54 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::ExpandableVariableGenerator, f
expect(generator.for(var)).to match_array([{ key: 'VAR1', value: 'ref 1 ref 2 ' }])
end
end
+
+ context 'when given a variable with nested interpolation' do
+ it 'returns an array containing the expanded variables' do
+ var = Gitlab::Ci::Variables::Collection::Item.fabricate({ key: 'VAR1', value: '$REF1 $REF2 $NESTED_REF1' })
+
+ expect(generator.for(var)).to match_array([{ key: 'VAR1', value: 'ref 1 ref 2 nested $REF1' }])
+ end
+ end
+
+ context 'when given a variable with expansion on a file variable' do
+ let(:all_bridge_variables) do
+ Gitlab::Ci::Variables::Collection.fabricate(
+ [
+ { key: 'REF1', value: 'ref 1' },
+ { key: 'FILE_REF2', value: 'ref 2', file: true },
+ { key: 'NESTED_REF3', value: 'ref 3 $REF1 and $FILE_REF2', file: true }
+ ]
+ )
+ end
+
+ context 'when expand_file_refs is false' do
+ let(:expand_file_refs) { false }
+
+ it 'returns an array containing the unexpanded variable and the file variable dependency' do
+ var = { key: 'VAR1', value: '$REF1 $FILE_REF2 $FILE_REF3 $NESTED_REF3' }
+ var = Gitlab::Ci::Variables::Collection::Item.fabricate(var)
+
+ expected = [
+ { key: 'VAR1', value: 'ref 1 $FILE_REF2 $NESTED_REF3' },
+ { key: 'FILE_REF2', value: 'ref 2', variable_type: :file },
+ { key: 'NESTED_REF3', value: 'ref 3 $REF1 and $FILE_REF2', variable_type: :file }
+ ]
+
+ expect(generator.for(var)).to match_array(expected)
+ end
+ end
+
+ context 'when expand_file_refs is true' do
+ let(:expand_file_refs) { true }
+
+ it 'returns an array containing the expanded variables' do
+ var = { key: 'VAR1', value: '$REF1 $FILE_REF2 $FILE_REF3 $NESTED_REF3' }
+ var = Gitlab::Ci::Variables::Collection::Item.fabricate(var)
+
+ expected = { key: 'VAR1', value: 'ref 1 ref 2 ref 3 $REF1 and $FILE_REF2' }
+ expect(generator.for(var)).to contain_exactly(expected)
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb b/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb
index 61e8b9a8c4a..cd68b0cdf2b 100644
--- a/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb
+++ b/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb
@@ -45,6 +45,7 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
variables: bridge_variables,
forward_yaml_variables?: true,
forward_pipeline_variables?: true,
+ expand_file_refs?: false,
yaml_variables: yaml_variables,
pipeline_variables: pipeline_variables,
pipeline_schedule_variables: pipeline_schedule_variables
@@ -81,5 +82,61 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
expect(generator.calculate).to be_empty
end
+
+ context 'with file variable interpolation' do
+ let(:bridge_variables) do
+ Gitlab::Ci::Variables::Collection.fabricate(
+ [
+ { key: 'REF1', value: 'ref 1' },
+ { key: 'FILE_REF3', value: 'ref 3', file: true }
+ ]
+ )
+ end
+
+ let(:yaml_variables) do
+ [{ key: 'INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }]
+ end
+
+ let(:pipeline_variables) do
+ [{ key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }]
+ end
+
+ let(:pipeline_schedule_variables) do
+ [{ key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }]
+ end
+
+ context 'when expand_file_refs is true' do
+ before do
+ allow(bridge).to receive(:expand_file_refs?).and_return(true)
+ end
+
+ it 'expands file variables' do
+ expected = [
+ { key: 'INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' },
+ { key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' },
+ { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' }
+ ]
+
+ expect(generator.calculate).to contain_exactly(*expected)
+ end
+ end
+
+ context 'when expand_file_refs is false' do
+ before do
+ allow(bridge).to receive(:expand_file_refs?).and_return(false)
+ end
+
+ it 'does not expand file variables and adds file variables' do
+ expected = [
+ { key: 'INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
+ { key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
+ { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
+ { key: 'FILE_REF3', value: 'ref 3', variable_type: :file }
+ ]
+
+ expect(generator.calculate).to contain_exactly(*expected)
+ end
+ 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 c4e27d0e420..f8f1d71e773 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -2675,6 +2675,42 @@ module Gitlab
it_behaves_like 'returns errors', 'jobs:test1 dependencies should be an array of strings'
end
+
+ context 'needs with parallel:matrix' do
+ let(:config) do
+ {
+ build1: {
+ stage: 'build',
+ script: 'build',
+ parallel: { matrix: [{ 'PROVIDER': ['aws'], 'STACK': %w[monitoring app1 app2] }] }
+ },
+ test1: {
+ stage: 'test',
+ script: 'test',
+ needs: [{ job: 'build1', parallel: { matrix: [{ 'PROVIDER': ['aws'], 'STACK': ['app1'] }] } }]
+ }
+ }
+ end
+
+ it "does create jobs with valid specification" do
+ expect(subject.builds.size).to eq(4)
+ expect(subject.builds[3]).to eq(
+ stage: "test",
+ stage_idx: 2,
+ name: "test1",
+ only: { refs: %w[branches tags] },
+ options: { script: ["test"] },
+ needs_attributes: [
+ { name: "build1: [aws, app1]", artifacts: true, optional: false }
+ ],
+ when: "on_success",
+ allow_failure: false,
+ job_variables: [],
+ root_variables_inheritance: true,
+ scheduling_type: :dag
+ )
+ end
+ end
end
context 'with when/rules' do
diff --git a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
index d03d4f64a0f..56745759c5a 100644
--- a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
+++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
@@ -23,26 +23,8 @@ RSpec.describe Gitlab::Cleanup::OrphanJobArtifactFilesBatch do
expect(batch.artifact_files.count).to eq(2)
expect(batch.lost_and_found.count).to eq(1)
expect(batch.lost_and_found.first.artifact_id).to eq(orphan_artifact.id)
- end
-
- it 'does not mix up job ID and artifact ID' do
- # take maximum ID of both tables to avoid any collision
- max_id = [Ci::Build.maximum(:id), Ci::JobArtifact.maximum(:id)].compact.max.to_i
- job_a = create(:ci_build, id: max_id + 1)
- job_b = create(:ci_build, id: max_id + 2)
- # reuse the build IDs for the job artifact IDs, but swap them
- job_artifact_b = create(:ci_job_artifact, :archive, job: job_b, id: max_id + 1)
- job_artifact_a = create(:ci_job_artifact, :archive, job: job_a, id: max_id + 2)
-
- batch << artifact_path(job_artifact_a)
- batch << artifact_path(job_artifact_b)
-
- job_artifact_b.delete
-
- batch.clean!
-
- expect(File.exist?(job_artifact_a.file.path)).to be_truthy
- expect(File.exist?(job_artifact_b.file.path)).to be_falsey
+ expect(File.exist?(job_artifact.file.path)).to be_truthy
+ expect(File.exist?(orphan_artifact.file.path)).to be_falsey
end
end
diff --git a/spec/lib/gitlab/config/entry/validators_spec.rb b/spec/lib/gitlab/config/entry/validators_spec.rb
index abf3dbacb3d..6fa9f9d0767 100644
--- a/spec/lib/gitlab/config/entry/validators_spec.rb
+++ b/spec/lib/gitlab/config/entry/validators_spec.rb
@@ -102,4 +102,37 @@ RSpec.describe Gitlab::Config::Entry::Validators, feature_category: :pipeline_co
end
end
end
+
+ describe described_class::OnlyOneOfKeysValidator do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:config, :valid_result) do
+ { foo: '1' } | true
+ { foo: '1', bar: '2', baz: '3' } | false
+ { bar: '2' } | true
+ { foo: '1' } | true
+ {} | false
+ { baz: '3' } | false
+ end
+
+ with_them do
+ before do
+ klass.instance_eval do
+ validates :config, only_one_of_keys: %i[foo bar]
+ end
+
+ allow(instance).to receive(:config).and_return(config)
+ end
+
+ it 'validates the instance' do
+ expect(instance.valid?).to be(valid_result)
+
+ unless valid_result
+ expect(instance.errors.messages_for(:config)).to(
+ include "must use exactly one of these keys: foo, bar"
+ )
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/container_repository/tags/cache_spec.rb b/spec/lib/gitlab/container_repository/tags/cache_spec.rb
index 4b8c843eb3a..fcfc8e7a348 100644
--- a/spec/lib/gitlab/container_repository/tags/cache_spec.rb
+++ b/spec/lib/gitlab/container_repository/tags/cache_spec.rb
@@ -81,9 +81,7 @@ RSpec.describe ::Gitlab::ContainerRepository::Tags::Cache, :clean_gitlab_redis_c
::Gitlab::Redis::Cache.with do |redis|
expect(redis).to receive(:pipelined).and_call_original
- times = Gitlab::Redis::ClusterUtil.cluster?(redis) ? 2 : 1
-
- expect_next_instances_of(Redis::PipelinedConnection, times) do |pipeline|
+ expect_next_instance_of(Redis::PipelinedConnection) do |pipeline|
expect(pipeline)
.to receive(:set)
.with(cache_key(tag), rfc3339(tag.created_at), ex: ttl.to_i)
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 b40829d72a0..dd633820ad9 100644
--- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
+++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
@@ -2,8 +2,11 @@
require 'spec_helper'
-RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
+RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader, feature_category: :shared do
let(:policy) { ActionDispatch::ContentSecurityPolicy.new }
+ let(:lfs_enabled) { false }
+ let(:proxy_download) { false }
+
let(:csp_config) do
{
enabled: true,
@@ -20,6 +23,32 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
}
end
+ let(:lfs_config) do
+ {
+ enabled: lfs_enabled,
+ remote_directory: 'lfs-objects',
+ connection: object_store_connection_config,
+ direct_upload: false,
+ proxy_download: proxy_download,
+ storage_options: {}
+ }
+ end
+
+ let(:object_store_connection_config) do
+ {
+ provider: 'AWS',
+ aws_access_key_id: 'AWS_ACCESS_KEY_ID',
+ aws_secret_access_key: 'AWS_SECRET_ACCESS_KEY'
+ }
+ end
+
+ before do
+ stub_lfs_setting(enabled: lfs_enabled)
+ allow(LfsObjectUploader)
+ .to receive(:object_store_options)
+ .and_return(GitlabSettings::Options.build(lfs_config))
+ end
+
describe '.default_enabled' do
let(:enabled) { described_class.default_enabled }
@@ -29,7 +58,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
context 'when in production' do
before do
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
+ stub_rails_env('production')
end
it 'is disabled' do
@@ -40,6 +69,16 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
describe '.default_directives' do
let(:directives) { described_class.default_directives }
+ let(:child_src) { directives['child_src'] }
+ let(:connect_src) { directives['connect_src'] }
+ let(:font_src) { directives['font_src'] }
+ let(:frame_src) { directives['frame_src'] }
+ let(:img_src) { directives['img_src'] }
+ let(:media_src) { directives['media_src'] }
+ let(:report_uri) { directives['report_uri'] }
+ let(:script_src) { directives['script_src'] }
+ let(:style_src) { directives['style_src'] }
+ let(:worker_src) { directives['worker_src'] }
it 'returns default directives' do
directive_names = (described_class::DIRECTIVES - ['report_uri'])
@@ -49,68 +88,231 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
end
expect(directives.has_key?('report_uri')).to be_truthy
- expect(directives['report_uri']).to be_nil
- expect(directives['child_src']).to eq("#{directives['frame_src']} #{directives['worker_src']}")
+ expect(report_uri).to be_nil
+ expect(child_src).to eq("#{frame_src} #{worker_src}")
end
describe 'the images-src directive' do
it 'can be loaded from anywhere' do
- expect(directives['img_src']).to include('http: https:')
+ expect(img_src).to include('http: https:')
end
end
describe 'the media-src directive' do
it 'can be loaded from anywhere' do
- expect(directives['media_src']).to include('http: https:')
+ expect(media_src).to include('http: https:')
end
end
- context 'adds all websocket origins to support Safari' do
+ describe 'Webpack dev server websocket connections' do
+ let(:webpack_dev_server_host) { 'webpack-dev-server.com' }
+ let(:webpack_dev_server_port) { '9999' }
+ let(:webpack_dev_server_https) { true }
+
+ before do
+ stub_config_setting(
+ webpack: { dev_server: {
+ host: webpack_dev_server_host,
+ webpack_dev_server_port: webpack_dev_server_port,
+ https: webpack_dev_server_https
+ } }
+ )
+ end
+
+ context 'when in production' do
+ before do
+ stub_rails_env('production')
+ end
+
+ context 'with secure domain' do
+ it 'does not include webpack dev server in connect-src' do
+ expect(connect_src).not_to include(webpack_dev_server_host)
+ expect(connect_src).not_to include(webpack_dev_server_port)
+ end
+ end
+
+ context 'with insecure domain' do
+ let(:webpack_dev_server_https) { false }
+
+ it 'does not include webpack dev server in connect-src' do
+ expect(connect_src).not_to include(webpack_dev_server_host)
+ expect(connect_src).not_to include(webpack_dev_server_port)
+ end
+ end
+ end
+
+ context 'when in development' do
+ before do
+ stub_rails_env('development')
+ end
+
+ context 'with secure domain' do
+ before do
+ stub_config_setting(host: webpack_dev_server_host, port: webpack_dev_server_port, https: true)
+ end
+
+ it 'includes secure websocket url for webpack dev server in connect-src' do
+ expect(connect_src).to include("wss://#{webpack_dev_server_host}:#{webpack_dev_server_port}")
+ expect(connect_src).not_to include("ws://#{webpack_dev_server_host}:#{webpack_dev_server_port}")
+ end
+ end
+
+ context 'with insecure domain' do
+ before do
+ stub_config_setting(host: webpack_dev_server_host, port: webpack_dev_server_port, https: false)
+ end
+
+ it 'includes insecure websocket url for webpack dev server in connect-src' do
+ expect(connect_src).not_to include("wss://#{webpack_dev_server_host}:#{webpack_dev_server_port}")
+ expect(connect_src).to include("ws://#{webpack_dev_server_host}:#{webpack_dev_server_port}")
+ end
+ end
+ end
+ end
+
+ describe 'Websocket connections' do
it 'with insecure domain' do
stub_config_setting(host: 'example.com', https: false)
- expect(directives['connect_src']).to eq("'self' ws://example.com")
+ expect(connect_src).to eq("'self' ws://example.com")
end
it 'with secure domain' do
stub_config_setting(host: 'example.com', https: true)
- expect(directives['connect_src']).to eq("'self' wss://example.com")
+ expect(connect_src).to eq("'self' wss://example.com")
end
it 'with custom port' do
stub_config_setting(host: 'example.com', port: '1234')
- expect(directives['connect_src']).to eq("'self' ws://example.com:1234")
+ expect(connect_src).to eq("'self' ws://example.com:1234")
end
it 'with custom port and secure domain' do
stub_config_setting(host: 'example.com', https: true, port: '1234')
- expect(directives['connect_src']).to eq("'self' wss://example.com:1234")
+ expect(connect_src).to eq("'self' wss://example.com:1234")
+ end
+
+ it 'when port is included in HTTP_PORTS' do
+ described_class::HTTP_PORTS.each do |port|
+ stub_config_setting(host: 'example.com', https: true, port: port)
+ expect(connect_src).to eq("'self' wss://example.com")
+ end
end
end
- context 'when CDN host is defined' do
+ describe 'LFS connect-src headers' do
+ let(:url_for_provider) { described_class.send(:build_lfs_url) }
+
+ context 'when LFS is enabled' do
+ let(:lfs_enabled) { true }
+
+ context 'and direct downloads are enabled' do
+ let(:provider) { LfsObjectUploader.object_store_options.connection.provider }
+
+ context 'when provider is AWS' do
+ it { expect(provider).to eq('AWS') }
+
+ it { expect(url_for_provider).to be_present }
+
+ it { expect(directives['connect_src']).to include(url_for_provider) }
+ end
+
+ context 'when provider is AzureRM' do
+ let(:object_store_connection_config) do
+ {
+ provider: 'AzureRM',
+ azure_storage_account_name: 'azuretest',
+ azure_storage_access_key: 'ABCD1234'
+ }
+ end
+
+ it { expect(provider).to eq('AzureRM') }
+
+ it { expect(url_for_provider).to be_present }
+
+ it { expect(directives['connect_src']).to include(url_for_provider) }
+ end
+
+ context 'when provider is Google' do
+ let(:object_store_connection_config) do
+ {
+ provider: 'Google',
+ google_project: 'GOOGLE_PROJECT',
+ google_application_default: true
+ }
+ end
+
+ it { expect(provider).to eq('Google') }
+
+ it { expect(url_for_provider).to be_present }
+
+ it { expect(directives['connect_src']).to include(url_for_provider) }
+ end
+ end
+
+ context 'but direct downloads are disabled' do
+ let(:proxy_download) { true }
+
+ it { expect(directives['connect_src']).not_to include(url_for_provider) }
+ end
+ end
+
+ context 'when LFS is disabled' do
+ let(:proxy_download) { true }
+
+ it { expect(directives['connect_src']).not_to include(url_for_provider) }
+ end
+ end
+
+ describe 'CDN connections' do
before do
- stub_config_setting(cdn_host: 'https://cdn.example.com')
+ allow(described_class).to receive(:allow_letter_opener)
+ allow(described_class).to receive(:allow_zuora)
+ allow(described_class).to receive(:allow_framed_gitlab_paths)
+ allow(described_class).to receive(:allow_customersdot)
+ allow(described_class).to receive(:csp_level_3_backport)
+ end
+
+ context 'when CDN host is defined' do
+ let(:cdn_host) { 'https://cdn.example.com' }
+
+ before do
+ stub_config_setting(cdn_host: cdn_host)
+ end
+
+ it 'adds CDN host to CSP' do
+ expect(script_src).to include(cdn_host)
+ expect(style_src).to include(cdn_host)
+ expect(font_src).to include(cdn_host)
+ expect(worker_src).to include(cdn_host)
+ expect(frame_src).to include(cdn_host)
+ end
end
- it 'adds CDN host to CSP' do
- expect(directives['script_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.script_src + " https://cdn.example.com")
- expect(directives['style_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.style_src + " https://cdn.example.com")
- expect(directives['font_src']).to eq("'self' https://cdn.example.com")
- expect(directives['worker_src']).to eq('http://localhost/assets/ blob: data: https://cdn.example.com')
- expect(directives['frame_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src + " https://cdn.example.com http://localhost/admin/ http://localhost/assets/ http://localhost/-/speedscope/index.html http://localhost/-/sandbox/")
+ context 'when CDN host is undefined' do
+ before do
+ stub_config_setting(cdn_host: nil)
+ end
+
+ it 'does not include CDN host in CSP' do
+ expect(script_src).to eq(::Gitlab::ContentSecurityPolicy::Directives.script_src)
+ expect(style_src).to eq(::Gitlab::ContentSecurityPolicy::Directives.style_src)
+ expect(font_src).to eq("'self'")
+ expect(worker_src).to eq("http://localhost/assets/ blob: data:")
+ expect(frame_src).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src)
+ end
end
end
describe 'Zuora directives' do
context 'when on SaaS', :saas do
it 'adds Zuora host to CSP' do
- expect(directives['frame_src']).to include('https://*.zuora.com/apps/PublicHostedPageLite.do')
+ expect(frame_src).to include('https://*.zuora.com/apps/PublicHostedPageLite.do')
end
end
context 'when is not Gitlab.com?' do
it 'does not add Zuora host to CSP' do
- expect(directives['frame_src']).not_to include('https://*.zuora.com/apps/PublicHostedPageLite.do')
+ expect(frame_src).not_to include('https://*.zuora.com/apps/PublicHostedPageLite.do')
end
end
end
@@ -131,7 +333,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
end
it 'adds legacy sentry path to CSP' do
- expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com dummy://legacy-sentry.example.com")
+ expect(connect_src).to eq("'self' ws://gitlab.example.com dummy://legacy-sentry.example.com")
end
end
@@ -143,7 +345,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
end
it 'adds new sentry path to CSP' do
- expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com dummy://sentry.example.com")
+ expect(connect_src).to eq("'self' ws://gitlab.example.com dummy://sentry.example.com")
end
end
@@ -159,11 +361,22 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
end
it 'config is backwards compatible, does not add sentry path to CSP' do
- expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com")
+ expect(connect_src).to eq("'self' ws://gitlab.example.com")
end
end
context 'when legacy sentry and sentry are both configured' do
+ let(:connect_src_expectation) do
+ # rubocop:disable Lint/PercentStringArray
+ %w[
+ 'self'
+ ws://gitlab.example.com
+ dummy://legacy-sentry.example.com
+ dummy://sentry.example.com
+ ].join(' ')
+ # rubocop:enable Lint/PercentStringArray
+ end
+
before do
allow(Gitlab.config.sentry).to receive(:enabled).and_return(true)
allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return(legacy_dsn)
@@ -173,24 +386,57 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
end
it 'adds both sentry paths to CSP' do
- expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com dummy://legacy-sentry.example.com dummy://sentry.example.com")
+ expect(connect_src).to eq(connect_src_expectation)
end
end
end
- context 'when CUSTOMER_PORTAL_URL is set' do
- let(:customer_portal_url) { 'https://customers.example.com' }
+ describe 'Customer portal frames' do
+ context 'when CUSTOMER_PORTAL_URL is set' do
+ let(:customer_portal_url) { 'https://customers.example.com' }
+ let(:frame_src_expectation) do
+ [
+ ::Gitlab::ContentSecurityPolicy::Directives.frame_src,
+ 'http://localhost/admin/',
+ 'http://localhost/assets/',
+ 'http://localhost/-/speedscope/index.html',
+ 'http://localhost/-/sandbox/',
+ customer_portal_url
+ ].join(' ')
+ end
- before do
- stub_env('CUSTOMER_PORTAL_URL', customer_portal_url)
+ before do
+ stub_env('CUSTOMER_PORTAL_URL', customer_portal_url)
+ end
+
+ it 'adds CUSTOMER_PORTAL_URL to CSP' do
+ expect(frame_src).to eq(frame_src_expectation)
+ end
end
- it 'adds CUSTOMER_PORTAL_URL to CSP' do
- expect(directives['frame_src']).to eq(::Gitlab::ContentSecurityPolicy::Directives.frame_src + " http://localhost/admin/ http://localhost/assets/ http://localhost/-/speedscope/index.html http://localhost/-/sandbox/ #{customer_portal_url}")
+ context 'when CUSTOMER_PORTAL_URL is blank' do
+ let(:customer_portal_url) { '' }
+ let(:frame_src_expectation) do
+ [
+ ::Gitlab::ContentSecurityPolicy::Directives.frame_src,
+ 'http://localhost/admin/',
+ 'http://localhost/assets/',
+ 'http://localhost/-/speedscope/index.html',
+ 'http://localhost/-/sandbox/'
+ ].join(' ')
+ end
+
+ before do
+ stub_env('CUSTOMER_PORTAL_URL', customer_portal_url)
+ end
+
+ it 'adds CUSTOMER_PORTAL_URL to CSP' do
+ expect(frame_src).to eq(frame_src_expectation)
+ end
end
end
- context 'letter_opener application URL' do
+ describe 'letter_opener application URL' do
let(:gitlab_url) { 'http://gitlab.example.com' }
let(:letter_opener_url) { "#{gitlab_url}/rails/letter_opener/" }
@@ -200,21 +446,21 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
context 'when in production' do
before do
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
+ stub_rails_env('production')
end
it 'does not add letter_opener to CSP' do
- expect(directives['frame_src']).not_to include(letter_opener_url)
+ expect(frame_src).not_to include(letter_opener_url)
end
end
context 'when in development' do
before do
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
+ stub_rails_env('development')
end
it 'adds letter_opener to CSP' do
- expect(directives['frame_src']).to include(letter_opener_url)
+ expect(frame_src).to include(letter_opener_url)
end
end
end
@@ -234,7 +480,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
end
it 'does not add Snowplow Micro URL to connect-src' do
- expect(directives['connect_src']).not_to include(snowplow_micro_url)
+ expect(connect_src).not_to include(snowplow_micro_url)
end
end
@@ -244,7 +490,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
end
it 'adds Snowplow Micro URL with trailing slash to connect-src' do
- expect(directives['connect_src']).to match(Regexp.new(snowplow_micro_url))
+ expect(connect_src).to match(Regexp.new(snowplow_micro_url))
end
context 'when not enabled using config' do
@@ -253,7 +499,7 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
end
it 'does not add Snowplow Micro URL to connect-src' do
- expect(directives['connect_src']).not_to include(snowplow_micro_url)
+ expect(connect_src).not_to include(snowplow_micro_url)
end
end
@@ -262,8 +508,18 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
stub_env('REVIEW_APPS_ENABLED', 'true')
end
- it 'adds gitlab-org/gitlab merge requests API endpoint to CSP' do
- expect(directives['connect_src']).to include('https://gitlab.com/api/v4/projects/278964/merge_requests/')
+ it "includes review app's merge requests API endpoint in the CSP" do
+ expect(connect_src).to include('https://gitlab.com/api/v4/projects/278964/merge_requests/')
+ end
+ end
+
+ context 'when REVIEW_APPS_ENABLED is blank' do
+ before do
+ stub_env('REVIEW_APPS_ENABLED', '')
+ end
+
+ it "does not include review app's merge requests API endpoint in the CSP" do
+ expect(connect_src).not_to include('https://gitlab.com/api/v4/projects/278964/merge_requests/')
end
end
end
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index 7cd0af0dcec..66890315ee8 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -53,7 +53,9 @@ RSpec.describe Gitlab::DataBuilder::Build, feature_category: :integrations do
it { expect(data[:runner][:description]).to eq(ci_build.runner.description) }
it { expect(data[:runner][:runner_type]).to eq(ci_build.runner.runner_type) }
it { expect(data[:runner][:is_shared]).to eq(ci_build.runner.instance_type?) }
+ it { expect(data[:project]).to eq(ci_build.project.hook_attrs(backward: false)) }
it { expect(data[:environment]).to be_nil }
+ it { expect(data[:source_pipeline]).to be_nil }
it 'does not exceed number of expected queries' do
ci_build # Make sure the Ci::Build model is created before recording.
@@ -63,7 +65,7 @@ RSpec.describe Gitlab::DataBuilder::Build, feature_category: :integrations do
described_class.build(b) # Don't use ci_build variable here since it has all associations loaded into memory
end
- expect(control.count).to eq(14)
+ expect(control.count).to eq(16)
end
context 'commit author_url' do
@@ -98,5 +100,33 @@ RSpec.describe Gitlab::DataBuilder::Build, feature_category: :integrations do
it { expect(data[:environment][:action]).to eq(ci_build.environment_action) }
end
end
+
+ context 'when the build job has an upstream' do
+ let(:source_pipeline_attrs) { data[:source_pipeline] }
+
+ shared_examples 'source pipeline attributes' do
+ it 'has source pipeline attributes', :aggregate_failures do
+ expect(source_pipeline_attrs[:pipeline_id]).to eq upstream_pipeline.id
+ expect(source_pipeline_attrs[:job_id]).to eq pipeline.reload.source_bridge.id
+ expect(source_pipeline_attrs[:project][:id]).to eq upstream_pipeline.project.id
+ expect(source_pipeline_attrs[:project][:web_url]).to eq upstream_pipeline.project.web_url
+ expect(source_pipeline_attrs[:project][:path_with_namespace]).to eq upstream_pipeline.project.full_path
+ end
+ end
+
+ context 'in same project' do
+ let_it_be(:upstream_pipeline) { create(:ci_pipeline, upstream_of: pipeline, project: ci_build.project) }
+
+ it_behaves_like 'source pipeline attributes'
+ end
+
+ context 'in different project' do
+ let_it_be(:upstream_pipeline) { create(:ci_pipeline, upstream_of: pipeline) }
+
+ it_behaves_like 'source pipeline attributes'
+
+ it { expect(source_pipeline_attrs[:project][:id]).not_to eq pipeline.project.id }
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/data_builder/deployment_spec.rb b/spec/lib/gitlab/data_builder/deployment_spec.rb
index 82ec3e791a4..bbcfa1973ea 100644
--- a/spec/lib/gitlab/data_builder/deployment_spec.rb
+++ b/spec/lib/gitlab/data_builder/deployment_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment, feature_category: :continuous_de
subject(:data) { described_class.build(deployment, 'created', Time.current) }
- before(:all) do
+ before_all do
project.repository.remove
end
@@ -74,7 +74,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment, feature_category: :continuous_de
subject(:data) { described_class.build(deployment, 'created', Time.current) }
- before(:all) do
+ before_all do
deployment.user = nil
end
diff --git a/spec/lib/gitlab/data_builder/issuable_spec.rb b/spec/lib/gitlab/data_builder/issuable_spec.rb
index 455800a3f7d..22c0eb1c7f9 100644
--- a/spec/lib/gitlab/data_builder/issuable_spec.rb
+++ b/spec/lib/gitlab/data_builder/issuable_spec.rb
@@ -4,6 +4,8 @@ require 'spec_helper'
RSpec.describe Gitlab::DataBuilder::Issuable do
let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:reusable_project) { create(:project, :repository, group: group) }
# This shared example requires a `builder` and `user` variable
shared_examples 'issuable hook data' do |kind, hook_data_issuable_builder_class|
@@ -96,17 +98,17 @@ RSpec.describe Gitlab::DataBuilder::Issuable do
describe '#build' do
it_behaves_like 'issuable hook data', 'issue', Gitlab::HookData::IssueBuilder do
- let(:issuable) { create(:issue, description: 'A description') }
+ let_it_be(:issuable) { create(:issue, description: 'A description', project: reusable_project) }
let(:builder) { described_class.new(issuable) }
end
it_behaves_like 'issuable hook data', 'merge_request', Gitlab::HookData::MergeRequestBuilder do
- let(:issuable) { create(:merge_request, description: 'A description') }
+ let_it_be(:issuable) { create(:merge_request, description: 'A description', source_project: reusable_project) }
let(:builder) { described_class.new(issuable) }
end
context 'issue is assigned' do
- let(:issue) { create(:issue, assignees: [user]) }
+ let(:issue) { create(:issue, assignees: [user], project: reusable_project) }
let(:data) { described_class.new(issue).build(user: user) }
it 'returns correct hook data' do
@@ -117,8 +119,21 @@ RSpec.describe Gitlab::DataBuilder::Issuable do
end
end
+ context 'when issuable is a group level work item' do
+ let(:work_item) { create(:work_item, namespace: group, description: 'work item description') }
+
+ it 'returns correct hook data', :aggregate_failures do
+ data = described_class.new(work_item).build(user: user)
+
+ expect(data[:object_kind]).to eq('work_item')
+ expect(data[:event_type]).to eq('work_item')
+ expect(data.dig(:object_attributes, :id)).to eq(work_item.id)
+ expect(data.dig(:object_attributes, :iid)).to eq(work_item.iid)
+ end
+ end
+
context 'merge_request is assigned' do
- let(:merge_request) { create(:merge_request, assignees: [user]) }
+ let(:merge_request) { create(:merge_request, assignees: [user], source_project: reusable_project) }
let(:data) { described_class.new(merge_request).build(user: user) }
it 'returns correct hook data' do
@@ -129,7 +144,7 @@ RSpec.describe Gitlab::DataBuilder::Issuable do
end
context 'merge_request is assigned reviewers' do
- let(:merge_request) { create(:merge_request, reviewers: [user]) }
+ let(:merge_request) { create(:merge_request, reviewers: [user], source_project: reusable_project) }
let(:data) { described_class.new(merge_request).build(user: user) }
it 'returns correct hook data' do
@@ -139,7 +154,7 @@ RSpec.describe Gitlab::DataBuilder::Issuable do
end
context 'when merge_request does not have reviewers and assignees' do
- let(:merge_request) { create(:merge_request) }
+ let(:merge_request) { create(:merge_request, source_project: reusable_project) }
let(:data) { described_class.new(merge_request).build(user: user) }
it 'returns correct hook data' do
diff --git a/spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb b/spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb
index 52fbf6d2f9b..02b84085cc4 100644
--- a/spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb
+++ b/spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb
@@ -80,12 +80,16 @@ RSpec.describe Gitlab::Database::AsyncConstraints::PostgresAsyncConstraintValida
it { expect(described_class.constraint_type_exists?).to be_truthy }
it 'always asks the database' do
- control = ActiveRecord::QueryRecorder.new(skip_schema_queries: false) do
+ control1 = ActiveRecord::QueryRecorder.new(skip_schema_queries: false) do
described_class.constraint_type_exists?
end
- expect(control.count).to be >= 1
- expect { described_class.constraint_type_exists? }.to issue_same_number_of_queries_as(control)
+ control2 = ActiveRecord::QueryRecorder.new(skip_schema_queries: false) do
+ described_class.constraint_type_exists?
+ end
+
+ expect(control1.count).to eq(1)
+ expect(control2.count).to eq(1)
end
end
diff --git a/spec/lib/gitlab/database/batch_count_spec.rb b/spec/lib/gitlab/database/batch_count_spec.rb
index 53f8fe3dcd2..89652b81fde 100644
--- a/spec/lib/gitlab/database/batch_count_spec.rb
+++ b/spec/lib/gitlab/database/batch_count_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::BatchCount do
let_it_be(:fallback) { ::Gitlab::Database::BatchCounter::FALLBACK }
let_it_be(:small_batch_size) { calculate_batch_size(::Gitlab::Database::BatchCounter::MIN_REQUIRED_BATCH_SIZE) }
+ let_it_be(:max_allowed_loops) { ::Gitlab::Database::BatchCounter::MAX_ALLOWED_LOOPS }
let(:model) { Issue }
let(:column) { :author_id }
@@ -34,7 +35,7 @@ RSpec.describe Gitlab::Database::BatchCount do
end
it 'returns fallback if loops more than allowed' do
- large_finish = Gitlab::Database::BatchCounter::MAX_ALLOWED_LOOPS * default_batch_size + 1
+ large_finish = max_allowed_loops * default_batch_size + 1
expect(described_class.public_send(method, *args, start: 1, finish: large_finish)).to eq(fallback)
end
@@ -81,6 +82,7 @@ RSpec.describe Gitlab::Database::BatchCount do
relation: model.table_name,
operation: operation,
operation_args: operation_args,
+ max_allowed_loops: max_allowed_loops,
start: 0,
mode: mode,
query: batch_count_query,
diff --git a/spec/lib/gitlab/database/bump_sequences_spec.rb b/spec/lib/gitlab/database/bump_sequences_spec.rb
new file mode 100644
index 00000000000..db420123350
--- /dev/null
+++ b/spec/lib/gitlab/database/bump_sequences_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::BumpSequences, feature_category: :cell, query_analyzers: false do
+ let!(:gitlab_schema) { :gitlab_main_cell }
+ let!(:increment_by) { 1000 }
+
+ let!(:main_cell_sequence_name) { 'namespaces_id_seq' }
+ let!(:main_sequence_name) { 'vulnerabilities_id_seq' }
+ let!(:main_clusterwide_sequence_name) { 'users_id_seq' }
+ let!(:ci_sequence_name) { 'ci_build_needs_id_seq' }
+
+ # This is just to make sure that all of the sequences start with `is_called=True`
+ # which means that the next call to nextval() is going to increment the sequence.
+ # To give predictable test results.
+ before do
+ ApplicationRecord.connection.select_value("select nextval($1)", nil, [main_cell_sequence_name])
+ ApplicationRecord.connection.select_value("select nextval($1)", nil, [main_sequence_name])
+ ApplicationRecord.connection.select_value("select nextval($1)", nil, [main_clusterwide_sequence_name])
+ ApplicationRecord.connection.select_value("select nextval($1)", nil, [ci_sequence_name])
+ end
+
+ describe '#execute' do
+ subject { described_class.new(gitlab_schema, increment_by).execute }
+
+ context 'when bumping the sequences' do
+ it 'changes sequences by the passed argument `increase_by` value on the main database' do
+ expect do
+ subject
+ end.to change {
+ last_value_of_sequence(ApplicationRecord.connection, main_cell_sequence_name)
+ }.by(1001) # the +1 is because the sequence has is_called = true
+ end
+
+ it 'will still increase the value of sequences that have is_called = False' do
+ # see `is_called`: https://www.postgresql.org/docs/12/functions-sequence.html
+ # choosing a new arbitrary value for the sequence
+ new_value = last_value_of_sequence(ApplicationRecord.connection, main_cell_sequence_name) + 1000
+ ApplicationRecord.connection.select_value(
+ "select setval($1, $2, false)", nil, [main_cell_sequence_name, new_value]
+ )
+ expect do
+ subject
+ end.to change {
+ last_value_of_sequence(ApplicationRecord.connection, main_cell_sequence_name)
+ }.by(1000)
+ end
+
+ it 'resets the INCREMENT value of the sequences back to 1 for the following calls to nextval()' do
+ subject
+ value_1 = ApplicationRecord.connection.select_value("select nextval($1)", nil, [main_cell_sequence_name])
+ value_2 = ApplicationRecord.connection.select_value("select nextval($1)", nil, [main_cell_sequence_name])
+ expect(value_2 - value_1).to eq(1)
+ end
+
+ it 'increments the sequence of the tables in the given schema, but not in other schemas' do
+ expect do
+ subject
+ end.to change {
+ last_value_of_sequence(ApplicationRecord.connection, main_cell_sequence_name)
+ }.by(1001)
+ .and change {
+ last_value_of_sequence(ApplicationRecord.connection, main_sequence_name)
+ }.by(0)
+ .and change {
+ last_value_of_sequence(ApplicationRecord.connection, main_clusterwide_sequence_name)
+ }.by(0)
+ .and change {
+ last_value_of_sequence(ApplicationRecord.connection, ci_sequence_name)
+ }.by(0)
+ end
+ end
+ end
+
+ private
+
+ def last_value_of_sequence(connection, sequence_name)
+ allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408220') do
+ connection.select_value("select last_value from #{sequence_name}")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/click_house_client_spec.rb b/spec/lib/gitlab/database/click_house_client_spec.rb
index 502d879bf6a..50086795b2b 100644
--- a/spec/lib/gitlab/database/click_house_client_spec.rb
+++ b/spec/lib/gitlab/database/click_house_client_spec.rb
@@ -12,25 +12,12 @@ RSpec.describe 'ClickHouse::Client', feature_category: :database do
end
describe 'when click_house spec tag is added', :click_house do
- around do |example|
- with_net_connect_allowed do
- example.run
- end
- end
-
it 'has a ClickHouse database configured' do
databases = ClickHouse::Client.configuration.databases
expect(databases).not_to be_empty
end
- it 'returns data from the DB via `select` method' do
- result = ClickHouse::Client.select("SELECT 1 AS value", :main)
-
- # returns JSON if successful. Otherwise error
- expect(result).to eq([{ 'value' => 1 }])
- end
-
it 'does not return data via `execute` method' do
result = ClickHouse::Client.execute("SELECT 1 AS value", :main)
diff --git a/spec/lib/gitlab/database/gitlab_schema_spec.rb b/spec/lib/gitlab/database/gitlab_schema_spec.rb
index 1c864239ae6..14ff1a462e3 100644
--- a/spec/lib/gitlab/database/gitlab_schema_spec.rb
+++ b/spec/lib/gitlab/database/gitlab_schema_spec.rb
@@ -148,7 +148,7 @@ RSpec.describe Gitlab::Database::GitlabSchema, feature_category: :database do
subject { described_class.table_schemas!(tables) }
it 'returns the matched schemas' do
- expect(subject).to match_array %i[gitlab_main gitlab_ci].to_set
+ expect(subject).to match_array %i[gitlab_main_cell gitlab_main gitlab_ci].to_set
end
context 'when one of the tables does not have a matching table schema' do
diff --git a/spec/lib/gitlab/database/health_status/indicators/patroni_apdex_spec.rb b/spec/lib/gitlab/database/health_status/indicators/patroni_apdex_spec.rb
index e0e3a0a7c23..9382074f584 100644
--- a/spec/lib/gitlab/database/health_status/indicators/patroni_apdex_spec.rb
+++ b/spec/lib/gitlab/database/health_status/indicators/patroni_apdex_spec.rb
@@ -3,150 +3,27 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::HealthStatus::Indicators::PatroniApdex, :aggregate_failures, feature_category: :database do # rubocop:disable Layout/LineLength
- let(:schema) { :main }
- let(:connection) { Gitlab::Database.database_base_models[schema].connection }
-
- around do |example|
- Gitlab::Database::SharedModel.using_connection(connection) do
- example.run
- end
- end
-
- describe '#evaluate' do
- let(:prometheus_url) { 'http://thanos:9090' }
- let(:prometheus_config) { [prometheus_url, { allow_local_requests: true, verify: true }] }
-
- let(:prometheus_client) { instance_double(Gitlab::PrometheusClient) }
-
- let(:context) do
- Gitlab::Database::HealthStatus::Context.new(
- described_class,
- connection,
- ['users'],
- gitlab_schema
- )
- end
-
- let(:gitlab_schema) { "gitlab_#{schema}" }
- let(:client_ready) { true }
- let(:database_apdex_sli_query_main) { 'Apdex query for main' }
- let(:database_apdex_sli_query_ci) { 'Apdex query for ci' }
- let(:database_apdex_slo_main) { 0.99 }
- let(:database_apdex_slo_ci) { 0.95 }
- let(:database_apdex_settings) do
+ it_behaves_like 'Prometheus Alert based health indicator' do
+ let(:feature_flag) { :batched_migrations_health_status_patroni_apdex }
+ let(:sli_query_main) { 'Apdex query for main' }
+ let(:sli_query_ci) { 'Apdex query for ci' }
+ let(:slo_main) { 0.99 }
+ let(:slo_ci) { 0.95 }
+ let(:sli_with_good_condition) { { main: 0.991, ci: 0.951 } }
+ let(:sli_with_bad_condition) { { main: 0.989, ci: 0.949 } }
+
+ let(:prometheus_alert_db_indicators_settings) do
{
prometheus_api_url: prometheus_url,
apdex_sli_query: {
- main: database_apdex_sli_query_main,
- ci: database_apdex_sli_query_ci
+ main: sli_query_main,
+ ci: sli_query_ci
},
apdex_slo: {
- main: database_apdex_slo_main,
- ci: database_apdex_slo_ci
+ main: slo_main,
+ ci: slo_ci
}
}
end
-
- subject(:evaluate) { described_class.new(context).evaluate }
-
- before do
- stub_application_setting(database_apdex_settings: database_apdex_settings)
-
- allow(Gitlab::PrometheusClient).to receive(:new).with(*prometheus_config).and_return(prometheus_client)
- allow(prometheus_client).to receive(:ready?).and_return(client_ready)
- end
-
- shared_examples 'Patroni Apdex Evaluator' do |schema|
- context "with #{schema} schema" do
- let(:schema) { schema }
- let(:apdex_slo_above_sli) { { main: 0.991, ci: 0.951 } }
- let(:apdex_slo_below_sli) { { main: 0.989, ci: 0.949 } }
-
- it 'returns NoSignal signal in case the feature flag is disabled' do
- stub_feature_flags(batched_migrations_health_status_patroni_apdex: false)
-
- expect(evaluate).to be_a(Gitlab::Database::HealthStatus::Signals::NotAvailable)
- expect(evaluate.reason).to include('indicator disabled')
- end
-
- context 'without database_apdex_settings' do
- let(:database_apdex_settings) { nil }
-
- it 'returns Unknown signal' do
- expect(evaluate).to be_a(Gitlab::Database::HealthStatus::Signals::Unknown)
- expect(evaluate.reason).to include('Patroni Apdex Settings not configured')
- end
- end
-
- context 'when Prometheus client is not ready' do
- let(:client_ready) { false }
-
- it 'returns Unknown signal' do
- expect(evaluate).to be_a(Gitlab::Database::HealthStatus::Signals::Unknown)
- expect(evaluate.reason).to include('Prometheus client is not ready')
- end
- end
-
- context 'when apdex SLI query is not configured' do
- let(:"database_apdex_sli_query_#{schema}") { nil }
-
- it 'returns Unknown signal' do
- expect(evaluate).to be_a(Gitlab::Database::HealthStatus::Signals::Unknown)
- expect(evaluate.reason).to include('Apdex SLI query is not configured')
- end
- end
-
- context 'when slo is not configured' do
- let(:"database_apdex_slo_#{schema}") { nil }
-
- it 'returns Unknown signal' do
- expect(evaluate).to be_a(Gitlab::Database::HealthStatus::Signals::Unknown)
- expect(evaluate.reason).to include('Apdex SLO is not configured')
- end
- end
-
- it 'returns Normal signal when Patroni apdex SLI is above SLO' do
- expect(prometheus_client).to receive(:query)
- .with(send("database_apdex_sli_query_#{schema}"))
- .and_return([{ "value" => [1662423310.878, apdex_slo_above_sli[schema]] }])
- expect(evaluate).to be_a(Gitlab::Database::HealthStatus::Signals::Normal)
- expect(evaluate.reason).to include('Patroni service apdex is above SLO')
- end
-
- it 'returns Stop signal when Patroni apdex is below SLO' do
- expect(prometheus_client).to receive(:query)
- .with(send("database_apdex_sli_query_#{schema}"))
- .and_return([{ "value" => [1662423310.878, apdex_slo_below_sli[schema]] }])
- expect(evaluate).to be_a(Gitlab::Database::HealthStatus::Signals::Stop)
- expect(evaluate.reason).to include('Patroni service apdex is below SLO')
- end
-
- context 'when Patroni apdex can not be calculated' do
- where(:result) do
- [
- nil,
- [],
- [{}],
- [{ 'value' => 1 }],
- [{ 'value' => [1] }]
- ]
- end
-
- with_them do
- it 'returns Unknown signal' do
- expect(prometheus_client).to receive(:query).and_return(result)
- expect(evaluate).to be_a(Gitlab::Database::HealthStatus::Signals::Unknown)
- expect(evaluate.reason).to include('Patroni service apdex can not be calculated')
- end
- end
- end
- end
- end
-
- Gitlab::Database.database_base_models.each do |database_base_model, connection|
- next unless connection.present?
-
- it_behaves_like 'Patroni Apdex Evaluator', database_base_model.to_sym
- end
end
end
diff --git a/spec/lib/gitlab/database/health_status/indicators/prometheus_alert_indicator_spec.rb b/spec/lib/gitlab/database/health_status/indicators/prometheus_alert_indicator_spec.rb
new file mode 100644
index 00000000000..393bbf6beff
--- /dev/null
+++ b/spec/lib/gitlab/database/health_status/indicators/prometheus_alert_indicator_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::HealthStatus::Indicators::PrometheusAlertIndicator, :aggregate_failures, feature_category: :database do # rubocop:disable Layout/LineLength
+ let(:connection) { Gitlab::Database.database_base_models[:main].connection }
+
+ let(:context) do
+ Gitlab::Database::HealthStatus::Context.new(
+ described_class,
+ connection,
+ ['users'],
+ :gitlab_main
+ )
+ end
+
+ let(:invalid_indicator) do
+ Class.new(described_class).new(context)
+ end
+
+ let(:valid_indicator) do
+ Class.new(described_class) do
+ def enabled?
+ true
+ end
+
+ def slo_key
+ :test_indicator_slo
+ end
+
+ def sli_key
+ :test_indicator_sli
+ end
+ end.new(context)
+ end
+
+ describe '#enabled?' do
+ it 'throws NotImplementedError for invalid indicator' do
+ expect { invalid_indicator.send(:enabled?) }.to raise_error(NotImplementedError)
+ end
+
+ it 'returns the defined value for valid indicator' do
+ expect(valid_indicator.send(:enabled?)).to eq(true)
+ end
+ end
+
+ describe '#slo_key' do
+ it 'throws NotImplementedError for invalid indicator' do
+ expect { invalid_indicator.send(:slo_key) }.to raise_error(NotImplementedError)
+ end
+
+ it 'returns the defined value for valid indicator' do
+ expect(valid_indicator.send(:slo_key)).to eq(:test_indicator_slo)
+ end
+ end
+
+ describe '#sli_key' do
+ it 'throws NotImplementedError for invalid indicator' do
+ expect { invalid_indicator.send(:sli_key) }.to raise_error(NotImplementedError)
+ end
+
+ it 'returns the defined value for valid indicator' do
+ expect(valid_indicator.send(:sli_key)).to eq(:test_indicator_sli)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/health_status/indicators/wal_rate_spec.rb b/spec/lib/gitlab/database/health_status/indicators/wal_rate_spec.rb
new file mode 100644
index 00000000000..d6fe7f0cead
--- /dev/null
+++ b/spec/lib/gitlab/database/health_status/indicators/wal_rate_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::HealthStatus::Indicators::WalRate, :aggregate_failures, feature_category: :database do # rubocop:disable Layout/LineLength
+ it_behaves_like 'Prometheus Alert based health indicator' do
+ let(:feature_flag) { :db_health_check_wal_rate }
+ let(:sli_query_main) { 'WAL rate query for main' }
+ let(:sli_query_ci) { 'WAL rate query for ci' }
+ let(:slo_main) { 100 }
+ let(:slo_ci) { 100 }
+ let(:sli_with_good_condition) { { main: 70, ci: 70 } }
+ let(:sli_with_bad_condition) { { main: 120, ci: 120 } }
+
+ let(:prometheus_alert_db_indicators_settings) do
+ {
+ prometheus_api_url: prometheus_url,
+ wal_rate_sli_query: {
+ main: sli_query_main,
+ ci: sli_query_ci
+ },
+ wal_rate_slo: {
+ main: slo_main,
+ ci: slo_ci
+ }
+ }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/health_status_spec.rb b/spec/lib/gitlab/database/health_status_spec.rb
index 4a2b9eee45a..95f74929b84 100644
--- a/spec/lib/gitlab/database/health_status_spec.rb
+++ b/spec/lib/gitlab/database/health_status_spec.rb
@@ -21,9 +21,11 @@ RSpec.describe Gitlab::Database::HealthStatus, feature_category: :database do
let(:autovacuum_indicator_class) { health_status::Indicators::AutovacuumActiveOnTable }
let(:wal_indicator_class) { health_status::Indicators::WriteAheadLog }
let(:patroni_apdex_indicator_class) { health_status::Indicators::PatroniApdex }
+ let(:wal_rate_indicator_class) { health_status::Indicators::WalRate }
let(:autovacuum_indicator) { instance_double(autovacuum_indicator_class) }
let(:wal_indicator) { instance_double(wal_indicator_class) }
let(:patroni_apdex_indicator) { instance_double(patroni_apdex_indicator_class) }
+ let(:wal_rate_indicator) { instance_double(wal_rate_indicator_class) }
before do
allow(autovacuum_indicator_class).to receive(:new).with(health_context).and_return(autovacuum_indicator)
@@ -39,11 +41,17 @@ RSpec.describe Gitlab::Database::HealthStatus, feature_category: :database do
expect(autovacuum_indicator).to receive(:evaluate).and_return(normal_signal)
expect(wal_indicator_class).to receive(:new).with(health_context).and_return(wal_indicator)
expect(wal_indicator).to receive(:evaluate).and_return(not_available_signal)
- expect(patroni_apdex_indicator_class).to receive(:new).with(health_context)
- .and_return(patroni_apdex_indicator)
+ expect(patroni_apdex_indicator_class).to receive(:new).with(health_context).and_return(patroni_apdex_indicator)
expect(patroni_apdex_indicator).to receive(:evaluate).and_return(not_available_signal)
-
- expect(evaluate).to contain_exactly(normal_signal, not_available_signal, not_available_signal)
+ expect(wal_rate_indicator_class).to receive(:new).with(health_context).and_return(wal_rate_indicator)
+ expect(wal_rate_indicator).to receive(:evaluate).and_return(not_available_signal)
+
+ expect(evaluate).to contain_exactly(
+ normal_signal,
+ not_available_signal,
+ not_available_signal,
+ not_available_signal
+ )
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb b/spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb
index cee5f54bd6a..1ff157b51d4 100644
--- a/spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb
@@ -3,7 +3,15 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::MigrationHelpers::ConvertToBigint, feature_category: :database do
- describe 'com_or_dev_or_test_but_not_jh?' do
+ let(:migration) do
+ Class
+ .new
+ .include(described_class)
+ .include(Gitlab::Database::MigrationHelpers)
+ .new
+ end
+
+ describe '#com_or_dev_or_test_but_not_jh?' do
using RSpec::Parameterized::TableSyntax
where(:dot_com, :dev_or_test, :jh, :expectation) do
@@ -23,13 +31,46 @@ RSpec.describe Gitlab::Database::MigrationHelpers::ConvertToBigint, feature_cate
allow(Gitlab).to receive(:dev_or_test_env?).and_return(dev_or_test)
allow(Gitlab).to receive(:jh?).and_return(jh)
- migration = Class
- .new
- .include(Gitlab::Database::MigrationHelpers::ConvertToBigint)
- .new
-
expect(migration.com_or_dev_or_test_but_not_jh?).to eq(expectation)
end
end
end
+
+ describe '#temp_column_removed?' do
+ it 'return true when column is not present' do
+ expect(migration).to receive(:column_exists?).with('test_table', 'id_convert_to_bigint').and_return(false)
+
+ expect(migration.temp_column_removed?(:test_table, :id)).to eq(true)
+ end
+
+ it 'return false when column present' do
+ expect(migration).to receive(:column_exists?).with('test_table', 'id_convert_to_bigint').and_return(true)
+
+ expect(migration.temp_column_removed?(:test_table, :id)).to eq(false)
+ end
+ end
+
+ describe '#columns_swapped?' do
+ it 'returns true if columns are already swapped' do
+ columns = [
+ Struct.new(:name, :sql_type).new('id', 'bigint'),
+ Struct.new(:name, :sql_type).new('id_convert_to_bigint', 'integer')
+ ]
+
+ expect(migration).to receive(:columns).with('test_table').and_return(columns)
+
+ expect(migration.columns_swapped?(:test_table, :id)).to eq(true)
+ end
+
+ it 'returns false if columns are not yet swapped' do
+ columns = [
+ Struct.new(:name, :sql_type).new('id', 'integer'),
+ Struct.new(:name, :sql_type).new('id_convert_to_bigint', 'bigint')
+ ]
+
+ expect(migration).to receive(:columns).with('test_table').and_return(columns)
+
+ expect(migration.columns_swapped?(:test_table, :id)).to eq(false)
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index b1e8301d69f..f3c181db3aa 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -2867,4 +2867,43 @@ RSpec.describe Gitlab::Database::MigrationHelpers, feature_category: :database d
it { is_expected.to be_falsey }
end
end
+
+ describe '#remove_column_default' do
+ let(:test_table) { :_test_defaults_table }
+ let(:drop_default_statement) do
+ /ALTER TABLE "#{test_table}" ALTER COLUMN "#{column_name}" SET DEFAULT NULL/
+ end
+
+ subject(:recorder) do
+ ActiveRecord::QueryRecorder.new do
+ model.remove_column_default(test_table, column_name)
+ end
+ end
+
+ before do
+ model.create_table(test_table) do |t|
+ t.integer :int_with_default, default: 100
+ t.integer :int_with_default_function, default: -> { 'ceil(random () * 100)::int' }
+ t.integer :int_without_default
+ end
+ end
+
+ context 'with default values' do
+ let(:column_name) { :int_with_default }
+
+ it { expect(recorder.log).to include(drop_default_statement) }
+ end
+
+ context 'with default functions' do
+ let(:column_name) { :int_with_default_function }
+
+ it { expect(recorder.log).to include(drop_default_statement) }
+ end
+
+ context 'without any defaults' do
+ let(:column_name) { :int_without_default }
+
+ it { expect(recorder.log).to be_empty }
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
index 82f77d2bb19..158497b1fef 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
@@ -473,7 +473,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
"\n\n" \
"For more information, check the documentation" \
"\n\n" \
- "\thttps://docs.gitlab.com/ee/user/admin_area/monitoring/background_migrations.html#database-migrations-failing-because-of-batched-background-migration-not-finished"
+ "\thttps://docs.gitlab.com/ee/update/background_migrations.html#database-migrations-failing-because-of-batched-background-migration-not-finished"
end
it 'does not raise error when migration exists and is marked as finished' do
diff --git a/spec/lib/gitlab/database/migrations/squasher_spec.rb b/spec/lib/gitlab/database/migrations/squasher_spec.rb
new file mode 100644
index 00000000000..e7ab5873f73
--- /dev/null
+++ b/spec/lib/gitlab/database/migrations/squasher_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+RSpec.describe Gitlab::Database::Migrations::Squasher, feature_category: :database do
+ let(:git_output) do
+ <<~FILES
+ db/migrate/misplaced.txt
+ db/migrate/20221003041700_init_schema.rb
+ db/migrate/20221003041800_foo_migrate.rb
+ db/migrate/20221003041900_foo_migrate_two.rb
+ db/migrate/20221003042000_add_name_to_widgets.rb
+ db/migrate/20221003042200_add_enterprise.rb
+ db/post_migrate/20221003042100_post_migrate.rb
+ FILES
+ end
+
+ let(:spec_files) do
+ [
+ 'spec/migrations/add_name_to_widgets_spec.rb',
+ 'spec/migrations/20221003041800_foo_migrate_spec.rb',
+ 'spec/migrations/foo_migrate_three_spec.rb',
+ 'spec/migrations/foo_migrate_two_spec.rb',
+ 'spec/migrations/post_migrate_spec.rb'
+ ]
+ end
+
+ let(:ee_spec_files) do
+ [
+ 'ee/spec/migrations/add_enterprise_spec.rb'
+ ]
+ end
+
+ let(:expected_list) do
+ [
+ 'db/migrate/20221003041800_foo_migrate.rb',
+ 'db/migrate/20221003041900_foo_migrate_two.rb',
+ 'db/migrate/20221003042000_add_name_to_widgets.rb',
+ 'spec/migrations/add_name_to_widgets_spec.rb',
+ 'spec/migrations/20221003041800_foo_migrate_spec.rb',
+ 'spec/migrations/foo_migrate_two_spec.rb',
+ 'db/schema_migrations/20221003041800',
+ 'db/schema_migrations/20221003041900',
+ 'db/schema_migrations/20221003042000',
+ 'db/schema_migrations/20221003042100',
+ 'db/schema_migrations/20221003042200',
+ 'db/post_migrate/20221003042100_post_migrate.rb',
+ 'spec/migrations/post_migrate_spec.rb',
+ 'ee/spec/migrations/add_enterprise_spec.rb',
+ 'db/migrate/20221003042200_add_enterprise.rb'
+ ]
+ end
+
+ describe "#files_to_delete" do
+ before do
+ allow(Dir).to receive(:glob).with(Rails.root.join('spec/migrations/*.rb')).and_return(spec_files)
+ allow(Dir).to receive(:glob).with(Rails.root.join('ee/spec/migrations/*.rb')).and_return(ee_spec_files)
+ end
+
+ let(:squasher) { described_class.new(git_output) }
+
+ it 'only deletes the files we\'re expecting' do
+ expect(squasher.files_to_delete).to match_array expected_list
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
index 7899c1588b2..6cac7abb703 100644
--- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
@@ -3,12 +3,27 @@
require 'spec_helper'
RSpec.describe 'cross-database foreign keys' do
- # Since we don't expect to have any cross-database foreign keys
- # this is empty. If we will have an entry like
- # `ci_daily_build_group_report_results.project_id`
- # should be added.
- let(:allowed_cross_database_foreign_keys) do
- %w[].freeze
+ # While we are building out Cells, we will be moving tables from gitlab_main schema
+ # to either gitlab_main_clusterwide schema or gitlab_main_cell schema.
+ # During this transition phase, cross database foreign keys need
+ # to be temporarily allowed to exist, until we can work on converting these columns to loose foreign keys.
+ # The issue corresponding to the loose foreign key conversion
+ # should be added as a comment along with the name of the column.
+ let!(:allowed_cross_database_foreign_keys) do
+ [
+ 'gitlab_subscriptions.hosted_plan_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422012
+ 'group_import_states.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/421210
+ 'identities.saml_provider_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422010
+ 'project_authorizations.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422044
+ 'merge_requests.assignee_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080
+ 'merge_requests.updated_by_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080
+ 'merge_requests.merge_user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080
+ 'merge_requests.author_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080
+ 'projects.creator_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/421844
+ 'projects.marked_for_deletion_by_user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/421844
+ 'routes.namespace_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/420869
+ 'user_group_callouts.user_id' # https://gitlab.com/gitlab-org/gitlab/-/issues/421287
+ ]
end
def foreign_keys_for(table_name)
diff --git a/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb b/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
index 399fcae2fa0..3650ca1d904 100644
--- a/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
+++ b/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Database::PostgresqlAdapter::ForceDisconnectableMixin, :reestablished_active_record_base do
+RSpec.describe Gitlab::Database::PostgresqlAdapter::ForceDisconnectableMixin, :delete, :reestablished_active_record_base do
describe 'checking in a connection to the pool' do
let(:model) do
Class.new(ActiveRecord::Base) do
@@ -32,14 +32,29 @@ RSpec.describe Gitlab::Database::PostgresqlAdapter::ForceDisconnectableMixin, :r
let(:timer) { connection.force_disconnect_timer }
context 'when the timer is expired' do
- it 'disconnects from the database' do
+ before do
allow(timer).to receive(:expired?).and_return(true)
+ end
+ it 'disconnects from the database' do
expect(connection).to receive(:disconnect!).and_call_original
expect(timer).to receive(:reset!).and_call_original
connection.force_disconnect_if_old!
end
+
+ context 'when the connection has an open transaction' do
+ it 'does not disconnect from the database' do
+ connection.begin_transaction
+
+ expect(connection).not_to receive(:disconnect!)
+ expect(timer).not_to receive(:reset!)
+
+ connection.force_disconnect_if_old!
+
+ connection.rollback_transaction
+ end
+ end
end
context 'when the timer is not expired' do
diff --git a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb
index b5e08f58608..f325060e592 100644
--- a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb
+++ b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana
model: ApplicationRecord,
sql: "SELECT 1 FROM projects",
expectations: {
- gitlab_schemas: "gitlab_main",
+ gitlab_schemas: "gitlab_main_cell",
db_config_name: "main"
}
},
@@ -37,7 +37,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana
model: ApplicationRecord,
sql: "SELECT 1 FROM projects LEFT JOIN ci_builds ON ci_builds.project_id=projects.id",
expectations: {
- gitlab_schemas: "gitlab_ci,gitlab_main",
+ gitlab_schemas: "gitlab_ci,gitlab_main_cell",
db_config_name: "main"
}
},
@@ -45,7 +45,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana
model: ApplicationRecord,
sql: "SELECT 1 FROM ci_builds LEFT JOIN projects ON ci_builds.project_id=projects.id",
expectations: {
- gitlab_schemas: "gitlab_ci,gitlab_main",
+ gitlab_schemas: "gitlab_ci,gitlab_main_cell",
db_config_name: "main"
}
},
diff --git a/spec/lib/gitlab/database/query_analyzers/query_recorder_spec.rb b/spec/lib/gitlab/database/query_analyzers/query_recorder_spec.rb
deleted file mode 100644
index 22ff66ff55e..00000000000
--- a/spec/lib/gitlab/database/query_analyzers/query_recorder_spec.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Database::QueryAnalyzers::QueryRecorder, feature_category: :database, query_analyzers: false do
- # We keep only the QueryRecorder analyzer running
- around do |example|
- described_class.with_suppressed(false) do
- example.run
- end
- end
-
- context 'with query analyzer' do
- let(:log_path) { Rails.root.join(described_class::LOG_PATH) }
- let(:log_file) { described_class.log_file }
-
- after do
- ::Gitlab::Database::QueryAnalyzer.instance.end!([described_class])
- end
-
- shared_examples_for 'an enabled query recorder' do
- using RSpec::Parameterized::TableSyntax
-
- normalized_query = <<~SQL.strip.tr("\n", ' ')
- SELECT \\\\"projects\\\\".\\\\"id\\\\"
- FROM \\\\"projects\\\\"
- WHERE \\\\"projects\\\\".\\\\"namespace_id\\\\" = \\?
- AND \\\\"projects\\\\".\\\\"id\\\\" IN \\(\\?,\\?,\\?\\);
- SQL
-
- where(:list_parameter, :bind_parameters) do
- '$2, $3' | [1, 2, 3]
- '$2, $3, $4' | [1, 2, 3, 4]
- '$2 ,$3 ,$4 ,$5' | [1, 2, 3, 4, 5]
- '$2 , $3 , $4 , $5, $6' | [1, 2, 3, 4, 5, 6]
- '$2, $3 ,$4 , $5,$6,$7' | [1, 2, 3, 4, 5, 6, 7]
- '$2,$3,$4,$5,$6,$7,$8' | [1, 2, 3, 4, 5, 6, 7, 8]
- end
-
- with_them do
- before do
- allow(described_class).to receive(:analyze).and_call_original
- allow(FileUtils).to receive(:mkdir_p)
- .with(log_path)
- end
-
- it 'logs normalized queries to a file' do
- expect(File).to receive(:write)
- .with(log_file, /^{"normalized":"#{normalized_query}/, mode: 'a')
-
- expect do
- ApplicationRecord.connection.exec_query(<<~SQL.strip.tr("\n", ' '), 'SQL', bind_parameters)
- SELECT "projects"."id"
- FROM "projects"
- WHERE "projects"."namespace_id" = $1
- AND "projects"."id" IN (#{list_parameter});
- SQL
- end.not_to raise_error
- end
- end
- end
-
- context 'on default branch' do
- before do
- stub_env('CI_MERGE_REQUEST_LABELS', nil)
- stub_env('CI_DEFAULT_BRANCH', 'default_branch_name')
- stub_env('CI_COMMIT_REF_NAME', 'default_branch_name')
-
- # This is needed to be able to stub_env the CI variable
- ::Gitlab::Database::QueryAnalyzer.instance.begin!([described_class])
- end
-
- it_behaves_like 'an enabled query recorder'
- end
-
- context 'on database merge requests' do
- before do
- stub_env('CI_MERGE_REQUEST_LABELS', 'database')
-
- # This is needed to be able to stub_env the CI variable
- ::Gitlab::Database::QueryAnalyzer.instance.begin!([described_class])
- end
-
- it_behaves_like 'an enabled query recorder'
- end
- end
-
- describe '.log_file' do
- let(:folder) { 'query_recorder' }
- let(:extension) { 'ndjson' }
- let(:default_name) { 'rspec' }
- let(:job_name) { 'test-job-1' }
-
- subject { described_class.log_file.to_s }
-
- context 'when in CI' do
- before do
- stub_env('CI_JOB_NAME_SLUG', job_name)
- end
-
- it { is_expected.to include("#{folder}/#{job_name}.#{extension}") }
- it { is_expected.not_to include("#{folder}/#{default_name}.#{extension}") }
- end
-
- context 'when not in CI' do
- before do
- stub_env('CI_JOB_NAME_SLUG', nil)
- end
-
- it { is_expected.to include("#{folder}/#{default_name}.#{extension}") }
- it { is_expected.not_to include("#{folder}/#{job_name}.#{extension}") }
- end
- end
-end
diff --git a/spec/lib/gitlab/database/schema_validation/schema_inconsistency_spec.rb b/spec/lib/gitlab/database/schema_validation/schema_inconsistency_spec.rb
deleted file mode 100644
index fbaf8474f22..00000000000
--- a/spec/lib/gitlab/database/schema_validation/schema_inconsistency_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Database::SchemaValidation::SchemaInconsistency, type: :model, feature_category: :database do
- it { is_expected.to be_a ApplicationRecord }
-
- describe 'associations' do
- it { is_expected.to belong_to(:issue) }
- end
-
- describe "Validations" do
- it { is_expected.to validate_presence_of(:object_name) }
- it { is_expected.to validate_presence_of(:valitador_name) }
- it { is_expected.to validate_presence_of(:table_name) }
- it { is_expected.to validate_presence_of(:diff) }
- end
-
- describe 'scopes' do
- describe '.with_open_issues' do
- subject(:inconsistencies) { described_class.with_open_issues }
-
- let(:closed_issue) { create(:issue, :closed) }
- let(:open_issue) { create(:issue, :opened) }
-
- let!(:schema_inconsistency_with_issue_closed) do
- create(:schema_inconsistency, object_name: 'index_name', table_name: 'achievements',
- valitador_name: 'different_definition_indexes', issue: closed_issue)
- end
-
- let!(:schema_inconsistency_with_issue_opened) do
- create(:schema_inconsistency, object_name: 'index_name', table_name: 'achievements',
- valitador_name: 'different_definition_indexes', issue: open_issue)
- end
-
- it 'returns only schema inconsistencies with GitLab issues open' do
- expect(inconsistencies).to eq([schema_inconsistency_with_issue_opened])
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/database/tables_sorted_by_foreign_keys_spec.rb b/spec/lib/gitlab/database/tables_sorted_by_foreign_keys_spec.rb
index aa25590ed58..70352775fe5 100644
--- a/spec/lib/gitlab/database/tables_sorted_by_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/tables_sorted_by_foreign_keys_spec.rb
@@ -2,11 +2,12 @@
require 'spec_helper'
-RSpec.describe Gitlab::Database::TablesSortedByForeignKeys do
- let(:connection) { ApplicationRecord.connection }
+RSpec.describe Gitlab::Database::TablesSortedByForeignKeys, feature_category: :cell do
+ let(:connection) { Ci::ApplicationRecord.connection }
let(:tables) do
%w[_test_gitlab_main_items _test_gitlab_main_references _test_gitlab_partition_parent
- gitlab_partitions_dynamic._test_gitlab_partition_20220101]
+ gitlab_partitions_dynamic._test_gitlab_partition_20220101
+ gitlab_partitions_dynamic._test_gitlab_partition_20220102]
end
subject do
@@ -35,7 +36,18 @@ RSpec.describe Gitlab::Database::TablesSortedByForeignKeys do
PARTITION OF _test_gitlab_partition_parent
FOR VALUES FROM ('20220101') TO ('20220131');
+ CREATE TABLE gitlab_partitions_dynamic._test_gitlab_partition_20220102
+ PARTITION OF _test_gitlab_partition_parent
+ FOR VALUES FROM ('20220201') TO ('20220228');
+
ALTER TABLE _test_gitlab_partition_parent DETACH PARTITION gitlab_partitions_dynamic._test_gitlab_partition_20220101;
+ ALTER TABLE _test_gitlab_partition_parent DETACH PARTITION gitlab_partitions_dynamic._test_gitlab_partition_20220102;
+
+ /* For some reason FK is now created in gitlab_partitions_dynamic */
+ ALTER TABLE gitlab_partitions_dynamic._test_gitlab_partition_20220101
+ DROP CONSTRAINT fk_constrained_1;
+ ALTER TABLE gitlab_partitions_dynamic._test_gitlab_partition_20220101
+ ADD CONSTRAINT fk_constrained_1 FOREIGN KEY(item_id) REFERENCES _test_gitlab_main_items(id);
SQL
connection.execute(statement)
end
@@ -47,6 +59,7 @@ RSpec.describe Gitlab::Database::TablesSortedByForeignKeys do
['_test_gitlab_main_references'],
['_test_gitlab_partition_parent'],
['gitlab_partitions_dynamic._test_gitlab_partition_20220101'],
+ ['gitlab_partitions_dynamic._test_gitlab_partition_20220102'],
['_test_gitlab_main_items']
])
end
@@ -62,6 +75,7 @@ RSpec.describe Gitlab::Database::TablesSortedByForeignKeys do
[
['_test_gitlab_partition_parent'],
['gitlab_partitions_dynamic._test_gitlab_partition_20220101'],
+ ['gitlab_partitions_dynamic._test_gitlab_partition_20220102'],
%w[_test_gitlab_main_items _test_gitlab_main_references]
])
end
diff --git a/spec/lib/gitlab/database/tables_truncate_spec.rb b/spec/lib/gitlab/database/tables_truncate_spec.rb
index ef76c9b8da3..04bec50088d 100644
--- a/spec/lib/gitlab/database/tables_truncate_spec.rb
+++ b/spec/lib/gitlab/database/tables_truncate_spec.rb
@@ -155,6 +155,7 @@ RSpec.describe Gitlab::Database::TablesTruncate, :reestablished_active_record_ba
"_test_gitlab_shared_items" => :gitlab_shared,
"_test_gitlab_geo_items" => :gitlab_geo,
"detached_partitions" => :gitlab_shared,
+ "postgres_foreign_keys" => :gitlab_shared,
"postgres_partitions" => :gitlab_shared
}
)
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index d51319d462b..0d8fa4dad6d 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -344,6 +344,33 @@ RSpec.describe Gitlab::Database, feature_category: :database do
end
end
+ describe '.db_config_share_with' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:db_config_name, :db_config_attributes, :expected_db_config_share_with) do
+ 'main' | { database_tasks: true } | nil
+ 'main' | { database_tasks: false } | nil
+ 'ci' | { database_tasks: true } | nil
+ 'ci' | { database_tasks: false } | 'main'
+ 'main_clusterwide' | { database_tasks: true } | nil
+ 'main_clusterwide' | { database_tasks: false } | 'main'
+ '_test_unknown' | { database_tasks: true } | nil
+ '_test_unknown' | { database_tasks: false } | 'main'
+ end
+
+ with_them do
+ it 'returns the expected result' do
+ db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new(
+ Rails.env,
+ db_config_name,
+ db_config_attributes
+ )
+
+ expect(described_class.db_config_share_with(db_config)).to eq(expected_db_config_share_with)
+ end
+ end
+ end
+
describe '.gitlab_schemas_for_connection' do
it 'does return a valid schema depending on a base model used', :request_store do
expect(described_class.gitlab_schemas_for_connection(Project.connection)).to include(:gitlab_main, :gitlab_shared)
diff --git a/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
index 8068fa30367..7f6b3b86799 100644
--- a/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
@@ -32,6 +32,7 @@ RSpec.describe Gitlab::DependencyLinker::CargoTomlLinker do
# Default dependencies format with fixed version and version range
chrono = "0.4.7"
xml-rs = ">=0.8.0"
+ indicatif = { version = "0.17.5", features = ["rayon"] }
[dependencies.memchr]
# Specific dependency with optional info
@@ -45,6 +46,24 @@ RSpec.describe Gitlab::DependencyLinker::CargoTomlLinker do
[build-dependencies]
# Build dependency with version wildcard
thread_local = "0.3.*"
+
+ # Dependencies with a custom location should be ignored
+ path-ignored = { path = "local" }
+ git-ignored = { git = "https://example.com/.git" }
+ registry-ignored = { registry = "custom-registry" }
+
+ [build-dependencies.bracked-ignored]
+ path = "local"
+
+ # Unless they specify a version and no registry
+ [build-dependencies.rand]
+ version = "0.8.5"
+ path = "../rand"
+
+ [build-dependencies.custom-rand]
+ version = "0.8.5"
+ path = "../custom-rand"
+ registry = "custom-registry"
CONTENT
end
@@ -62,8 +81,27 @@ RSpec.describe Gitlab::DependencyLinker::CargoTomlLinker do
expect(subject).to include(link('thread_local', 'https://crates.io/crates/thread_local'))
end
+ it 'links dependencies that use an inline table' do
+ expect(subject).to include(link('indicatif', 'https://crates.io/crates/indicatif'))
+ end
+
+ it 'links dependencies that include a version but no registry' do
+ expect(subject).to include(link('rand', 'https://crates.io/crates/rand'))
+ end
+
it 'does not contain metadata identified as package' do
expect(subject).not_to include(link('version', 'https://crates.io/crates/version'))
end
+
+ it 'does not link dependencies without a version' do
+ expect(subject).not_to include(link('path-ignored', 'https://crates.io/crates/path-ignored'))
+ expect(subject).not_to include(link('git-ignored', 'https://crates.io/crates/git-ignored'))
+ expect(subject).not_to include(link('bracked-ignored', 'https://crates.io/crates/bracked-ignored'))
+ end
+
+ it 'does not link dependencies with a custom registry' do
+ expect(subject).not_to include(link('registry-ignored', 'https://crates.io/crates/registry-ignored'))
+ expect(subject).not_to include(link('custom-rand', 'https://crates.io/crates/custom-rand'))
+ end
end
end
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index 968d26e1c38..c8325c5b359 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -2,7 +2,8 @@
require 'spec_helper'
-RSpec.describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do
+RSpec.describe Gitlab::ExclusiveLease, :request_store, :clean_gitlab_redis_shared_state,
+ :clean_gitlab_redis_cluster_shared_state, feature_category: :shared do
let(:unique_key) { SecureRandom.hex(10) }
describe '#try_obtain' do
@@ -19,6 +20,67 @@ RSpec.describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do
sleep(2 * timeout) # lease should have expired now
expect(lease.try_obtain).to be_present
end
+
+ context 'when migrating across stores' do
+ let(:lease) { described_class.new(unique_key, timeout: 3600) }
+
+ before do
+ stub_feature_flags(use_cluster_shared_state_for_exclusive_lease: false)
+ allow(lease).to receive(:same_store).and_return(false)
+ end
+
+ it 'acquires 2 locks' do
+ # stub first SETNX
+ Gitlab::Redis::SharedState.with { |r| expect(r).to receive(:set).and_return(true) }
+ Gitlab::Redis::ClusterSharedState.with { |r| expect(r).to receive(:set).and_call_original }
+
+ expect(lease.try_obtain).to be_truthy
+ end
+
+ it 'rollback first lock if second lock is not acquired' do
+ Gitlab::Redis::ClusterSharedState.with do |r|
+ expect(r).to receive(:set).and_return(false)
+ expect(r).to receive(:eval).and_call_original
+ end
+
+ Gitlab::Redis::SharedState.with do |r|
+ expect(r).to receive(:set).and_call_original
+ expect(r).to receive(:eval).and_call_original
+ end
+
+ expect(lease.try_obtain).to be_falsey
+ end
+ end
+
+ context 'when cutting over to ClusterSharedState' do
+ context 'when lock is not acquired' do
+ it 'waits for existing holder to yield the lock' do
+ Gitlab::Redis::ClusterSharedState.with { |r| expect(r).to receive(:set).and_call_original }
+ Gitlab::Redis::SharedState.with { |r| expect(r).not_to receive(:set) }
+
+ lease = described_class.new(unique_key, timeout: 3600)
+ expect(lease.try_obtain).to be_truthy
+ end
+ end
+
+ context 'when lock is still acquired' do
+ let(:lease) { described_class.new(unique_key, timeout: 3600) }
+
+ before do
+ # simulates cutover where some application's feature-flag has not updated
+ stub_feature_flags(use_cluster_shared_state_for_exclusive_lease: false)
+ lease.try_obtain
+ stub_feature_flags(use_cluster_shared_state_for_exclusive_lease: true)
+ end
+
+ it 'waits for existing holder to yield the lock' do
+ Gitlab::Redis::ClusterSharedState.with { |r| expect(r).not_to receive(:set) }
+ Gitlab::Redis::SharedState.with { |r| expect(r).not_to receive(:set) }
+
+ expect(lease.try_obtain).to be_falsey
+ end
+ end
+ end
end
describe '.redis_shared_state_key' do
@@ -42,131 +104,159 @@ RSpec.describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do
end
end
- describe '#renew' do
- it 'returns true when we have the existing lease' do
- lease = described_class.new(unique_key, timeout: 3600)
- expect(lease.try_obtain).to be_present
- expect(lease.renew).to be_truthy
- end
+ shared_examples 'write operations' do
+ describe '#renew' do
+ it 'returns true when we have the existing lease' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ expect(lease.try_obtain).to be_present
+ expect(lease.renew).to be_truthy
+ end
- it 'returns false when we dont have a lease' do
- lease = described_class.new(unique_key, timeout: 3600)
- expect(lease.renew).to be_falsey
+ it 'returns false when we dont have a lease' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ expect(lease.renew).to be_falsey
+ end
end
- end
- describe '#exists?' do
- it 'returns true for an existing lease' do
- lease = described_class.new(unique_key, timeout: 3600)
- lease.try_obtain
+ describe 'cancellation' do
+ def new_lease(key)
+ described_class.new(key, timeout: 3600)
+ end
- expect(lease.exists?).to eq(true)
- end
+ shared_examples 'cancelling a lease' do
+ let(:lease) { new_lease(unique_key) }
- it 'returns false for a lease that does not exist' do
- lease = described_class.new(unique_key, timeout: 3600)
+ it 'releases the held lease' do
+ uuid = lease.try_obtain
+ expect(uuid).to be_present
+ expect(new_lease(unique_key).try_obtain).to eq(false)
- expect(lease.exists?).to eq(false)
- end
- end
+ cancel_lease(uuid)
- describe '.get_uuid' do
- it 'gets the uuid if lease with the key associated exists' do
- uuid = described_class.new(unique_key, timeout: 3600).try_obtain
+ expect(new_lease(unique_key).try_obtain).to be_present
+ end
+ end
- expect(described_class.get_uuid(unique_key)).to eq(uuid)
- end
+ describe '.cancel' do
+ def cancel_lease(uuid)
+ described_class.cancel(release_key, uuid)
+ end
- it 'returns false if the lease does not exist' do
- expect(described_class.get_uuid(unique_key)).to be false
- end
- end
+ context 'when called with the unprefixed key' do
+ it_behaves_like 'cancelling a lease' do
+ let(:release_key) { unique_key }
+ end
+ end
- describe 'cancellation' do
- def new_lease(key)
- described_class.new(key, timeout: 3600)
- end
+ context 'when called with the prefixed key' do
+ it_behaves_like 'cancelling a lease' do
+ let(:release_key) { described_class.redis_shared_state_key(unique_key) }
+ end
+ end
- shared_examples 'cancelling a lease' do
- let(:lease) { new_lease(unique_key) }
+ it 'does not raise errors when given a nil key' do
+ expect { described_class.cancel(nil, nil) }.not_to raise_error
+ end
+ end
- it 'releases the held lease' do
- uuid = lease.try_obtain
- expect(uuid).to be_present
- expect(new_lease(unique_key).try_obtain).to eq(false)
+ describe '#cancel' do
+ def cancel_lease(_uuid)
+ lease.cancel
+ end
- cancel_lease(uuid)
+ it_behaves_like 'cancelling a lease'
- expect(new_lease(unique_key).try_obtain).to be_present
- end
- end
+ it 'is safe to call even if the lease was never obtained' do
+ lease = new_lease(unique_key)
- describe '.cancel' do
- def cancel_lease(uuid)
- described_class.cancel(release_key, uuid)
- end
+ lease.cancel
- context 'when called with the unprefixed key' do
- it_behaves_like 'cancelling a lease' do
- let(:release_key) { unique_key }
+ expect(new_lease(unique_key).try_obtain).to be_present
end
end
+ end
- context 'when called with the prefixed key' do
- it_behaves_like 'cancelling a lease' do
- let(:release_key) { described_class.redis_shared_state_key(unique_key) }
- end
- end
+ describe '.reset_all!' do
+ it 'removes all existing lease keys from redis' do
+ uuid = described_class.new(unique_key, timeout: 3600).try_obtain
- it 'does not raise errors when given a nil key' do
- expect { described_class.cancel(nil, nil) }.not_to raise_error
+ expect(described_class.get_uuid(unique_key)).to eq(uuid)
+
+ described_class.reset_all!
+
+ expect(described_class.get_uuid(unique_key)).to be_falsey
end
end
+ end
- describe '#cancel' do
- def cancel_lease(_uuid)
- lease.cancel
+ shared_examples 'read operations' do
+ describe '#exists?' do
+ it 'returns true for an existing lease' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ lease.try_obtain
+
+ expect(lease.exists?).to eq(true)
end
- it_behaves_like 'cancelling a lease'
+ it 'returns false for a lease that does not exist' do
+ lease = described_class.new(unique_key, timeout: 3600)
+
+ expect(lease.exists?).to eq(false)
+ end
+ end
- it 'is safe to call even if the lease was never obtained' do
- lease = new_lease(unique_key)
+ describe '.get_uuid' do
+ it 'gets the uuid if lease with the key associated exists' do
+ uuid = described_class.new(unique_key, timeout: 3600).try_obtain
- lease.cancel
+ expect(described_class.get_uuid(unique_key)).to eq(uuid)
+ end
- expect(new_lease(unique_key).try_obtain).to be_present
+ it 'returns false if the lease does not exist' do
+ expect(described_class.get_uuid(unique_key)).to be false
end
end
- end
- describe '#ttl' do
- it 'returns the TTL of the Redis key' do
- lease = described_class.new('kittens', timeout: 100)
- lease.try_obtain
+ describe '#ttl' do
+ it 'returns the TTL of the Redis key' do
+ lease = described_class.new('kittens', timeout: 100)
+ lease.try_obtain
- expect(lease.ttl <= 100).to eq(true)
- end
+ expect(lease.ttl <= 100).to eq(true)
+ end
- it 'returns nil when the lease does not exist' do
- lease = described_class.new('kittens', timeout: 10)
+ it 'returns nil when the lease does not exist' do
+ lease = described_class.new('kittens', timeout: 10)
- expect(lease.ttl).to be_nil
+ expect(lease.ttl).to be_nil
+ end
end
end
- describe '.reset_all!' do
- it 'removes all existing lease keys from redis' do
- uuid = described_class.new(unique_key, timeout: 3600).try_obtain
-
- expect(described_class.get_uuid(unique_key)).to eq(uuid)
+ context 'when migrating across stores' do
+ before do
+ stub_feature_flags(use_cluster_shared_state_for_exclusive_lease: false)
+ end
- described_class.reset_all!
+ it_behaves_like 'read operations'
+ it_behaves_like 'write operations'
+ end
- expect(described_class.get_uuid(unique_key)).to be_falsey
+ context 'when feature flags are all disabled' do
+ before do
+ stub_feature_flags(
+ use_cluster_shared_state_for_exclusive_lease: false,
+ enable_exclusive_lease_double_lock_rw: false
+ )
end
+
+ it_behaves_like 'read operations'
+ it_behaves_like 'write operations'
end
+ it_behaves_like 'read operations'
+ it_behaves_like 'write operations'
+
describe '.throttle' do
it 'prevents repeated execution of the block' do
number = 0
@@ -244,4 +334,74 @@ RSpec.describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do
described_class.throttle(1, count: 48, period: 1.day) {}
end
end
+
+ describe 'transitions between feature-flag toggles' do
+ shared_examples 'retains behaviours across transitions' do |flag|
+ it 'retains read behaviour' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ uuid = lease.try_obtain
+
+ expect(lease.ttl).not_to eq(nil)
+ expect(lease.exists?).to be_truthy
+ expect(described_class.get_uuid(unique_key)).to eq(uuid)
+
+ # simulates transition
+ stub_feature_flags({ flag => true })
+ Gitlab::SafeRequestStore.clear!
+
+ expect(lease.ttl).not_to eq(nil)
+ expect(lease.exists?).to be_truthy
+ expect(described_class.get_uuid(unique_key)).to eq(uuid)
+ end
+
+ it 'retains renew behaviour' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ lease.try_obtain
+
+ expect(lease.renew).to be_truthy
+
+ # simulates transition
+ stub_feature_flags({ flag => true })
+ Gitlab::SafeRequestStore.clear!
+
+ expect(lease.renew).to be_truthy
+ end
+
+ it 'retains renew behaviour' do
+ lease = described_class.new(unique_key, timeout: 3600)
+ uuid = lease.try_obtain
+ lease.cancel
+
+ # proves successful cancellation
+ expect(lease.try_obtain).to eq(uuid)
+
+ # simulates transition
+ stub_feature_flags({ flag => true })
+ Gitlab::SafeRequestStore.clear!
+
+ expect(lease.try_obtain).to be_falsey
+ lease.cancel
+ expect(lease.try_obtain).to eq(uuid)
+ end
+ end
+
+ context 'when enabling enable_exclusive_lease_double_lock_rw' do
+ before do
+ stub_feature_flags(
+ enable_exclusive_lease_double_lock_rw: false,
+ use_cluster_shared_state_for_exclusive_lease: false
+ )
+ end
+
+ it_behaves_like 'retains behaviours across transitions', :enable_exclusive_lease_double_lock_rw
+ end
+
+ context 'when enabling use_cluster_shared_state_for_exclusive_lease' do
+ before do
+ stub_feature_flags(use_cluster_shared_state_for_exclusive_lease: false)
+ end
+
+ it_behaves_like 'retains behaviours across transitions', :use_cluster_shared_state_for_exclusive_lease
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb
index 676ea2663d2..d21ac36bf34 100644
--- a/spec/lib/gitlab/git/blame_spec.rb
+++ b/spec/lib/gitlab/git/blame_spec.rb
@@ -13,13 +13,17 @@ RSpec.describe Gitlab::Git::Blame do
let(:result) do
[].tap do |data|
- blame.each do |commit, line, previous_path|
- data << { commit: commit, line: line, previous_path: previous_path }
+ blame.each do |commit, line, previous_path, span|
+ data << { commit: commit, line: line, previous_path: previous_path, span: span }
end
end
end
describe 'blaming a file' do
+ it 'has the right commit span' do
+ expect(result.first[:span]).to eq(95)
+ end
+
it 'has the right number of lines' do
expect(result.size).to eq(95)
expect(result.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index dd9f77f0211..5c4be1003c3 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -3,15 +3,16 @@
require "spec_helper"
RSpec.describe Gitlab::Git::Commit, feature_category: :source_code_management do
- let(:repository) { create(:project, :repository).repository.raw }
+ let_it_be(:repository) { create(:project, :repository).repository.raw }
let(:commit) { described_class.find(repository, SeedRepo::Commit::ID) }
describe "Commit info from gitaly commit" do
let(:subject) { (+"My commit").force_encoding('ASCII-8BIT') }
let(:body) { subject + (+"My body").force_encoding('ASCII-8BIT') }
let(:body_size) { body.length }
- let(:gitaly_commit) { build(:gitaly_commit, subject: subject, body: body, body_size: body_size) }
+ let(:gitaly_commit) { build(:gitaly_commit, subject: subject, body: body, body_size: body_size, tree_id: tree_id) }
let(:id) { gitaly_commit.id }
+ let(:tree_id) { 'd7f32d821c9cc7b1a9166ca7c4ba95b5c2d0d000' }
let(:committer) { gitaly_commit.committer }
let(:author) { gitaly_commit.author }
let(:commit) { described_class.new(repository, gitaly_commit) }
@@ -26,6 +27,7 @@ RSpec.describe Gitlab::Git::Commit, feature_category: :source_code_management do
it { expect(commit.committer_name).to eq(committer.name) }
it { expect(commit.committer_email).to eq(committer.email) }
it { expect(commit.parent_ids).to eq(gitaly_commit.parent_ids) }
+ it { expect(commit.tree_id).to eq(tree_id) }
context 'non-UTC dates' do
let(:seconds) { Time.now.to_i }
@@ -577,6 +579,14 @@ RSpec.describe Gitlab::Git::Commit, feature_category: :source_code_management do
it { is_expected.to eq(sample_commit_hash[:message]) }
end
+
+ describe '#tree_id' do
+ subject { super().tree_id }
+
+ it "doesn't return tree id for non-Gitaly commits" do
+ is_expected.to be_nil
+ end
+ end
end
describe '#stats' do
@@ -681,6 +691,100 @@ RSpec.describe Gitlab::Git::Commit, feature_category: :source_code_management do
end
end
+ describe 'SHA patterns' do
+ shared_examples 'a SHA-matching pattern' do
+ let(:expected_match) { sha }
+
+ shared_examples 'a match' do
+ it 'matches the pattern' do
+ expect(value).to match(pattern)
+ expect(pattern.match(value).to_a).to eq([expected_match])
+ end
+ end
+
+ shared_examples 'no match' do
+ it 'does not match the pattern' do
+ expect(value).not_to match(pattern)
+ end
+ end
+
+ shared_examples 'a SHA pattern' do
+ context "with too short value" do
+ let(:value) { sha[0, described_class::MIN_SHA_LENGTH - 1] }
+
+ it_behaves_like 'no match'
+ end
+
+ context "with full length" do
+ let(:value) { sha }
+
+ it_behaves_like 'a match'
+ end
+
+ context "with exceeeding length" do
+ let(:value) { sha + sha }
+
+ # This case is not exactly pretty for SHA1 as we would still match the full SHA256 length. It's arguable what
+ # the correct behaviour would be, but without starting to distinguish SHA1 and SHA256 hashes this is the best
+ # we can do.
+ let(:expected_match) { (sha + sha)[0, described_class::MAX_SHA_LENGTH] }
+
+ it_behaves_like 'a match'
+ end
+
+ context "with embedded SHA" do
+ let(:value) { "xxx#{sha}xxx" }
+
+ it_behaves_like 'a match'
+ end
+ end
+
+ context 'abbreviated SHA pattern' do
+ let(:pattern) { described_class::SHA_PATTERN }
+
+ context "with minimum length" do
+ let(:value) { sha[0, described_class::MIN_SHA_LENGTH] }
+ let(:expected_match) { value }
+
+ it_behaves_like 'a match'
+ end
+
+ context "with medium length" do
+ let(:value) { sha[0, described_class::MIN_SHA_LENGTH + 20] }
+ let(:expected_match) { value }
+
+ it_behaves_like 'a match'
+ end
+
+ it_behaves_like 'a SHA pattern'
+ end
+
+ context 'full SHA pattern' do
+ let(:pattern) { described_class::FULL_SHA_PATTERN }
+
+ context 'with abbreviated length' do
+ let(:value) { sha[0, described_class::SHA1_LENGTH - 1] }
+
+ it_behaves_like 'no match'
+ end
+
+ it_behaves_like 'a SHA pattern'
+ end
+ end
+
+ context 'SHA1' do
+ let(:sha) { "5716ca5987cbf97d6bb54920bea6adde242d87e6" }
+
+ it_behaves_like 'a SHA-matching pattern'
+ end
+
+ context 'SHA256' do
+ let(:sha) { "a52e146ac2ab2d0efbb768ab8ebd1e98a6055764c81fe424fbae4522f5b4cb92" }
+
+ it_behaves_like 'a SHA-matching pattern'
+ end
+ end
+
def sample_commit_hash
{
author_email: "dmitriy.zaporozhets@gmail.com",
diff --git a/spec/lib/gitlab/git/diff_tree_spec.rb b/spec/lib/gitlab/git/diff_tree_spec.rb
new file mode 100644
index 00000000000..614a8f03dd8
--- /dev/null
+++ b/spec/lib/gitlab/git/diff_tree_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Gitlab::Git::DiffTree, feature_category: :source_code_management do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:repository) { project.repository }
+
+ describe '.from_commit' do
+ subject(:diff_tree) { described_class.from_commit(commit) }
+
+ context 'when commit is an initial commit' do
+ let(:commit) { repository.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') }
+
+ it 'returns the expected diff tree object' do
+ expect(diff_tree.left_tree_id).to eq(Gitlab::Git::EMPTY_TREE_ID)
+ expect(diff_tree.right_tree_id).to eq(commit.tree_id)
+ end
+ end
+
+ context 'when commit is a regular commit' do
+ let(:commit) { repository.commit('60ecb67744cb56576c30214ff52294f8ce2def98') }
+
+ it 'returns the expected diff tree object' do
+ expect(diff_tree.left_tree_id).to eq(commit.parent.tree_id)
+ expect(diff_tree.right_tree_id).to eq(commit.tree_id)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/object_pool_spec.rb b/spec/lib/gitlab/git/object_pool_spec.rb
index b158c7227d4..f65ed319462 100644
--- a/spec/lib/gitlab/git/object_pool_spec.rb
+++ b/spec/lib/gitlab/git/object_pool_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Git::ObjectPool do
+RSpec.describe Gitlab::Git::ObjectPool, feature_category: :source_code_management do
let(:pool_repository) { create(:pool_repository) }
let(:source_repository) { pool_repository.source_project.repository }
@@ -15,6 +15,29 @@ RSpec.describe Gitlab::Git::ObjectPool do
end
end
+ describe '.init_from_gitaly' do
+ let(:gitaly_object_pool) { Gitaly::ObjectPool.new(repository: repository) }
+ let(:repository) do
+ Gitaly::Repository.new(
+ storage_name: 'default',
+ relative_path: '@pools/ef/2d/ef2d127d',
+ gl_project_path: ''
+ )
+ end
+
+ it 'returns an object pool object' do
+ object_pool = described_class.init_from_gitaly(gitaly_object_pool, source_repository)
+
+ expect(object_pool).to be_kind_of(described_class)
+ expect(object_pool).to have_attributes(
+ storage: repository.storage_name,
+ relative_path: repository.relative_path,
+ source_repository: source_repository,
+ gl_project_path: repository.gl_project_path
+ )
+ end
+ end
+
describe '#create' do
before do
subject.create # rubocop:disable Rails/SaveBang
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 9ce8a674146..e27b97ea0e6 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -309,6 +309,32 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
end
end
+ describe '#recent_objects_size' do
+ subject(:recent_objects_size) { repository.recent_objects_size }
+
+ it { is_expected.to be_a(Float) }
+
+ it 'uses repository_info for size' do
+ expect(repository.gitaly_repository_client).to receive(:repository_info).and_call_original
+
+ recent_objects_size
+ end
+
+ it 'returns the recent objects size' do
+ objects_response = Gitaly::RepositoryInfoResponse::ObjectsInfo.new(recent_size: 5.megabytes)
+
+ allow(repository.gitaly_repository_client).to receive(:repository_info).and_return(
+ Gitaly::RepositoryInfoResponse.new(objects: objects_response)
+ )
+
+ expect(recent_objects_size).to eq 5.0
+ end
+
+ it_behaves_like 'wrapping gRPC errors', Gitaly::RepositoryInfoResponse::ObjectsInfo, :recent_size do
+ subject { recent_objects_size }
+ end
+ end
+
describe '#to_s' do
subject { repository.to_s }
@@ -1675,9 +1701,13 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
end
describe '#find_changed_paths' do
- let(:commit_1) { TestEnv::BRANCH_SHA['with-executables'] }
- let(:commit_2) { TestEnv::BRANCH_SHA['master'] }
- let(:commit_3) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' }
+ let_it_be(:commit_1) { repository.commit(TestEnv::BRANCH_SHA['with-executables']) }
+ let_it_be(:commit_2) { repository.commit(TestEnv::BRANCH_SHA['master']) }
+ let_it_be(:commit_3) { repository.commit('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
+
+ let_it_be(:initial_commit) { repository.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') }
+ let_it_be(:diff_tree) { Gitlab::Git::DiffTree.from_commit(initial_commit) }
+
let(:commit_1_files) do
[Gitlab::Git::ChangedPath.new(status: :ADDED, path: "files/executables/ls")]
end
@@ -1693,18 +1723,26 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
]
end
+ let(:diff_tree_files) do
+ [
+ Gitlab::Git::ChangedPath.new(status: :ADDED, path: ".gitignore"),
+ Gitlab::Git::ChangedPath.new(status: :ADDED, path: "LICENSE"),
+ Gitlab::Git::ChangedPath.new(status: :ADDED, path: "README.md")
+ ]
+ end
+
it 'returns a list of paths' do
- collection = repository.find_changed_paths([commit_1, commit_2, commit_3])
+ collection = repository.find_changed_paths([commit_1, commit_2, commit_3, diff_tree])
expect(collection).to be_a(Enumerable)
- expect(collection.as_json).to eq((commit_1_files + commit_2_files + commit_3_files).as_json)
+ expect(collection.as_json).to eq((commit_1_files + commit_2_files + commit_3_files + diff_tree_files).as_json)
end
- it 'returns no paths when SHAs are invalid' do
+ it 'returns only paths with valid SHAs' do
collection = repository.find_changed_paths(['invalid', commit_1])
expect(collection).to be_a(Enumerable)
- expect(collection.to_a).to be_empty
+ expect(collection.as_json).to eq(commit_1_files.as_json)
end
it 'returns a list of paths even when containing a blank ref' do
@@ -2535,6 +2573,12 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
end
end
+ describe '#get_patch_id' do
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :get_patch_id do
+ subject { repository.get_patch_id('HEAD~', 'HEAD') }
+ end
+ end
+
def create_remote_branch(remote_name, branch_name, source_branch_name)
source_branch = repository.find_branch(source_branch_name)
repository.write_ref("refs/remotes/#{remote_name}/#{branch_name}", source_branch.dereferenced_target.sha)
@@ -2723,4 +2767,39 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
expect(repository.check_objects_exist(single_sha)).to eq({ single_sha => true })
end
end
+
+ describe '#list_all_blobs' do
+ subject { repository.list_all_blobs(expected_params) }
+
+ let(:expected_params) { { bytes_limit: 0, dynamic_timeout: nil, ignore_alternate_object_directories: true } }
+
+ it 'calls delegates to BlobService' do
+ expect(repository.gitaly_blob_client).to receive(:list_all_blobs).with(expected_params)
+ subject
+ end
+ end
+
+ describe '#object_pool' do
+ subject { repository.object_pool }
+
+ context 'without object pool' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'when pool repository exists' do
+ let!(:pool) { create(:pool_repository, :ready, source_project: project) }
+
+ it { is_expected.to be_nil }
+
+ context 'when repository is linked to the pool repository' do
+ before do
+ pool.link_repository(pool.source_project.repository)
+ end
+
+ it 'returns a object pool for the repository' do
+ is_expected.to be_kind_of(Gitaly::ObjectPool)
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
index c5b44b260c6..d320b9c4091 100644
--- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
+++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Gitlab::Git::RuggedImpl::UseRugged, feature_category: :gitaly do
let(:feature_flag_name) { wrapper.rugged_feature_keys.first }
let(:temp_gitaly_metadata_file) { create_temporary_gitaly_metadata_file }
- before(:all) do
+ before_all do
create_gitaly_metadata_file
end
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index 4a20e0b1156..84ab8376fe1 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -9,13 +9,14 @@ RSpec.describe Gitlab::Git::Tree do
let(:repository) { project.repository.raw }
shared_examples 'repo' do
- subject(:tree) { Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, pagination_params) }
+ subject(:tree) { Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, pagination_params) }
let(:sha) { SeedRepo::Commit::ID }
let(:path) { nil }
let(:recursive) { false }
let(:pagination_params) { nil }
let(:skip_flat_paths) { false }
+ let(:rescue_not_found) { true }
let(:entries) { tree.first }
let(:cursor) { tree.second }
@@ -30,8 +31,14 @@ RSpec.describe Gitlab::Git::Tree do
context 'with an invalid ref' do
let(:sha) { 'foobar-does-not-exist' }
- it { expect(entries).to eq([]) }
- it { expect(cursor).to be_nil }
+ context 'when handle_structured_gitaly_errors feature is disabled' do
+ before do
+ stub_feature_flags(handle_structured_gitaly_errors: false)
+ end
+
+ it { expect(entries).to eq([]) }
+ it { expect(cursor).to be_nil }
+ end
end
context 'when path is provided' do
@@ -162,11 +169,23 @@ RSpec.describe Gitlab::Git::Tree do
end
context 'and invalid reference is used' do
- it 'returns no entries and nil cursor' do
+ before do
allow(repository.gitaly_commit_client).to receive(:tree_entries).and_raise(Gitlab::Git::Index::IndexError)
+ end
+
+ context 'when rescue_not_found is set to false' do
+ let(:rescue_not_found) { false }
- expect(entries.count).to eq(0)
- expect(cursor).to be_nil
+ it 'raises an IndexError error' do
+ expect { entries }.to raise_error(Gitlab::Git::Index::IndexError)
+ end
+ end
+
+ context 'when rescue_not_found is set to true' do
+ it 'returns no entries and nil cursor' do
+ expect(entries.count).to eq(0)
+ expect(cursor).to be_nil
+ end
end
end
end
@@ -196,7 +215,7 @@ RSpec.describe Gitlab::Git::Tree do
let(:entries_count) { entries.count }
it 'returns all entries without a cursor' do
- result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, { limit: entries_count, page_token: nil })
+ result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: entries_count, page_token: nil })
expect(cursor).to be_nil
expect(result.entries.count).to eq(entries_count)
@@ -225,7 +244,7 @@ RSpec.describe Gitlab::Git::Tree do
let(:entries_count) { entries.count }
it 'returns all entries' do
- result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, { limit: -1, page_token: nil })
+ result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: -1, page_token: nil })
expect(result.count).to eq(entries_count)
expect(cursor).to be_nil
@@ -236,7 +255,7 @@ RSpec.describe Gitlab::Git::Tree do
let(:token) { entries.second.id }
it 'returns all entries after token' do
- result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, { limit: -1, page_token: token })
+ result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: -1, page_token: token })
expect(result.count).to eq(entries.count - 2)
expect(cursor).to be_nil
@@ -268,7 +287,7 @@ RSpec.describe Gitlab::Git::Tree do
expected_entries = entries
loop do
- result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, { limit: 5, page_token: token })
+ result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: 5, page_token: token })
collected_entries += result.entries
token = cursor&.next_cursor
diff --git a/spec/lib/gitlab/git_access_snippet_spec.rb b/spec/lib/gitlab/git_access_snippet_spec.rb
index becf97bb24e..9ba021e838e 100644
--- a/spec/lib/gitlab/git_access_snippet_spec.rb
+++ b/spec/lib/gitlab/git_access_snippet_spec.rb
@@ -346,7 +346,7 @@ RSpec.describe Gitlab::GitAccessSnippet do
expect(snippet.repository_size_checker).to receive(:above_size_limit?).and_return(false)
expect(snippet.repository_size_checker)
.to receive(:changes_will_exceed_size_limit?)
- .with(change_size)
+ .with(change_size, nil)
.and_return(false)
expect { push_access_check }.not_to raise_error
@@ -360,7 +360,7 @@ RSpec.describe Gitlab::GitAccessSnippet do
expect(snippet.repository_size_checker).to receive(:above_size_limit?).and_return(false)
expect(snippet.repository_size_checker)
.to receive(:changes_will_exceed_size_limit?)
- .with(change_size)
+ .with(change_size, nil)
.and_return(true)
expect do
diff --git a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
index d02b4492216..ee76811fea5 100644
--- a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
@@ -209,4 +209,23 @@ RSpec.describe Gitlab::GitalyClient::BlobService do
end
end
end
+
+ describe '#list_all_blobs' do
+ subject { client.list_all_blobs(**expected_params) }
+
+ let(:expected_params) { { limit: 0, bytes_limit: 0 } }
+
+ before do
+ ::Gitlab::GitalyClient.clear_stubs!
+ end
+
+ it 'sends a list all blobs message' do
+ expect_next_instance_of(Gitaly::BlobService::Stub) do |service|
+ expect(service).to receive(:list_all_blobs)
+ .with(gitaly_request_with_params(expected_params), kind_of(Hash))
+ end
+
+ subject
+ end
+ 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 fd66efe12c8..2ee9d85c723 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -192,7 +192,9 @@ RSpec.describe Gitlab::GitalyClient::CommitService, feature_category: :gitaly do
Gitaly::FindChangedPathsRequest.new(repository: repository_message, requests: requests, merge_commit_diff_mode: merge_commit_diff_mode)
end
- subject { described_class.new(repository).find_changed_paths(commits, merge_commit_diff_mode: merge_commit_diff_mode).as_json }
+ let(:treeish_objects) { repository.commits_by(oids: commits) }
+
+ subject { described_class.new(repository).find_changed_paths(treeish_objects, merge_commit_diff_mode: merge_commit_diff_mode).as_json }
before do
allow(Gitaly::FindChangedPathsRequest).to receive(:new).and_call_original
@@ -334,6 +336,40 @@ RSpec.describe Gitlab::GitalyClient::CommitService, feature_category: :gitaly do
include_examples 'uses requests format'
end
end
+
+ context 'when all requested objects are invalid' do
+ it 'does not send RPC request' do
+ expect_any_instance_of(Gitaly::DiffService::Stub).not_to receive(:find_changed_paths)
+
+ returned_value = described_class.new(repository).find_changed_paths(%w[wrong values])
+
+ expect(returned_value).to eq([])
+ end
+ end
+
+ context 'when commit has an empty SHA' do
+ let(:empty_commit) { build(:commit, project: project, sha: '0000000000000000000000000000000000000000') }
+
+ it 'does not send RPC request' do
+ expect_any_instance_of(Gitaly::DiffService::Stub).not_to receive(:find_changed_paths)
+
+ returned_value = described_class.new(repository).find_changed_paths([empty_commit])
+
+ expect(returned_value).to eq([])
+ end
+ end
+
+ context 'when commit sha is not set' do
+ let(:empty_commit) { build(:commit, project: project, sha: nil) }
+
+ it 'does not send RPC request' do
+ expect_any_instance_of(Gitaly::DiffService::Stub).not_to receive(:find_changed_paths)
+
+ returned_value = described_class.new(repository).find_changed_paths([empty_commit])
+
+ expect(returned_value).to eq([])
+ end
+ end
end
describe '#tree_entries' do
@@ -1072,4 +1108,22 @@ RSpec.describe Gitlab::GitalyClient::CommitService, feature_category: :gitaly do
expect(signatures[large_signed_text][:signed_text].size).to eq(4971878)
end
end
+
+ describe '#get_patch_id' do
+ it 'returns patch_id of given revisions' do
+ expect(client.get_patch_id('HEAD~', 'HEAD')).to eq('45435e5d7b339dd76d939508c7687701d0c17fff')
+ end
+
+ context 'when one of the param is invalid' do
+ it 'raises an GRPC::InvalidArgument error' do
+ expect { client.get_patch_id('HEAD', nil) }.to raise_error(GRPC::InvalidArgument)
+ end
+ end
+
+ context 'when two revisions are the same' do
+ it 'raises an GRPC::FailedPrecondition error' do
+ expect { client.get_patch_id('HEAD', 'HEAD') }.to raise_error(GRPC::FailedPrecondition)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
index 89a41ae71f3..bdc16f16e66 100644
--- a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
@@ -14,6 +14,32 @@ RSpec.describe Gitlab::GitalyClient::ConflictsService do
described_class.new(target_repository, our_commit_oid, their_commit_oid)
end
+ describe '#conflicts' do
+ subject(:conflicts) { client.conflicts? }
+
+ context "with the `skip_conflict_files_in_gitaly` feature flag on" do
+ it 'calls list_conflict_files with `skip_content: true`' do
+ expect_any_instance_of(described_class).to receive(:list_conflict_files)
+ .with(skip_content: true).and_return(["let's pretend i'm a conflicted file"])
+
+ conflicts
+ end
+ end
+
+ context "with the `skip_conflict_files_in_gitaly` feature flag off" do
+ before do
+ stub_feature_flags(skip_conflict_files_in_gitaly: false)
+ end
+
+ it 'calls list_conflict_files with no parameters' do
+ expect_any_instance_of(described_class).to receive(:list_conflict_files)
+ .with(skip_content: false).and_return(["let's pretend i'm a conflicted file"])
+
+ conflicts
+ end
+ end
+ end
+
describe '#list_conflict_files' do
let(:allow_tree_conflicts) { false }
let(:request) do
diff --git a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
index baf7076c718..ae2bb5af2b1 100644
--- a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GitalyClient::ObjectPoolService do
+RSpec.describe Gitlab::GitalyClient::ObjectPoolService, feature_category: :source_code_management do
let(:pool_repository) { create(:pool_repository) }
let(:project) { pool_repository.source_project }
let(:raw_repository) { project.repository.raw }
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 4a3607ed6db..9055b284119 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -183,6 +183,7 @@ RSpec.describe Gitlab::GitalyClient::OperationService, feature_category: :source
expect(request.to_h).to eq(
payload.merge({
allow_conflicts: false,
+ expected_old_oid: "",
repository: repository.gitaly_repository.to_h,
message: message.dup.force_encoding(Encoding::ASCII_8BIT),
user: Gitlab::Git::User.from_gitlab(user).to_gitaly.to_h,
@@ -730,6 +731,39 @@ RSpec.describe Gitlab::GitalyClient::OperationService, feature_category: :source
end
end
+ describe '#user_rebase_to_ref' do
+ let(:first_parent_ref) { 'refs/heads/my-branch' }
+ let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
+ let(:target_ref) { 'refs/merge-requests/x/merge' }
+ let(:response) { Gitaly::UserRebaseToRefResponse.new(commit_id: 'new-commit-id') }
+
+ let(:payload) do
+ { source_sha: source_sha, target_ref: target_ref, first_parent_ref: first_parent_ref }
+ end
+
+ it 'sends a user_rebase_to_ref message' do
+ freeze_time do
+ expect_any_instance_of(Gitaly::OperationService::Stub).to receive(:user_rebase_to_ref) do |_, request, options|
+ expect(options).to be_kind_of(Hash)
+ expect(request.to_h).to(
+ eq(
+ payload.merge(
+ {
+ expected_old_oid: "",
+ repository: repository.gitaly_repository.to_h,
+ user: Gitlab::Git::User.from_gitlab(user).to_gitaly.to_h,
+ timestamp: { nanos: 0, seconds: Time.current.to_i }
+ }
+ )
+ )
+ )
+ end.and_return(response)
+
+ client.user_rebase_to_ref(user, **payload)
+ end
+ end
+ end
+
describe '#user_squash' do
let(:start_sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
let(:end_sha) { '54cec5282aa9f21856362fe321c800c236a61615' }
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index 08457e20ec3..d8ae7d70bb2 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -452,4 +452,14 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService, feature_category: :gital
client.find_license
end
end
+
+ describe '#object_pool' do
+ it 'sends a get_object_pool_request message' do
+ expect_any_instance_of(Gitaly::ObjectPoolService::Stub)
+ .to receive(:get_object_pool)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+
+ client.object_pool
+ end
+ end
end
diff --git a/spec/lib/gitlab/github_import_spec.rb b/spec/lib/gitlab/github_import_spec.rb
index c4ed4b09f04..898bc40ec1f 100644
--- a/spec/lib/gitlab/github_import_spec.rb
+++ b/spec/lib/gitlab/github_import_spec.rb
@@ -61,7 +61,7 @@ RSpec.describe Gitlab::GithubImport, feature_category: :importers do
expect(described_class::ClientPool)
.to receive(:new)
- .with(token_pool: %w[foo bar], host: nil, parallel: true, per_page: 100)
+ .with(token_pool: %w[foo bar 123], host: nil, parallel: true, per_page: 100)
described_class.new_client_for(project)
end
diff --git a/spec/lib/gitlab/graphql/pagination/connections_spec.rb b/spec/lib/gitlab/graphql/pagination/connections_spec.rb
index 97389b6250e..0c4ca5570f8 100644
--- a/spec/lib/gitlab/graphql/pagination/connections_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/connections_spec.rb
@@ -6,7 +6,7 @@ require 'spec_helper'
RSpec.describe ::Gitlab::Graphql::Pagination::Connections do
include GraphqlHelpers
- before(:all) do
+ before_all do
ActiveRecord::Schema.define do
create_table :_test_testing_pagination_nodes, force: true do |t|
t.integer :value, null: false
diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb
index 1206a1c9131..071b303d777 100644
--- a/spec/lib/gitlab/group_search_results_spec.rb
+++ b/spec/lib/gitlab/group_search_results_spec.rb
@@ -32,8 +32,12 @@ RSpec.describe Gitlab::GroupSearchResults, feature_category: :global_search do
end
describe 'merge_requests search' do
+ let_it_be(:unarchived_project) { create(:project, :public, group: group) }
+ let_it_be(:archived_project) { create(:project, :public, :archived, group: group) }
let(:opened_result) { create(:merge_request, :opened, source_project: project, title: 'foo opened') }
let(:closed_result) { create(:merge_request, :closed, source_project: project, title: 'foo closed') }
+ let_it_be(:unarchived_result) { create(:merge_request, source_project: unarchived_project, title: 'foo') }
+ let_it_be(:archived_result) { create(:merge_request, source_project: archived_project, title: 'foo') }
let(:query) { 'foo' }
let(:scope) { 'merge_requests' }
@@ -44,6 +48,7 @@ RSpec.describe Gitlab::GroupSearchResults, feature_category: :global_search do
end
include_examples 'search results filtered by state'
+ include_examples 'search results filtered by archived', 'search_merge_requests_hide_archived_projects'
end
describe '#projects' do
@@ -52,10 +57,10 @@ RSpec.describe Gitlab::GroupSearchResults, feature_category: :global_search do
describe 'filtering' do
let_it_be(:group) { create(:group) }
- let_it_be(:unarchived_project) { create(:project, :public, group: group, name: 'Test1') }
- let_it_be(:archived_project) { create(:project, :archived, :public, group: group, name: 'Test2') }
+ let_it_be(:unarchived_result) { create(:project, :public, group: group, name: 'Test1') }
+ let_it_be(:archived_result) { create(:project, :archived, :public, group: group, name: 'Test2') }
- it_behaves_like 'search results filtered by archived'
+ it_behaves_like 'search results filtered by archived', 'search_projects_hide_archived'
end
end
diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
index b9490306410..9f7aaa21f5b 100644
--- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
@@ -2,9 +2,13 @@
require 'spec_helper'
-RSpec.describe Gitlab::HookData::IssueBuilder do
- let_it_be(:label) { create(:label) }
- let_it_be(:issue) { create(:labeled_issue, labels: [label], project: label.project) }
+RSpec.describe Gitlab::HookData::IssueBuilder, feature_category: :webhooks do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:label) { create(:label, project: project) }
+ let_it_be(:issue) { create(:labeled_issue, labels: [label], project: project) }
+ let_it_be(:contact) { create(:contact, group: project.group) }
+ let_it_be(:issue_contact) { create(:issue_customer_relations_contact, issue: issue, contact: contact) }
let(:builder) { described_class.new(issue) }
@@ -50,6 +54,7 @@ RSpec.describe Gitlab::HookData::IssueBuilder do
expect(data).to include(:state)
expect(data).to include(:severity)
expect(data).to include('labels' => [label.hook_attrs])
+ expect(data).to include('customer_relations_contacts' => [contact.reload.hook_attrs])
end
context 'when the issue has an image in the description' do
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 f9a6c25b786..1818693974e 100644
--- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
@@ -39,6 +39,7 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
title
updated_at
updated_by_id
+ draft
].freeze
expect(safe_attribute_keys).to match_array(expected_safe_attribute_keys)
@@ -66,6 +67,7 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
url
last_commit
work_in_progress
+ draft
total_time_spent
time_change
human_total_time_spent
diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb
index 133cd3b2f49..93d48379414 100644
--- a/spec/lib/gitlab/http_spec.rb
+++ b/spec/lib/gitlab/http_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Gitlab::HTTP do
end
context 'when reading the response is too slow' do
- before(:all) do
+ before_all do
# Override Net::HTTP to add a delay between sending each response chunk
mocked_http = Class.new(Net::HTTP) do
def request(*)
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 981802ad09d..5bbb95b3ea5 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -162,6 +162,7 @@ releases:
- milestone_releases
- milestones
- evidences
+- catalog_resource_version
links:
- release
project_members:
@@ -325,7 +326,7 @@ ci_pipelines:
- security_findings
- daily_build_group_report_results
- latest_builds
-- latest_successful_builds
+- latest_successful_jobs
- daily_report_results
- latest_builds_report_results
- messages
@@ -432,6 +433,7 @@ builds:
- dast_scanner_profiles_build
- dast_scanner_profile
- job_annotations
+- job_artifacts_annotations
bridges:
- user
- pipeline
@@ -440,6 +442,7 @@ bridges:
- needs
- resource
- sourced_pipeline
+- deployment
- resource_group
- metadata
- trigger_request
@@ -516,6 +519,8 @@ container_repositories:
- name
project:
- catalog_resource
+- catalog_resource_versions
+- ci_components
- external_status_checks
- base_tags
- project_topics
@@ -817,6 +822,7 @@ project:
- scan_result_policy_reads
- project_state
- security_policy_bots
+- target_branch_rules
award_emoji:
- awardable
- user
@@ -1039,6 +1045,13 @@ iterations_cadence:
- iterations
catalog_resource:
- project
+ - catalog_resource_components
+ - catalog_resource_versions
+catalog_resource_versions:
+ - project
+ - release
+ - catalog_resource
+ - catalog_resource_components
approval_rules:
- users
- groups
diff --git a/spec/lib/gitlab/import_export/command_line_util_spec.rb b/spec/lib/gitlab/import_export/command_line_util_spec.rb
index b2e047f5621..8ed3a60d7fc 100644
--- a/spec/lib/gitlab/import_export/command_line_util_spec.rb
+++ b/spec/lib/gitlab/import_export/command_line_util_spec.rb
@@ -103,7 +103,7 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importe
let(:local) { false }
it 'downloads the file' do
- expect(subject).to receive(:download).with(:url, upload_path, size_limit: nil)
+ expect(subject).to receive(:download).with(:url, upload_path, size_limit: 0)
subject.download_or_copy_upload(uploader, upload_path)
end
diff --git a/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb b/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb
index 8c5823edc51..aceea70be92 100644
--- a/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb
+++ b/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator, feature_category: :importers do
let_it_be(:filepath) { File.join(Dir.tmpdir, 'decompressed_archive_size_validator_spec.gz') }
- before(:all) do
+ before_all do
create_compressed_file
end
@@ -47,6 +47,25 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator, feature_c
end
end
+ context 'when max_decompressed_archive_size is set to 0' do
+ subject { described_class.new(archive_path: filepath) }
+
+ before do
+ stub_application_setting(max_decompressed_archive_size: 0)
+ end
+
+ it 'is valid and does not log error message' do
+ expect(Gitlab::Import::Logger)
+ .not_to receive(:info)
+ .with(
+ import_upload_archive_path: filepath,
+ import_upload_archive_size: File.size(filepath),
+ message: 'Decompressed archive size limit reached'
+ )
+ expect(subject.valid?).to eq(true)
+ end
+ end
+
context 'when exception occurs during decompression' do
shared_examples 'logs raised exception and terminates validator process group' do
let(:std) { double(:std, close: nil, value: nil) }
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index aff11f7ac30..d449446d7be 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -84,7 +84,7 @@ RSpec.describe Gitlab::ImportExport::FileImporter, feature_category: :importers
.with(
import_export_upload.import_file,
kind_of(String),
- size_limit: ::Import::GitlabProjects::RemoteFileValidator::FILE_SIZE_LIMIT
+ size_limit: Gitlab::CurrentSettings.current_application_settings.max_import_remote_file_size.megabytes
)
described_class.import(importable: project, archive_file: nil, shared: shared)
@@ -104,7 +104,7 @@ RSpec.describe Gitlab::ImportExport::FileImporter, feature_category: :importers
.with(
file_url,
kind_of(String),
- size_limit: ::Import::GitlabProjects::RemoteFileValidator::FILE_SIZE_LIMIT
+ size_limit: Gitlab::CurrentSettings.current_application_settings.max_import_remote_file_size.megabytes
)
described_class.import(importable: project, archive_file: nil, shared: shared)
diff --git a/spec/lib/gitlab/import_export/project/relation_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/relation_tree_restorer_spec.rb
index 180a6b6ff0a..0f4f2eb573c 100644
--- a/spec/lib/gitlab/import_export/project/relation_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/relation_tree_restorer_spec.rb
@@ -60,7 +60,7 @@ RSpec.describe Gitlab::ImportExport::Project::RelationTreeRestorer, feature_cate
let(:relation_reader) { Gitlab::ImportExport::Json::NdjsonReader.new(path) }
let_it_be(:group) do
- create(:group, :disabled_and_unoverridable).tap { |g| g.add_maintainer(user) }
+ create(:group, :shared_runners_disabled_and_unoverridable).tap { |g| g.add_maintainer(user) }
end
before do
diff --git a/spec/lib/gitlab/internal_events/event_definitions_spec.rb b/spec/lib/gitlab/internal_events/event_definitions_spec.rb
index f6f79d9d906..924845504ca 100644
--- a/spec/lib/gitlab/internal_events/event_definitions_spec.rb
+++ b/spec/lib/gitlab/internal_events/event_definitions_spec.rb
@@ -2,9 +2,9 @@
require "spec_helper"
-RSpec.describe Gitlab::InternalEvents::EventDefinitions, feature_category: :product_analytics do
+RSpec.describe Gitlab::InternalEvents::EventDefinitions, feature_category: :product_analytics_data_management do
after(:all) do
- described_class.clear_events
+ described_class.instance_variable_set(:@events, nil)
end
context 'when using actual metric definitions' do
diff --git a/spec/lib/gitlab/internal_events_spec.rb b/spec/lib/gitlab/internal_events_spec.rb
index 86215434ba4..c2615e0f22c 100644
--- a/spec/lib/gitlab/internal_events_spec.rb
+++ b/spec/lib/gitlab/internal_events_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_analytics do
+RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_analytics_data_management do
include TrackingHelpers
include SnowplowHelpers
diff --git a/spec/lib/gitlab/jwt_authenticatable_spec.rb b/spec/lib/gitlab/jwt_authenticatable_spec.rb
index 98c87ef627a..eea93c4e3fe 100644
--- a/spec/lib/gitlab/jwt_authenticatable_spec.rb
+++ b/spec/lib/gitlab/jwt_authenticatable_spec.rb
@@ -148,9 +148,9 @@ RSpec.describe Gitlab::JwtAuthenticatable, feature_category: :system_access do
it 'returns decoded payload if issuer is correct' do
encoded_message = JWT.encode(payload, test_class.secret, 'HS256')
- payload = test_class.decode_jwt(encoded_message, issuer: 'test_issuer')
+ decoded_payload = test_class.decode_jwt(encoded_message, issuer: 'test_issuer')
- expect(payload[0]).to match a_hash_including('iss' => 'test_issuer')
+ expect(decoded_payload[0]).to match a_hash_including('iss' => 'test_issuer')
end
it 'raises an error when the issuer is incorrect' do
@@ -159,6 +159,38 @@ RSpec.describe Gitlab::JwtAuthenticatable, feature_category: :system_access do
expect { test_class.decode_jwt(encoded_message, issuer: 'test_issuer') }.to raise_error(JWT::DecodeError)
end
+
+ it 'raises an error when the issuer is nil' do
+ payload['iss'] = nil
+ encoded_message = JWT.encode(payload, test_class.secret, 'HS256')
+
+ expect { test_class.decode_jwt(encoded_message, issuer: 'test_issuer') }.to raise_error(JWT::DecodeError)
+ end
+ end
+
+ context 'audience option' do
+ let(:payload) { { 'aud' => 'test_audience' } }
+
+ it 'returns decoded payload if audience is correct' do
+ encoded_message = JWT.encode(payload, test_class.secret, 'HS256')
+ decoded_payload = test_class.decode_jwt(encoded_message, audience: 'test_audience')
+
+ expect(decoded_payload[0]).to match a_hash_including('aud' => 'test_audience')
+ end
+
+ it 'raises an error when the audience is incorrect' do
+ payload['aud'] = 'somebody else'
+ encoded_message = JWT.encode(payload, test_class.secret, 'HS256')
+
+ expect { test_class.decode_jwt(encoded_message, audience: 'test_audience') }.to raise_error(JWT::DecodeError)
+ end
+
+ it 'raises an error when the audience is nil' do
+ payload['aud'] = nil
+ encoded_message = JWT.encode(payload, test_class.secret, 'HS256')
+
+ expect { test_class.decode_jwt(encoded_message, audience: 'test_audience') }.to raise_error(JWT::DecodeError)
+ end
end
context 'iat_after option' do
diff --git a/spec/lib/gitlab/kas_spec.rb b/spec/lib/gitlab/kas_spec.rb
index 34eb48a3221..69d9ca7d4ed 100644
--- a/spec/lib/gitlab/kas_spec.rb
+++ b/spec/lib/gitlab/kas_spec.rb
@@ -10,20 +10,41 @@ RSpec.describe Gitlab::Kas do
end
describe '.verify_api_request' do
- let(:payload) { { 'iss' => described_class::JWT_ISSUER } }
+ let(:payload) { { 'iss' => described_class::JWT_ISSUER, 'aud' => described_class::JWT_AUDIENCE } }
- it 'returns nil if fails to validate the JWT' do
- encoded_token = JWT.encode(payload, 'wrongsecret', 'HS256')
- headers = { described_class::INTERNAL_API_REQUEST_HEADER => encoded_token }
+ context 'returns nil if fails to validate the JWT' do
+ it 'when secret is wrong' do
+ encoded_token = JWT.encode(payload, 'wrong secret', 'HS256')
+ headers = { described_class::INTERNAL_API_REQUEST_HEADER => encoded_token }
+
+ expect(described_class.verify_api_request(headers)).to be_nil
+ end
+
+ it 'when issuer is wrong' do
+ payload['iss'] = 'wrong issuer'
+ encoded_token = JWT.encode(payload, described_class.secret, 'HS256')
+ headers = { described_class::INTERNAL_API_REQUEST_HEADER => encoded_token }
+
+ expect(described_class.verify_api_request(headers)).to be_nil
+ end
- expect(described_class.verify_api_request(headers)).to be_nil
+ it 'when audience is wrong' do
+ payload['aud'] = 'wrong audience'
+ encoded_token = JWT.encode(payload, described_class.secret, 'HS256')
+ headers = { described_class::INTERNAL_API_REQUEST_HEADER => encoded_token }
+
+ expect(described_class.verify_api_request(headers)).to be_nil
+ end
end
it 'returns the decoded JWT' do
encoded_token = JWT.encode(payload, described_class.secret, 'HS256')
headers = { described_class::INTERNAL_API_REQUEST_HEADER => encoded_token }
- expect(described_class.verify_api_request(headers)).to eq([{ "iss" => described_class::JWT_ISSUER }, { "alg" => "HS256" }])
+ expect(described_class.verify_api_request(headers)).to eq([
+ { 'iss' => described_class::JWT_ISSUER, 'aud' => described_class::JWT_AUDIENCE },
+ { 'alg' => 'HS256' }
+ ])
end
end
@@ -111,6 +132,52 @@ RSpec.describe Gitlab::Kas do
end
end
+ describe '.tunnel_ws_url' do
+ before do
+ stub_config(gitlab_kas: { external_url: external_url })
+ end
+
+ let(:external_url) { 'xyz' }
+
+ subject { described_class.tunnel_ws_url }
+
+ context 'with a gitlab_kas.external_k8s_proxy_url setting' do
+ let(:external_k8s_proxy_url) { 'http://abc' }
+
+ before do
+ stub_config(gitlab_kas: { external_k8s_proxy_url: external_k8s_proxy_url })
+ end
+
+ it { is_expected.to eq('ws://abc') }
+ end
+
+ context 'without a gitlab_kas.external_k8s_proxy_url setting' do
+ context 'external_url uses wss://' do
+ let(:external_url) { 'wss://kas.gitlab.example.com' }
+
+ it { is_expected.to eq('wss://kas.gitlab.example.com/k8s-proxy') }
+ end
+
+ context 'external_url uses ws://' do
+ let(:external_url) { 'ws://kas.gitlab.example.com' }
+
+ it { is_expected.to eq('ws://kas.gitlab.example.com/k8s-proxy') }
+ end
+
+ context 'external_url uses grpcs://' do
+ let(:external_url) { 'grpcs://kas.gitlab.example.com' }
+
+ it { is_expected.to eq('wss://kas.gitlab.example.com/k8s-proxy') }
+ end
+
+ context 'external_url uses grpc://' do
+ let(:external_url) { 'grpc://kas.gitlab.example.com' }
+
+ it { is_expected.to eq('ws://kas.gitlab.example.com/k8s-proxy') }
+ end
+ end
+ end
+
describe '.internal_url' do
it 'returns gitlab_kas internal_url config' do
expect(described_class.internal_url).to eq(Gitlab.config.gitlab_kas.internal_url)
diff --git a/spec/lib/gitlab/merge_requests/message_generator_spec.rb b/spec/lib/gitlab/merge_requests/message_generator_spec.rb
index df8804d38d4..b1a8ff26a86 100644
--- a/spec/lib/gitlab/merge_requests/message_generator_spec.rb
+++ b/spec/lib/gitlab/merge_requests/message_generator_spec.rb
@@ -96,6 +96,26 @@ RSpec.describe Gitlab::MergeRequests::MessageGenerator, feature_category: :code_
end
end
+ context 'when project has commit template with source project id' do
+ let(:merge_request) do
+ double(
+ :merge_request,
+ title: 'Fixes',
+ target_project: project,
+ source_project: project,
+ to_reference: '!123',
+ metrics: nil,
+ merge_user: nil
+ )
+ end
+
+ let(message_template_name) { '%{source_project_id}' }
+
+ it 'evaluates only necessary variables' do
+ expect(result_message).to eq project.id.to_s
+ end
+ end
+
context 'when project has commit template with closed issues' do
let(message_template_name) { <<~MSG.rstrip }
Merge branch '%{source_branch}' into '%{target_branch}'
diff --git a/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb
deleted file mode 100644
index b8556829a59..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Defaults do
- it { is_expected.to be_const_defined(:DEFAULT_PANEL_TYPE) }
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
deleted file mode 100644
index d3cb9760052..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
+++ /dev/null
@@ -1,178 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_caching do
- include MetricsDashboardHelpers
-
- let_it_be(:project) { create(:project) }
- let_it_be(:user) { create(:user) }
- let_it_be(:environment) { create(:environment, project: project) }
-
- before do
- project.add_maintainer(user)
- end
-
- describe '.find' do
- let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
- let(:service_call) { described_class.find(project, user, environment: environment, dashboard_path: dashboard_path) }
-
- it_behaves_like 'misconfigured dashboard service response', :not_found
-
- context 'when the dashboard exists' do
- let(:project) { project_with_dashboard(dashboard_path) }
-
- it_behaves_like 'valid dashboard service response'
- end
-
- context 'when the dashboard is configured incorrectly' do
- let(:project) { project_with_dashboard(dashboard_path, {}) }
-
- it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity
- end
-
- context 'when the dashboard contains a metric without a query' do
- let(:dashboard) { { 'panel_groups' => [{ 'panels' => [{ 'metrics' => [{ 'id' => 'mock' }] }] }] } }
- let(:project) { project_with_dashboard(dashboard_path, dashboard.to_yaml) }
-
- it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity
- end
-
- context 'when the system dashboard is specified' do
- let(:dashboard_path) { system_dashboard_path }
-
- it_behaves_like 'valid dashboard service response'
- end
-
- context 'when no dashboard is specified' do
- let(:service_call) { described_class.find(project, user, environment: environment) }
-
- it_behaves_like 'valid dashboard service response'
- end
-
- context 'when the dashboard is expected to be embedded' do
- let(:service_call) { described_class.find(project, user, **params) }
- let(:params) { { environment: environment, embedded: true } }
-
- it_behaves_like 'valid embedded dashboard service response'
-
- context 'when params are incomplete' do
- let(:params) { { environment: environment, embedded: true, dashboard_path: system_dashboard_path } }
-
- it_behaves_like 'valid embedded dashboard service response'
- end
-
- context 'when the panel is specified' do
- context 'as a custom metric' do
- let(:params) do
- {
- environment: environment,
- embedded: true,
- dashboard_path: system_dashboard_path,
- group: business_metric_title,
- title: 'title',
- y_label: 'y_label'
- }
- end
-
- it_behaves_like 'misconfigured dashboard service response', :not_found
-
- context 'when the metric exists' do
- before do
- create(:prometheus_metric, project: project)
- end
-
- it_behaves_like 'valid embedded dashboard service response'
- end
- end
-
- context 'as a project-defined panel' do
- let(:dashboard_path) { '.gitlab/dashboard/test.yml' }
- let(:params) do
- {
- environment: environment,
- embedded: true,
- dashboard_path: dashboard_path,
- group: 'Group A',
- title: 'Super Chart A1',
- y_label: 'y_label'
- }
- end
-
- it_behaves_like 'misconfigured dashboard service response', :not_found
-
- context 'when the metric exists' do
- let(:project) { project_with_dashboard(dashboard_path) }
-
- it_behaves_like 'valid embedded dashboard service response'
- end
- end
- end
- end
- end
-
- describe '.find_raw' do
- let(:dashboard) { load_dashboard_yaml(File.read(Rails.root.join('config', 'prometheus', 'common_metrics.yml'))) }
- let(:params) { {} }
-
- subject { described_class.find_raw(project, **params) }
-
- it { is_expected.to eq dashboard }
-
- context 'when the system dashboard is specified' do
- let(:params) { { dashboard_path: system_dashboard_path } }
-
- it { is_expected.to eq dashboard }
- end
-
- context 'when an existing project dashboard is specified' do
- let(:dashboard) { load_sample_dashboard }
- let(:params) { { dashboard_path: '.gitlab/dashboards/test.yml' } }
- let(:project) { project_with_dashboard(params[:dashboard_path]) }
-
- it { is_expected.to eq dashboard }
- end
- end
-
- describe '.find_all_paths' do
- let(:all_dashboard_paths) { described_class.find_all_paths(project) }
- let(:system_dashboard) { { path: system_dashboard_path, display_name: 'Overview', default: true, system_dashboard: true, out_of_the_box_dashboard: true } }
- let(:k8s_pod_health_dashboard) { { path: pod_dashboard_path, display_name: 'K8s pod health', default: false, system_dashboard: false, out_of_the_box_dashboard: true } }
-
- it 'includes OOTB dashboards by default' do
- expect(all_dashboard_paths).to eq([k8s_pod_health_dashboard, system_dashboard])
- end
-
- context 'when the project contains dashboards' do
- let(:dashboard_content) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
- let(:project) { project_with_dashboards(dashboards) }
-
- let(:dashboards) do
- {
- '.gitlab/dashboards/metrics.yml' => dashboard_content,
- '.gitlab/dashboards/better_metrics.yml' => dashboard_content
- }
- end
-
- it 'includes OOTB and project dashboards' do
- project_dashboard1 = {
- path: '.gitlab/dashboards/metrics.yml',
- display_name: 'metrics.yml',
- default: false,
- system_dashboard: false,
- out_of_the_box_dashboard: false
- }
-
- project_dashboard2 = {
- path: '.gitlab/dashboards/better_metrics.yml',
- display_name: 'better_metrics.yml',
- default: false,
- system_dashboard: false,
- out_of_the_box_dashboard: false
- }
-
- expect(all_dashboard_paths).to eq([project_dashboard2, k8s_pod_health_dashboard, project_dashboard1, system_dashboard])
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/importer_spec.rb b/spec/lib/gitlab/metrics/dashboard/importer_spec.rb
deleted file mode 100644
index 8b705395a2c..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/importer_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Importer do
- include MetricsDashboardHelpers
-
- let_it_be(:dashboard_path) { '.gitlab/dashboards/sample_dashboard.yml' }
- let_it_be(:project) { create(:project) }
-
- before do
- allow(subject).to receive(:dashboard_hash).and_return(dashboard_hash)
- end
-
- subject { described_class.new(dashboard_path, project) }
-
- describe '.execute' do
- context 'valid dashboard hash' do
- let(:dashboard_hash) { load_sample_dashboard }
-
- it 'imports metrics to database' do
- expect { subject.execute }
- .to change { PrometheusMetric.count }.from(0).to(3)
- end
- end
-
- context 'invalid dashboard hash' do
- let(:dashboard_hash) { {} }
-
- it 'returns false' do
- expect(subject.execute).to be(false)
- end
- end
- end
-
- describe '.execute!' do
- context 'valid dashboard hash' do
- let(:dashboard_hash) { load_sample_dashboard }
-
- it 'imports metrics to database' do
- expect { subject.execute }
- .to change { PrometheusMetric.count }.from(0).to(3)
- end
- end
-
- context 'invalid dashboard hash' do
- let(:dashboard_hash) { {} }
-
- it 'raises error' do
- expect { subject.execute! }.to raise_error(Gitlab::Metrics::Dashboard::Validator::Errors::SchemaValidationError,
- 'root is missing required keys: dashboard, panel_groups')
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/importers/prometheus_metrics_spec.rb b/spec/lib/gitlab/metrics/dashboard/importers/prometheus_metrics_spec.rb
deleted file mode 100644
index bc6cd383758..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/importers/prometheus_metrics_spec.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Importers::PrometheusMetrics do
- include MetricsDashboardHelpers
-
- describe '#execute' do
- let(:project) { create(:project) }
- let(:dashboard_path) { 'path/to/dashboard.yml' }
- let(:prometheus_adapter) { double('adapter', clear_prometheus_reactive_cache!: nil) }
-
- subject { described_class.new(dashboard_hash, project: project, dashboard_path: dashboard_path) }
-
- context 'valid dashboard' do
- let(:dashboard_hash) { load_sample_dashboard }
-
- context 'with all new metrics' do
- it 'creates PrometheusMetrics' do
- expect { subject.execute }.to change { PrometheusMetric.count }.by(3)
- end
- end
-
- context 'with existing metrics' do
- let(:existing_metric_attributes) do
- {
- project: project,
- identifier: 'metric_b',
- title: 'overwrite',
- y_label: 'overwrite',
- query: 'overwrite',
- unit: 'overwrite',
- legend: 'overwrite',
- dashboard_path: dashboard_path
- }
- end
-
- let!(:existing_metric) do
- create(:prometheus_metric, existing_metric_attributes)
- end
-
- it 'updates existing PrometheusMetrics' do
- subject.execute
-
- expect(existing_metric.reload.attributes.with_indifferent_access).to include({
- title: 'Super Chart B',
- y_label: 'y_label',
- query: 'query',
- unit: 'unit',
- legend: 'Legend Label'
- })
- end
-
- it 'creates new PrometheusMetrics' do
- expect { subject.execute }.to change { PrometheusMetric.count }.by(2)
- end
-
- context 'with stale metrics' do
- let!(:stale_metric) do
- create(:prometheus_metric,
- project: project,
- identifier: 'stale_metric',
- dashboard_path: dashboard_path,
- group: 3
- )
- end
-
- it 'updates existing PrometheusMetrics' do
- subject.execute
-
- expect(existing_metric.reload.attributes.with_indifferent_access).to include({
- title: 'Super Chart B',
- y_label: 'y_label',
- query: 'query',
- unit: 'unit',
- legend: 'Legend Label'
- })
- end
-
- it 'deletes stale metrics' do
- subject.execute
-
- expect { stale_metric.reload }.to raise_error(ActiveRecord::RecordNotFound)
- end
- end
- end
- end
-
- context 'invalid dashboard' do
- let(:dashboard_hash) { {} }
-
- it 'returns false' do
- expect(subject.execute).to eq(false)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
index 52908a0b339..11b587e4905 100644
--- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
@@ -12,11 +12,6 @@ RSpec.describe Gitlab::Metrics::Dashboard::Processor do
describe 'process' do
let(:sequence) do
[
- Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
- Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter,
- Gitlab::Metrics::Dashboard::Stages::CustomMetricsDetailsInserter,
- Gitlab::Metrics::Dashboard::Stages::MetricEndpointInserter,
- Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter,
Gitlab::Metrics::Dashboard::Stages::UrlValidator
]
end
@@ -24,16 +19,6 @@ RSpec.describe Gitlab::Metrics::Dashboard::Processor do
let(:process_params) { [project, dashboard_yml, sequence, { environment: environment }] }
let(:dashboard) { described_class.new(*process_params).process }
- it 'includes an id for each dashboard panel' do
- expect(all_panels).to satisfy_all do |panel|
- panel[:id].present?
- end
- end
-
- it 'includes boolean to indicate if panel group has custom metrics' do
- expect(dashboard[:panel_groups]).to all(include( { has_custom_metrics: boolean } ))
- end
-
context 'when the dashboard is not present' do
let(:dashboard_yml) { nil }
@@ -41,168 +26,5 @@ RSpec.describe Gitlab::Metrics::Dashboard::Processor do
expect(dashboard).to be_nil
end
end
-
- context 'when dashboard config corresponds to common metrics' do
- let!(:common_metric) { create(:prometheus_metric, :common, identifier: 'metric_a1') }
-
- it 'inserts metric ids into the config' do
- target_metric = all_metrics.find { |metric| metric[:id] == 'metric_a1' }
-
- expect(target_metric).to include(:metric_id)
- expect(target_metric[:metric_id]).to eq(common_metric.id)
- end
- end
-
- context 'when the project has associated metrics' do
- let!(:project_response_metric) { create(:prometheus_metric, project: project, group: :response) }
- let!(:project_system_metric) { create(:prometheus_metric, project: project, group: :system) }
- let!(:project_business_metric) { create(:prometheus_metric, project: project, group: :business) }
-
- it 'includes project-specific metrics' do
- expect(all_metrics).to include get_metric_details(project_system_metric)
- expect(all_metrics).to include get_metric_details(project_response_metric)
- expect(all_metrics).to include get_metric_details(project_business_metric)
- end
-
- it 'display groups and panels in the order they are defined' do
- expected_metrics_order = [
- 'metric_b',
- 'metric_a2',
- 'metric_a1',
- project_business_metric.id,
- project_response_metric.id,
- project_system_metric.id
- ]
- actual_metrics_order = all_metrics.map { |m| m[:id] || m[:metric_id] }
-
- expect(actual_metrics_order).to eq expected_metrics_order
- end
-
- context 'when the project has multiple metrics in the same group' do
- let!(:project_response_metric) { create(:prometheus_metric, project: project, group: :response) }
- let!(:project_response_metric_2) { create(:prometheus_metric, project: project, group: :response) }
-
- it 'includes multiple metrics' do
- expect(all_metrics).to include get_metric_details(project_response_metric)
- expect(all_metrics).to include get_metric_details(project_response_metric_2)
- end
- end
-
- context 'when the dashboard should not include project metrics' do
- let(:sequence) do
- [
- Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
- Gitlab::Metrics::Dashboard::Stages::MetricEndpointInserter
- ]
- end
-
- let(:dashboard) { described_class.new(*process_params).process }
-
- it 'includes only dashboard metrics' do
- metrics = all_metrics.map { |m| m[:id] }
-
- expect(metrics.length).to be(3)
- expect(metrics).to eq %w(metric_b metric_a2 metric_a1)
- end
- end
-
- context 'when sample_metrics are requested' do
- let(:process_params) { [project, dashboard_yml, sequence, { environment: environment, sample_metrics: true }] }
-
- it 'includes a sample metrics path for the prometheus endpoint with each metric' do
- expect(all_metrics).to satisfy_all do |metric|
- metric[:prometheus_endpoint_path] == sample_metrics_path(metric[:id])
- end
- end
- end
- end
-
- context 'when there are no alerts' do
- let!(:persisted_metric) { create(:prometheus_metric, :common, identifier: 'metric_a1') }
-
- it 'does not insert an alert_path' do
- target_metric = all_metrics.find { |metric| metric[:metric_id] == persisted_metric.id }
-
- expect(target_metric).to be_a Hash
- expect(target_metric).not_to include(:alert_path)
- end
- end
-
- shared_examples_for 'errors with message' do |expected_message|
- it 'raises a DashboardLayoutError' do
- error_class = Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError
-
- expect { dashboard }.to raise_error(error_class, expected_message)
- end
- end
-
- context 'when the dashboard is missing panel_groups' do
- let(:dashboard_yml) { {} }
-
- it_behaves_like 'errors with message', 'Top-level key :panel_groups must be an array'
- end
-
- context 'when the dashboard contains a panel_group which is missing panels' do
- let(:dashboard_yml) { { panel_groups: [{}] } }
-
- it_behaves_like 'errors with message', 'Each "panel_group" must define an array :panels'
- end
-
- context 'when the dashboard contains a panel which is missing metrics' do
- let(:dashboard_yml) { { panel_groups: [{ panels: [{}] }] } }
-
- it_behaves_like 'errors with message', 'Each "panel" must define an array :metrics'
- end
-
- context 'when the dashboard contains a metric which is missing a query' do
- let(:dashboard_yml) { { panel_groups: [{ panels: [{ metrics: [{}] }] }] } }
-
- it_behaves_like 'errors with message', 'Each "metric" must define one of :query or :query_range'
- end
- end
-
- private
-
- def all_metrics
- all_panels.flat_map { |panel| panel[:metrics] }
- end
-
- def all_panels
- dashboard[:panel_groups].flat_map { |group| group[:panels] }
- end
-
- def get_metric_details(metric)
- {
- query_range: metric.query,
- unit: metric.unit,
- label: metric.legend,
- metric_id: metric.id,
- prometheus_endpoint_path: prometheus_path(metric.query),
- edit_path: edit_metric_path(metric)
- }
- end
-
- def prometheus_path(query)
- Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
- project,
- environment,
- proxy_path: :query_range,
- query: query
- )
- end
-
- def sample_metrics_path(metric)
- Gitlab::Routing.url_helpers.sample_metrics_project_environment_path(
- project,
- environment,
- identifier: metric
- )
- end
-
- def edit_metric_path(metric)
- Gitlab::Routing.url_helpers.edit_project_prometheus_metric_path(
- project,
- metric.id
- )
end
end
diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
deleted file mode 100644
index 343596af5cf..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
+++ /dev/null
@@ -1,148 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::ServiceSelector do
- include MetricsDashboardHelpers
-
- describe '#call' do
- let(:arguments) { {} }
-
- subject { described_class.call(arguments) }
-
- it { is_expected.to be Metrics::Dashboard::SystemDashboardService }
-
- context 'when just the dashboard path is provided' do
- let(:arguments) { { dashboard_path: '.gitlab/dashboards/test.yml' } }
-
- it { is_expected.to be Metrics::Dashboard::CustomDashboardService }
-
- context 'when the path is for the system dashboard' do
- let(:arguments) { { dashboard_path: system_dashboard_path } }
-
- it { is_expected.to be Metrics::Dashboard::SystemDashboardService }
- end
-
- context 'when the path is for the pod dashboard' do
- let(:arguments) { { dashboard_path: pod_dashboard_path } }
-
- it { is_expected.to be Metrics::Dashboard::PodDashboardService }
- end
- end
-
- context 'when the embedded flag is provided' do
- let(:arguments) { { embedded: true } }
-
- it { is_expected.to be Metrics::Dashboard::DefaultEmbedService }
-
- context 'when an incomplete set of dashboard identifiers are provided' do
- let(:arguments) { { embedded: true, dashboard_path: '.gitlab/dashboards/test.yml' } }
-
- it { is_expected.to be Metrics::Dashboard::DefaultEmbedService }
- end
-
- context 'when all the chart identifiers are provided' do
- let(:arguments) do
- {
- embedded: true,
- dashboard_path: '.gitlab/dashboards/test.yml',
- group: 'Important Metrics',
- title: 'Total Requests',
- y_label: 'req/sec'
- }
- end
-
- it { is_expected.to be Metrics::Dashboard::DynamicEmbedService }
- end
-
- context 'when all chart params expect dashboard_path are provided' do
- let(:arguments) do
- {
- embedded: true,
- group: 'Important Metrics',
- title: 'Total Requests',
- y_label: 'req/sec'
- }
- end
-
- it { is_expected.to be Metrics::Dashboard::DynamicEmbedService }
- end
-
- context 'with a system dashboard and "custom" group' do
- let(:arguments) do
- {
- embedded: true,
- dashboard_path: system_dashboard_path,
- group: business_metric_title,
- title: 'Total Requests',
- y_label: 'req/sec'
- }
- end
-
- it { is_expected.to be Metrics::Dashboard::CustomMetricEmbedService }
- end
-
- context 'with a grafana link' do
- let(:arguments) do
- {
- embedded: true,
- grafana_url: 'https://grafana.example.com'
- }
- end
-
- it { is_expected.to be Metrics::Dashboard::GrafanaMetricEmbedService }
- end
-
- context 'with the embed defined in the arguments' do
- let(:arguments) do
- {
- embedded: true,
- embed_json: '{}'
- }
- end
-
- it { is_expected.to be Metrics::Dashboard::TransientEmbedService }
- end
-
- context 'when cluster is provided' do
- let(:arguments) { { cluster: "some cluster" } }
-
- it { is_expected.to be Metrics::Dashboard::ClusterDashboardService }
- end
-
- context 'when cluster is provided and embedded is not true' do
- let(:arguments) { { cluster: "some cluster", embedded: 'false' } }
-
- it { is_expected.to be Metrics::Dashboard::ClusterDashboardService }
- end
-
- context 'when cluster dashboard_path is provided' do
- let(:arguments) { { dashboard_path: ::Metrics::Dashboard::ClusterDashboardService::DASHBOARD_PATH } }
-
- it { is_expected.to be Metrics::Dashboard::ClusterDashboardService }
- end
-
- context 'when cluster is provided and embed params' do
- let(:arguments) do
- {
- cluster: "some cluster",
- embedded: 'true',
- cluster_type: 'project',
- format: :json,
- group: 'Food metrics',
- title: 'Pizza Consumption',
- y_label: 'Slice Count'
- }
- end
-
- it { is_expected.to be Metrics::Dashboard::ClusterMetricsEmbedService }
- end
-
- context 'when metrics embed is for an alert' do
- let(:arguments) { { embedded: true, prometheus_alert_id: 5 } }
-
- it { is_expected.to be Metrics::Dashboard::GitlabAlertEmbedService }
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb
deleted file mode 100644
index 3cfdfafb0c5..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Stages::GrafanaFormatter do
- include GrafanaApiHelpers
-
- let_it_be(:namespace) { create(:namespace, path: 'foo') }
- let_it_be(:project) { create(:project, namespace: namespace, path: 'bar') }
-
- describe '#transform!' do
- let(:grafana_dashboard) { Gitlab::Json.parse(fixture_file('grafana/simplified_dashboard_response.json'), symbolize_names: true) }
- let(:datasource) { Gitlab::Json.parse(fixture_file('grafana/datasource_response.json'), symbolize_names: true) }
- let(:expected_dashboard) { Gitlab::Json.parse(fixture_file('grafana/expected_grafana_embed.json'), symbolize_names: true) }
-
- subject(:dashboard) { described_class.new(project, {}, params).transform! }
-
- let(:params) do
- {
- grafana_dashboard: grafana_dashboard,
- datasource: datasource,
- grafana_url: valid_grafana_dashboard_link('https://grafana.example.com')
- }
- end
-
- context 'when the query and resources are configured correctly' do
- it { is_expected.to eq expected_dashboard }
- end
-
- context 'when a panelId is not included in the grafana_url' do
- before do
- params[:grafana_url].gsub('&panelId=8', '')
- end
-
- it { is_expected.to eq expected_dashboard }
-
- context 'when there is also no valid panel in the dashboard' do
- before do
- params[:grafana_dashboard][:dashboard][:panels] = []
- end
-
- it 'raises a processing error' do
- expect { dashboard }.to raise_error(::Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError)
- end
- end
- end
-
- context 'when an input is invalid' do
- before do
- params[:datasource][:access] = 'not-proxy'
- end
-
- it 'raises a processing error' do
- expect { dashboard }.to raise_error(::Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/metric_endpoint_inserter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/metric_endpoint_inserter_spec.rb
deleted file mode 100644
index bb3c8626d32..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/stages/metric_endpoint_inserter_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Stages::MetricEndpointInserter do
- include MetricsDashboardHelpers
-
- let(:project) { build_stubbed(:project) }
- let(:environment) { build_stubbed(:environment, project: project) }
-
- describe '#transform!' do
- subject(:transform!) { described_class.new(project, dashboard, environment: environment).transform! }
-
- let(:dashboard) { load_sample_dashboard.deep_symbolize_keys }
-
- it 'generates prometheus_endpoint_path without newlines' do
- query = 'avg( sum( container_memory_usage_bytes{ container_name!="POD", '\
- 'pod_name=~"^{{ci_environment_slug}}-(.*)", namespace="{{kube_namespace}}" } ) '\
- 'by (job) ) without (job) /1024/1024/1024'
-
- transform!
-
- expect(all_metrics[2][:prometheus_endpoint_path]).to eq(prometheus_path(query))
- end
-
- it 'includes a path for the prometheus endpoint with each metric' do
- transform!
-
- expect(all_metrics).to satisfy_all do |metric|
- metric[:prometheus_endpoint_path].present? && !metric[:prometheus_endpoint_path].include?("\n")
- end
- end
-
- it 'works when query/query_range is a number' do
- query = 2000
-
- transform!
-
- expect(all_metrics[1][:prometheus_endpoint_path]).to eq(prometheus_path(query))
- end
- end
-
- private
-
- def all_metrics
- dashboard[:panel_groups].flat_map do |group|
- group[:panels].flat_map { |panel| panel[:metrics] }
- end
- end
-
- def prometheus_path(query)
- Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
- project,
- environment,
- proxy_path: :query_range,
- query: query
- )
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
deleted file mode 100644
index 7a3a9021f86..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter do
- include MetricsDashboardHelpers
-
- let(:project) { build_stubbed(:project) }
-
- def fetch_panel_ids(dashboard_hash)
- dashboard_hash[:panel_groups].flat_map { |group| group[:panels].flat_map { |panel| panel[:id] } }
- end
-
- describe '#transform!' do
- subject(:transform!) { described_class.new(project, dashboard, nil).transform! }
-
- let(:dashboard) { load_sample_dashboard.deep_symbolize_keys }
-
- context 'when dashboard panels are present' do
- it 'assigns unique ids to each panel using PerformanceMonitoring::PrometheusPanel', :aggregate_failures do
- dashboard.fetch(:panel_groups).each do |group|
- group.fetch(:panels).each do |panel|
- panel_double = instance_double(::PerformanceMonitoring::PrometheusPanel)
-
- expect(::PerformanceMonitoring::PrometheusPanel).to receive(:new).with(panel).and_return(panel_double)
- expect(panel_double).to receive(:id).with(group[:group]).and_return(FFaker::Lorem.unique.characters(125))
- end
- end
-
- transform!
-
- expect(fetch_panel_ids(dashboard)).not_to include nil
- end
- end
-
- context 'when dashboard panels has duplicated ids' do
- it 'no panel has assigned id' do
- panel_double = instance_double(::PerformanceMonitoring::PrometheusPanel)
- allow(::PerformanceMonitoring::PrometheusPanel).to receive(:new).and_return(panel_double)
- allow(panel_double).to receive(:id).and_return('duplicated id')
-
- transform!
-
- expect(fetch_panel_ids(dashboard)).to all be_nil
- expect(fetch_panel_ids(dashboard)).not_to include 'duplicated id'
- end
- end
-
- context 'when there are no panels in the dashboard' do
- it 'raises a processing error' do
- dashboard[:panel_groups][0].delete(:panels)
-
- expect { transform! }.to(
- raise_error(::Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError)
- )
- end
- end
-
- context 'when there are no panel_groups in the dashboard' do
- it 'raises a processing error' do
- dashboard.delete(:panel_groups)
-
- expect { transform! }.to(
- raise_error(::Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError)
- )
- end
- end
-
- context 'when dashboard panels has unknown schema attributes' do
- before do
- error = ActiveModel::UnknownAttributeError.new(double, 'unknown_panel_attribute')
- allow(::PerformanceMonitoring::PrometheusPanel).to receive(:new).and_raise(error)
- end
-
- it 'no panel has assigned id' do
- transform!
-
- expect(fetch_panel_ids(dashboard)).to all be_nil
- end
-
- it 'logs the failure' do
- expect(Gitlab::ErrorTracking).to receive(:log_exception)
-
- transform!
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/track_panel_type_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/track_panel_type_spec.rb
deleted file mode 100644
index 60010b9f257..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/stages/track_panel_type_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Stages::TrackPanelType do
- include MetricsDashboardHelpers
-
- let(:project) { build_stubbed(:project) }
- let(:environment) { build_stubbed(:environment, project: project) }
-
- describe '#transform!', :snowplow do
- subject { described_class.new(project, dashboard, environment: environment) }
-
- let(:dashboard) { load_sample_dashboard.deep_symbolize_keys }
-
- it 'creates tracking event' do
- subject.transform!
-
- expect_snowplow_event(
- category: 'MetricsDashboard::Chart',
- action: 'chart_rendered',
- label: 'area-chart'
- )
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter_spec.rb
deleted file mode 100644
index 9303ff981fb..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Stages::VariableEndpointInserter do
- include MetricsDashboardHelpers
-
- let(:project) { build_stubbed(:project) }
- let(:environment) { build_stubbed(:environment, project: project) }
-
- describe '#transform!' do
- subject(:transform!) { described_class.new(project, dashboard, environment: environment).transform! }
-
- let(:dashboard) { load_sample_dashboard.deep_symbolize_keys }
-
- context 'when dashboard variables are present' do
- it 'assigns prometheus_endpoint_path to metric_label_values variable type' do
- endpoint_path = Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
- project,
- environment,
- proxy_path: :series,
- match: ['backend:haproxy_backend_availability:ratio{env="{{env}}"}']
- )
-
- transform!
-
- expect(
- dashboard.dig(:templating, :variables, :metric_label_values_variable, :options)
- ).to include(prometheus_endpoint_path: endpoint_path)
- end
-
- it 'does not modify other variable types' do
- original_text_variable = dashboard[:templating][:variables][:text_variable_full_syntax].deep_dup
-
- transform!
-
- expect(dashboard[:templating][:variables][:text_variable_full_syntax]).to eq(original_text_variable)
- end
-
- context 'when variable does not have the required series_selector' do
- it 'adds prometheus_endpoint_path without match parameter' do
- dashboard[:templating][:variables][:metric_label_values_variable][:options].delete(:series_selector)
- endpoint_path = Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
- project,
- environment,
- proxy_path: :series
- )
-
- transform!
-
- expect(
- dashboard.dig(:templating, :variables, :metric_label_values_variable, :options)
- ).to include(prometheus_endpoint_path: endpoint_path)
- end
- end
- end
-
- context 'when no variables are present' do
- it 'does not fail' do
- dashboard.delete(:templating)
-
- expect { transform! }.not_to raise_error
- end
- end
-
- context 'with no environment' do
- subject(:transform!) { described_class.new(project, dashboard, {}).transform! }
-
- it 'raises error' do
- expect { transform! }.to raise_error(
- Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError,
- 'Environment is required for Stages::VariableEndpointInserter'
- )
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/transformers/yml/v1/prometheus_metrics_spec.rb b/spec/lib/gitlab/metrics/dashboard/transformers/yml/v1/prometheus_metrics_spec.rb
deleted file mode 100644
index 3af8b51c889..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/transformers/yml/v1/prometheus_metrics_spec.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Transformers::Yml::V1::PrometheusMetrics do
- include MetricsDashboardHelpers
-
- describe '#execute' do
- subject { described_class.new(dashboard_hash) }
-
- context 'valid dashboard' do
- let_it_be(:dashboard_hash) do
- {
- panel_groups: [{
- panels: [
- {
- title: 'Panel 1 title',
- y_label: 'Panel 1 y_label',
- metrics: [
- {
- query_range: 'Panel 1 metric 1 query_range',
- unit: 'Panel 1 metric 1 unit',
- label: 'Panel 1 metric 1 label',
- id: 'Panel 1 metric 1 id'
- },
- {
- query: 'Panel 1 metric 2 query',
- unit: 'Panel 1 metric 2 unit',
- label: 'Panel 1 metric 2 label',
- id: 'Panel 1 metric 2 id'
- }
- ]
- },
- {
- title: 'Panel 2 title',
- y_label: 'Panel 2 y_label',
- metrics: [{
- query_range: 'Panel 2 metric 1 query_range',
- unit: 'Panel 2 metric 1 unit',
- label: 'Panel 2 metric 1 label',
- id: 'Panel 2 metric 1 id'
- }]
- }
- ]
- }]
- }
- end
-
- let(:expected_metrics) do
- [
- {
- title: 'Panel 1 title',
- y_label: 'Panel 1 y_label',
- query: "Panel 1 metric 1 query_range",
- unit: 'Panel 1 metric 1 unit',
- legend: 'Panel 1 metric 1 label',
- identifier: 'Panel 1 metric 1 id',
- group: 3,
- common: false
- },
- {
- title: 'Panel 1 title',
- y_label: 'Panel 1 y_label',
- query: 'Panel 1 metric 2 query',
- unit: 'Panel 1 metric 2 unit',
- legend: 'Panel 1 metric 2 label',
- identifier: 'Panel 1 metric 2 id',
- group: 3,
- common: false
- },
- {
- title: 'Panel 2 title',
- y_label: 'Panel 2 y_label',
- query: 'Panel 2 metric 1 query_range',
- unit: 'Panel 2 metric 1 unit',
- legend: 'Panel 2 metric 1 label',
- identifier: 'Panel 2 metric 1 id',
- group: 3,
- common: false
- }
- ]
- end
-
- it 'returns collection of metrics with correct attributes' do
- expect(subject.execute).to match_array(expected_metrics)
- end
- end
-
- context 'invalid dashboard' do
- let(:dashboard_hash) { {} }
-
- it 'raises missing attribute error' do
- expect { subject.execute }.to raise_error(
- ::Gitlab::Metrics::Dashboard::Transformers::Errors::MissingAttribute, "Missing attribute: 'panel_groups'"
- )
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/validator/client_spec.rb b/spec/lib/gitlab/metrics/dashboard/validator/client_spec.rb
deleted file mode 100644
index 4b07f9dbbab..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/validator/client_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Validator::Client do
- include MetricsDashboardHelpers
-
- let_it_be(:schema_path) { 'lib/gitlab/metrics/dashboard/validator/schemas/dashboard.json' }
-
- subject { described_class.new(dashboard, schema_path) }
-
- describe '#execute' do
- context 'with no validation errors' do
- let(:dashboard) { load_sample_dashboard }
-
- it 'returns empty array' do
- expect(subject.execute).to eq([])
- end
- end
-
- context 'with validation errors' do
- let(:dashboard) { load_dashboard_yaml(fixture_file('lib/gitlab/metrics/dashboard/invalid_dashboard.yml')) }
-
- it 'returns array of error objects' do
- expect(subject.execute).to include(Gitlab::Metrics::Dashboard::Validator::Errors::SchemaValidationError)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/validator/custom_formats_spec.rb b/spec/lib/gitlab/metrics/dashboard/validator/custom_formats_spec.rb
deleted file mode 100644
index 129fb631f3e..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/validator/custom_formats_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Validator::CustomFormats do
- describe '#format_handlers' do
- describe 'add_to_metric_id_cache' do
- it 'adds data to metric id cache' do
- subject.format_handlers['add_to_metric_id_cache'].call('metric_id', '_schema')
-
- expect(subject.metric_ids_cache).to eq(["metric_id"])
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/validator/errors_spec.rb b/spec/lib/gitlab/metrics/dashboard/validator/errors_spec.rb
deleted file mode 100644
index a50c2a506cb..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/validator/errors_spec.rb
+++ /dev/null
@@ -1,149 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Validator::Errors do
- describe Gitlab::Metrics::Dashboard::Validator::Errors::SchemaValidationError do
- context 'empty error hash' do
- let(:error_hash) { {} }
-
- it 'uses default error message' do
- expect(described_class.new(error_hash).message).to eq('Dashboard failed schema validation')
- end
- end
-
- context 'formatted message' do
- subject { described_class.new(error_hash).message }
-
- let(:error_hash) do
- {
- 'data' => 'property_name',
- 'data_pointer' => pointer,
- 'type' => type,
- 'schema' => 'schema',
- 'details' => details
- }
- end
-
- context 'for root object' do
- let(:pointer) { '' }
-
- context 'when required keys are missing' do
- let(:type) { 'required' }
- let(:details) { { 'missing_keys' => ['one'] } }
-
- it { is_expected.to eq 'root is missing required keys: one' }
- end
-
- context 'when there is type mismatch' do
- %w(null string boolean integer number array object).each do |expected_type|
- context "on type: #{expected_type}" do
- let(:type) { expected_type }
- let(:details) { nil }
-
- it { is_expected.to eq "'property_name' at root is not of type: #{expected_type}" }
- end
- end
- end
- end
-
- context 'for nested object' do
- let(:pointer) { '/nested_objects/0' }
-
- context 'when required keys are missing' do
- let(:type) { 'required' }
- let(:details) { { 'missing_keys' => ['two'] } }
-
- it { is_expected.to eq '/nested_objects/0 is missing required keys: two' }
- end
-
- context 'when there is type mismatch' do
- %w(null string boolean integer number array object).each do |expected_type|
- context "on type: #{expected_type}" do
- let(:type) { expected_type }
- let(:details) { nil }
-
- it { is_expected.to eq "'property_name' at /nested_objects/0 is not of type: #{expected_type}" }
- end
- end
- end
-
- context 'when data does not match pattern' do
- let(:type) { 'pattern' }
- let(:error_hash) do
- {
- 'data' => 'property_name',
- 'data_pointer' => pointer,
- 'type' => type,
- 'schema' => { 'pattern' => 'aa.*' }
- }
- end
-
- it { is_expected.to eq "'property_name' at /nested_objects/0 does not match pattern: aa.*" }
- end
-
- context 'when data does not match format' do
- let(:type) { 'format' }
- let(:error_hash) do
- {
- 'data' => 'property_name',
- 'data_pointer' => pointer,
- 'type' => type,
- 'schema' => { 'format' => 'date-time' }
- }
- end
-
- it { is_expected.to eq "'property_name' at /nested_objects/0 does not match format: date-time" }
- end
-
- context 'when data is not const' do
- let(:type) { 'const' }
- let(:error_hash) do
- {
- 'data' => 'property_name',
- 'data_pointer' => pointer,
- 'type' => type,
- 'schema' => { 'const' => 'one' }
- }
- end
-
- it { is_expected.to eq "'property_name' at /nested_objects/0 is not: \"one\"" }
- end
-
- context 'when data is not included in enum' do
- let(:type) { 'enum' }
- let(:error_hash) do
- {
- 'data' => 'property_name',
- 'data_pointer' => pointer,
- 'type' => type,
- 'schema' => { 'enum' => %w(one two) }
- }
- end
-
- it { is_expected.to eq "'property_name' at /nested_objects/0 is not one of: [\"one\", \"two\"]" }
- end
-
- context 'when data is not included in enum' do
- let(:type) { 'unknown' }
- let(:error_hash) do
- {
- 'data' => 'property_name',
- 'data_pointer' => pointer,
- 'type' => type,
- 'schema' => 'schema'
- }
- end
-
- it { is_expected.to eq "'property_name' at /nested_objects/0 is invalid: error_type=unknown" }
- end
- end
- end
- end
-
- describe Gitlab::Metrics::Dashboard::Validator::Errors::DuplicateMetricIds do
- it 'has custom error message' do
- expect(described_class.new.message).to eq('metric_id must be unique across a project')
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/validator/post_schema_validator_spec.rb b/spec/lib/gitlab/metrics/dashboard/validator/post_schema_validator_spec.rb
deleted file mode 100644
index e7cb1429ca9..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/validator/post_schema_validator_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Validator::PostSchemaValidator do
- describe '#validate' do
- context 'with no project and dashboard_path provided' do
- context 'unique local metric_ids' do
- it 'returns empty array' do
- expect(described_class.new(metric_ids: [1, 2, 3]).validate).to eq([])
- end
- end
-
- context 'duplicate local metrics_ids' do
- it 'returns error' do
- expect(described_class.new(metric_ids: [1, 1]).validate)
- .to eq([Gitlab::Metrics::Dashboard::Validator::Errors::DuplicateMetricIds])
- end
- end
- end
-
- context 'with project and dashboard_path' do
- let(:project) { create(:project) }
-
- subject do
- described_class.new(
- project: project,
- metric_ids: ['some_identifier'],
- dashboard_path: 'test/path.yml'
- ).validate
- end
-
- context 'with unique metric identifiers' do
- before do
- create(:prometheus_metric,
- project: project,
- identifier: 'some_other_identifier',
- dashboard_path: 'test/path.yml'
- )
- end
-
- it 'returns empty array' do
- expect(subject).to eq([])
- end
- end
-
- context 'duplicate metric identifiers in database' do
- context 'with different dashboard_path' do
- before do
- create(:prometheus_metric,
- project: project,
- identifier: 'some_identifier',
- dashboard_path: 'some/other/path.yml'
- )
- end
-
- it 'returns error' do
- expect(subject).to include(Gitlab::Metrics::Dashboard::Validator::Errors::DuplicateMetricIds)
- end
- end
-
- context 'with same dashboard_path' do
- before do
- create(:prometheus_metric,
- project: project,
- identifier: 'some_identifier',
- dashboard_path: 'test/path.yml'
- )
- end
-
- it 'returns empty array' do
- expect(subject).to eq([])
- end
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/validator_spec.rb b/spec/lib/gitlab/metrics/dashboard/validator_spec.rb
deleted file mode 100644
index fb55b736354..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/validator_spec.rb
+++ /dev/null
@@ -1,146 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Validator do
- include MetricsDashboardHelpers
-
- let_it_be(:valid_dashboard) { load_sample_dashboard }
- let_it_be(:invalid_dashboard) { load_dashboard_yaml(fixture_file('lib/gitlab/metrics/dashboard/invalid_dashboard.yml')) }
- let_it_be(:duplicate_id_dashboard) { load_dashboard_yaml(fixture_file('lib/gitlab/metrics/dashboard/duplicate_id_dashboard.yml')) }
-
- let_it_be(:project) { create(:project) }
-
- describe '#validate' do
- context 'valid dashboard schema' do
- it 'returns true' do
- expect(described_class.validate(valid_dashboard)).to be true
- end
-
- context 'with duplicate metric_ids' do
- it 'returns false' do
- expect(described_class.validate(duplicate_id_dashboard)).to be false
- end
- end
-
- context 'with dashboard_path and project' do
- subject { described_class.validate(valid_dashboard, dashboard_path: 'test/path.yml', project: project) }
-
- context 'with no conflicting metric identifiers in db' do
- it { is_expected.to be true }
- end
-
- context 'with metric identifier present in current dashboard' do
- before do
- create(:prometheus_metric,
- identifier: 'metric_a1',
- dashboard_path: 'test/path.yml',
- project: project
- )
- end
-
- it { is_expected.to be true }
- end
-
- context 'with metric identifier present in another dashboard' do
- before do
- create(:prometheus_metric,
- identifier: 'metric_a1',
- dashboard_path: 'some/other/dashboard/path.yml',
- project: project
- )
- end
-
- it { is_expected.to be false }
- end
- end
- end
-
- context 'invalid dashboard schema' do
- it 'returns false' do
- expect(described_class.validate(invalid_dashboard)).to be false
- end
- end
- end
-
- describe '#validate!' do
- shared_examples 'validation failed' do |errors_message|
- it 'raises error with corresponding messages', :aggregate_failures do
- expect { subject }.to raise_error do |error|
- expect(error).to be_kind_of(Gitlab::Metrics::Dashboard::Validator::Errors::InvalidDashboardError)
- expect(error.message).to eq(errors_message)
- end
- end
- end
-
- context 'valid dashboard schema' do
- it 'returns true' do
- expect(described_class.validate!(valid_dashboard)).to be true
- end
-
- context 'with duplicate metric_ids' do
- subject { described_class.validate!(duplicate_id_dashboard) }
-
- it_behaves_like 'validation failed', 'metric_id must be unique across a project'
- end
-
- context 'with dashboard_path and project' do
- subject { described_class.validate!(valid_dashboard, dashboard_path: 'test/path.yml', project: project) }
-
- context 'with no conflicting metric identifiers in db' do
- it { is_expected.to be true }
- end
-
- context 'with metric identifier present in current dashboard' do
- before do
- create(:prometheus_metric,
- identifier: 'metric_a1',
- dashboard_path: 'test/path.yml',
- project: project
- )
- end
-
- it { is_expected.to be true }
- end
-
- context 'with metric identifier present in another dashboard' do
- before do
- create(:prometheus_metric,
- identifier: 'metric_a1',
- dashboard_path: 'some/other/dashboard/path.yml',
- project: project
- )
- end
-
- it_behaves_like 'validation failed', 'metric_id must be unique across a project'
- end
- end
- end
-
- context 'invalid dashboard schema' do
- subject { described_class.validate!(invalid_dashboard) }
-
- context 'wrong property type' do
- it_behaves_like 'validation failed', "'this_should_be_a_int' at /panel_groups/0/panels/0/weight is not of type: number"
- end
-
- context 'panel groups missing' do
- let_it_be(:invalid_dashboard) { load_dashboard_yaml(fixture_file('lib/gitlab/metrics/dashboard/dashboard_missing_panel_groups.yml')) }
-
- it_behaves_like 'validation failed', 'root is missing required keys: panel_groups'
- end
-
- context 'groups are missing panels and group keys' do
- let_it_be(:invalid_dashboard) { load_dashboard_yaml(fixture_file('lib/gitlab/metrics/dashboard/dashboard_groups_missing_panels_and_group.yml')) }
-
- it_behaves_like 'validation failed', '/panel_groups/0 is missing required keys: group'
- end
-
- context 'panel is missing metrics key' do
- let_it_be(:invalid_dashboard) { load_dashboard_yaml(fixture_file('lib/gitlab/metrics/dashboard/dashboard_panel_is_missing_metrics.yml')) }
-
- it_behaves_like 'validation failed', '/panel_groups/0/panels/0 is missing required keys: metrics'
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/global_search_slis_spec.rb b/spec/lib/gitlab/metrics/global_search_slis_spec.rb
index 5248cd08770..68793db6e41 100644
--- a/spec/lib/gitlab/metrics/global_search_slis_spec.rb
+++ b/spec/lib/gitlab/metrics/global_search_slis_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Metrics::GlobalSearchSlis do
+RSpec.describe Gitlab::Metrics::GlobalSearchSlis, feature_category: :global_search do
using RSpec::Parameterized::TableSyntax
describe '#initialize_slis!' do
@@ -92,6 +92,7 @@ RSpec.describe Gitlab::Metrics::GlobalSearchSlis do
'basic' | true | 27.538
'advanced' | false | 2.452
'advanced' | true | 15.52
+ 'zoekt' | true | 15.52
end
with_them do
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index afb029a96cb..2ec31a5cc3e 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -384,7 +384,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
end
it 'does not store DB roles into into RequestStore' do
- Gitlab::WithRequestStore.with_request_store do
+ Gitlab::SafeRequestStore.ensure_request_store do
subscriber.sql(event)
expect(described_class.db_counter_payload).to include(
diff --git a/spec/lib/gitlab/middleware/webhook_recursion_detection_spec.rb b/spec/lib/gitlab/middleware/webhook_recursion_detection_spec.rb
index c8dbc990f8c..5394cea64af 100644
--- a/spec/lib/gitlab/middleware/webhook_recursion_detection_spec.rb
+++ b/spec/lib/gitlab/middleware/webhook_recursion_detection_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Gitlab::Middleware::WebhookRecursionDetection do
let(:env) { Rack::MockRequest.env_for("/").merge(headers) }
around do |example|
- Gitlab::WithRequestStore.with_request_store { example.run }
+ Gitlab::SafeRequestStore.ensure_request_store { example.run }
end
describe '#call' do
diff --git a/spec/lib/gitlab/null_request_store_spec.rb b/spec/lib/gitlab/null_request_store_spec.rb
deleted file mode 100644
index f68f478c73e..00000000000
--- a/spec/lib/gitlab/null_request_store_spec.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::NullRequestStore do
- let(:null_store) { described_class.new }
-
- describe '#store' do
- it 'returns an empty hash' do
- expect(null_store.store).to eq({})
- end
- end
-
- describe '#active?' do
- it 'returns falsey' do
- expect(null_store.active?).to be_falsey
- end
- end
-
- describe '#read' do
- it 'returns nil' do
- expect(null_store.read('foo')).to be nil
- end
- end
-
- describe '#[]' do
- it 'returns nil' do
- expect(null_store['foo']).to be nil
- end
- end
-
- describe '#write' do
- it 'returns the same value' do
- expect(null_store.write('key', 'value')).to eq('value')
- end
- end
-
- describe '#[]=' do
- it 'returns the same value' do
- expect(null_store['key'] = 'value').to eq('value')
- end
- end
-
- describe '#exist?' do
- it 'returns falsey' do
- expect(null_store.exist?('foo')).to be_falsey
- end
- end
-
- describe '#fetch' do
- it 'returns the block result' do
- expect(null_store.fetch('key') { 'block result' }).to eq('block result') # rubocop:disable Style/RedundantFetchBlock
- end
- end
-
- describe '#delete' do
- context 'when a block is given' do
- it 'yields the key to the block' do
- expect do |b|
- null_store.delete('foo', &b)
- end.to yield_with_args('foo')
- end
-
- it 'returns the block result' do
- expect(null_store.delete('foo') { |key| 'block result' }).to eq('block result')
- end
- end
-
- context 'when a block is not given' do
- it 'returns nil' do
- expect(null_store.delete('foo')).to be nil
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/pages/url_builder_spec.rb b/spec/lib/gitlab/pages/url_builder_spec.rb
index 8e1581704cb..ae94bbadffe 100644
--- a/spec/lib/gitlab/pages/url_builder_spec.rb
+++ b/spec/lib/gitlab/pages/url_builder_spec.rb
@@ -83,60 +83,32 @@ RSpec.describe Gitlab::Pages::UrlBuilder, feature_category: :pages do
context 'when not using pages_unique_domain' do
subject(:pages_url) { builder.pages_url(with_unique_domain: false) }
- context 'when pages_unique_domain feature flag is disabled' do
- before do
- stub_feature_flags(pages_unique_domain: false)
- end
+ context 'when pages_unique_domain_enabled is false' do
+ let(:unique_domain_enabled) { false }
it { is_expected.to eq('http://group.example.com/project') }
end
- context 'when pages_unique_domain feature flag is enabled' do
- before do
- stub_feature_flags(pages_unique_domain: true)
- end
-
- context 'when pages_unique_domain_enabled is false' do
- let(:unique_domain_enabled) { false }
-
- it { is_expected.to eq('http://group.example.com/project') }
- end
-
- context 'when pages_unique_domain_enabled is true' do
- let(:unique_domain_enabled) { true }
+ context 'when pages_unique_domain_enabled is true' do
+ let(:unique_domain_enabled) { true }
- it { is_expected.to eq('http://group.example.com/project') }
- end
+ it { is_expected.to eq('http://group.example.com/project') }
end
end
context 'when using pages_unique_domain' do
subject(:pages_url) { builder.pages_url(with_unique_domain: true) }
- context 'when pages_unique_domain feature flag is disabled' do
- before do
- stub_feature_flags(pages_unique_domain: false)
- end
+ context 'when pages_unique_domain_enabled is false' do
+ let(:unique_domain_enabled) { false }
it { is_expected.to eq('http://group.example.com/project') }
end
- context 'when pages_unique_domain feature flag is enabled' do
- before do
- stub_feature_flags(pages_unique_domain: true)
- end
-
- context 'when pages_unique_domain_enabled is false' do
- let(:unique_domain_enabled) { false }
-
- it { is_expected.to eq('http://group.example.com/project') }
- end
-
- context 'when pages_unique_domain_enabled is true' do
- let(:unique_domain_enabled) { true }
+ context 'when pages_unique_domain_enabled is true' do
+ let(:unique_domain_enabled) { true }
- it { is_expected.to eq('http://unique-domain.example.com') }
- end
+ it { is_expected.to eq('http://unique-domain.example.com') }
end
end
end
@@ -144,30 +116,16 @@ RSpec.describe Gitlab::Pages::UrlBuilder, feature_category: :pages do
describe '#unique_host' do
subject(:unique_host) { builder.unique_host }
- context 'when pages_unique_domain feature flag is disabled' do
- before do
- stub_feature_flags(pages_unique_domain: false)
- end
+ context 'when pages_unique_domain_enabled is false' do
+ let(:unique_domain_enabled) { false }
it { is_expected.to be_nil }
end
- context 'when pages_unique_domain feature flag is enabled' do
- before do
- stub_feature_flags(pages_unique_domain: true)
- end
+ context 'when pages_unique_domain_enabled is true' do
+ let(:unique_domain_enabled) { true }
- context 'when pages_unique_domain_enabled is false' do
- let(:unique_domain_enabled) { false }
-
- it { is_expected.to be_nil }
- end
-
- context 'when pages_unique_domain_enabled is true' do
- let(:unique_domain_enabled) { true }
-
- it { is_expected.to eq('unique-domain.example.com') }
- end
+ it { is_expected.to eq('unique-domain.example.com') }
end
end
diff --git a/spec/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy_spec.rb b/spec/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy_spec.rb
index 3fe858f33da..ddaf555dae6 100644
--- a/spec/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/in_operator_optimization/strategies/record_loader_strategy_spec.rb
@@ -32,6 +32,12 @@ RSpec.describe Gitlab::Pagination::Keyset::InOperatorOptimization::Strategies::R
end
end
+ let_it_be(:model_without_ignored_columns) do
+ Class.new(ApplicationRecord) do
+ self.table_name = 'projects'
+ end
+ end
+
subject(:strategy) { described_class.new(finder_query, model, order_by_columns) }
describe '#initializer_columns' do
@@ -70,6 +76,8 @@ RSpec.describe Gitlab::Pagination::Keyset::InOperatorOptimization::Strategies::R
describe '#final_projections' do
context 'when model does not have ignored columns' do
+ let(:model) { model_without_ignored_columns }
+
it 'does not specify the selected column names' do
expect(strategy.final_projections).to contain_exactly("(#{described_class::RECORDS_COLUMN}).*")
end
diff --git a/spec/lib/gitlab/plantuml_spec.rb b/spec/lib/gitlab/plantuml_spec.rb
index c783dd66c48..c2cce59cf90 100644
--- a/spec/lib/gitlab/plantuml_spec.rb
+++ b/spec/lib/gitlab/plantuml_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Gitlab::Plantuml, feature_category: :shared do
let(:plantuml_url) { "http://plantuml.foo.bar" }
before do
- allow(Gitlab::CurrentSettings).to receive(:plantuml_url).and_return(plantuml_url)
+ stub_application_setting(plantuml_url: plantuml_url)
end
context "when PlantUML is enabled" do
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index a762fdbde6b..8f74963d60b 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::ProjectSearchResults do
+RSpec.describe Gitlab::ProjectSearchResults, feature_category: :global_search do
include SearchHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/redis/cache_spec.rb b/spec/lib/gitlab/redis/cache_spec.rb
index b7b4ba0eb2f..a48bde5e4ab 100644
--- a/spec/lib/gitlab/redis/cache_spec.rb
+++ b/spec/lib/gitlab/redis/cache_spec.rb
@@ -17,5 +17,9 @@ RSpec.describe Gitlab::Redis::Cache do
expect(described_class.active_support_config[:expires_in]).to eq(1.day)
end
+
+ it 'has a pool set to false' do
+ expect(described_class.active_support_config[:pool]).to eq(false)
+ end
end
end
diff --git a/spec/lib/gitlab/redis/cluster_cache_spec.rb b/spec/lib/gitlab/redis/cluster_shared_state_spec.rb
index e448d608c53..11a574c79c4 100644
--- a/spec/lib/gitlab/redis/cluster_cache_spec.rb
+++ b/spec/lib/gitlab/redis/cluster_shared_state_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-RSpec.describe Gitlab::Redis::ClusterCache, feature_category: :redis do
- include_examples "redis_new_instance_shared_examples", 'cluster_cache', Gitlab::Redis::Cache
+RSpec.describe Gitlab::Redis::ClusterSharedState, feature_category: :redis do
+ include_examples "redis_new_instance_shared_examples", 'cluster_shared_state', Gitlab::Redis::SharedState
end
diff --git a/spec/lib/gitlab/redis/etag_cache_spec.rb b/spec/lib/gitlab/redis/etag_cache_spec.rb
new file mode 100644
index 00000000000..182a41bac80
--- /dev/null
+++ b/spec/lib/gitlab/redis/etag_cache_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Redis::EtagCache, feature_category: :shared do
+ # Note: this is a pseudo-store in front of `Cache`, meant only as a tool
+ # to move away from `SharedState` for etag cache data. Thus, we use the
+ # same store configuration as the former.
+ let(:instance_specific_config_file) { "config/redis.cache.yml" }
+
+ include_examples "redis_shared_examples"
+
+ describe '#pool' do
+ let(:config_new_format_host) { "spec/fixtures/config/redis_new_format_host.yml" }
+ let(:config_new_format_socket) { "spec/fixtures/config/redis_new_format_socket.yml" }
+ let(:rails_root) { mktmpdir }
+
+ subject { described_class.pool }
+
+ before do
+ # Override rails root to avoid having our fixtures overwritten by `redis.yml` if it exists
+ allow(Gitlab::Redis::SharedState).to receive(:rails_root).and_return(rails_root)
+ allow(Gitlab::Redis::Cache).to receive(:rails_root).and_return(rails_root)
+
+ allow(Gitlab::Redis::SharedState).to receive(:config_file_name).and_return(config_new_format_host)
+ allow(Gitlab::Redis::Cache).to receive(:config_file_name).and_return(config_new_format_socket)
+ end
+
+ around do |example|
+ clear_pool
+ example.run
+ ensure
+ clear_pool
+ end
+
+ it 'instantiates an instance of MultiStore' do
+ subject.with do |redis_instance|
+ expect(redis_instance).to be_instance_of(::Gitlab::Redis::MultiStore)
+
+ expect(redis_instance.primary_store.connection[:id]).to eq("unix:///path/to/redis.sock/0")
+ expect(redis_instance.secondary_store.connection[:id]).to eq("redis://test-host:6379/99")
+
+ expect(redis_instance.instance_name).to eq('EtagCache')
+ end
+ end
+
+ it_behaves_like 'multi store feature flags', :use_primary_and_secondary_stores_for_etag_cache,
+ :use_primary_store_as_default_for_etag_cache
+ end
+
+ describe '#store_name' do
+ it 'returns the name of the Cache store' do
+ expect(described_class.store_name).to eq('Cache')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/regex_requires_app_spec.rb b/spec/lib/gitlab/regex_requires_app_spec.rb
index 780184cdfd2..bea5d25dbc8 100644
--- a/spec/lib/gitlab/regex_requires_app_spec.rb
+++ b/spec/lib/gitlab/regex_requires_app_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# Only specs that *cannot* be run with fast_spec_helper only
# See regex_spec for tests that do not require the full spec_helper
-RSpec.describe Gitlab::Regex do
+RSpec.describe Gitlab::Regex, feature_category: :tooling do
describe '.debian_architecture_regex' do
subject { described_class.debian_architecture_regex }
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 5e58282ff92..c91b99caba2 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -65,13 +65,25 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do
describe '.project_name_regex_message' do
subject { described_class.project_name_regex_message }
- it { is_expected.to eq("can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces. It must start with a letter, digit, emoji, or '_'.") }
+ it { is_expected.to eq("can contain only letters, digits, emoji, '_', '.', '+', dashes, or spaces. It must start with a letter, digit, emoji, or '_'.") }
end
describe '.group_name_regex_message' do
subject { described_class.group_name_regex_message }
- it { is_expected.to eq("can contain only letters, digits, emojis, '_', '.', dash, space, parenthesis. It must start with letter, digit, emoji or '_'.") }
+ it { is_expected.to eq("can contain only letters, digits, emoji, '_', '.', dash, space, parenthesis. It must start with letter, digit, emoji or '_'.") }
+ end
+
+ describe '.slack_link_regex' do
+ subject { described_class.slack_link_regex }
+
+ it { is_expected.not_to match('http://custom-url.com|click here') }
+ it { is_expected.not_to match('custom-url.com|any-Charact3r$') }
+ it { is_expected.not_to match("&lt;custom-url.com|any-Charact3r$&gt;") }
+
+ it { is_expected.to match('<http://custom-url.com|click here>') }
+ it { is_expected.to match('<custom-url.com|any-Charact3r$>') }
+ it { is_expected.to match('<any-Charact3r$|any-Charact3r$>') }
end
describe '.bulk_import_destination_namespace_path_regex_message' do
@@ -820,6 +832,7 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do
it { is_expected.to match('1.2.3') }
it { is_expected.to match('1.2.3-beta') }
it { is_expected.to match('1.2.3-alpha.3') }
+ it { is_expected.to match('1.2.3-alpha.3+abcd') }
it { is_expected.not_to match('1') }
it { is_expected.not_to match('1.2') }
it { is_expected.not_to match('1./2.3') }
diff --git a/spec/lib/gitlab/repository_size_checker_spec.rb b/spec/lib/gitlab/repository_size_checker_spec.rb
index 559f5fa66c6..15c05a07ebb 100644
--- a/spec/lib/gitlab/repository_size_checker_spec.rb
+++ b/spec/lib/gitlab/repository_size_checker_spec.rb
@@ -36,13 +36,14 @@ RSpec.describe Gitlab::RepositorySizeChecker do
describe '#changes_will_exceed_size_limit?' do
let(:current_size) { 49 }
+ let(:project) { double }
it 'returns true when changes go over' do
- expect(subject.changes_will_exceed_size_limit?(2.megabytes)).to eq(true)
+ expect(subject.changes_will_exceed_size_limit?(2.megabytes, project)).to eq(true)
end
it 'returns false when changes do not go over' do
- expect(subject.changes_will_exceed_size_limit?(1.megabytes)).to eq(false)
+ expect(subject.changes_will_exceed_size_limit?(1.megabytes, project)).to eq(false)
end
end
diff --git a/spec/lib/gitlab/safe_request_store_spec.rb b/spec/lib/gitlab/safe_request_store_spec.rb
deleted file mode 100644
index accc491fbb7..00000000000
--- a/spec/lib/gitlab/safe_request_store_spec.rb
+++ /dev/null
@@ -1,257 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::SafeRequestStore do
- describe '.store' do
- context 'when RequestStore is active', :request_store do
- it 'uses RequestStore' do
- expect(described_class.store).to eq(RequestStore)
- end
- end
-
- context 'when RequestStore is NOT active' do
- it 'does not use RequestStore' do
- expect(described_class.store).to be_a(Gitlab::NullRequestStore)
- end
- end
- end
-
- describe '.begin!' do
- context 'when RequestStore is active', :request_store do
- it 'uses RequestStore' do
- expect(RequestStore).to receive(:begin!)
-
- described_class.begin!
- end
- end
-
- context 'when RequestStore is NOT active' do
- it 'uses RequestStore' do
- expect(RequestStore).to receive(:begin!)
-
- described_class.begin!
- end
- end
- end
-
- describe '.clear!' do
- context 'when RequestStore is active', :request_store do
- it 'uses RequestStore' do
- expect(RequestStore).to receive(:clear!).once.and_call_original
-
- described_class.clear!
- end
- end
-
- context 'when RequestStore is NOT active' do
- it 'uses RequestStore' do
- expect(RequestStore).to receive(:clear!).and_call_original
-
- described_class.clear!
- end
- end
- end
-
- describe '.end!' do
- context 'when RequestStore is active', :request_store do
- it 'uses RequestStore' do
- expect(RequestStore).to receive(:end!).once.and_call_original
-
- described_class.end!
- end
- end
-
- context 'when RequestStore is NOT active' do
- it 'uses RequestStore' do
- expect(RequestStore).to receive(:end!).and_call_original
-
- described_class.end!
- end
- end
- end
-
- describe '.write' do
- context 'when RequestStore is active', :request_store do
- it 'uses RequestStore' do
- expect do
- described_class.write('foo', true)
- end.to change { described_class.read('foo') }.from(nil).to(true)
- end
-
- it 'does not pass the options hash to the underlying store implementation' do
- expect(described_class.store).to receive(:write).with('foo', true)
-
- described_class.write('foo', true, expires_in: 15.seconds)
- end
- end
-
- context 'when RequestStore is NOT active' do
- it 'does not use RequestStore' do
- expect do
- described_class.write('foo', true)
- end.not_to change { described_class.read('foo') }.from(nil)
- end
-
- it 'does not pass the options hash to the underlying store implementation' do
- expect(described_class.store).to receive(:write).with('foo', true)
-
- described_class.write('foo', true, expires_in: 15.seconds)
- end
- end
- end
-
- describe '.[]=' do
- context 'when RequestStore is active', :request_store do
- it 'uses RequestStore' do
- expect do
- described_class['foo'] = true
- end.to change { described_class.read('foo') }.from(nil).to(true)
- end
- end
-
- context 'when RequestStore is NOT active' do
- it 'does not use RequestStore' do
- expect do
- described_class['foo'] = true
- end.not_to change { described_class.read('foo') }.from(nil)
- end
- end
- end
-
- describe '.read' do
- context 'when RequestStore is active', :request_store do
- it 'uses RequestStore' do
- expect do
- RequestStore.write('foo', true)
- end.to change { described_class.read('foo') }.from(nil).to(true)
- end
- end
-
- context 'when RequestStore is NOT active' do
- it 'does not use RequestStore' do
- expect do
- RequestStore.write('foo', true)
- end.not_to change { described_class.read('foo') }.from(nil)
-
- RequestStore.clear! # Clean up
- end
- end
- end
-
- describe '.[]' do
- context 'when RequestStore is active', :request_store do
- it 'uses RequestStore' do
- expect do
- RequestStore.write('foo', true)
- end.to change { described_class['foo'] }.from(nil).to(true)
- end
- end
-
- context 'when RequestStore is NOT active' do
- it 'does not use RequestStore' do
- expect do
- RequestStore.write('foo', true)
- end.not_to change { described_class['foo'] }.from(nil)
-
- RequestStore.clear! # Clean up
- end
- end
- end
-
- describe '.exist?' do
- context 'when RequestStore is active', :request_store do
- it 'uses RequestStore' do
- expect do
- RequestStore.write('foo', 'not nil')
- end.to change { described_class.exist?('foo') }.from(false).to(true)
- end
- end
-
- context 'when RequestStore is NOT active' do
- it 'does not use RequestStore' do
- expect do
- RequestStore.write('foo', 'not nil')
- end.not_to change { described_class.exist?('foo') }.from(false)
-
- RequestStore.clear! # Clean up
- end
- end
- end
-
- describe '.fetch' do
- context 'when RequestStore is active', :request_store do
- it 'uses RequestStore' do
- expect do
- described_class.fetch('foo') { 'block result' } # rubocop:disable Style/RedundantFetchBlock
- end.to change { described_class.read('foo') }.from(nil).to('block result')
- end
- end
-
- context 'when RequestStore is NOT active' do
- it 'does not use RequestStore' do
- RequestStore.clear! # Ensure clean
-
- expect do
- described_class.fetch('foo') { 'block result' } # rubocop:disable Style/RedundantFetchBlock
- end.not_to change { described_class.read('foo') }.from(nil)
-
- RequestStore.clear! # Clean up
- end
- end
- end
-
- describe '.delete' do
- context 'when RequestStore is active', :request_store do
- it 'uses RequestStore' do
- described_class.write('foo', true)
-
- expect do
- described_class.delete('foo')
- end.to change { described_class.read('foo') }.from(true).to(nil)
- end
-
- context 'when given a block and the key exists' do
- it 'does not execute the block' do
- described_class.write('foo', true)
-
- expect do |b|
- described_class.delete('foo', &b)
- end.not_to yield_control
- end
- end
-
- context 'when given a block and the key does not exist' do
- it 'yields the key and returns the block result' do
- result = described_class.delete('foo') { |key| "#{key} block result" }
-
- expect(result).to eq('foo block result')
- end
- end
- end
-
- context 'when RequestStore is NOT active' do
- before do
- RequestStore.write('foo', true)
- end
-
- after do
- RequestStore.clear! # Clean up
- end
-
- it 'does not use RequestStore' do
- expect do
- described_class.delete('foo')
- end.not_to change { RequestStore.read('foo') }.from(true)
- end
-
- context 'when given a block' do
- it 'yields the key and returns the block result' do
- result = described_class.delete('foo') { |key| "#{key} block result" }
-
- expect(result).to eq('foo block result')
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 662eab11cc0..725b7901e68 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -187,11 +187,16 @@ RSpec.describe Gitlab::SearchResults, feature_category: :global_search do
end
context 'filtering' do
+ let_it_be(:unarchived_project) { create(:project, :public) }
+ let_it_be(:archived_project) { create(:project, :public, :archived) }
let!(:opened_result) { create(:merge_request, :opened, source_project: project, title: 'foo opened') }
let!(:closed_result) { create(:merge_request, :closed, source_project: project, title: 'foo closed') }
+ let(:unarchived_result) { create(:merge_request, source_project: unarchived_project, title: 'foo unarchived') }
+ let(:archived_result) { create(:merge_request, source_project: archived_project, title: 'foo archived') }
let(:query) { 'foo' }
include_examples 'search results filtered by state'
+ include_examples 'search results filtered by archived', 'search_merge_requests_hide_archived_projects'
end
context 'ordering' do
@@ -266,25 +271,10 @@ RSpec.describe Gitlab::SearchResults, feature_category: :global_search do
describe 'filtering' do
let_it_be(:group) { create(:group) }
- let_it_be(:unarchived_project) { create(:project, :public, group: group, name: 'Test1') }
- let_it_be(:archived_project) { create(:project, :archived, :public, group: group, name: 'Test2') }
+ let_it_be(:unarchived_result) { create(:project, :public, group: group, name: 'Test1') }
+ let_it_be(:archived_result) { create(:project, :archived, :public, group: group, name: 'Test2') }
- it_behaves_like 'search results filtered by archived'
-
- context 'when the search_projects_hide_archived feature flag is disabled' do
- before do
- stub_feature_flags(search_projects_hide_archived: false)
- end
-
- context 'when filter not provided' do
- let(:filters) { {} }
-
- it 'returns archived and unarchived results', :aggregate_failures do
- expect(results.objects('projects')).to include unarchived_project
- expect(results.objects('projects')).to include archived_project
- end
- end
- end
+ it_behaves_like 'search results filtered by archived', 'search_projects_hide_archived'
end
end
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index 4e46a26e89f..4550ccc2fff 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -463,11 +463,12 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
let(:expected_end_payload) do
end_payload.merge(
'urgency' => 'high',
- 'target_duration_s' => 10
+ 'target_duration_s' => 10,
+ 'target_scheduling_latency_s' => 10
)
end
- it 'logs job done with urgency and target_duration_s fields' do
+ it 'logs job done with urgency, target_duration_s and target_scheduling_latency_s fields' do
travel_to(timestamp) do
expect(logger).to receive(:info).with(start_payload).ordered
expect(logger).to receive(:info).with(expected_end_payload).ordered
diff --git a/spec/lib/gitlab/sidekiq_middleware/pause_control/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/pause_control/client_spec.rb
new file mode 100644
index 00000000000..0a837f6f932
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/pause_control/client_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::PauseControl::Client, :clean_gitlab_redis_queues, feature_category: :global_search do
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ 'TestPauseWorker'
+ end
+
+ include ApplicationWorker
+
+ pause_control :zoekt
+
+ def perform(*); end
+ end
+ end
+
+ before do
+ stub_const('TestPauseWorker', worker_class)
+ end
+
+ describe '#call' do
+ context 'when strategy is enabled' do
+ before do
+ stub_feature_flags(zoekt_pause_indexing: true)
+ end
+
+ it 'does not schedule the job' do
+ expect(Gitlab::SidekiqMiddleware::PauseControl::PauseControlService).to receive(:add_to_waiting_queue!).once
+
+ TestPauseWorker.perform_async('args1')
+
+ expect(TestPauseWorker.jobs.count).to eq(0)
+ end
+ end
+
+ context 'when strategy is disabled' do
+ before do
+ stub_feature_flags(zoekt_pause_indexing: false)
+ end
+
+ it 'schedules the job' do
+ expect(Gitlab::SidekiqMiddleware::PauseControl::PauseControlService).not_to receive(:add_to_waiting_queue!)
+
+ TestPauseWorker.perform_async('args1')
+
+ expect(TestPauseWorker.jobs.count).to eq(1)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/pause_control/pause_control_service_spec.rb b/spec/lib/gitlab/sidekiq_middleware/pause_control/pause_control_service_spec.rb
new file mode 100644
index 00000000000..1de8bd9f7ad
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/pause_control/pause_control_service_spec.rb
@@ -0,0 +1,178 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::PauseControl::PauseControlService, :clean_gitlab_redis_shared_state, feature_category: :global_search do
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ 'DummyWorker'
+ end
+
+ include ApplicationWorker
+ end
+ end
+
+ let(:worker_class_name) { worker_class.name }
+
+ let(:worker_context) do
+ { 'correlation_id' => 'context_correlation_id',
+ 'meta.project' => 'gitlab-org/gitlab' }
+ end
+
+ let(:stored_context) do
+ { "#{Gitlab::ApplicationContext::LOG_KEY}.project" => 'gitlab-org/gitlab' }
+ end
+
+ let(:worker_args) { [1, 2] }
+
+ subject { described_class.new(worker_class_name) }
+
+ before do
+ stub_const(worker_class_name, worker_class)
+ end
+
+ describe '.add_to_waiting_queue!' do
+ it 'calls an instance method' do
+ expect_next_instance_of(described_class) do |instance|
+ expect(instance).to receive(:add_to_waiting_queue!).with(worker_args, worker_context)
+ end
+
+ described_class.add_to_waiting_queue!(worker_class_name, worker_args, worker_context)
+ end
+ end
+
+ describe '.has_jobs_in_waiting_queue?' do
+ it 'calls an instance method' do
+ expect_next_instance_of(described_class) do |instance|
+ expect(instance).to receive(:has_jobs_in_waiting_queue?)
+ end
+
+ described_class.has_jobs_in_waiting_queue?(worker_class_name)
+ end
+ end
+
+ describe '.resume_processing!' do
+ it 'calls an instance method' do
+ expect_next_instance_of(described_class) do |instance|
+ expect(instance).to receive(:resume_processing!)
+ end
+
+ described_class.resume_processing!(worker_class_name)
+ end
+ end
+
+ describe '.queue_size' do
+ it 'reports the queue size' do
+ expect(described_class.queue_size(worker_class_name)).to eq(0)
+
+ subject.add_to_waiting_queue!(worker_args, worker_context)
+
+ expect(described_class.queue_size(worker_class_name)).to eq(1)
+
+ expect { subject.resume_processing! }.to change { described_class.queue_size(worker_class_name) }.by(-1)
+ end
+ end
+
+ describe '#add_to_waiting_queue!' do
+ it 'adds a job to the set' do
+ expect { subject.add_to_waiting_queue!(worker_args, worker_context) }
+ .to change { subject.queue_size }
+ .from(0).to(1)
+ end
+
+ it 'adds only one unique job to the set' do
+ expect do
+ 2.times { subject.add_to_waiting_queue!(worker_args, worker_context) }
+ end.to change { subject.queue_size }.from(0).to(1)
+ end
+
+ it 'only stores `project` context information' do
+ subject.add_to_waiting_queue!(worker_args, worker_context)
+
+ subject.send(:with_redis) do |r|
+ set_key = subject.send(:redis_set_key)
+ stored_job = subject.send(:deserialize, r.zrange(set_key, 0, -1).first)
+
+ expect(stored_job['context']).to eq(stored_context)
+ end
+ end
+ end
+
+ describe '#has_jobs_in_waiting_queue?' do
+ it 'checks set existence' do
+ expect { subject.add_to_waiting_queue!(worker_args, worker_context) }
+ .to change { subject.has_jobs_in_waiting_queue? }
+ .from(false).to(true)
+ end
+ end
+
+ describe '#resume_processing!' do
+ let(:jobs) { [[1], [2], [3]] }
+
+ it 'puts jobs back into the queue and respects order' do
+ # We stub this const to test at least a couple of loop iterations
+ stub_const("#{described_class}::LIMIT", 2)
+
+ jobs.each do |j|
+ subject.add_to_waiting_queue!(j, worker_context)
+ end
+
+ expect(worker_class).to receive(:perform_async).with(1).ordered
+ expect(worker_class).to receive(:perform_async).with(2).ordered
+ expect(worker_class).not_to receive(:perform_async).with(3).ordered
+
+ expect(Gitlab::SidekiqLogging::PauseControlLogger.instance).to receive(:resumed_log).with(worker_class_name, [1])
+ expect(Gitlab::SidekiqLogging::PauseControlLogger.instance).to receive(:resumed_log).with(worker_class_name, [2])
+
+ subject.resume_processing!
+ end
+
+ it 'drops a set after execution' do
+ jobs.each do |j|
+ subject.add_to_waiting_queue!(j, worker_context)
+ end
+
+ expect(Gitlab::ApplicationContext).to receive(:with_raw_context)
+ .with(stored_context)
+ .exactly(jobs.count).times.and_call_original
+ expect(worker_class).to receive(:perform_async).exactly(jobs.count).times
+
+ expect { subject.resume_processing! }.to change { subject.has_jobs_in_waiting_queue? }.from(true).to(false)
+ end
+ end
+
+ context 'with concurrent changes to different queues' do
+ let(:second_worker_class) do
+ Class.new do
+ def self.name
+ 'SecondDummyIndexingWorker'
+ end
+
+ include ApplicationWorker
+ end
+ end
+
+ let(:other_subject) { described_class.new(second_worker_class.name) }
+
+ before do
+ stub_const(second_worker_class.name, second_worker_class)
+ end
+
+ it 'allows to use queues independently of each other' do
+ expect { subject.add_to_waiting_queue!(worker_args, worker_context) }
+ .to change { subject.queue_size }
+ .from(0).to(1)
+
+ expect { other_subject.add_to_waiting_queue!(worker_args, worker_context) }
+ .to change { other_subject.queue_size }
+ .from(0).to(1)
+
+ expect { subject.resume_processing! }.to change { subject.has_jobs_in_waiting_queue? }
+ .from(true).to(false)
+
+ expect { other_subject.resume_processing! }.to change { other_subject.has_jobs_in_waiting_queue? }
+ .from(true).to(false)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/pause_control/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/pause_control/server_spec.rb
new file mode 100644
index 00000000000..c577f9697b2
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/pause_control/server_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::PauseControl::Server, :clean_gitlab_redis_queues, feature_category: :global_search do
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ 'TestPauseWorker'
+ end
+
+ include ApplicationWorker
+
+ pause_control :zoekt
+
+ def perform(*)
+ self.class.work
+ end
+
+ def self.work; end
+ end
+ end
+
+ before do
+ stub_const('TestPauseWorker', worker_class)
+ end
+
+ around do |example|
+ with_sidekiq_server_middleware do |chain|
+ chain.add described_class
+ Sidekiq::Testing.inline! { example.run }
+ end
+ end
+
+ describe '#call' do
+ context 'when strategy is enabled' do
+ before do
+ stub_feature_flags(zoekt_pause_indexing: true)
+ end
+
+ it 'puts the job to another queue without execution' do
+ bare_job = { 'class' => 'TestPauseWorker', 'args' => ['hello'] }
+ job_definition = Gitlab::SidekiqMiddleware::PauseControl::StrategyHandler.new(TestPauseWorker, bare_job.dup)
+
+ expect(Gitlab::SidekiqMiddleware::PauseControl::StrategyHandler)
+ .to receive(:new).with(TestPauseWorker, a_hash_including(bare_job))
+ .and_return(job_definition).once
+
+ expect(TestPauseWorker).not_to receive(:work)
+ expect(Gitlab::SidekiqMiddleware::PauseControl::PauseControlService).to receive(:add_to_waiting_queue!).once
+
+ TestPauseWorker.perform_async('hello')
+ end
+ end
+
+ context 'when strategy is disabled' do
+ before do
+ stub_feature_flags(zoekt_pause_indexing: false)
+ end
+
+ it 'executes the job' do
+ bare_job = { 'class' => 'TestPauseWorker', 'args' => ['hello'] }
+ job_definition = Gitlab::SidekiqMiddleware::PauseControl::StrategyHandler.new(TestPauseWorker, bare_job.dup)
+
+ expect(Gitlab::SidekiqMiddleware::PauseControl::StrategyHandler)
+ .to receive(:new).with(TestPauseWorker, hash_including(bare_job))
+ .and_return(job_definition).twice
+
+ expect(TestPauseWorker).to receive(:work)
+ expect(Gitlab::SidekiqMiddleware::PauseControl::PauseControlService).not_to receive(:add_to_waiting_queue!)
+
+ TestPauseWorker.perform_async('hello')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/pause_control/strategy_handler_spec.rb b/spec/lib/gitlab/sidekiq_middleware/pause_control/strategy_handler_spec.rb
new file mode 100644
index 00000000000..da53abec479
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/pause_control/strategy_handler_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::PauseControl::StrategyHandler, :clean_gitlab_redis_queues, feature_category: :global_search do
+ subject(:pause_control) do
+ described_class.new(TestPauseWorker, job)
+ end
+
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ 'TestPauseWorker'
+ end
+
+ include ApplicationWorker
+
+ pause_control :zoekt
+
+ def perform(*); end
+ end
+ end
+
+ let(:job) { { 'class' => 'TestPauseWorker', 'args' => [1], 'jid' => '123' } }
+
+ before do
+ stub_const('TestPauseWorker', worker_class)
+ end
+
+ describe '#schedule' do
+ shared_examples 'scheduling with pause control class' do |strategy_class|
+ it 'calls schedule on the strategy' do
+ expect do |block|
+ klass = "Gitlab::SidekiqMiddleware::PauseControl::Strategies::#{strategy_class}".constantize
+ expect_next_instance_of(klass) do |strategy|
+ expect(strategy).to receive(:schedule).with(job, &block)
+ end
+
+ pause_control.schedule(&block)
+ end.to yield_control
+ end
+ end
+
+ it_behaves_like 'scheduling with pause control class', 'Zoekt'
+ end
+
+ describe '#perform' do
+ it 'calls perform on the strategy' do
+ expect do |block|
+ expect_next_instance_of(Gitlab::SidekiqMiddleware::PauseControl::Strategies::Zoekt) do |strategy|
+ expect(strategy).to receive(:perform).with(job, &block)
+ end
+
+ pause_control.perform(&block)
+ end.to yield_control
+ end
+
+ it 'pauses job' do
+ expect_next_instance_of(Gitlab::SidekiqMiddleware::PauseControl::Strategies::Zoekt) do |strategy|
+ expect(strategy).to receive(:should_pause?).and_return(true)
+ end
+
+ expect { pause_control.perform }.to change {
+ Gitlab::SidekiqMiddleware::PauseControl::PauseControlService.queue_size('TestPauseWorker')
+ }.by(1)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/pause_control_spec.rb b/spec/lib/gitlab/sidekiq_middleware/pause_control_spec.rb
new file mode 100644
index 00000000000..a0cce0f61a0
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/pause_control_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::PauseControl, feature_category: :global_search do
+ describe '.for' do
+ it 'returns the right class for `zoekt`' do
+ expect(described_class.for(:zoekt)).to eq(::Gitlab::SidekiqMiddleware::PauseControl::Strategies::Zoekt)
+ end
+
+ it 'returns the right class for `none`' do
+ expect(described_class.for(:none)).to eq(::Gitlab::SidekiqMiddleware::PauseControl::Strategies::None)
+ end
+
+ it 'returns nil when passing an unknown key' do
+ expect(described_class.for(:unknown)).to eq(::Gitlab::SidekiqMiddleware::PauseControl::Strategies::None)
+ 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 bc69f232d9e..0cbf9eab3d8 100644
--- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
@@ -59,30 +59,14 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
described_class.initialize_process_metrics
end
- shared_examples "initializes sidekiq SLIs for the workers in the current process" do
+ context 'when emit_sidekiq_histogram FF is disabled' do
before do
- allow(Gitlab::SidekiqConfig)
- .to receive(:current_worker_queue_mappings)
- .and_return('MergeWorker' => 'merge', 'Ci::BuildFinishedWorker' => 'default')
- allow(completion_seconds_metric).to receive(:get)
+ stub_feature_flags(emit_sidekiq_histogram_metrics: false)
+ allow(Gitlab::SidekiqConfig).to receive(:current_worker_queue_mappings).and_return('MergeWorker' => 'merge')
end
- it "initializes the SLIs with labels" do
- expect(Gitlab::Metrics::SidekiqSlis)
- .to receive(initialize_sli_method).with([
- {
- worker: 'MergeWorker',
- urgency: 'high',
- feature_category: 'source_code_management',
- external_dependencies: 'no'
- },
- {
- worker: 'Ci::BuildFinishedWorker',
- urgency: 'high',
- feature_category: 'continuous_integration',
- external_dependencies: 'no'
- }
- ])
+ it 'does not initialize sidekiq_jobs_completion_seconds' do
+ expect(completion_seconds_metric).not_to receive(:get)
described_class.initialize_process_metrics
end
@@ -97,35 +81,38 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
end
end
- context 'initializing execution SLIs' do
- let(:initialize_sli_method) { :initialize_execution_slis! }
-
- context 'when sidekiq_execution_application_slis FF is turned on' do
- it_behaves_like "initializes sidekiq SLIs for the workers in the current process"
+ context 'initializing execution and queueing SLIs' do
+ before do
+ allow(Gitlab::SidekiqConfig)
+ .to receive(:current_worker_queue_mappings)
+ .and_return('MergeWorker' => 'merge', 'Ci::BuildFinishedWorker' => 'default')
+ allow(completion_seconds_metric).to receive(:get)
end
- context 'when sidekiq_execution_application_slis FF is turned off' do
- before do
- stub_feature_flags(sidekiq_execution_application_slis: false)
- end
-
- it_behaves_like "not initializing sidekiq SLIs"
- end
- end
+ it "initializes the execution and queueing SLIs with labels" do
+ expected_labels = [
+ {
+ worker: 'MergeWorker',
+ urgency: 'high',
+ feature_category: 'source_code_management',
+ external_dependencies: 'no',
+ queue: 'merge'
+ },
+ {
+ worker: 'Ci::BuildFinishedWorker',
+ urgency: 'high',
+ feature_category: 'continuous_integration',
+ external_dependencies: 'no',
+ queue: 'default'
+ }
+ ]
- context 'initializing queueing SLIs' do
- let(:initialize_sli_method) { :initialize_queueing_slis! }
-
- context 'when sidekiq_queueing_application_slis FF is turned on' do
- it_behaves_like "initializes sidekiq SLIs for the workers in the current process"
- end
-
- context 'when sidekiq_queueing_application_slis FF is turned off' do
- before do
- stub_feature_flags(sidekiq_queueing_application_slis: false)
- end
+ expect(Gitlab::Metrics::SidekiqSlis)
+ .to receive(:initialize_execution_slis!).with(expected_labels)
+ expect(Gitlab::Metrics::SidekiqSlis)
+ .to receive(:initialize_queueing_slis!).with(expected_labels)
- it_behaves_like "not initializing sidekiq SLIs"
+ described_class.initialize_process_metrics
end
end
@@ -192,20 +179,26 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
expect(redis_requests_total).to receive(:increment).with(labels_with_job_status, redis_calls)
expect(elasticsearch_requests_total).to receive(:increment).with(labels_with_job_status, elasticsearch_calls)
expect(sidekiq_mem_total_bytes).to receive(:set).with(labels_with_job_status, mem_total_bytes)
- expect(Gitlab::Metrics::SidekiqSlis).to receive(:record_execution_apdex).with(labels.slice(:worker,
- :feature_category,
- :urgency,
- :external_dependencies), monotonic_time_duration)
- expect(Gitlab::Metrics::SidekiqSlis).to receive(:record_execution_error).with(labels.slice(:worker,
- :feature_category,
- :urgency,
- :external_dependencies), false)
+ expect(Gitlab::Metrics::SidekiqSlis).to receive(:record_execution_apdex)
+ .with(labels.slice(:worker,
+ :feature_category,
+ :urgency,
+ :external_dependencies,
+ :queue), monotonic_time_duration)
+ expect(Gitlab::Metrics::SidekiqSlis).to receive(:record_execution_error)
+ .with(labels.slice(:worker,
+ :feature_category,
+ :urgency,
+ :external_dependencies,
+ :queue), false)
if queue_duration_for_job
- expect(Gitlab::Metrics::SidekiqSlis).to receive(:record_queueing_apdex).with(labels.slice(:worker,
- :feature_category,
- :urgency,
- :external_dependencies), queue_duration_for_job)
+ expect(Gitlab::Metrics::SidekiqSlis).to receive(:record_queueing_apdex)
+ .with(labels.slice(:worker,
+ :feature_category,
+ :urgency,
+ :external_dependencies,
+ :queue), queue_duration_for_job)
end
subject.call(worker, job, :test) { nil }
@@ -260,10 +253,12 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
it 'records sidekiq SLI error but does not record sidekiq SLI apdex' do
expect(failed_total_metric).to receive(:increment)
expect(Gitlab::Metrics::SidekiqSlis).not_to receive(:record_execution_apdex)
- expect(Gitlab::Metrics::SidekiqSlis).to receive(:record_execution_error).with(labels.slice(:worker,
- :feature_category,
- :urgency,
- :external_dependencies), true)
+ expect(Gitlab::Metrics::SidekiqSlis).to receive(:record_execution_error)
+ .with(labels.slice(:worker,
+ :feature_category,
+ :urgency,
+ :external_dependencies,
+ :queue), true)
expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed")
end
@@ -288,31 +283,6 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
subject.call(worker, job, :test) { nil }
end
end
-
- context 'when sidekiq_execution_application_slis FF is turned off' do
- before do
- stub_feature_flags(sidekiq_execution_application_slis: false)
- end
-
- it 'does not call record_execution_apdex nor record_execution_error' do
- expect(Gitlab::Metrics::SidekiqSlis).not_to receive(:record_execution_apdex)
- expect(Gitlab::Metrics::SidekiqSlis).not_to receive(:record_execution_error)
-
- subject.call(worker, job, :test) { nil }
- end
- end
-
- context 'when sidekiq_queueing_application_slis FF is turned off' do
- before do
- stub_feature_flags(sidekiq_queueing_application_slis: false)
- end
-
- it 'does not call record_queueing_apdex' do
- expect(Gitlab::Metrics::SidekiqSlis).not_to receive(:record_queueing_apdex)
-
- subject.call(worker, job, :test) { nil }
- end
- end
end
end
@@ -484,5 +454,53 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
end
end
end
+
+ context 'when emit_sidekiq_histogram_metrics FF is disabled' do
+ include_context 'server metrics with mocked prometheus'
+ include_context 'server metrics call' do
+ let(:stub_subject) { false }
+ end
+
+ subject(:middleware) { described_class.new }
+
+ let(:job) { {} }
+ let(:queue) { :test }
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ "TestWorker"
+ end
+ include ApplicationWorker
+ end
+ end
+
+ let(:worker) { worker_class.new }
+ let(:labels) do
+ { queue: queue.to_s,
+ worker: worker.class.name,
+ boundary: "",
+ external_dependencies: "no",
+ feature_category: "",
+ urgency: "low" }
+ end
+
+ before do
+ stub_feature_flags(emit_sidekiq_histogram_metrics: false)
+ end
+
+ it 'does not emit histogram metrics' do
+ expect(completion_seconds_metric).not_to receive(:observe)
+ expect(queue_duration_seconds).not_to receive(:observe)
+ expect(failed_total_metric).not_to receive(:increment)
+
+ middleware.call(worker, job, queue) { nil }
+ end
+
+ it 'emits sidekiq_jobs_completion_seconds_sum metric' do
+ expect(completion_seconds_sum_metric).to receive(:increment).with(labels, monotonic_time_duration)
+
+ middleware.call(worker, job, queue) { nil }
+ end
+ end
end
# rubocop: enable RSpec/MultipleMemoizedHelpers
diff --git a/spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb b/spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb
index 4be21591a40..620de7e7671 100644
--- a/spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/skip_jobs_spec.rb
@@ -115,7 +115,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::SkipJobs, feature_category: :scalabili
end
context 'with worker opted for database health check' do
- let(:health_signal_attrs) { { gitlab_schema: :gitlab_main, delay: 1.minute, tables: [:users] } }
+ let(:health_signal_attrs) { { gitlab_schema: :gitlab_main, tables: [:users], delay: 1.minute } }
around do |example|
with_sidekiq_server_middleware do |chain|
diff --git a/spec/lib/gitlab/time_tracking_formatter_spec.rb b/spec/lib/gitlab/time_tracking_formatter_spec.rb
index 4203a76cbfb..aa755d64a7a 100644
--- a/spec/lib/gitlab/time_tracking_formatter_spec.rb
+++ b/spec/lib/gitlab/time_tracking_formatter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::TimeTrackingFormatter do
+RSpec.describe Gitlab::TimeTrackingFormatter, feature_category: :team_planning do
describe '#parse' do
let(:keep_zero) { false }
diff --git a/spec/lib/gitlab/tracking/standard_context_spec.rb b/spec/lib/gitlab/tracking/standard_context_spec.rb
index e1ae362e797..c44cfdea1cd 100644
--- a/spec/lib/gitlab/tracking/standard_context_spec.rb
+++ b/spec/lib/gitlab/tracking/standard_context_spec.rb
@@ -3,10 +3,6 @@
require 'spec_helper'
RSpec.describe Gitlab::Tracking::StandardContext do
- let_it_be(:project) { create(:project) }
- let_it_be(:namespace) { create(:namespace) }
- let_it_be(:user) { create(:user) }
-
let(:snowplow_context) { subject.to_context }
describe '#to_context' do
@@ -62,21 +58,27 @@ RSpec.describe Gitlab::Tracking::StandardContext do
expect(snowplow_context.to_json.dig(:data, :context_generated_at)).to eq(Time.current)
end
- context 'plan' do
- context 'when namespace is not available' do
- it 'is nil' do
- expect(snowplow_context.to_json.dig(:data, :plan)).to be_nil
- end
- end
+ it 'contains standard properties' do
+ standard_properties = [:user_id, :project_id, :namespace_id, :plan]
+ expect(snowplow_context.to_json[:data].keys).to include(*standard_properties)
+ end
- context 'when namespace is available' do
- let(:namespace) { create(:namespace) }
+ context 'with standard properties' do
+ let(:user_id) { 1 }
+ let(:project_id) { 2 }
+ let(:namespace_id) { 3 }
+ let(:plan_name) { "plan name" }
- subject { described_class.new(namespace_id: namespace.id, plan_name: namespace.actual_plan_name) }
+ subject do
+ described_class.new(user_id: user_id, project_id: project_id, namespace_id: namespace_id, plan_name: plan_name)
+ end
- it 'contains plan name' do
- expect(snowplow_context.to_json.dig(:data, :plan)).to eq(Plan::DEFAULT)
- end
+ it 'holds the correct values', :aggregate_failures do
+ json_data = snowplow_context.to_json.fetch(:data)
+ expect(json_data[:user_id]).to eq(user_id)
+ expect(json_data[:project_id]).to eq(project_id)
+ expect(json_data[:namespace_id]).to eq(namespace_id)
+ expect(json_data[:plan]).to eq(plan_name)
end
end
@@ -95,24 +97,12 @@ RSpec.describe Gitlab::Tracking::StandardContext do
end
context 'with incorrect argument type' do
- subject { described_class.new(project_id: create(:group)) }
+ subject { described_class.new(project_id: "a string") }
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
-
- it 'contains user id' do
- expect(snowplow_context.to_json[:data].keys).to include(:user_id)
- end
-
- it 'contains namespace and project ids' do
- expect(snowplow_context.to_json[:data].keys).to include(:project_id, :namespace_id)
- end
-
- it 'accepts just project id as integer' do
- expect { described_class.new(project: 1).to_context }.not_to raise_error
- end
end
end
diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb
index d67bb477350..859f3f7a8d7 100644
--- a/spec/lib/gitlab/usage/metric_definition_spec.rb
+++ b/spec/lib/gitlab/usage/metric_definition_spec.rb
@@ -18,7 +18,6 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
data_source: 'database',
distribution: %w(ee ce),
tier: %w(free starter premium ultimate bronze silver gold),
- name: 'uuid',
data_category: 'standard',
removed_by_url: 'http://gdk.test'
}
@@ -129,7 +128,6 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
:distribution | nil
:distribution | 'test'
:tier | %w(test ee)
- :name | 'count_<adjective_describing>_boards'
:repair_issue_url | nil
:removed_by_url | 1
diff --git a/spec/lib/gitlab/usage/metric_spec.rb b/spec/lib/gitlab/usage/metric_spec.rb
index d0ea4e7aa16..a4135b143dd 100644
--- a/spec/lib/gitlab/usage/metric_spec.rb
+++ b/spec/lib/gitlab/usage/metric_spec.rb
@@ -45,12 +45,6 @@ RSpec.describe Gitlab::Usage::Metric do
end
end
- describe '#with_suggested_name' do
- it 'returns key_path metric with the corresponding generated query' do
- expect(described_class.new(issue_count_metric_definiton).with_suggested_name).to eq({ counts: { issues: 'count_issues' } })
- end
- end
-
context 'unavailable metric' do
let(:instrumentation_class) { "UnavailableMetric" }
let(:issue_count_metric_definiton) do
@@ -69,7 +63,7 @@ RSpec.describe Gitlab::Usage::Metric do
stub_const("Gitlab::Usage::Metrics::Instrumentations::#{instrumentation_class}", unavailable_metric_class)
end
- [:with_value, :with_instrumentation, :with_suggested_name].each do |method_name|
+ [:with_value, :with_instrumentation].each do |method_name|
describe "##{method_name}" do
it 'returns an empty hash' do
expect(described_class.new(issue_count_metric_definiton).public_send(method_name)).to eq({})
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/batched_background_migration_failed_jobs_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/batched_background_migration_failed_jobs_metric_spec.rb
new file mode 100644
index 00000000000..e66dd04b69b
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/batched_background_migration_failed_jobs_metric_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::BatchedBackgroundMigrationFailedJobsMetric, feature_category: :database do
+ let(:expected_value) do
+ [
+ {
+ job_class_name: 'job',
+ number_of_failed_jobs: 1,
+ table_name: 'jobs'
+ },
+ {
+ job_class_name: 'test',
+ number_of_failed_jobs: 2,
+ table_name: 'users'
+ }
+ ]
+ end
+
+ let_it_be(:active_migration) do
+ create(:batched_background_migration, :active, table_name: 'users', job_class_name: 'test', created_at: 5.days.ago)
+ end
+
+ let_it_be(:failed_migration) do
+ create(:batched_background_migration, :failed, table_name: 'jobs', job_class_name: 'job', created_at: 4.days.ago)
+ end
+
+ let_it_be(:batched_job) { create(:batched_background_migration_job, :failed, batched_migration: active_migration) }
+
+ let_it_be(:batched_job_2) { create(:batched_background_migration_job, :failed, batched_migration: active_migration) }
+
+ let_it_be(:batched_job_3) { create(:batched_background_migration_job, :failed, batched_migration: failed_migration) }
+
+ let_it_be(:old_migration) { create(:batched_background_migration, :failed, created_at: 99.days.ago) }
+
+ let_it_be(:old_batched_job) { create(:batched_background_migration_job, :failed, batched_migration: old_migration) }
+
+ it_behaves_like 'a correct instrumented metric value', { time_frame: '7d' }
+end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb
index eee5396bdbf..0deb586d488 100644
--- a/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb
@@ -165,7 +165,7 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitie
end
context 'with has_failures: true' do
- before(:all) do
+ before_all do
create_list(:bulk_import_entity, 3, :project_entity, :finished, created_at: 3.weeks.ago, has_failures: true)
create_list(:bulk_import_entity, 2, :project_entity, :finished, created_at: 2.months.ago, has_failures: true)
create_list(:bulk_import_entity, 3, :group_entity, :finished, created_at: 3.weeks.ago, has_failures: true)
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_deployments_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_deployments_metric_spec.rb
index 538be7bbdc4..7fd5b135a4a 100644
--- a/spec/lib/gitlab/usage/metrics/instrumentations/count_deployments_metric_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_deployments_metric_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountDeploymentsMetric, feature_category: :service_ping do
using RSpec::Parameterized::TableSyntax
- before(:all) do
+ before_all do
env = create(:environment)
[3, 60].each do |n|
deployment_options = { created_at: n.days.ago, project: env.project, environment: env }
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric_spec.rb
deleted file mode 100644
index 35e5d7f2796..00000000000
--- a/spec/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric_spec.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Usage::Metrics::Instrumentations::WorkItemsActivityAggregatedMetric do
- let(:metric_definition) do
- {
- data_source: 'redis_hll',
- time_frame: time_frame,
- options: {
- aggregate: {
- operator: 'OR'
- },
- events: %w[
- users_creating_work_items
- users_updating_work_item_title
- users_updating_work_item_dates
- users_updating_work_item_labels
- users_updating_work_item_milestone
- users_updating_work_item_iteration
- ]
- }
- }
- end
-
- around do |example|
- freeze_time { example.run }
- end
-
- where(:time_frame) { [['28d'], ['7d']] }
-
- with_them do
- describe '#available?' do
- it 'returns false without track_work_items_activity feature' do
- stub_feature_flags(track_work_items_activity: false)
-
- expect(described_class.new(metric_definition).available?).to eq(false)
- end
-
- it 'returns true with track_work_items_activity feature' do
- stub_feature_flags(track_work_items_activity: true)
-
- expect(described_class.new(metric_definition).available?).to eq(true)
- end
- end
-
- describe '#value', :clean_gitlab_redis_shared_state do
- let(:counter) { Gitlab::UsageDataCounters::HLLRedisCounter }
- let(:author1_id) { 1 }
- let(:author2_id) { 2 }
- let(:event_time) { 1.week.ago }
-
- before do
- counter.track_event(:users_creating_work_items, values: author1_id, time: event_time)
- end
-
- it 'has correct value after events are tracked', :aggregate_failures do
- expect do
- counter.track_event(:users_updating_work_item_title, values: author1_id, time: event_time)
- counter.track_event(:users_updating_work_item_dates, values: author1_id, time: event_time)
- counter.track_event(:users_updating_work_item_labels, values: author1_id, time: event_time)
- counter.track_event(:users_updating_work_item_milestone, values: author1_id, time: event_time)
- end.to not_change { described_class.new(metric_definition).value }
-
- expect do
- counter.track_event(:users_updating_work_item_iteration, values: author2_id, time: event_time)
- counter.track_event(:users_updating_weight_estimate, values: author1_id, time: event_time)
- end.to change { described_class.new(metric_definition).value }.from(1).to(2)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb b/spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb
deleted file mode 100644
index 9dba64ff59f..00000000000
--- a/spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Usage::Metrics::NameSuggestion do
- describe '#for' do
- shared_examples 'name suggestion' do
- it 'return correct name' do
- expect(described_class.for(operation, relation: relation, column: column)).to match name_suggestion
- end
- end
-
- context 'for count with nil column' do
- it_behaves_like 'name suggestion' do
- let(:operation) { :count }
- let(:relation) { Board }
- let(:column) { nil }
- let(:name_suggestion) { /count_boards/ }
- end
- end
-
- context 'for count with column :id' do
- it_behaves_like 'name suggestion' do
- let(:operation) { :count }
- let(:relation) { Board }
- let(:column) { :id }
- let(:name_suggestion) { /count_boards/ }
- end
- end
-
- context 'for count distinct with column defined metrics' do
- it_behaves_like 'name suggestion' do
- let(:operation) { :distinct_count }
- let(:relation) { ZoomMeeting }
- let(:column) { :issue_id }
- let(:name_suggestion) { /count_distinct_issue_id_from_zoom_meetings/ }
- end
- end
-
- context 'joined relations' do
- context 'counted attribute comes from source relation' do
- it_behaves_like 'name suggestion' do
- # corresponding metric is collected with count(Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id)
- let(:operation) { :count }
- let(:relation) { Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot) }
- let(:column) { nil }
- let(:name_suggestion) { /count_<adjective describing: '\(issues\.author_id != \d+\)'>_issues_<with>_alert_management_alerts/ }
- end
- end
- end
-
- context 'strips off time period constraint' do
- it_behaves_like 'name suggestion' do
- # corresponding metric is collected with distinct_count(::Clusters::Cluster.aws_installed.enabled.where(time_period), :user_id)
- let(:operation) { :distinct_count }
- let(:relation) { ::Clusters::Cluster.aws_installed.enabled.where(created_at: 30.days.ago..2.days.ago ) }
- let(:column) { :user_id }
- let(:constraints) { /<adjective describing: '\(clusters.provider_type = \d+ AND \(cluster_providers_aws\.status IN \(\d+\)\) AND clusters\.enabled = TRUE\)'>/ }
- let(:name_suggestion) { /count_distinct_user_id_from_#{constraints}_clusters_<with>_#{constraints}_cluster_providers_aws/ }
- end
- end
-
- context 'for sum metrics' do
- it_behaves_like 'name suggestion' do
- # corresponding metric is collected with sum(JiraImportState.finished, :imported_issues_count)
- let(:operation) { :sum }
- let(:relation) { JiraImportState.finished }
- let(:column) { :imported_issues_count }
- let(:name_suggestion) { /sum_imported_issues_count_from_<adjective describing: '\(jira_imports\.status = \d+\)'>_jira_imports/ }
- end
- end
-
- context 'for average metrics' do
- it_behaves_like 'name suggestion' do
- # corresponding metric is collected with average(Ci::Pipeline, :duration)
- let(:operation) { :average }
- let(:relation) { Ci::Pipeline }
- let(:column) { :duration }
- let(:name_suggestion) { /average_duration_from_ci_pipelines/ }
- end
- end
-
- context 'for redis metrics' do
- it_behaves_like 'name suggestion' do
- let(:operation) { :redis }
- let(:column) { nil }
- let(:relation) { nil }
- let(:name_suggestion) { /<please fill metric name, suggested format is: {subject}_{verb}{ing|ed}_{object} eg: users_creating_epics or merge_requests_viewed_in_single_file_mode>/ }
- end
- end
-
- context 'for alt_usage_data metrics' do
- it_behaves_like 'name suggestion' do
- # corresponding metric is collected with alt_usage_data(fallback: nil) { operating_system }
- let(:operation) { :alt }
- let(:column) { nil }
- let(:relation) { nil }
- let(:name_suggestion) { /<please fill metric name>/ }
- end
- end
-
- context 'for metrics with `having` keyword' do
- it_behaves_like 'name suggestion' do
- let(:operation) { :count }
- let(:relation) { Issue.with_alert_management_alerts.having('COUNT(alert_management_alerts) > 1').group(:id) }
-
- let(:column) { nil }
- let(:constraints) { /<adjective describing: '\(\(COUNT\(alert_management_alerts\) > 1\)\)'>/ }
- let(:name_suggestion) { /count_#{constraints}_issues_<with>_alert_management_alerts/ }
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb b/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
deleted file mode 100644
index 884d73a70f3..00000000000
--- a/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator, feature_category: :service_ping do
- include UsageDataHelpers
-
- before do
- stub_usage_data_connections
- end
-
- describe '#generate' do
- shared_examples 'name suggestion' do
- it 'return correct name' do
- expect(described_class.generate(key_path)).to match name_suggestion
- end
- end
-
- describe '#add_metric' do
- let(:metric) { 'CountIssuesMetric' }
-
- it 'computes the suggested name for given metric' do
- expect(described_class.add_metric(metric)).to eq('count_issues')
- end
- end
-
- context 'for count with default column metrics' do
- it_behaves_like 'name suggestion' do
- # corresponding metric is collected with count(Board)
- let(:key_path) { 'counts.issues' }
- let(:name_suggestion) { /count_issues/ }
- end
- end
-
- context 'for count distinct with column defined metrics' do
- it_behaves_like 'name suggestion' do
- # corresponding metric is collected with distinct_count(ZoomMeeting, :issue_id)
- let(:key_path) { 'counts.issues_using_zoom_quick_actions' }
- let(:name_suggestion) { /count_distinct_issue_id_from_zoom_meetings/ }
- end
- end
-
- context 'joined relations' do
- context 'counted attribute comes from source relation' do
- it_behaves_like 'name suggestion' do
- # corresponding metric is collected with distinct_count(Release.with_milestones, :author_id)
- let(:key_path) { 'usage_activity_by_stage.release.releases_with_milestones' }
- let(:name_suggestion) { /count_distinct_author_id_from_releases_<with>_milestone_releases/ }
- end
- end
- end
-
- context 'strips off time period constraint' do
- it_behaves_like 'name suggestion' do
- # corresponding metric is collected with distinct_count(::Clusters::Cluster.aws_installed.enabled.where(time_period), :user_id)
- let(:key_path) { 'usage_activity_by_stage_monthly.configure.clusters_platforms_eks' }
- let(:constraints) { /<adjective describing: '\(clusters.provider_type = \d+ AND \(cluster_providers_aws\.status IN \(\d+\)\) AND clusters\.enabled = TRUE\)'>/ }
- let(:name_suggestion) { /count_distinct_user_id_from_#{constraints}_clusters_<with>_#{constraints}_cluster_providers_aws/ }
- end
- end
-
- context 'for sum metrics' do
- it_behaves_like 'name suggestion' do
- # corresponding metric is collected with sum(JiraImportState.finished, :imported_issues_count)
- let(:key_path) { 'counts.jira_imports_total_imported_issues_count' }
- let(:name_suggestion) { /sum_imported_issues_count_from_<adjective describing: '\(jira_imports\.status = \d+\)'>_jira_imports/ }
- end
- end
-
- context 'for add metrics' do
- before do
- pending 'https://gitlab.com/gitlab-org/gitlab/-/issues/414887'
- end
-
- it_behaves_like 'name suggestion' do
- # corresponding metric is collected with add(data[:personal_snippets], data[:project_snippets])
- let(:key_path) { 'counts.snippets' }
- let(:name_suggestion) { /add_count_<adjective describing: '\(snippets\.type = 'PersonalSnippet'\)'>_snippets_and_count_<adjective describing: '\(snippets\.type = 'ProjectSnippet'\)'>_snippets/ }
- end
- end
-
- context 'for redis metrics', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/399421' do
- it_behaves_like 'name suggestion' do
- let(:key_path) { 'usage_activity_by_stage_monthly.create.merge_requests_users' }
- let(:name_suggestion) { /<please fill metric name, suggested format is: {subject}_{verb}{ing|ed}_{object} eg: users_creating_epics or merge_requests_viewed_in_single_file_mode>/ }
- end
- end
-
- context 'for alt_usage_data metrics' do
- it_behaves_like 'name suggestion' do
- # corresponding metric is collected with alt_usage_data { ApplicationRecord.database.version }
- let(:key_path) { 'database.version' }
- let(:name_suggestion) { /<please fill metric name>/ }
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/having_constraints_spec.rb b/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/having_constraints_spec.rb
deleted file mode 100644
index 492acf2a902..00000000000
--- a/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/having_constraints_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::RelationParsers::HavingConstraints do
- describe '#accept' do
- let(:connection) { ApplicationRecord.connection }
- let(:collector) { Arel::Collectors::SubstituteBinds.new(connection, Arel::Collectors::SQLString.new) }
-
- it 'builds correct constraints description' do
- table = Arel::Table.new('records')
- havings = table[:attribute].sum.eq(6).and(table[:attribute].count.gt(5))
- arel = table.from.project(table['id'].count).having(havings).group(table[:attribute2])
- described_class.new(connection).accept(arel, collector)
-
- expect(collector.value).to eql '(SUM(records.attribute) = 6 AND COUNT(records.attribute) > 5)'
- end
- end
-end
diff --git a/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins_spec.rb b/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins_spec.rb
deleted file mode 100644
index 3e72d118ac6..00000000000
--- a/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::RelationParsers::Joins do
- describe '#accept' do
- let(:collector) do
- Arel::Collectors::SubstituteBinds.new(ApplicationRecord.connection, Arel::Collectors::SQLString.new)
- end
-
- context 'with join added via string' do
- it 'collects join parts' do
- arel = Issue.joins('LEFT JOIN projects ON projects.id = issue.project_id')
-
- arel = arel.arel
- result = described_class.new(ApplicationRecord.connection).accept(arel)
-
- expect(result).to match_array [{ source: "projects", constraints: "projects.id = issue.project_id" }]
- end
- end
-
- context 'with join added via arel node' do
- it 'collects join parts' do
- source_table = Arel::Table.new('records')
- joined_table = Arel::Table.new('joins')
- second_level_joined_table = Arel::Table.new('second_level_joins')
-
- arel = source_table
- .from
- .project(source_table['id'].count)
- .join(joined_table, Arel::Nodes::OuterJoin)
- .on(source_table[:id].eq(joined_table[:records_id]))
- .join(second_level_joined_table, Arel::Nodes::OuterJoin)
- .on(joined_table[:id].eq(second_level_joined_table[:joins_id]))
-
- result = described_class.new(ApplicationRecord.connection).accept(arel)
-
- expect(result).to match_array [
- { source: "joins", constraints: "records.id = joins.records_id" },
- { source: "second_level_joins", constraints: "joins.id = second_level_joins.joins_id" }
- ]
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/where_constraints_spec.rb b/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/where_constraints_spec.rb
deleted file mode 100644
index 42a776478a4..00000000000
--- a/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/where_constraints_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::RelationParsers::WhereConstraints do
- describe '#accept' do
- let(:connection) { ApplicationRecord.connection }
- let(:collector) { Arel::Collectors::SubstituteBinds.new(connection, Arel::Collectors::SQLString.new) }
-
- it 'builds correct constraints description' do
- table = Arel::Table.new('records')
- arel = table.from.project(table['id'].count).where(table[:attribute].eq(true).and(table[:some_value].gt(5)))
- described_class.new(connection).accept(arel, collector)
-
- expect(collector.value).to eql '(records.attribute = true AND records.some_value > 5)'
- 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 19236cdbba0..ab92b59c845 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
@@ -3,41 +3,31 @@
require 'spec_helper'
RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_redis_shared_state do
- let(:user1) { build(:user, id: 1) }
+ let(:user) { build(:user, id: 1) }
let(:user2) { build(:user, id: 2) }
let(:user3) { build(:user, id: 3) }
let(:project) { build(:project) }
+ let(:namespace) { project.namespace }
let(:time) { Time.zone.now }
shared_examples 'tracks and counts action' do
+ subject { track_action(author: user, project: project) }
+
before do
stub_application_setting(usage_ping_enabled: true)
end
specify do
aggregate_failures do
- expect(track_action(author: user1, project: project)).to be_truthy
+ expect(track_action(author: user, project: project)).to be_truthy
expect(track_action(author: user2, project: project)).to be_truthy
- expect(track_action(author: user3, time: time.end_of_week - 3.days, project: project)).to be_truthy
+ expect(track_action(author: user3, project: project)).to be_truthy
expect(count_unique(date_from: time.beginning_of_week, date_to: 1.week.from_now)).to eq(3)
end
end
- it 'track snowplow event' do
- track_action(author: user1, project: project)
-
- expect_snowplow_event(
- category: described_class.name,
- action: 'ide_edit',
- label: 'usage_activity_by_stage_monthly.create.action_monthly_active_users_ide_edit',
- namespace: project.namespace,
- property: event_name,
- project: project,
- user: user1,
- context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_h]
- )
- end
+ it_behaves_like 'internal event tracking'
it 'does not track edit actions if author is not present' do
expect(track_action(author: nil, project: project)).to be_nil
@@ -45,7 +35,7 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
end
context 'for web IDE edit actions' do
- let(:event_name) { described_class::EDIT_BY_WEB_IDE }
+ let(:action) { described_class::EDIT_BY_WEB_IDE }
it_behaves_like 'tracks and counts action' do
def track_action(params)
@@ -59,7 +49,7 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
end
context 'for SFE edit actions' do
- let(:event_name) { described_class::EDIT_BY_SFE }
+ let(:action) { described_class::EDIT_BY_SFE }
it_behaves_like 'tracks and counts action' do
def track_action(params)
@@ -73,7 +63,7 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
end
context 'for snippet editor edit actions' do
- let(:event_name) { described_class::EDIT_BY_SNIPPET_EDITOR }
+ let(:action) { described_class::EDIT_BY_SNIPPET_EDITOR }
it_behaves_like 'tracks and counts action' do
def track_action(params)
diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
index fc1d66d1d62..7bef14d5f7a 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
@@ -8,9 +8,6 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
let(:entity3) { '34rfjuuy-ce56-sa35-ds34-dfer567dfrf2' }
let(:entity4) { '8b9a2671-2abf-4bec-a682-22f6a8f7bf31' }
- let(:default_context) { 'default' }
- let(:invalid_context) { 'invalid' }
-
around do |example|
# We need to freeze to a reference time
# because visits are grouped by the week number in the year
@@ -27,62 +24,39 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
describe '.known_events' do
let(:ce_event) { { "name" => "ce_event" } }
-
- context 'with use_metric_definitions_for_events_list disabled' do
- let(:ce_temp_dir) { Dir.mktmpdir }
- let(:ce_temp_file) { Tempfile.new(%w[common .yml], ce_temp_dir) }
-
- before do
- stub_feature_flags(use_metric_definitions_for_events_list: false)
- 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
-
- after do
- ce_temp_file.unlink
- FileUtils.remove_entry(ce_temp_dir) if Dir.exist?(ce_temp_dir)
- end
-
- it 'returns ce events' do
- expect(described_class.known_events).to include(ce_event)
- end
+ let(:removed_ce_event) { { "name" => "removed_ce_event" } }
+ let(:metric_definition) do
+ Gitlab::Usage::MetricDefinition.new('ce_metric',
+ {
+ key_path: 'ce_metric_weekly',
+ status: 'active',
+ options: {
+ events: [ce_event['name']]
+ }
+ })
end
- context 'with use_metric_definitions_for_events_list enabled' do
- let(:removed_ce_event) { { "name" => "removed_ce_event" } }
- let(:metric_definition) do
- Gitlab::Usage::MetricDefinition.new('ce_metric',
- {
- key_path: 'ce_metric_weekly',
- status: 'active',
- options: {
- events: [ce_event['name']]
- }
- })
- end
-
- let(:removed_metric_definition) do
- Gitlab::Usage::MetricDefinition.new('removed_ce_metric',
- {
- key_path: 'removed_ce_metric_weekly',
- status: 'removed',
- options: {
- events: [removed_ce_event['name']]
- }
- })
- end
+ let(:removed_metric_definition) do
+ Gitlab::Usage::MetricDefinition.new('removed_ce_metric',
+ {
+ key_path: 'removed_ce_metric_weekly',
+ status: 'removed',
+ options: {
+ events: [removed_ce_event['name']]
+ }
+ })
+ end
- before do
- allow(Gitlab::Usage::MetricDefinition).to receive(:all).and_return([metric_definition, removed_metric_definition])
- end
+ before do
+ allow(Gitlab::Usage::MetricDefinition).to receive(:all).and_return([metric_definition, removed_metric_definition])
+ end
- it 'returns ce events' do
- expect(described_class.known_events).to include(ce_event)
- end
+ it 'returns ce events' do
+ expect(described_class.known_events).to include(ce_event)
+ end
- it 'does not return removed events' do
- expect(described_class.known_events).not_to include(removed_ce_event)
- end
+ it 'does not return removed events' do
+ expect(described_class.known_events).not_to include(removed_ce_event)
end
end
@@ -96,7 +70,6 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
let(:no_slot) { 'no_slot' }
let(:different_aggregation) { 'different_aggregation' }
let(:custom_daily_event) { 'g_analytics_custom' }
- let(:context_event) { 'context_event' }
let(:global_category) { 'global' }
let(:compliance_category) { 'compliance' }
@@ -111,8 +84,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
{ name: category_productivity_event },
{ name: compliance_slot_event },
{ name: no_slot },
- { name: different_aggregation },
- { name: context_event }
+ { name: different_aggregation }
].map(&:with_indifferent_access)
end
@@ -214,43 +186,6 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
end
- describe '.track_event_in_context' do
- context 'with valid contex' do
- it 'increments context event counter' do
- expect(Gitlab::Redis::HLL).to receive(:add) do |kwargs|
- expect(kwargs[:key]).to match(/^#{default_context}_.*/)
- end
-
- described_class.track_event_in_context(context_event, values: entity1, context: default_context)
- end
-
- it 'tracks events with multiple values' do
- values = [entity1, entity2]
- expect(Gitlab::Redis::HLL).to receive(:add).with(key: /g_analytics_contribution/,
- value: values,
- expiry: described_class::KEY_EXPIRY_LENGTH)
-
- described_class.track_event_in_context(:g_analytics_contribution, values: values, context: default_context)
- end
- end
-
- context 'with empty context' do
- it 'does not increment a counter' do
- expect(Gitlab::Redis::HLL).not_to receive(:add)
-
- described_class.track_event_in_context(context_event, values: entity1, context: '')
- end
- end
-
- context 'when sending invalid context' do
- it 'does not increment a counter' do
- expect(Gitlab::Redis::HLL).not_to receive(:add)
-
- described_class.track_event_in_context(context_event, values: entity1, context: invalid_context)
- end
- end
- end
-
describe '.unique_events' do
before do
# events in current week, should not be counted as week is not complete
@@ -360,48 +295,6 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
end
- describe 'context level tracking' do
- using RSpec::Parameterized::TableSyntax
-
- let(:known_events) do
- [
- { name: 'event_name_1' },
- { name: 'event_name_2' },
- { name: 'event_name_3' }
- ].map(&:with_indifferent_access)
- end
-
- before do
- allow(described_class).to receive(:known_events).and_return(known_events)
- allow(described_class).to receive(:categories).and_return(%w(category1 category2))
-
- described_class.track_event_in_context('event_name_1', values: [entity1, entity3], context: default_context, time: 2.days.ago)
- described_class.track_event_in_context('event_name_1', values: entity3, context: default_context, time: 2.days.ago)
- described_class.track_event_in_context('event_name_1', values: entity3, context: invalid_context, time: 2.days.ago)
- described_class.track_event_in_context('event_name_2', values: [entity1, entity2], context: '', time: 2.weeks.ago)
- end
-
- subject(:unique_events) { described_class.unique_events(event_names: event_names, start_date: 4.weeks.ago, end_date: Date.current, context: context) }
-
- context 'with correct arguments' do
- where(:event_names, :context, :value) do
- ['event_name_1'] | 'default' | 2
- ['event_name_1'] | '' | 0
- ['event_name_2'] | '' | 0
- end
-
- with_them do
- it { is_expected.to eq value }
- end
- end
-
- context 'with invalid context' do
- it 'raise error' do
- expect { described_class.unique_events(event_names: 'event_name_1', start_date: 4.weeks.ago, end_date: Date.current, context: invalid_context) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::InvalidContext)
- end
- end
- end
-
describe '.calculate_events_union' do
let(:time_range) { { start_date: 7.days.ago, end_date: DateTime.current } }
let(:known_events) do
diff --git a/spec/lib/gitlab/usage_data_counters/neovim_plugin_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/neovim_plugin_activity_unique_counter_spec.rb
new file mode 100644
index 00000000000..274a3ffc843
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/neovim_plugin_activity_unique_counter_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::NeovimPluginActivityUniqueCounter, :clean_gitlab_redis_shared_state, feature_category: :editor_extensions do
+ let(:user1) { build(:user, id: 1) }
+ let(:user2) { build(:user, id: 2) }
+ let(:time) { Time.current }
+ let(:action) { described_class::NEOVIM_PLUGIN_API_REQUEST_ACTION }
+ let(:user_agent_string) do
+ 'code-completions-language-server-experiment (Neovim:0.9.0; gitlab.vim (v0.1.0); arch:amd64; os:darwin)'
+ end
+
+ let(:user_agent) { { user_agent: user_agent_string } }
+
+ context 'when tracking a neovim plugin api request' do
+ it_behaves_like 'a request from an extension'
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/visual_studio_extension_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/visual_studio_extension_activity_unique_counter_spec.rb
new file mode 100644
index 00000000000..57cf173f793
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/visual_studio_extension_activity_unique_counter_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::VisualStudioExtensionActivityUniqueCounter, :clean_gitlab_redis_shared_state, feature_category: :editor_extensions do
+ let(:user1) { build(:user, id: 1) }
+ let(:user2) { build(:user, id: 2) }
+ let(:time) { Time.current }
+ let(:action) { described_class::VISUAL_STUDIO_EXTENSION_API_REQUEST_ACTION }
+ let(:user_agent_string) do
+ 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)'
+ end
+
+ let(:user_agent) { { user_agent: user_agent_string } }
+
+ context 'when tracking a visual studio api request' do
+ it_behaves_like 'a request from an extension'
+ end
+end
diff --git a/spec/lib/gitlab/with_request_store_spec.rb b/spec/lib/gitlab/with_request_store_spec.rb
deleted file mode 100644
index 353ad02fbd8..00000000000
--- a/spec/lib/gitlab/with_request_store_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require 'request_store'
-
-RSpec.describe Gitlab::WithRequestStore do
- let(:fake_class) { Class.new { include Gitlab::WithRequestStore } }
-
- subject(:object) { fake_class.new }
-
- describe "#with_request_store" do
- it 'starts a request store and yields control' do
- expect(RequestStore).to receive(:begin!).ordered
- expect(RequestStore).to receive(:end!).ordered
- expect(RequestStore).to receive(:clear!).ordered
-
- expect { |b| object.with_request_store(&b) }.to yield_control
- end
-
- it 'only starts a request store once when nested' do
- expect(RequestStore).to receive(:begin!).ordered.once.and_call_original
- expect(RequestStore).to receive(:end!).ordered.once.and_call_original
- expect(RequestStore).to receive(:clear!).ordered.once.and_call_original
-
- object.with_request_store do
- expect { |b| object.with_request_store(&b) }.to yield_control
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/x509/signature_spec.rb b/spec/lib/gitlab/x509/signature_spec.rb
index d119a4e2b9d..e0823aa8153 100644
--- a/spec/lib/gitlab/x509/signature_spec.rb
+++ b/spec/lib/gitlab/x509/signature_spec.rb
@@ -36,6 +36,7 @@ RSpec.describe Gitlab::X509::Signature do
it 'returns a verified signature if email does match' do
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
+
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_truthy
expect(signature.verification_status).to eq(:verified)
@@ -55,6 +56,27 @@ RSpec.describe Gitlab::X509::Signature do
expect(signature.verification_status).to eq(:verified)
end
+ context 'when the certificate contains multiple emails' do
+ before do
+ allow_any_instance_of(described_class).to receive(:get_certificate_extension).and_call_original
+
+ allow_any_instance_of(described_class).to receive(:get_certificate_extension)
+ .with('subjectAltName')
+ .and_return("email:gitlab2@example.com, othername:<unsupported>, email:#{X509Helpers::User1.certificate_email}")
+ end
+
+ context 'and the email matches one of them' do
+ it 'returns a verified signature' do
+ expect(signature.x509_certificate).to have_attributes(certificate_attributes.except(:email, :emails))
+ expect(signature.x509_certificate.email).to eq('gitlab2@example.com')
+ expect(signature.x509_certificate.emails).to contain_exactly('gitlab2@example.com', X509Helpers::User1.certificate_email)
+ expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
+ expect(signature.verified_signature).to be_truthy
+ expect(signature.verification_status).to eq(:verified)
+ end
+ end
+ end
+
context "if the email matches but isn't confirmed" do
let!(:user) { create(:user, :unconfirmed, email: X509Helpers::User1.certificate_email) }
@@ -106,6 +128,7 @@ RSpec.describe Gitlab::X509::Signature do
subject_key_identifier: X509Helpers::User1.certificate_subject_key_identifier,
subject: X509Helpers::User1.certificate_subject,
email: X509Helpers::User1.certificate_email,
+ emails: [X509Helpers::User1.certificate_email],
serial_number: X509Helpers::User1.certificate_serial
}
end
@@ -248,15 +271,31 @@ RSpec.describe Gitlab::X509::Signature do
.and_return("email:gitlab@example.com, othername:<unsupported>")
end
- it 'extracts email' do
- signature = described_class.new(
+ let(:signature) do
+ described_class.new(
X509Helpers::User1.signed_commit_signature,
X509Helpers::User1.signed_commit_base_data,
'gitlab@example.com',
X509Helpers::User1.signed_commit_time
)
+ end
+ it 'extracts email' do
expect(signature.x509_certificate.email).to eq("gitlab@example.com")
+ expect(signature.x509_certificate.emails).to contain_exactly("gitlab@example.com")
+ end
+
+ context 'when there are multiple emails' do
+ before do
+ allow_any_instance_of(described_class).to receive(:get_certificate_extension)
+ .with('subjectAltName')
+ .and_return("email:gitlab@example.com, othername:<unsupported>, email:gitlab2@example.com")
+ end
+
+ it 'extracts all the emails' do
+ expect(signature.x509_certificate.email).to eq("gitlab@example.com")
+ expect(signature.x509_certificate.emails).to contain_exactly("gitlab@example.com", "gitlab2@example.com")
+ end
end
end
@@ -311,6 +350,7 @@ RSpec.describe Gitlab::X509::Signature do
subject_key_identifier: X509Helpers::User1.tag_certificate_subject_key_identifier,
subject: X509Helpers::User1.certificate_subject,
email: X509Helpers::User1.certificate_email,
+ emails: [X509Helpers::User1.certificate_email],
serial_number: X509Helpers::User1.tag_certificate_serial
}
end