summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab')
-rw-r--r--spec/lib/gitlab/alert_management/alert_status_counts_spec.rb1
-rw-r--r--spec/lib/gitlab/alert_management/payload/base_spec.rb1
-rw-r--r--spec/lib/gitlab/alert_management/payload/generic_spec.rb1
-rw-r--r--spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb2
-rw-r--r--spec/lib/gitlab/alert_management/payload/prometheus_spec.rb1
-rw-r--r--spec/lib/gitlab/alert_management/payload_spec.rb1
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb1
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb44
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/unique_visits_spec.rb22
-rw-r--r--spec/lib/gitlab/application_context_spec.rb16
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb6
-rw-r--r--spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/otp/strategies/devise_spec.rb1
-rw-r--r--spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb1
-rw-r--r--spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb1
-rw-r--r--spec/lib/gitlab/auth_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb8
-rw-r--r--spec/lib/gitlab/background_migration/migrate_pages_to_zip_storage_spec.rb43
-rw-r--r--spec/lib/gitlab/bullet/exclusions_spec.rb155
-rw-r--r--spec/lib/gitlab/bullet_spec.rb51
-rw-r--r--spec/lib/gitlab/changelog/config_spec.rb4
-rw-r--r--spec/lib/gitlab/checks/project_created_spec.rb1
-rw-r--r--spec/lib/gitlab/checks/project_moved_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/ansi2json/style_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/bridge_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/jobs_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb35
-rw-r--r--spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/lint_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb61
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb13
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/pipeline/process_spec.rb31
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb159
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb201
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb139
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb109
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/reports/test_failure_history_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/runner_instructions_spec.rb116
-rw-r--r--spec/lib/gitlab/ci/status/build/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/composite_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/variables/helpers_spec.rb46
-rw-r--r--spec/lib/gitlab/ci/yaml_processor/result_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb139
-rw-r--r--spec/lib/gitlab/composer/version_index_spec.rb5
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb6
-rw-r--r--spec/lib/gitlab/crypto_helper_spec.rb58
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb3
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb1
-rw-r--r--spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb31
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb198
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_spec.rb51
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb94
-rw-r--r--spec/lib/gitlab/database/background_migration/scheduler_spec.rb182
-rw-r--r--spec/lib/gitlab/database/batch_count_spec.rb76
-rw-r--r--spec/lib/gitlab/database/loose_index_scan_distinct_count_spec.rb71
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb241
-rw-r--r--spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb28
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb66
-rw-r--r--spec/lib/gitlab/database/pg_class_spec.rb37
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb3
-rw-r--r--spec/lib/gitlab/database/unidirectional_copy_trigger_spec.rb191
-rw-r--r--spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb2
-rw-r--r--spec/lib/gitlab/database_spec.rb4
-rw-r--r--spec/lib/gitlab/diff/char_diff_spec.rb6
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb26
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb20
-rw-r--r--spec/lib/gitlab/diff/inline_diff_spec.rb74
-rw-r--r--spec/lib/gitlab/diff/line_spec.rb35
-rw-r--r--spec/lib/gitlab/diff/lines_unfolder_spec.rb18
-rw-r--r--spec/lib/gitlab/diff/suggestions_parser_spec.rb3
-rw-r--r--spec/lib/gitlab/downtime_check/message_spec.rb41
-rw-r--r--spec/lib/gitlab/downtime_check_spec.rb116
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb8
-rw-r--r--spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb93
-rw-r--r--spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb90
-rw-r--r--spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb96
-rw-r--r--spec/lib/gitlab/error_tracking_spec.rb108
-rw-r--r--spec/lib/gitlab/experimentation_spec.rb1
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb86
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb40
-rw-r--r--spec/lib/gitlab/git/tag_spec.rb13
-rw-r--r--spec/lib/gitlab/gitaly_client/blob_service_spec.rb43
-rw-r--r--spec/lib/gitlab/gitaly_client/call_spec.rb13
-rw-r--r--spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb4
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb26
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb17
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb1
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb1
-rw-r--r--spec/lib/gitlab/github_import/milestone_finder_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb253
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb80
-rw-r--r--spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb64
-rw-r--r--spec/lib/gitlab/graphql/batch_key_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/deprecation_spec.rb213
-rw-r--r--spec/lib/gitlab/graphql/docs/renderer_spec.rb80
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/markdown_field_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/negatable_arguments_spec.rb45
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb47
-rw-r--r--spec/lib/gitlab/highlight_spec.rb15
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/hook_data/release_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/hook_data/user_builder_spec.rb90
-rw-r--r--spec/lib/gitlab/http_connection_adapter_spec.rb125
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml5
-rw-r--r--spec/lib/gitlab/import_export/design_repo_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/project/export_task_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/tree_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/repo_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/instrumentation_helper_spec.rb47
-rw-r--r--spec/lib/gitlab/jira_import/base_importer_spec.rb1
-rw-r--r--spec/lib/gitlab/jira_import/handle_labels_service_spec.rb1
-rw-r--r--spec/lib/gitlab/jira_import_spec.rb1
-rw-r--r--spec/lib/gitlab/json_spec.rb60
-rw-r--r--spec/lib/gitlab/legacy_github_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb26
-rw-r--r--spec/lib/gitlab/markdown_cache/redis/extension_spec.rb3
-rw-r--r--spec/lib/gitlab/markdown_cache/redis/store_spec.rb4
-rw-r--r--spec/lib/gitlab/marker_range_spec.rb33
-rw-r--r--spec/lib/gitlab/metrics/background_transaction_spec.rb53
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb171
-rw-r--r--spec/lib/gitlab/metrics/subscribers/external_http_spec.rb57
-rw-r--r--spec/lib/gitlab/middleware/rack_multipart_tempfile_factory_spec.rb94
-rw-r--r--spec/lib/gitlab/object_hierarchy_spec.rb97
-rw-r--r--spec/lib/gitlab/pages/settings_spec.rb42
-rw-r--r--spec/lib/gitlab/pages/stores/local_store_spec.rb25
-rw-r--r--spec/lib/gitlab/pages_transfer_spec.rb4
-rw-r--r--spec/lib/gitlab/pagination/keyset/order_spec.rb55
-rw-r--r--spec/lib/gitlab/pagination/offset_header_builder_with_controller_spec.rb54
-rw-r--r--spec/lib/gitlab/phabricator_import/cache/map_spec.rb1
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb1
-rw-r--r--spec/lib/gitlab/profiler_spec.rb9
-rw-r--r--spec/lib/gitlab/prometheus/adapter_spec.rb16
-rw-r--r--spec/lib/gitlab/query_limiting/transaction_spec.rb10
-rw-r--r--spec/lib/gitlab/query_limiting_spec.rb82
-rw-r--r--spec/lib/gitlab/quick_actions/command_definition_spec.rb8
-rw-r--r--spec/lib/gitlab/regex_spec.rb9
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb31
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb23
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb2
-rw-r--r--spec/lib/gitlab/search_context/builder_spec.rb29
-rw-r--r--spec/lib/gitlab/sidekiq_cluster/cli_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb84
-rw-r--r--spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb129
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb97
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb4
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb4
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb153
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb101
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb318
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware_spec.rb6
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb1
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb1
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb13
-rw-r--r--spec/lib/gitlab/slash_commands/run_spec.rb20
-rw-r--r--spec/lib/gitlab/snippet_search_results_spec.rb1
-rw-r--r--spec/lib/gitlab/sourcegraph_spec.rb1
-rw-r--r--spec/lib/gitlab/sql/cte_spec.rb20
-rw-r--r--spec/lib/gitlab/sql/recursive_cte_spec.rb13
-rw-r--r--spec/lib/gitlab/subscription_portal_spec.rb56
-rw-r--r--spec/lib/gitlab/template/finders/repo_template_finders_spec.rb1
-rw-r--r--spec/lib/gitlab/tracking/destinations/snowplow_spec.rb23
-rw-r--r--spec/lib/gitlab/tracking/standard_context_spec.rb12
-rw-r--r--spec/lib/gitlab/tracking_spec.rb32
-rw-r--r--spec/lib/gitlab/tree_summary_spec.rb1
-rw-r--r--spec/lib/gitlab/untrusted_regexp_spec.rb18
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/usage/metric_definition_spec.rb85
-rw-r--r--spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb1
-rw-r--r--spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb51
-rw-r--r--spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins_spec.rb40
-rw-r--r--spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb95
-rw-r--r--spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb36
-rw-r--r--spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb18
-rw-r--r--spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb20
-rw-r--r--spec/lib/gitlab/usage_data_non_sql_metrics_spec.rb55
-rw-r--r--spec/lib/gitlab/usage_data_queries_spec.rb32
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb42
-rw-r--r--spec/lib/gitlab/utils/lazy_attributes_spec.rb6
-rw-r--r--spec/lib/gitlab/utils/usage_data_spec.rb19
-rw-r--r--spec/lib/gitlab/utils_spec.rb8
-rw-r--r--spec/lib/gitlab/web_ide/config/entry/global_spec.rb1
-rw-r--r--spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb5
-rw-r--r--spec/lib/gitlab/word_diff/chunk_collection_spec.rb23
-rw-r--r--spec/lib/gitlab/word_diff/parser_spec.rb40
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb1
217 files changed, 5289 insertions, 2488 deletions
diff --git a/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb b/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb
index fceda763717..1ed43145aa6 100644
--- a/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb
+++ b/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe Gitlab::AlertManagement::AlertStatusCounts do
let_it_be(:alert_resolved) { create(:alert_management_alert, :resolved, project: project) }
let_it_be(:alert_ignored) { create(:alert_management_alert, :ignored, project: project) }
let_it_be(:alert_triggered) { create(:alert_management_alert) }
+
let(:params) { {} }
describe '#execute' do
diff --git a/spec/lib/gitlab/alert_management/payload/base_spec.rb b/spec/lib/gitlab/alert_management/payload/base_spec.rb
index 0c26e94e596..e093b3587c2 100644
--- a/spec/lib/gitlab/alert_management/payload/base_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload/base_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::AlertManagement::Payload::Base do
let_it_be(:project) { create(:project) }
+
let(:raw_payload) { {} }
let(:payload_class) { described_class }
diff --git a/spec/lib/gitlab/alert_management/payload/generic_spec.rb b/spec/lib/gitlab/alert_management/payload/generic_spec.rb
index b0c238c62c8..59933f7459d 100644
--- a/spec/lib/gitlab/alert_management/payload/generic_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload/generic_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::AlertManagement::Payload::Generic do
let_it_be(:project) { build_stubbed(:project) }
+
let(:raw_payload) { {} }
let(:parsed_payload) { described_class.new(project: project, payload: raw_payload) }
diff --git a/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb b/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb
index 862b5b2bdc3..fa8afd47c53 100644
--- a/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb
@@ -4,6 +4,7 @@ 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) }
@@ -136,6 +137,7 @@ RSpec.describe Gitlab::AlertManagement::Payload::ManagedPrometheus do
context 'with sufficient fallback info' do
let_it_be(:environment) { create(:environment, project: project, name: 'production') }
+
let(:raw_payload) do
{
'labels' => {
diff --git a/spec/lib/gitlab/alert_management/payload/prometheus_spec.rb b/spec/lib/gitlab/alert_management/payload/prometheus_spec.rb
index f574f5ba6a3..6a4f35c01e3 100644
--- a/spec/lib/gitlab/alert_management/payload/prometheus_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload/prometheus_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::AlertManagement::Payload::Prometheus do
let_it_be(:project) { create(:project) }
+
let(:raw_payload) { {} }
let(:parsed_payload) { described_class.new(project: project, payload: raw_payload) }
diff --git a/spec/lib/gitlab/alert_management/payload_spec.rb b/spec/lib/gitlab/alert_management/payload_spec.rb
index 7c129a8a48e..efde7ed3772 100644
--- a/spec/lib/gitlab/alert_management/payload_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::AlertManagement::Payload do
describe '#parse' do
let_it_be(:project) { build_stubbed(:project) }
+
let(:payload) { {} }
context 'without a monitoring_tool specified by caller' do
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
index 80d3f82b404..0a333965f68 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
let_it_be(:mr1) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 3.months.ago) }
let_it_be(:mr2) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 1.month.ago) }
let_it_be(:user) { create(:user) }
+
let(:params) { { current_user: user } }
let(:records) do
stage = build(:cycle_analytics_project_stage, {
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
index c1ea000eb7b..14768025932 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Analytics::CycleAnalytics::Median do
let_it_be(:project) { create(:project, :repository) }
+
let(:query) { Project.joins(merge_requests: :metrics) }
let(:stage) 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 b8f9dde4291..ebc5ae2a632 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
@@ -7,16 +7,15 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
Timecop.freeze { example.run }
end
+ let(:params) { { from: 1.year.ago, current_user: user } }
+
let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:user) { create(:user) }
subject do
Gitlab::Analytics::CycleAnalytics::DataCollector.new(
stage: stage,
- params: {
- from: 1.year.ago,
- current_user: user
- }
+ params: params
).records_fetcher.serialized_records
end
@@ -34,6 +33,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
describe 'for issue based stage' do
let_it_be(:issue1) { create(:issue, project: project) }
let_it_be(:issue2) { create(:issue, project: project, confidential: true) }
+
let(:stage) do
build(:cycle_analytics_project_stage, {
start_event_identifier: :plan_stage_start,
@@ -130,4 +130,40 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
end
end
end
+
+ describe 'pagination' do
+ let_it_be(:issue1) { create(:issue, project: project) }
+ let_it_be(:issue2) { create(:issue, project: project) }
+ let_it_be(:issue3) { create(:issue, project: project) }
+
+ let(:stage) do
+ build(:cycle_analytics_project_stage, {
+ start_event_identifier: :plan_stage_start,
+ end_event_identifier: :issue_first_mentioned_in_commit,
+ project: project
+ })
+ end
+
+ 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)
+ end
+
+ before do
+ project.add_user(user, Gitlab::Access::DEVELOPER)
+
+ stub_const('Gitlab::Analytics::CycleAnalytics::RecordsFetcher::MAX_RECORDS', 2)
+ end
+
+ it 'limits the results' do
+ expect(subject.size).to eq(2)
+ end
+
+ it 'loads the record for the next page' do
+ params[:page] = 2
+
+ expect(subject.size).to eq(1)
+ end
+ end
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
index 52e9f2d9846..b6f9c8106c9 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::CodeStageStart do
other_merge_request = create(:merge_request, source_project: project, source_branch: 'a', target_branch: 'master')
- records = subject.apply_query_customization(MergeRequest.all).where('merge_requests_closing_issues.issue_id IS NOT NULL')
+ records = subject.apply_query_customization(MergeRequest.all).where.not('merge_requests_closing_issues.issue_id' => nil)
expect(records).to eq([merge_request])
expect(records).not_to include(other_merge_request)
end
diff --git a/spec/lib/gitlab/analytics/unique_visits_spec.rb b/spec/lib/gitlab/analytics/unique_visits_spec.rb
index 6ac58e13f4c..f4d5c0b1eca 100644
--- a/spec/lib/gitlab/analytics/unique_visits_spec.rb
+++ b/spec/lib/gitlab/analytics/unique_visits_spec.rb
@@ -24,18 +24,18 @@ RSpec.describe Gitlab::Analytics::UniqueVisits, :clean_gitlab_redis_shared_state
describe '#track_visit' do
it 'tracks the unique weekly visits for targets' do
- unique_visits.track_visit(visitor1_id, target1_id, 7.days.ago)
- unique_visits.track_visit(visitor1_id, target1_id, 7.days.ago)
- unique_visits.track_visit(visitor2_id, target1_id, 7.days.ago)
+ unique_visits.track_visit(target1_id, values: visitor1_id, time: 7.days.ago)
+ unique_visits.track_visit(target1_id, values: visitor1_id, time: 7.days.ago)
+ unique_visits.track_visit(target1_id, values: visitor2_id, time: 7.days.ago)
- unique_visits.track_visit(visitor2_id, target2_id, 7.days.ago)
- unique_visits.track_visit(visitor1_id, target2_id, 8.days.ago)
- unique_visits.track_visit(visitor1_id, target2_id, 15.days.ago)
+ unique_visits.track_visit(target2_id, values: visitor2_id, time: 7.days.ago)
+ unique_visits.track_visit(target2_id, values: visitor1_id, time: 8.days.ago)
+ unique_visits.track_visit(target2_id, values: visitor1_id, time: 15.days.ago)
- unique_visits.track_visit(visitor3_id, target4_id, 7.days.ago)
+ unique_visits.track_visit(target4_id, values: visitor3_id, time: 7.days.ago)
- unique_visits.track_visit(visitor3_id, target5_id, 15.days.ago)
- unique_visits.track_visit(visitor2_id, target5_id, 15.days.ago)
+ unique_visits.track_visit(target5_id, values: visitor3_id, time: 15.days.ago)
+ unique_visits.track_visit(target5_id, values: visitor2_id, time: 15.days.ago)
expect(unique_visits.unique_visits_for(targets: target1_id)).to eq(2)
expect(unique_visits.unique_visits_for(targets: target2_id)).to eq(1)
@@ -61,7 +61,7 @@ RSpec.describe Gitlab::Analytics::UniqueVisits, :clean_gitlab_redis_shared_state
end
it 'sets the keys in Redis to expire automatically after 12 weeks' do
- unique_visits.track_visit(visitor1_id, target1_id)
+ unique_visits.track_visit(target1_id, values: visitor1_id)
Gitlab::Redis::SharedState.with do |redis|
redis.scan_each(match: "{#{target1_id}}-*").each do |key|
@@ -74,7 +74,7 @@ RSpec.describe Gitlab::Analytics::UniqueVisits, :clean_gitlab_redis_shared_state
invalid_target_id = "x_invalid"
expect do
- unique_visits.track_visit(visitor1_id, invalid_target_id)
+ unique_visits.track_visit(invalid_target_id, values: visitor1_id)
end.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent)
end
end
diff --git a/spec/lib/gitlab/application_context_spec.rb b/spec/lib/gitlab/application_context_spec.rb
index 0fbbc67ef6a..c4fe2ebaba9 100644
--- a/spec/lib/gitlab/application_context_spec.rb
+++ b/spec/lib/gitlab/application_context_spec.rb
@@ -27,6 +27,20 @@ RSpec.describe Gitlab::ApplicationContext do
end
end
+ describe '.with_raw_context' do
+ it 'yields the block' do
+ expect { |b| described_class.with_raw_context({}, &b) }.to yield_control
+ end
+
+ it 'passes the attributes unaltered on to labkit' do
+ attrs = { foo: :bar }
+
+ expect(Labkit::Context).to receive(:with_context).with(attrs)
+
+ described_class.with_raw_context(attrs) {}
+ end
+ end
+
describe '.push' do
it 'passes the expected context on to labkit' do
fake_proc = duck_type(:call)
@@ -138,7 +152,7 @@ RSpec.describe Gitlab::ApplicationContext do
it 'does not cause queries' do
context = described_class.new(project: create(:project), namespace: create(:group, :nested), user: create(:user))
- expect { context.use { Labkit::Context.current.to_h } }.not_to exceed_query_limit(0)
+ expect { context.use { Gitlab::ApplicationContext.current } }.not_to exceed_query_limit(0)
end
end
end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 3eb015a5a22..f3799c58fed 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -83,7 +83,7 @@ module Gitlab
},
'fenced code with inline script' => {
input: '```mypre"><script>alert(3)</script>',
- output: "<div>\n<div>\n<pre class=\"code highlight js-syntax-highlight plaintext\" lang=\"plaintext\" v-pre=\"true\"><code><span id=\"LC1\" class=\"line\" lang=\"plaintext\">\"&gt;</span></code></pre>\n</div>\n</div>"
+ output: "<div>\n<div>\n<pre class=\"code highlight js-syntax-highlight language-plaintext\" lang=\"plaintext\" v-pre=\"true\"><code><span id=\"LC1\" class=\"line\" lang=\"plaintext\">\"&gt;</span></code></pre>\n</div>\n</div>"
}
}
@@ -353,7 +353,7 @@ module Gitlab
output = <<~HTML
<div>
<div>
- <pre class="code highlight js-syntax-highlight javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span></span></code></pre>
+ <pre class="code highlight js-syntax-highlight language-javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span></span></code></pre>
</div>
</div>
HTML
@@ -380,7 +380,7 @@ module Gitlab
<div>
<div>class.cpp</div>
<div>
- <pre class="code highlight js-syntax-highlight cpp" lang="cpp" v-pre="true"><code><span id="LC1" class="line" lang="cpp"><span class="cp">#include &lt;stdio.h&gt;</span></span>
+ <pre class="code highlight js-syntax-highlight language-cpp" lang="cpp" v-pre="true"><code><span id="LC1" class="line" lang="cpp"><span class="cp">#include &lt;stdio.h&gt;</span></span>
<span id="LC2" class="line" lang="cpp"></span>
<span id="LC3" class="line" lang="cpp"><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span></span>
<span id="LC4" class="line" lang="cpp"> <span class="n">std</span><span class="o">::</span><span class="n">cout</span><span class="o">&lt;&lt;</span><span class="s">"*"</span><span class="o">&lt;&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span></span>
diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
index 67ffdee0c4a..69068883096 100644
--- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Auth::OAuth::AuthHash do
- let(:provider) { 'ldap'.freeze }
+ let(:provider) { 'ldap' }
let(:auth_hash) do
described_class.new(
OmniAuth::AuthHash.new(
diff --git a/spec/lib/gitlab/auth/otp/strategies/devise_spec.rb b/spec/lib/gitlab/auth/otp/strategies/devise_spec.rb
index 0c88421d456..e51705bdb9c 100644
--- a/spec/lib/gitlab/auth/otp/strategies/devise_spec.rb
+++ b/spec/lib/gitlab/auth/otp/strategies/devise_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Auth::Otp::Strategies::Devise do
let_it_be(:user) { create(:user) }
+
let(:otp_code) { 42 }
subject(:validate) { described_class.new(user).validate(otp_code) }
diff --git a/spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb b/spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb
index 88a245b6b10..dc20df98185 100644
--- a/spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Auth::Otp::Strategies::FortiAuthenticator do
let_it_be(:user) { create(:user) }
+
let(:otp_code) { 42 }
let(:host) { 'forti_authenticator.example.com' }
diff --git a/spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb b/spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb
index 368cf98dfec..57ee53a452e 100644
--- a/spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb
+++ b/spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Auth::Otp::Strategies::FortiTokenCloud do
let_it_be(:user) { create(:user) }
+
let(:otp_code) { 42 }
let(:url) { 'https://ftc.example.com:9696/api/v1' }
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 4e4bbd1bb60..7a578ad3c90 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
let_it_be(:project) { create(:project) }
+
let(:gl_auth) { described_class }
describe 'constants' do
@@ -543,6 +544,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context 'and belong to different projects' do
let_it_be(:other_project) { create(:project) }
+
let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [project]) }
let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [other_project]) }
let(:auth_success) { Gitlab::Auth::Result.new(read_repository, other_project, :deploy_token, [:download_code]) }
diff --git a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
index 50e799908c6..dbf74bd9333 100644
--- a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, schema: 2020_04_20_094444 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, schema: 2021_03_13_045845 do
let(:gitlab_shell) { Gitlab::Shell.new }
let(:users) { table(:users) }
let(:snippets) { table(:snippets) }
diff --git a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
index 7ad93c3124a..c4c0247ad3e 100644
--- a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
+++ b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
@@ -64,5 +64,13 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
expect(test_table.where('name is NULL and name_convert_to_text is NULL').pluck(:id)).to contain_exactly(15)
expect(test_table.where("name_convert_to_text = 'no name'").count).to eq(0)
end
+
+ it 'tracks timings of queries' do
+ expect(subject.batch_metrics.timings).to be_empty
+
+ subject.perform(10, 20, table_name, 'id', sub_batch_size, 'name', 'name_convert_to_text')
+
+ expect(subject.batch_metrics.timings[:update_all]).not_to be_empty
+ end
end
end
diff --git a/spec/lib/gitlab/background_migration/migrate_pages_to_zip_storage_spec.rb b/spec/lib/gitlab/background_migration/migrate_pages_to_zip_storage_spec.rb
new file mode 100644
index 00000000000..557dd8ddee6
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/migrate_pages_to_zip_storage_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::MigratePagesToZipStorage do
+ let(:namespace) { create(:group) } # rubocop: disable RSpec/FactoriesInMigrationSpecs
+ let(:migration) { described_class.new }
+
+ describe '#perform' do
+ context 'when there is project to migrate' do
+ let!(:project) { create_project('project') }
+
+ after do
+ FileUtils.rm_rf(project.pages_path)
+ end
+
+ it 'migrates project to zip storage' do
+ expect_next_instance_of(::Pages::MigrateFromLegacyStorageService,
+ anything,
+ ignore_invalid_entries: false,
+ mark_projects_as_not_deployed: false) do |service|
+ expect(service).to receive(:execute_for_batch).with(project.id..project.id).and_call_original
+ end
+
+ migration.perform(project.id, project.id)
+
+ expect(project.reload.pages_metadatum.pages_deployment.file.filename).to eq("_migrated.zip")
+ end
+ end
+ end
+
+ def create_project(path)
+ project = create(:project) # rubocop: disable RSpec/FactoriesInMigrationSpecs
+ project.mark_pages_as_deployed
+
+ FileUtils.mkdir_p File.join(project.pages_path, "public")
+ File.open(File.join(project.pages_path, "public/index.html"), "w") do |f|
+ f.write("Hello!")
+ end
+
+ project
+ end
+end
diff --git a/spec/lib/gitlab/bullet/exclusions_spec.rb b/spec/lib/gitlab/bullet/exclusions_spec.rb
new file mode 100644
index 00000000000..ba42156b0c4
--- /dev/null
+++ b/spec/lib/gitlab/bullet/exclusions_spec.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Bullet::Exclusions do
+ let(:config_file) do
+ file = Tempfile.new('bullet.yml')
+ File.basename(file)
+ end
+
+ let(:exclude) { [] }
+ let(:config) do
+ {
+ exclusions: {
+ abc: {
+ merge_request: '_mr_',
+ path_with_method: true,
+ exclude: exclude
+ }
+ }
+ }
+ end
+
+ before do
+ File.write(config_file, config.deep_stringify_keys.to_yaml)
+ end
+
+ after do
+ FileUtils.rm_f(config_file)
+ end
+
+ describe '#execute' do
+ subject(:executor) { described_class.new(config_file).execute }
+
+ shared_examples_for 'loads exclusion results' do
+ let(:config) { { exclusions: { abc: { exclude: exclude } } } }
+ let(:results) { [exclude] }
+
+ specify do
+ expect(executor).to match(results)
+ end
+ end
+
+ context 'with preferred method of path and method name' do
+ it_behaves_like 'loads exclusion results' do
+ let(:exclude) { %w[_path_ _method_] }
+ end
+ end
+
+ context 'with file pattern' do
+ it_behaves_like 'loads exclusion results' do
+ let(:exclude) { ['_file_pattern_'] }
+ end
+ end
+
+ context 'with file name and line range' do
+ it_behaves_like 'loads exclusion results' do
+ let(:exclude) { ['file_name.rb', 5..10] }
+ end
+ end
+
+ context 'without exclusions' do
+ it_behaves_like 'loads exclusion results' do
+ let(:exclude) { [] }
+ end
+ end
+
+ context 'without exclusions key in config' do
+ it_behaves_like 'loads exclusion results' do
+ let(:config) { {} }
+ let(:results) { [] }
+ end
+ end
+
+ context 'when config file does not exist' do
+ it 'provides an empty array for exclusions' do
+ expect(described_class.new('_some_bogus_file_').execute).to match([])
+ end
+ end
+ end
+
+ describe '#validate_paths!' do
+ context 'when validating scenarios' do
+ let(:source_file) do
+ file = Tempfile.new('bullet_test_source_file.rb')
+ File.basename(file)
+ end
+
+ subject { described_class.new(config_file).validate_paths! }
+
+ before do
+ FileUtils.touch(source_file)
+ end
+
+ after do
+ FileUtils.rm_f(source_file)
+ end
+
+ context 'when using paths with method name' do
+ let(:exclude) { [source_file, '_method_'] }
+
+ context 'when source file for exclusion exists' do
+ specify do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context 'when source file for exclusion does not exist' do
+ let(:exclude) { %w[_bogus_file_ _method_] }
+
+ specify do
+ expect { subject }.to raise_error(RuntimeError)
+ end
+ end
+ end
+
+ context 'when using path only' do
+ let(:exclude) { [source_file] }
+
+ context 'when source file for exclusion exists' do
+ specify do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context 'when source file for exclusion does not exist' do
+ let(:exclude) { '_bogus_file_' }
+
+ specify do
+ expect { subject }.to raise_error(RuntimeError)
+ end
+ end
+ end
+
+ context 'when path_with_method is false for a file pattern' do
+ let(:exclude) { ['_file_pattern_'] }
+ let(:config) do
+ {
+ exclusions: {
+ abc: {
+ merge_request: '_mr_',
+ path_with_method: false,
+ exclude: exclude
+ }
+ }
+ }
+ end
+
+ specify do
+ expect { subject }.not_to raise_error
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bullet_spec.rb b/spec/lib/gitlab/bullet_spec.rb
new file mode 100644
index 00000000000..1262a0b8bde
--- /dev/null
+++ b/spec/lib/gitlab/bullet_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Bullet do
+ describe '#enabled?' do
+ it 'is enabled' do
+ stub_env('ENABLE_BULLET', true)
+
+ expect(described_class.enabled?).to be(true)
+ end
+
+ it 'is not enabled' do
+ stub_env('ENABLE_BULLET', nil)
+
+ expect(described_class.enabled?).to be(false)
+ end
+
+ it 'is correctly aliased for #extra_logging_enabled?' do
+ expect(described_class.method(:extra_logging_enabled?).original_name).to eq(:enabled?)
+ end
+ end
+
+ describe '#configure_bullet?' do
+ context 'with ENABLE_BULLET true' do
+ before do
+ stub_env('ENABLE_BULLET', true)
+ end
+
+ it 'is configurable' do
+ expect(described_class.configure_bullet?).to be(true)
+ end
+ end
+
+ context 'with ENABLE_BULLET falsey' do
+ before do
+ stub_env('ENABLE_BULLET', nil)
+ end
+
+ it 'is not configurable' do
+ expect(described_class.configure_bullet?).to be(false)
+ end
+
+ it 'is configurable in development' do
+ allow(Rails).to receive_message_chain(:env, :development?).and_return(true)
+
+ expect(described_class.configure_bullet?).to be(true)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/changelog/config_spec.rb b/spec/lib/gitlab/changelog/config_spec.rb
index 51988acf3d1..2809843b832 100644
--- a/spec/lib/gitlab/changelog/config_spec.rb
+++ b/spec/lib/gitlab/changelog/config_spec.rb
@@ -37,7 +37,8 @@ RSpec.describe Gitlab::Changelog::Config do
project,
'date_format' => 'foo',
'template' => 'bar',
- 'categories' => { 'foo' => 'bar' }
+ 'categories' => { 'foo' => 'bar' },
+ 'tag_regex' => 'foo'
)
expect(config.date_format).to eq('foo')
@@ -45,6 +46,7 @@ RSpec.describe Gitlab::Changelog::Config do
.to be_instance_of(Gitlab::Changelog::AST::Expressions)
expect(config.categories).to eq({ 'foo' => 'bar' })
+ expect(config.tag_regex).to eq('foo')
end
it 'raises Error when the categories are not a Hash' do
diff --git a/spec/lib/gitlab/checks/project_created_spec.rb b/spec/lib/gitlab/checks/project_created_spec.rb
index f099f19b061..74e43b04b6b 100644
--- a/spec/lib/gitlab/checks/project_created_spec.rb
+++ b/spec/lib/gitlab/checks/project_created_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, namespace: user.namespace) }
+
let(:protocol) { 'http' }
let(:git_user) { user }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb
index c7dad0a91d4..469aea8d093 100644
--- a/spec/lib/gitlab/checks/project_moved_spec.rb
+++ b/spec/lib/gitlab/checks/project_moved_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) }
+
let(:repository) { project.repository }
let(:protocol) { 'http' }
let(:git_user) { user }
@@ -101,6 +102,7 @@ RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
context 'with project snippet' do
let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) }
+
let(:repository) { snippet.repository }
it_behaves_like 'errors per protocol' do
@@ -111,6 +113,7 @@ RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
context 'with personal snippet' do
let_it_be(:snippet) { create(:personal_snippet, :repository, author: user) }
+
let(:repository) { snippet.repository }
it 'returns nil' do
diff --git a/spec/lib/gitlab/ci/ansi2json/style_spec.rb b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
index ff70ff69aaa..87085950a9f 100644
--- a/spec/lib/gitlab/ci/ansi2json/style_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
@@ -160,9 +160,9 @@ RSpec.describe Gitlab::Ci::Ansi2json::Style do
with_them do
it 'change the style' do
style = described_class.new
- style.update(initial_state)
+ style.update(initial_state) # rubocop:disable Rails/SaveBang
- style.update(ansi_commands)
+ style.update(ansi_commands) # rubocop:disable Rails/SaveBang
expect(style.to_s).to eq(result)
end
diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
index 179578fe0a8..d294eca7f15 100644
--- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
@@ -107,6 +107,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
stage: 'test',
only: { refs: %w[branches tags] },
variables: {},
+ job_variables: {},
+ root_variables_inheritance: true,
scheduling_type: :stage)
end
end
@@ -130,6 +132,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
stage: 'test',
only: { refs: %w[branches tags] },
variables: {},
+ job_variables: {},
+ root_variables_inheritance: true,
scheduling_type: :stage)
end
end
@@ -284,6 +288,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
parallel: { matrix: [{ 'PROVIDER' => ['aws'], 'STACK' => %w(monitoring app1) },
{ 'PROVIDER' => ['gcp'], 'STACK' => %w(data) }] },
variables: {},
+ job_variables: {},
+ root_variables_inheritance: true,
scheduling_type: :stage
)
end
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 064990667d5..cec1c97085b 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -13,6 +13,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
end
describe '#valid?' do
+ context 'with an empty hash as cache' do
+ let(:config) { {} }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
context 'when configuration is valid with a single cache' do
let(:config) { { key: 'key', paths: ["logs/"], untracked: true } }
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index a4167003987..ffcd029172a 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -663,6 +663,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
after_script: %w[cleanup],
only: { refs: %w[branches tags] },
variables: {},
+ job_variables: {},
+ root_variables_inheritance: true,
scheduling_type: :stage)
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
index ac6b589ec6b..cb73044b62b 100644
--- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
@@ -100,6 +100,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do
stage: 'test',
trigger: { project: 'my/project' },
variables: {},
+ job_variables: {},
+ root_variables_inheritance: true,
scheduling_type: :stage
},
regular_job: {
@@ -109,6 +111,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do
script: ['something'],
stage: 'test',
variables: {},
+ job_variables: {},
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index 04e80450263..016d59e98b9 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -382,7 +382,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
context 'with only job variables' do
it 'does return defined variables' do
expect(entry.value).to include(
- variables: { 'A' => 'job', 'B' => 'job' }
+ variables: { 'A' => 'job', 'B' => 'job' },
+ job_variables: { 'A' => 'job', 'B' => 'job' },
+ root_variables_inheritance: true
)
end
end
@@ -394,9 +396,11 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
).value
end
- it 'does return all variables and overwrite them' do
+ it 'does return job and root variables' do
expect(entry.value).to include(
- variables: { 'A' => 'job', 'B' => 'job', 'C' => 'root', 'D' => 'root' }
+ variables: { 'A' => 'job', 'B' => 'job', 'C' => 'root', 'D' => 'root' },
+ job_variables: { 'A' => 'job', 'B' => 'job' },
+ root_variables_inheritance: true
)
end
@@ -408,9 +412,11 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
}
end
- it 'does return only job variables' do
+ it 'does return job and root variables' do
expect(entry.value).to include(
- variables: { 'A' => 'job', 'B' => 'job' }
+ variables: { 'A' => 'job', 'B' => 'job' },
+ job_variables: { 'A' => 'job', 'B' => 'job' },
+ root_variables_inheritance: false
)
end
end
@@ -423,9 +429,11 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
}
end
- it 'does return only job variables' do
+ it 'does return job and root variables' do
expect(entry.value).to include(
- variables: { 'A' => 'job', 'B' => 'job', 'D' => 'root' }
+ variables: { 'A' => 'job', 'B' => 'job', 'D' => 'root' },
+ job_variables: { 'A' => 'job', 'B' => 'job' },
+ root_variables_inheritance: ['D']
)
end
end
@@ -493,7 +501,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
name: :rspec,
stage: 'test',
only: { refs: %w[branches tags] },
- variables: {}
+ variables: {},
+ job_variables: {},
+ root_variables_inheritance: true
)
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 7b38c21788f..041eb748fc9 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -133,6 +133,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
+ job_variables: {},
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -147,6 +149,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
+ job_variables: {},
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -163,6 +167,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' }],
only: { refs: %w(branches tags) },
variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
+ job_variables: { 'VAR' => 'job' },
+ root_variables_inheritance: true,
after_script: [],
ignore: false,
scheduling_type: :stage }
@@ -188,6 +194,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
+ job_variables: {},
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -202,6 +210,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
+ job_variables: {},
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -218,6 +228,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' },
only: { refs: %w(branches tags) },
variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
+ job_variables: { 'VAR' => 'job' },
+ root_variables_inheritance: true,
after_script: [],
ignore: false,
scheduling_type: :stage }
@@ -267,6 +279,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'root' },
+ job_variables: {},
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -279,6 +293,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'job' },
+ job_variables: { 'VAR' => 'job' },
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -311,6 +327,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
variables: { 'VAR' => 'root' },
+ job_variables: {},
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -323,6 +341,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
variables: { 'VAR' => 'job' },
+ job_variables: { 'VAR' => 'job' },
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 99f546ceb37..e5b008a482e 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -324,5 +324,39 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
end
end
end
+
+ context 'when local file path has wildcard' do
+ let(:project) { create(:project, :repository) }
+
+ let(:values) do
+ { include: 'myfolder/*.yml' }
+ end
+
+ before do
+ allow_next_instance_of(Repository) do |repository|
+ allow(repository).to receive(:search_files_by_wildcard_path).with('myfolder/*.yml', '123456') do
+ ['myfolder/file1.yml', 'myfolder/file2.yml']
+ end
+ end
+ end
+
+ it 'includes the matched local files' do
+ expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Local),
+ an_instance_of(Gitlab::Ci::Config::External::File::Local))
+
+ expect(subject.map(&:location)).to contain_exactly('myfolder/file1.yml', 'myfolder/file2.yml')
+ end
+
+ context 'when the FF ci_wildcard_file_paths is disabled' do
+ before do
+ stub_feature_flags(ci_wildcard_file_paths: false)
+ end
+
+ it 'cannot find any file returns an error message' do
+ expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Local))
+ expect(subject[0].errors).to eq(['Local file `myfolder/*.yml` does not exist!'])
+ 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 d2d7116bb12..d657c3e943f 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -366,5 +366,40 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
expect(output.keys).to match_array([:image, :my_build, :my_test])
end
end
+
+ context 'when local file path has wildcard' do
+ let_it_be(:project) { create(:project, :repository) }
+
+ let(:values) do
+ { include: 'myfolder/*.yml', image: 'ruby:2.7' }
+ end
+
+ before do
+ allow_next_instance_of(Repository) do |repository|
+ allow(repository).to receive(:search_files_by_wildcard_path).with('myfolder/*.yml', sha) do
+ ['myfolder/file1.yml', 'myfolder/file2.yml']
+ end
+
+ allow(repository).to receive(:blob_data_at).with(sha, 'myfolder/file1.yml') do
+ <<~HEREDOC
+ my_build:
+ script: echo Hello World
+ HEREDOC
+ end
+
+ allow(repository).to receive(:blob_data_at).with(sha, 'myfolder/file2.yml') do
+ <<~HEREDOC
+ my_test:
+ script: echo Hello World
+ HEREDOC
+ end
+ end
+ end
+
+ it 'fetches the matched files' do
+ output = processor.perform
+ expect(output.keys).to match_array([:image, :my_build, :my_test])
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb b/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb
index fbf86927bd9..e5f0341c5fe 100644
--- a/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb
+++ b/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb
@@ -1,8 +1,12 @@
# frozen_string_literal: true
require 'fast_spec_helper'
+require 'support/helpers/stubbed_feature'
+require 'support/helpers/stub_feature_flags'
RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
+ include StubFeatureFlags
+
describe '.applies_to?' do
subject { described_class.applies_to?(config) }
@@ -49,6 +53,10 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
variables: {
'PROVIDER' => 'aws',
'STACK' => 'app1'
+ },
+ job_variables: {
+ 'PROVIDER' => 'aws',
+ 'STACK' => 'app1'
}
},
{
@@ -58,6 +66,10 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
variables: {
'PROVIDER' => 'aws',
'STACK' => 'app2'
+ },
+ job_variables: {
+ 'PROVIDER' => 'aws',
+ 'STACK' => 'app2'
}
},
{
@@ -67,6 +79,10 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
variables: {
'PROVIDER' => 'ovh',
'STACK' => 'app'
+ },
+ job_variables: {
+ 'PROVIDER' => 'ovh',
+ 'STACK' => 'app'
}
},
{
@@ -76,6 +92,10 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
variables: {
'PROVIDER' => 'gcp',
'STACK' => 'app'
+ },
+ job_variables: {
+ 'PROVIDER' => 'gcp',
+ 'STACK' => 'app'
}
}
]
diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb
index 67324c09d86..aaa3a7a8b9d 100644
--- a/spec/lib/gitlab/ci/lint_spec.rb
+++ b/spec/lib/gitlab/ci/lint_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe Gitlab::Ci::Lint do
it 'sets merged_config' do
root_config = YAML.safe_load(content, [Symbol])
included_config = YAML.safe_load(included_content, [Symbol])
- expected_config = included_config.merge(root_config).except(:include)
+ expected_config = included_config.merge(root_config).except(:include).deep_stringify_keys
expect(subject.merged_yaml).to eq(expected_config.to_yaml)
end
diff --git a/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb b/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb
index c6b8cf2a985..6a08e8f0b7f 100644
--- a/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb
@@ -131,7 +131,6 @@ RSpec.describe Gitlab::Ci::Parsers::Codequality::CodeClimate do
expect { parse }.not_to raise_error
expect(codequality_report.degradations_count).to eq(0)
- expect(codequality_report.error_message).to eq("Invalid degradation format: The property '#/' did not contain a required property of 'location'")
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index 9ca5aeeea58..900dfec38e2 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -321,4 +321,25 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
it { is_expected.to be_falsey }
end
end
+
+ describe '#increment_pipeline_failure_reason_counter' do
+ let(:command) { described_class.new }
+ let(:reason) { :size_limit_exceeded }
+
+ subject { command.increment_pipeline_failure_reason_counter(reason) }
+
+ it 'increments the error metric' do
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { subject }.to change { counter.get(reason: reason.to_s) }.by(1)
+ end
+
+ context 'when the reason is nil' do
+ let(:reason) { nil }
+
+ it 'increments the error metric with unknown_failure' do
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { subject }.to change { counter.get(reason: 'unknown_failure') }.by(1)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
index 4ae51ac8bf9..e30a78546af 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
@@ -16,8 +16,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
describe '#perform!' do
context 'when pipeline has been skipped by workflow configuration' do
before do
- allow(step).to receive(:workflow_passed?)
- .and_return(false)
+ allow(step).to receive(:workflow_rules_result)
+ .and_return(
+ double(pass?: false, variables: {})
+ )
step.perform!
end
@@ -33,12 +35,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
it 'attaches an error to the pipeline' do
expect(pipeline.errors[:base]).to include('Pipeline filtered out by workflow rules.')
end
+
+ it 'saves workflow_rules_result' do
+ expect(command.workflow_rules_result.variables).to eq({})
+ end
end
context 'when pipeline has not been skipped by workflow configuration' do
before do
- allow(step).to receive(:workflow_passed?)
- .and_return(true)
+ allow(step).to receive(:workflow_rules_result)
+ .and_return(
+ double(pass?: true, variables: { 'VAR1' => 'val2' })
+ )
step.perform!
end
@@ -55,6 +63,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
it 'attaches no errors' do
expect(pipeline.errors).to be_empty
end
+
+ it 'saves workflow_rules_result' do
+ expect(command.workflow_rules_result.variables).to eq({ 'VAR1' => 'val2' })
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb
new file mode 100644
index 00000000000..bcea6462790
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Helpers do
+ let(:helper_class) do
+ Class.new do
+ include Gitlab::Ci::Pipeline::Chain::Helpers
+
+ attr_accessor :pipeline, :command
+
+ def initialize(pipeline, command)
+ self.pipeline = pipeline
+ self.command = command
+ end
+ end
+ end
+
+ subject(:helper) { helper_class.new(pipeline, command) }
+
+ let(:pipeline) { build(:ci_empty_pipeline) }
+ let(:command) { double(save_incompleted: true) }
+ let(:message) { 'message' }
+
+ describe '.error' do
+ shared_examples 'error function' do
+ specify do
+ expect(pipeline).to receive(:drop!).with(drop_reason).and_call_original
+ expect(pipeline).to receive(:add_error_message).with(message).and_call_original
+ expect(pipeline).to receive(:ensure_project_iid!).twice.and_call_original
+
+ subject.error(message, config_error: config_error, drop_reason: drop_reason)
+
+ expect(pipeline.yaml_errors).to eq(yaml_error)
+ expect(pipeline.errors[:base]).to include(message)
+ end
+ end
+
+ context 'when given a drop reason' do
+ context 'when config error is true' do
+ context 'sets the yaml error and overrides the drop reason' do
+ let(:drop_reason) { :config_error }
+ let(:config_error) { true }
+ let(:yaml_error) { message }
+
+ it_behaves_like "error function"
+ end
+ end
+
+ context 'when config error is false' do
+ context 'does not set the yaml error or override the drop reason' do
+ let(:drop_reason) { :size_limit_exceeded }
+ let(:config_error) { false }
+ let(:yaml_error) { nil }
+
+ it_behaves_like "error function"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
index 78363be7f36..23cdec61bb3 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
let(:save_incompleted) { false }
let(:command) do
- double(:command,
+ Gitlab::Ci::Pipeline::Chain::Command.new(
project: project,
pipeline_seed: pipeline_seed,
save_incompleted: save_incompleted
@@ -49,6 +49,11 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
expect(pipeline.deployments_limit_exceeded?).to be true
end
+
+ it 'calls increment_pipeline_failure_reason_counter' do
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { perform }.to change { counter.get(reason: 'deployments_limit_exceeded') }.by(1)
+ end
end
context 'when not saving incomplete pipelines' do
@@ -71,6 +76,12 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
expect(pipeline.errors.messages).to include(base: ['Pipeline has too many deployments! Requested 2, but the limit is 1.'])
end
+
+ it 'increments the error metric' do
+ expect(command).to receive(:increment_pipeline_failure_reason_counter).with(:deployments_limit_exceeded)
+
+ perform
+ end
end
it 'logs the error' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/pipeline/process_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/pipeline/process_spec.rb
new file mode 100644
index 00000000000..3885cea2d1b
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/pipeline/process_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Pipeline::Process do
+ let_it_be(:project) { build(:project) }
+ let_it_be(:user) { build(:user) }
+ let_it_be(:pipeline) { build(:ci_pipeline, project: project, id: 42) }
+
+ let_it_be(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user)
+ end
+
+ let(:step) { described_class.new(pipeline, command) }
+
+ describe '#perform!' do
+ subject(:perform) { step.perform! }
+
+ it 'schedules a job to process the pipeline' do
+ expect(Ci::InitialPipelineProcessWorker)
+ .to receive(:perform_async)
+ .with(42)
+
+ perform
+ end
+ end
+
+ describe '#break?' do
+ it { expect(step.break?).to be_falsey }
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 5506b079d0f..62de4d2e96d 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -22,6 +22,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Populate do
[
Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command),
+ Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules.new(pipeline, command),
Gitlab::Ci::Pipeline::Chain::SeedBlock.new(pipeline, command),
Gitlab::Ci::Pipeline::Chain::Seed.new(pipeline, command)
]
@@ -95,6 +96,11 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Populate do
it 'wastes pipeline iid' do
expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0
end
+
+ it 'increments the error metric' do
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { run_chain }.to change { counter.get(reason: 'unknown_failure') }.by(1)
+ end
end
describe 'pipeline protect' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
index 80013cab6ee..264076859cb 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
@@ -3,24 +3,16 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
- let(:project) { create(:project, :repository) }
- let(:user) { create(:user, developer_projects: [project]) }
- let(:seeds_block) { }
-
- let(:command) do
- Gitlab::Ci::Pipeline::Chain::Command.new(
- project: project,
- current_user: user,
- origin_ref: 'master',
- seeds_block: seeds_block)
- end
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user, developer_projects: [project]) }
+ let(:seeds_block) { }
+ let(:command) { initialize_command }
let(:pipeline) { build(:ci_pipeline, project: project) }
describe '#perform!' do
before do
stub_ci_pipeline_yaml_file(YAML.dump(config))
- run_chain
end
let(:config) do
@@ -28,23 +20,25 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
subject(:run_chain) do
- [
- Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
- Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command)
- ].map(&:perform!)
-
- described_class.new(pipeline, command).perform!
+ run_previous_chain(pipeline, command)
+ perform_seed(pipeline, command)
end
it 'allocates next IID' do
+ run_chain
+
expect(pipeline.iid).to be_present
end
it 'ensures ci_ref' do
+ run_chain
+
expect(pipeline.ci_ref).to be_present
end
it 'sets the seeds in the command object' do
+ run_chain
+
expect(command.pipeline_seed).to be_a(Gitlab::Ci::Pipeline::Seed::Pipeline)
expect(command.pipeline_seed.size).to eq 1
end
@@ -59,6 +53,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'correctly fabricates stages and builds' do
+ run_chain
+
seed = command.pipeline_seed
expect(seed.stages.size).to eq 2
@@ -84,6 +80,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'returns pipeline seed with jobs only assigned to master' do
+ run_chain
+
seed = command.pipeline_seed
expect(seed.size).to eq 1
@@ -103,6 +101,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'returns pipeline seed with jobs only assigned to schedules' do
+ run_chain
+
seed = command.pipeline_seed
expect(seed.size).to eq 1
@@ -130,6 +130,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
let(:pipeline) { build(:ci_pipeline, project: project) }
it 'returns seeds for kubernetes dependent job' do
+ run_chain
+
seed = command.pipeline_seed
expect(seed.size).to eq 2
@@ -141,6 +143,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
context 'when kubernetes is not active' do
it 'does not return seeds for kubernetes dependent job' do
+ run_chain
+
seed = command.pipeline_seed
expect(seed.size).to eq 1
@@ -158,6 +162,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'returns stage seeds only when variables expression is truthy' do
+ run_chain
+
seed = command.pipeline_seed
expect(seed.size).to eq 1
@@ -171,8 +177,125 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'does not execute the block' do
+ run_chain
+
expect(pipeline.variables.size).to eq(0)
end
end
+
+ describe '#root_variables' do
+ let(:config) do
+ {
+ variables: { VAR1: 'var 1' },
+ workflow: {
+ rules: [{ if: '$CI_PIPELINE_SOURCE',
+ variables: { VAR1: 'overridden var 1' } },
+ { when: 'always' }]
+ },
+ rspec: { script: 'rake' }
+ }
+ end
+
+ let(:rspec_variables) { command.pipeline_seed.stages[0].statuses[0].variables.to_hash }
+
+ it 'sends root variable with overridden by rules' do
+ run_chain
+
+ expect(rspec_variables['VAR1']).to eq('overridden var 1')
+ end
+
+ context 'when the FF ci_workflow_rules_variables is disabled' do
+ before do
+ stub_feature_flags(ci_workflow_rules_variables: false)
+ end
+
+ it 'sends root variable' do
+ run_chain
+
+ expect(rspec_variables['VAR1']).to eq('var 1')
+ end
+ end
+ end
+
+ context 'N+1 queries' do
+ it 'avoids N+1 queries when calculating variables of jobs' do
+ pipeline1, command1 = prepare_pipeline1
+ pipeline2, command2 = prepare_pipeline2
+
+ control = ActiveRecord::QueryRecorder.new do
+ perform_seed(pipeline1, command1)
+ end
+
+ expect { perform_seed(pipeline2, command2) }.not_to exceed_query_limit(
+ control.count + expected_extra_queries
+ )
+ end
+
+ private
+
+ def prepare_pipeline1
+ config1 = { build: { stage: 'build', script: 'build' } }
+ stub_ci_pipeline_yaml_file(YAML.dump(config1))
+ pipeline1 = build(:ci_pipeline, project: project)
+ command1 = initialize_command
+
+ run_previous_chain(pipeline1, command1)
+
+ [pipeline1, command1]
+ end
+
+ def prepare_pipeline2
+ config2 = { build1: { stage: 'build', script: 'build1' },
+ build2: { stage: 'build', script: 'build2' },
+ test: { stage: 'build', script: 'test' } }
+ stub_ci_pipeline_yaml_file(YAML.dump(config2))
+ pipeline2 = build(:ci_pipeline, project: project)
+ command2 = initialize_command
+
+ run_previous_chain(pipeline2, command2)
+
+ [pipeline2, command2]
+ end
+
+ def expected_extra_queries
+ extra_jobs = 2
+ non_handled_sql_queries = 3
+
+ # 1. Ci::Build Load () SELECT "ci_builds".* FROM "ci_builds"
+ # WHERE "ci_builds"."type" = 'Ci::Build'
+ # AND "ci_builds"."commit_id" IS NULL
+ # AND ("ci_builds"."retried" = FALSE OR "ci_builds"."retried" IS NULL)
+ # AND (stage_idx < 1)
+ # 2. Ci::InstanceVariable Load => `Ci::InstanceVariable#cached_data` => already cached with `fetch_memory_cache`
+ # 3. Ci::Variable Load => `Project#ci_variables_for` => already cached with `Gitlab::SafeRequestStore`
+
+ extra_jobs * non_handled_sql_queries
+ end
+ end
+
+ private
+
+ def run_previous_chain(pipeline, command)
+ [
+ Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
+ Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command),
+ Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules.new(pipeline, command)
+ ].map(&:perform!)
+ end
+
+ def perform_seed(pipeline, command)
+ described_class.new(pipeline, command).perform!
+ end
+ end
+
+ private
+
+ def initialize_command
+ Gitlab::Ci::Pipeline::Chain::Command.new(
+ project: project,
+ current_user: user,
+ origin_ref: 'master',
+ seeds_block: seeds_block
+ )
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
index e55281f9705..caf3a053c4e 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
let(:pipeline) { build(:ci_empty_pipeline, user: user, project: project) }
let!(:step) { described_class.new(pipeline, command) }
@@ -42,6 +42,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
let(:save_incompleted) { true }
+ let(:dot_com) { true }
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
project: project, current_user: user, yaml_processor_result: yaml_processor_result, save_incompleted: save_incompleted
@@ -51,11 +52,79 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
describe '#perform!' do
subject(:perform!) { step.perform! }
- context 'when validation returns true' do
+ let(:validation_service_url) { 'https://validation-service.external/' }
+
+ before do
+ stub_env('EXTERNAL_VALIDATION_SERVICE_URL', validation_service_url)
+ allow(Gitlab).to receive(:com?).and_return(dot_com)
+ allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('correlation-id')
+ end
+
+ context 'with configuration values in ApplicationSetting' do
+ let(:alternate_validation_service_url) { 'https://alternate-validation-service.external/' }
+ let(:validation_service_token) { 'SECURE_TOKEN' }
+ let(:shorter_timeout) { described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT - 1 }
+
before do
- allow(step).to receive(:validate_external).and_return(true)
+ stub_env('EXTERNAL_VALIDATION_SERVICE_TOKEN', 'TOKEN_IN_ENV')
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:external_pipeline_validation_service_timeout).and_return(shorter_timeout)
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:external_pipeline_validation_service_token).and_return(validation_service_token)
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:external_pipeline_validation_service_url).and_return(alternate_validation_service_url)
+ end
+
+ it 'uses those values rather than env vars or defaults' do
+ expect(::Gitlab::HTTP).to receive(:post) do |url, params|
+ expect(url).to eq(alternate_validation_service_url)
+ expect(params[:timeout]).to eq(shorter_timeout)
+ expect(params[:headers]).to include('X-Gitlab-Token' => validation_service_token)
+ expect(params[:timeout]).to eq(shorter_timeout)
+ end
+
+ perform!
+ end
+ end
+
+ it 'respects the defined payload schema' do
+ expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
+ expect(params[:body]).to match_schema('/external_validation')
+ expect(params[:timeout]).to eq(described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT)
+ expect(params[:headers]).to eq({ 'X-Gitlab-Correlation-id' => 'correlation-id' })
+ end
+
+ perform!
+ end
+
+ context 'with EXTERNAL_VALIDATION_SERVICE_TIMEOUT defined' do
+ before do
+ stub_env('EXTERNAL_VALIDATION_SERVICE_TIMEOUT', validation_service_timeout)
+ end
+
+ context 'with valid value' do
+ let(:validation_service_timeout) { '1' }
+
+ it 'uses defined timeout' do
+ expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
+ expect(params[:timeout]).to eq(1)
+ end
+
+ perform!
+ end
+ end
+
+ context 'with invalid value' do
+ let(:validation_service_timeout) { '??' }
+
+ it 'uses default timeout' do
+ expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
+ expect(params[:timeout]).to eq(described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT)
+ end
+
+ perform!
+ end
end
+ end
+ shared_examples 'successful external authorization' do
it 'does not drop the pipeline' do
perform!
@@ -76,9 +145,117 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
end
- context 'when validation return false' do
+ context 'when EXTERNAL_VALIDATION_SERVICE_TOKEN is set' do
+ before do
+ stub_env('EXTERNAL_VALIDATION_SERVICE_TOKEN', '123')
+ end
+
+ it 'passes token in X-Gitlab-Token header' do
+ expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
+ expect(params[:headers]).to include({ 'X-Gitlab-Token' => '123' })
+ end
+
+ perform!
+ end
+ end
+
+ context 'when validation returns 200 OK' do
+ before do
+ stub_request(:post, validation_service_url).to_return(status: 200, body: "{}")
+ end
+
+ it_behaves_like 'successful external authorization'
+ end
+
+ context 'when validation returns 404 Not Found' do
before do
- allow(step).to receive(:validate_external).and_return(false)
+ stub_request(:post, validation_service_url).to_return(status: 404, body: "{}")
+ end
+
+ it_behaves_like 'successful external authorization'
+ end
+
+ context 'when validation returns 500 Internal Server Error' do
+ before do
+ stub_request(:post, validation_service_url).to_return(status: 500, body: "{}")
+ end
+
+ it_behaves_like 'successful external authorization'
+ end
+
+ context 'when validation raises exceptions' do
+ before do
+ stub_request(:post, validation_service_url).to_raise(Net::OpenTimeout)
+ end
+
+ it_behaves_like 'successful external authorization'
+
+ it 'logs exceptions' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+ .with(instance_of(Net::OpenTimeout), { project_id: project.id })
+
+ perform!
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_external_validation_service: false)
+ stub_request(:post, validation_service_url)
+ end
+
+ it 'does not drop the pipeline' do
+ perform!
+
+ expect(pipeline.status).not_to eq('failed')
+ expect(pipeline.errors).to be_empty
+ end
+
+ it 'does not break the chain' do
+ perform!
+
+ expect(step.break?).to be false
+ end
+
+ it 'does not make requests' do
+ perform!
+
+ expect(WebMock).not_to have_requested(:post, validation_service_url)
+ end
+ end
+
+ context 'when not on .com' do
+ let(:dot_com) { false }
+
+ before do
+ stub_feature_flags(ci_external_validation_service: false)
+ stub_request(:post, validation_service_url).to_return(status: 404, body: "{}")
+ end
+
+ it 'drops the pipeline' do
+ perform!
+
+ expect(pipeline.status).to eq('failed')
+ expect(pipeline).to be_persisted
+ expect(pipeline.errors.to_a).to include('External validation failed')
+ end
+
+ it 'breaks the chain' do
+ perform!
+
+ expect(step.break?).to be true
+ end
+
+ it 'logs the authorization' do
+ expect(Gitlab::AppLogger).to receive(:info).with(message: 'Pipeline not authorized', project_id: project.id, user_id: user.id)
+
+ perform!
+ end
+ end
+
+ context 'when validation returns 406 Not Acceptable' do
+ before do
+ stub_request(:post, validation_service_url).to_return(status: 406, body: "{}")
end
it 'drops the pipeline' do
@@ -126,16 +303,4 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
end
end
-
- describe '#validation_service_payload' do
- subject(:validation_service_payload) { step.send(:validation_service_payload, pipeline, command.yaml_processor_result.stages_attributes) }
-
- it 'respects the defined schema' do
- expect(validation_service_payload).to match_schema('/external_validation')
- end
-
- it 'does not fire sql queries' do
- expect { validation_service_payload }.not_to exceed_query_limit(1)
- end
- end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 7ec6949f852..f97935feb86 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -6,10 +6,12 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:head_sha) { project.repository.head_commit.id }
let(:pipeline) { build(:ci_empty_pipeline, project: project, sha: head_sha) }
+ let(:root_variables) { [] }
+ let(:seed_context) { double(pipeline: pipeline, root_variables: root_variables) }
let(:attributes) { { name: 'rspec', ref: 'master', scheduling_type: :stage } }
let(:previous_stages) { [] }
- let(:seed_build) { described_class.new(pipeline, attributes, previous_stages) }
+ let(:seed_build) { described_class.new(seed_context, attributes, previous_stages) }
describe '#attributes' do
subject { seed_build.attributes }
@@ -75,8 +77,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
let(:attributes) do
{ name: 'rspec',
ref: 'master',
- yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true },
- { key: 'VAR2', value: 'var 2', public: true }],
+ job_variables: [{ key: 'VAR1', value: 'var 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true }],
rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] }
end
@@ -301,6 +303,133 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
end
end
+
+ context 'with workflow:rules:[variables:]' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ yaml_variables: [{ key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }],
+ job_variables: [{ key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }],
+ root_variables_inheritance: root_variables_inheritance }
+ end
+
+ context 'when the pipeline has variables' do
+ let(:root_variables) do
+ [{ key: 'VAR1', value: 'var overridden pipeline 1', public: true },
+ { key: 'VAR2', value: 'var pipeline 2', public: true },
+ { key: 'VAR3', value: 'var pipeline 3', public: true },
+ { key: 'VAR4', value: 'new var pipeline 4', public: true }]
+ end
+
+ context 'when root_variables_inheritance is true' do
+ let(:root_variables_inheritance) { true }
+
+ it 'returns calculated yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR1', value: 'var overridden pipeline 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true },
+ { key: 'VAR4', value: 'new var pipeline 4', public: true }]
+ )
+ end
+
+ context 'when FF ci_workflow_rules_variables is disabled' do
+ before do
+ stub_feature_flags(ci_workflow_rules_variables: false)
+ end
+
+ it 'returns existing yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }]
+ )
+ end
+ end
+ end
+
+ context 'when root_variables_inheritance is false' do
+ let(:root_variables_inheritance) { false }
+
+ it 'returns job variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }]
+ )
+ end
+ end
+
+ context 'when root_variables_inheritance is an array' do
+ let(:root_variables_inheritance) { %w(VAR1 VAR2 VAR3) }
+
+ it 'returns calculated yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR1', value: 'var overridden pipeline 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }]
+ )
+ end
+ end
+ end
+
+ context 'when the pipeline has not a variable' do
+ let(:root_variables_inheritance) { true }
+
+ it 'returns seed yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }])
+ end
+ end
+ end
+
+ context 'when the job rule depends on variables' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true }],
+ job_variables: [{ key: 'VAR1', value: 'var 1', public: true }],
+ root_variables_inheritance: root_variables_inheritance,
+ rules: rules }
+ end
+
+ let(:root_variables_inheritance) { true }
+
+ context 'when the rules use job variables' do
+ let(:rules) do
+ [{ if: '$VAR1 == "var 1"', variables: { VAR1: 'overridden var 1', VAR2: 'new var 2' } }]
+ end
+
+ it 'recalculates the variables' do
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1', public: true },
+ { key: 'VAR2', value: 'new var 2', public: true })
+ end
+ end
+
+ context 'when the rules use root variables' do
+ let(:root_variables) do
+ [{ key: 'VAR2', value: 'var pipeline 2', public: true }]
+ end
+
+ let(:rules) do
+ [{ if: '$VAR2 == "var pipeline 2"', variables: { VAR1: 'overridden var 1', VAR2: 'overridden var 2' } }]
+ end
+
+ it 'recalculates the variables' do
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1', public: true },
+ { key: 'VAR2', value: 'overridden var 2', public: true })
+ end
+
+ context 'when the root_variables_inheritance is false' do
+ let(:root_variables_inheritance) { false }
+
+ it 'does not recalculate the variables' do
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1', public: true })
+ end
+ end
+ end
+ end
end
describe '#bridge?' do
@@ -377,7 +506,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
it 'does not have environment' do
expect(subject).not_to be_has_environment
expect(subject.environment).to be_nil
- expect(subject.metadata).to be_nil
+ expect(subject.metadata&.expanded_environment_name).to be_nil
expect(Environment.exists?(name: expected_environment_name)).to eq(false)
end
end
@@ -1080,7 +1209,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
end
let(:stage_seed) do
- Gitlab::Ci::Pipeline::Seed::Stage.new(pipeline, stage_attributes, [])
+ Gitlab::Ci::Pipeline::Seed::Stage.new(seed_context, stage_attributes, [])
end
let(:previous_stages) { [stage_seed] }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
index 860b07647bd..21be8660def 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
@@ -6,6 +6,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:seed_context) { double(pipeline: pipeline, root_variables: []) }
+
let(:stages_attributes) do
[
{
@@ -29,7 +31,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do
end
subject(:seed) do
- described_class.new(pipeline, stages_attributes)
+ described_class.new(seed_context, stages_attributes)
end
describe '#stages' do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
index 4b9db9fa6c6..5b04d2abd88 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Stage do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:previous_stages) { [] }
+ let(:seed_context) { double(pipeline: pipeline, root_variables: []) }
let(:attributes) do
{ name: 'test',
@@ -16,7 +17,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Stage do
end
subject do
- described_class.new(pipeline, attributes, previous_stages)
+ described_class.new(seed_context, attributes, previous_stages)
end
describe '#size' do
diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
index b322e55cb5a..8378d096fcf 100644
--- a/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
@@ -6,15 +6,17 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
let(:comparer) { described_class.new(base_report, head_report) }
let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
- let(:degradation_1) { build(:codequality_degradation_1) }
- let(:degradation_2) { build(:codequality_degradation_2) }
+ let(:major_degradation) { build(:codequality_degradation, :major) }
+ let(:minor_degradation) { build(:codequality_degradation, :major) }
+ let(:critical_degradation) { build(:codequality_degradation, :critical) }
+ let(:blocker_degradation) { build(:codequality_degradation, :blocker) }
describe '#status' do
subject(:report_status) { comparer.status }
context 'when head report has an error' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'returns status failed' do
@@ -50,7 +52,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when head report has an error' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'returns the number of new errors' do
@@ -70,8 +72,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has an error and head has a different error' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(minor_degradation)
end
it 'counts the base report error as resolved' do
@@ -81,7 +83,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors head has no errors' do
before do
- base_report.add_degradation(degradation_1)
+ base_report.add_degradation(major_degradation)
end
it 'counts the base report errors as resolved' do
@@ -91,8 +93,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head has the same error' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_1)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(major_degradation)
end
it 'returns zero' do
@@ -102,7 +104,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report does not have errors and head has errors' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'returns zero' do
@@ -124,7 +126,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has an error' do
before do
- base_report.add_degradation(degradation_1)
+ base_report.add_degradation(major_degradation)
end
it 'returns zero' do
@@ -134,7 +136,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when head report has an error' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'includes the head report error in the count' do
@@ -144,8 +146,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head report has errors' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(minor_degradation)
end
it 'includes errors in the count' do
@@ -155,9 +157,9 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head report has the same error' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(major_degradation)
+ head_report.add_degradation(minor_degradation)
end
it 'includes errors in the count' do
@@ -179,20 +181,28 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head has the same error' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
- end
-
- it 'includes the base report errors' do
- expect(existing_errors).to contain_exactly(degradation_1)
+ base_report.add_degradation(major_degradation)
+ base_report.add_degradation(critical_degradation)
+ base_report.add_degradation(blocker_degradation)
+ head_report.add_degradation(critical_degradation)
+ head_report.add_degradation(blocker_degradation)
+ head_report.add_degradation(major_degradation)
+ head_report.add_degradation(minor_degradation)
+ end
+
+ it 'includes the base report errors sorted by severity' do
+ expect(existing_errors).to eq([
+ blocker_degradation,
+ critical_degradation,
+ major_degradation
+ ])
end
end
context 'when base report has errors and head has a different error' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(minor_degradation)
end
it 'returns an empty array' do
@@ -202,7 +212,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report does not have errors and head has errors' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'returns an empty array' do
@@ -224,19 +234,25 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head has more errors' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(critical_degradation)
+ head_report.add_degradation(minor_degradation)
+ head_report.add_degradation(blocker_degradation)
+ head_report.add_degradation(major_degradation)
end
- it 'includes errors not found in the base report' do
- expect(new_errors).to eq([degradation_2])
+ it 'includes errors not found in the base report sorted by severity' do
+ expect(new_errors).to eq([
+ blocker_degradation,
+ critical_degradation,
+ minor_degradation
+ ])
end
end
context 'when base report has an error and head has no errors' do
before do
- base_report.add_degradation(degradation_1)
+ base_report.add_degradation(major_degradation)
end
it 'returns an empty array' do
@@ -246,11 +262,11 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report does not have errors and head has errors' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'returns the head report error' do
- expect(new_errors).to eq([degradation_1])
+ expect(new_errors).to eq([major_degradation])
end
end
@@ -268,9 +284,9 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report errors are still found in the head report' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(major_degradation)
+ head_report.add_degradation(minor_degradation)
end
it 'returns an empty array' do
@@ -280,18 +296,25 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head has a different error' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ base_report.add_degradation(minor_degradation)
+ base_report.add_degradation(critical_degradation)
+ base_report.add_degradation(blocker_degradation)
+ head_report.add_degradation(major_degradation)
end
- it 'returns the base report error' do
- expect(resolved_errors).to eq([degradation_1])
+ it 'returns the base report errors not found in the head report, sorted by severity' do
+ expect(resolved_errors).to eq([
+ blocker_degradation,
+ critical_degradation,
+ minor_degradation
+ ])
end
end
context 'when base report does not have errors and head has errors' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'returns an empty array' do
diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
index ae9b2f2c62b..3b0eaffc54e 100644
--- a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
@@ -34,8 +34,6 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do
it 'sets location as an error' do
codequality_report.add_degradation(invalid_degradation)
-
- expect(codequality_report.error_message).to eq("Invalid degradation format: The property '#/' did not contain a required property of 'location'")
end
end
end
@@ -79,4 +77,36 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do
end
end
end
+
+ describe '#sort_degradations!' do
+ let(:major) { build(:codequality_degradation, :major) }
+ let(:minor) { build(:codequality_degradation, :minor) }
+ let(:blocker) { build(:codequality_degradation, :blocker) }
+ let(:info) { build(:codequality_degradation, :info) }
+ let(:major_2) { build(:codequality_degradation, :major) }
+ let(:critical) { build(:codequality_degradation, :critical) }
+ let(:codequality_report) { described_class.new }
+
+ before do
+ codequality_report.add_degradation(major)
+ codequality_report.add_degradation(minor)
+ codequality_report.add_degradation(blocker)
+ codequality_report.add_degradation(major_2)
+ codequality_report.add_degradation(info)
+ codequality_report.add_degradation(critical)
+
+ codequality_report.sort_degradations!
+ end
+
+ it 'sorts degradations based on severity' do
+ expect(codequality_report.degradations.values).to eq([
+ blocker,
+ critical,
+ major,
+ major_2,
+ minor,
+ info
+ ])
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/reports/test_failure_history_spec.rb b/spec/lib/gitlab/ci/reports/test_failure_history_spec.rb
index 831bc5e9f37..9ee55177ca0 100644
--- a/spec/lib/gitlab/ci/reports/test_failure_history_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_failure_history_spec.rb
@@ -13,9 +13,9 @@ RSpec.describe Gitlab::Ci::Reports::TestFailureHistory, :aggregate_failures do
subject(:load_history) { described_class.new([failed_rspec, failed_java], project).load! }
before do
- allow(Ci::TestCaseFailure)
+ allow(Ci::UnitTestFailure)
.to receive(:recent_failures_count)
- .with(project: project, test_case_keys: [failed_rspec.key, failed_java.key])
+ .with(project: project, unit_test_keys: [failed_rspec.key, failed_java.key])
.and_return(
failed_rspec.key => 2,
failed_java.key => 1
diff --git a/spec/lib/gitlab/ci/runner_instructions_spec.rb b/spec/lib/gitlab/ci/runner_instructions_spec.rb
index d1020026fe6..f872c631a50 100644
--- a/spec/lib/gitlab/ci/runner_instructions_spec.rb
+++ b/spec/lib/gitlab/ci/runner_instructions_spec.rb
@@ -6,7 +6,6 @@ RSpec.describe Gitlab::Ci::RunnerInstructions do
using RSpec::Parameterized::TableSyntax
let(:params) { {} }
- let(:user) { create(:user) }
describe 'OS' do
Gitlab::Ci::RunnerInstructions::OS.each do |name, subject|
@@ -37,7 +36,7 @@ RSpec.describe Gitlab::Ci::RunnerInstructions do
end
describe '#install_script' do
- subject { described_class.new(current_user: user, **params) }
+ subject { described_class.new(**params) }
context 'invalid params' do
where(:current_params, :expected_error_message) do
@@ -106,117 +105,18 @@ RSpec.describe Gitlab::Ci::RunnerInstructions do
end
end
- context 'group' do
- let(:group) { create(:group) }
-
- subject { described_class.new(current_user: user, group: group, **params) }
-
- context 'user is owner' do
- before do
- group.add_owner(user)
- end
-
- with_them do
- let(:params) { { os: commands.each_key.first, arch: 'foo' } }
-
- it 'have correct configurations' do
- result = subject.register_command
-
- expect(result).to include("#{commands[commands.each_key.first]} register")
- expect(result).to include("--registration-token #{group.runners_token}")
- expect(result).to include("--url #{Gitlab::Routing.url_helpers.root_url(only_path: false)}")
- end
- end
- end
-
- context 'user is not owner' do
- where(:user_permission) do
- [:maintainer, :developer, :reporter, :guest]
- end
-
- with_them do
- before do
- create(:group_member, user_permission, group: group, user: user)
- end
-
- it 'raises error' do
- result = subject.register_command
-
- expect(result).to be_nil
- expect(subject.errors).to include("Gitlab::Access::AccessDeniedError")
- end
- end
- end
- end
-
- context 'project' do
- let(:project) { create(:project) }
-
- subject { described_class.new(current_user: user, project: project, **params) }
-
- context 'user is maintainer' do
- before do
- project.add_maintainer(user)
- end
-
- with_them do
- let(:params) { { os: commands.each_key.first, arch: 'foo' } }
-
- it 'have correct configurations' do
- result = subject.register_command
-
- expect(result).to include("#{commands[commands.each_key.first]} register")
- expect(result).to include("--registration-token #{project.runners_token}")
- expect(result).to include("--url #{Gitlab::Routing.url_helpers.root_url(only_path: false)}")
- end
- end
- end
-
- context 'user is not maintainer' do
- where(:user_permission) do
- [:developer, :reporter, :guest]
- end
-
- with_them do
- before do
- create(:project_member, user_permission, project: project, user: user)
- end
-
- it 'raises error' do
- result = subject.register_command
-
- expect(result).to be_nil
- expect(subject.errors).to include("Gitlab::Access::AccessDeniedError")
- end
- end
- end
- end
-
context 'instance' do
- subject { described_class.new(current_user: user, **params) }
-
- context 'user is admin' do
- let(:user) { create(:user, :admin) }
-
- with_them do
- let(:params) { { os: commands.each_key.first, arch: 'foo' } }
+ subject { described_class.new(**params) }
- it 'have correct configurations' do
- result = subject.register_command
-
- expect(result).to include("#{commands[commands.each_key.first]} register")
- expect(result).to include("--registration-token #{Gitlab::CurrentSettings.runners_registration_token}")
- expect(result).to include("--url #{Gitlab::Routing.url_helpers.root_url(only_path: false)}")
- end
- end
- end
+ with_them do
+ let(:params) { { os: commands.each_key.first, arch: 'foo' } }
- context 'user is not admin' do
- it 'raises error' do
+ it 'have correct configurations' do
result = subject.register_command
- expect(result).to be_nil
- expect(subject.errors).to include("Gitlab::Access::AccessDeniedError")
+ expect(result).to include("#{commands[commands.each_key.first]} register")
+ expect(result).to include("--registration-token $REGISTRATION_TOKEN")
+ expect(result).to include("--url #{Gitlab::Routing.url_helpers.root_url(only_path: false)}")
end
end
end
diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb
index 924ee5ee1a4..c4e83c1796d 100644
--- a/spec/lib/gitlab/ci/status/build/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/common_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Common do
context 'when user does not have access to read build' do
before do
- project.update(public_builds: false)
+ project.update!(public_builds: false)
end
it { is_expected.not_to have_details }
diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb
index 543cfe874ca..2b9523bd83d 100644
--- a/spec/lib/gitlab/ci/status/composite_spec.rb
+++ b/spec/lib/gitlab/ci/status/composite_spec.rb
@@ -6,13 +6,13 @@ RSpec.describe Gitlab::Ci::Status::Composite do
let_it_be(:pipeline) { create(:ci_pipeline) }
before_all do
- @statuses = Ci::HasStatus::STATUSES_ENUM.map do |status, idx|
+ @statuses = Ci::HasStatus::STATUSES_ENUM.to_h do |status, idx|
[status, create(:ci_build, pipeline: pipeline, status: status, importing: true)]
- end.to_h
+ end
- @statuses_with_allow_failure = Ci::HasStatus::STATUSES_ENUM.map do |status, idx|
+ @statuses_with_allow_failure = Ci::HasStatus::STATUSES_ENUM.to_h do |status, idx|
[status, create(:ci_build, pipeline: pipeline, status: status, allow_failure: true, importing: true)]
- end.to_h
+ end
end
describe '#status' do
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index 597e4ca9b03..0fe7c731f27 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state, factory_default: :keep do
let_it_be(:project) { create_default(:project).freeze }
- let_it_be_with_reload(:build) { create(:ci_build) }
+ let_it_be_with_reload(:build) { create(:ci_build, :success) }
let(:trace) { described_class.new(build) }
describe "associations" do
@@ -63,9 +63,7 @@ RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state, factory_defa
describe '#update_interval' do
context 'it is not being watched' do
- it 'returns 30 seconds' do
- expect(trace.update_interval).to eq(30.seconds)
- end
+ it { expect(trace.update_interval).to eq(60.seconds) }
end
context 'it is being watched' do
diff --git a/spec/lib/gitlab/ci/variables/helpers_spec.rb b/spec/lib/gitlab/ci/variables/helpers_spec.rb
index b45abf8c0e1..f13b334c10e 100644
--- a/spec/lib/gitlab/ci/variables/helpers_spec.rb
+++ b/spec/lib/gitlab/ci/variables/helpers_spec.rb
@@ -100,4 +100,50 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do
it { is_expected.to eq(result) }
end
end
+
+ describe '.inherit_yaml_variables' do
+ let(:from) do
+ [{ key: 'key1', value: 'value1' },
+ { key: 'key2', value: 'value2' }]
+ end
+
+ let(:to) do
+ [{ key: 'key2', value: 'value22' },
+ { key: 'key3', value: 'value3' }]
+ end
+
+ let(:inheritance) { true }
+
+ let(:result) do
+ [{ key: 'key1', value: 'value1', public: true },
+ { key: 'key2', value: 'value22', public: true },
+ { key: 'key3', value: 'value3', public: true }]
+ end
+
+ subject { described_class.inherit_yaml_variables(from: from, to: to, inheritance: inheritance) }
+
+ it { is_expected.to eq(result) }
+
+ context 'when inheritance is false' do
+ let(:inheritance) { false }
+
+ let(:result) do
+ [{ key: 'key2', value: 'value22', public: true },
+ { key: 'key3', value: 'value3', public: true }]
+ end
+
+ it { is_expected.to eq(result) }
+ end
+
+ context 'when inheritance is array' do
+ let(:inheritance) { ['key2'] }
+
+ let(:result) do
+ [{ key: 'key2', value: 'value22', public: true },
+ { key: 'key3', value: 'value3', public: true }]
+ end
+
+ it { is_expected.to eq(result) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
index 7e3cd7ec254..e345cd4de9b 100644
--- a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
@@ -24,7 +24,7 @@ module Gitlab
let(:included_yml) do
YAML.dump(
- another_test: { stage: 'test', script: 'echo 2' }
+ { another_test: { stage: 'test', script: 'echo 2' } }.deep_stringify_keys
)
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 5462a587d16..ad94dfc9160 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -43,6 +43,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -74,6 +76,8 @@ module Gitlab
allow_failure: false,
when: 'on_success',
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -111,7 +115,9 @@ module Gitlab
tag_list: %w[A B],
allow_failure: false,
when: "on_success",
- yaml_variables: []
+ yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true
})
end
end
@@ -158,6 +164,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -347,6 +355,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage,
options: { script: ["rspec"] },
only: { refs: ["branches"] } }] },
@@ -359,6 +369,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage,
options: { script: ["cap prod"] },
only: { refs: ["tags"] } }] },
@@ -372,7 +384,7 @@ module Gitlab
end
end
- describe '#workflow_attributes' do
+ describe 'workflow attributes' do
context 'with disallowed workflow:variables' do
let(:config) do
<<-EOYML
@@ -403,11 +415,11 @@ module Gitlab
end
it 'parses the workflow:rules configuration' do
- expect(subject.workflow_attributes[:rules]).to contain_exactly({ if: '$VAR == "value"' })
+ expect(subject.workflow_rules).to contain_exactly({ if: '$VAR == "value"' })
end
- it 'parses the root:variables as yaml_variables:' do
- expect(subject.workflow_attributes[:yaml_variables])
+ it 'parses the root:variables as #root_variables' do
+ expect(subject.root_variables)
.to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true })
end
end
@@ -425,11 +437,11 @@ module Gitlab
end
it 'parses the workflow:rules configuration' do
- expect(subject.workflow_attributes[:rules]).to contain_exactly({ if: '$VAR == "value"' })
+ expect(subject.workflow_rules).to contain_exactly({ if: '$VAR == "value"' })
end
- it 'parses the root:variables as yaml_variables:' do
- expect(subject.workflow_attributes[:yaml_variables]).to eq([])
+ it 'parses the root:variables as #root_variables' do
+ expect(subject.root_variables).to eq([])
end
end
@@ -445,11 +457,11 @@ module Gitlab
end
it 'parses the workflow:rules configuration' do
- expect(subject.workflow_attributes[:rules]).to be_nil
+ expect(subject.workflow_rules).to be_nil
end
- it 'parses the root:variables as yaml_variables:' do
- expect(subject.workflow_attributes[:yaml_variables])
+ it 'parses the root:variables as #root_variables' do
+ expect(subject.root_variables)
.to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true })
end
end
@@ -463,11 +475,11 @@ module Gitlab
end
it 'parses the workflow:rules configuration' do
- expect(subject.workflow_attributes[:rules]).to be_nil
+ expect(subject.workflow_rules).to be_nil
end
- it 'parses the root:variables as yaml_variables:' do
- expect(subject.workflow_attributes[:yaml_variables]).to eq([])
+ it 'parses the root:variables as #root_variables' do
+ expect(subject.root_variables).to eq([])
end
end
end
@@ -853,6 +865,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -861,7 +875,7 @@ module Gitlab
config = YAML.dump({ image: "ruby:2.7",
services: ["mysql"],
before_script: ["pwd"],
- rspec: { image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+ rspec: { image: { name: "ruby:3.0", entrypoint: ["/usr/local/bin/init", "run"] },
services: [{ name: "postgresql", alias: "db-pg",
entrypoint: ["/usr/local/bin/init", "run"],
command: ["/usr/local/bin/init", "run"] }, "docker:dind"],
@@ -878,7 +892,7 @@ module Gitlab
options: {
before_script: ["pwd"],
script: ["rspec"],
- image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+ image: { name: "ruby:3.0", entrypoint: ["/usr/local/bin/init", "run"] },
services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"],
command: ["/usr/local/bin/init", "run"] },
{ name: "docker:dind" }]
@@ -886,6 +900,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -915,6 +931,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -923,7 +941,7 @@ module Gitlab
config = YAML.dump({ image: "ruby:2.7",
services: ["mysql"],
before_script: ["pwd"],
- rspec: { image: "ruby:2.5", services: ["postgresql", "docker:dind"], script: "rspec" } })
+ rspec: { image: "ruby:3.0", services: ["postgresql", "docker:dind"], script: "rspec" } })
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
@@ -936,12 +954,14 @@ module Gitlab
options: {
before_script: ["pwd"],
script: ["rspec"],
- image: { name: "ruby:2.5" },
+ image: { name: "ruby:3.0" },
services: [{ name: "postgresql" }, { name: "docker:dind" }]
},
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -951,7 +971,10 @@ module Gitlab
describe 'Variables' do
subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute }
- let(:build_variables) { subject.builds.first[:yaml_variables] }
+ let(:build) { subject.builds.first }
+ let(:yaml_variables) { build[:yaml_variables] }
+ let(:job_variables) { build[:job_variables] }
+ let(:root_variables_inheritance) { build[:root_variables_inheritance] }
context 'when global variables are defined' do
let(:variables) do
@@ -967,10 +990,12 @@ module Gitlab
end
it 'returns global variables' do
- expect(build_variables).to contain_exactly(
+ expect(yaml_variables).to contain_exactly(
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
)
+ expect(job_variables).to eq([])
+ expect(root_variables_inheritance).to eq(true)
end
end
@@ -979,7 +1004,7 @@ module Gitlab
{ 'VAR1' => 'global1', 'VAR3' => 'global3', 'VAR4' => 'global4' }
end
- let(:job_variables) do
+ let(:build_variables) do
{ 'VAR1' => 'value1', 'VAR2' => 'value2' }
end
@@ -987,20 +1012,25 @@ module Gitlab
{
before_script: ['pwd'],
variables: global_variables,
- rspec: { script: 'rspec', variables: job_variables, inherit: inherit }
+ rspec: { script: 'rspec', variables: build_variables, inherit: inherit }
}
end
context 'when no inheritance is specified' do
let(:inherit) { }
- it 'returns all unique variables' do
- expect(build_variables).to contain_exactly(
- { key: 'VAR4', value: 'global4', public: true },
+ it 'returns all variables' do
+ expect(yaml_variables).to contain_exactly(
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true },
{ key: 'VAR3', value: 'global3', public: true },
+ { key: 'VAR4', value: 'global4', public: true }
+ )
+ expect(job_variables).to contain_exactly(
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
)
+ expect(root_variables_inheritance).to eq(true)
end
end
@@ -1008,22 +1038,32 @@ module Gitlab
let(:inherit) { { variables: false } }
it 'does not inherit variables' do
- expect(build_variables).to contain_exactly(
+ expect(yaml_variables).to contain_exactly(
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
)
+ expect(job_variables).to contain_exactly(
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true }
+ )
+ expect(root_variables_inheritance).to eq(false)
end
end
context 'when specific variables are to inherited' do
let(:inherit) { { variables: %w[VAR1 VAR4] } }
- it 'returns all unique variables and inherits only specified variables' do
- expect(build_variables).to contain_exactly(
- { key: 'VAR4', value: 'global4', public: true },
+ it 'returns all variables and inherits only specified variables' do
+ expect(yaml_variables).to contain_exactly(
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true },
+ { key: 'VAR4', value: 'global4', public: true }
+ )
+ expect(job_variables).to contain_exactly(
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
)
+ expect(root_variables_inheritance).to eq(%w[VAR1 VAR4])
end
end
end
@@ -1042,10 +1082,15 @@ module Gitlab
end
it 'returns job variables' do
- expect(build_variables).to contain_exactly(
+ expect(yaml_variables).to contain_exactly(
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true }
+ )
+ expect(job_variables).to contain_exactly(
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
)
+ expect(root_variables_inheritance).to eq(true)
end
end
@@ -1068,8 +1113,11 @@ module Gitlab
# When variables config is empty, we assume this is a valid
# configuration, see issue #18775
#
- expect(build_variables).to be_an_instance_of(Array)
- expect(build_variables).to be_empty
+ expect(yaml_variables).to be_an_instance_of(Array)
+ expect(yaml_variables).to be_empty
+
+ expect(job_variables).to eq([])
+ expect(root_variables_inheritance).to eq(true)
end
end
end
@@ -1084,8 +1132,11 @@ module Gitlab
end
it 'returns empty array' do
- expect(build_variables).to be_an_instance_of(Array)
- expect(build_variables).to be_empty
+ expect(yaml_variables).to be_an_instance_of(Array)
+ expect(yaml_variables).to be_empty
+
+ expect(job_variables).to eq([])
+ expect(root_variables_inheritance).to eq(true)
end
end
end
@@ -1717,6 +1768,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -2080,6 +2133,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
)
expect(subject.builds[4]).to eq(
@@ -2095,6 +2150,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :dag
)
end
@@ -2122,6 +2179,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
)
expect(subject.builds[4]).to eq(
@@ -2139,6 +2198,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :dag
)
end
@@ -2162,6 +2223,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :dag
)
end
@@ -2193,6 +2256,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :dag
)
end
@@ -2391,6 +2456,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -2438,6 +2505,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
expect(subject.second).to eq({
@@ -2451,6 +2520,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
diff --git a/spec/lib/gitlab/composer/version_index_spec.rb b/spec/lib/gitlab/composer/version_index_spec.rb
index 7b0ed703f42..a4d016636aa 100644
--- a/spec/lib/gitlab/composer/version_index_spec.rb
+++ b/spec/lib/gitlab/composer/version_index_spec.rb
@@ -27,6 +27,11 @@ RSpec.describe Gitlab::Composer::VersionIndex do
'type' => 'zip',
'url' => "http://localhost/api/v4/projects/#{project.id}/packages/composer/archives/#{package.name}.zip?sha=#{branch.target}"
},
+ 'source' => {
+ 'reference' => branch.target,
+ 'type' => 'git',
+ 'url' => project.http_url_to_repo
+ },
'name' => package.name,
'uid' => package.id,
'version' => package.version
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index bb9bee763d8..46e5334cd81 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe Gitlab::Conflict::File do
let(:section_keys) { conflict_file.sections.map { |section| section[:id] }.compact }
context 'when resolving everything to the same side' do
- let(:resolution_hash) { section_keys.map { |key| [key, 'head'] }.to_h }
+ let(:resolution_hash) { section_keys.to_h { |key| [key, 'head'] } }
let(:resolved_lines) { conflict_file.resolve_lines(resolution_hash) }
let(:expected_lines) { conflict_file.lines.reject { |line| line.type == 'old' } }
@@ -54,8 +54,8 @@ RSpec.describe Gitlab::Conflict::File do
end
it 'raises ResolutionError when passed a hash without resolutions for all sections' do
- empty_hash = section_keys.map { |key| [key, nil] }.to_h
- invalid_hash = section_keys.map { |key| [key, 'invalid'] }.to_h
+ empty_hash = section_keys.to_h { |key| [key, nil] }
+ invalid_hash = section_keys.to_h { |key| [key, 'invalid'] }
expect { conflict_file.resolve_lines({}) }
.to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError)
diff --git a/spec/lib/gitlab/crypto_helper_spec.rb b/spec/lib/gitlab/crypto_helper_spec.rb
index 024564ea213..616a37a4cb9 100644
--- a/spec/lib/gitlab/crypto_helper_spec.rb
+++ b/spec/lib/gitlab/crypto_helper_spec.rb
@@ -20,22 +20,24 @@ RSpec.describe Gitlab::CryptoHelper do
expect(encrypted).not_to include "\n"
end
- it 'does not save hashed token with iv value in database' do
- expect { described_class.aes256_gcm_encrypt('some-value') }.not_to change { TokenWithIv.count }
- end
-
it 'encrypts using static iv' do
expect(Encryptor).to receive(:encrypt).with(described_class::AES256_GCM_OPTIONS.merge(value: 'some-value', iv: described_class::AES256_GCM_IV_STATIC)).and_return('hashed_value')
described_class.aes256_gcm_encrypt('some-value')
end
- end
- describe '.aes256_gcm_decrypt' do
- before do
- stub_feature_flags(dynamic_nonce_creation: false)
+ context 'with provided iv' do
+ let(:iv) { create_nonce }
+
+ it 'encrypts using provided iv' do
+ expect(Encryptor).to receive(:encrypt).with(described_class::AES256_GCM_OPTIONS.merge(value: 'some-value', iv: iv)).and_return('hashed_value')
+
+ described_class.aes256_gcm_encrypt('some-value', nonce: iv)
+ end
end
+ end
+ describe '.aes256_gcm_decrypt' do
context 'when token was encrypted using static nonce' do
let(:encrypted) { described_class.aes256_gcm_encrypt('some-value', nonce: described_class::AES256_GCM_IV_STATIC) }
@@ -50,54 +52,22 @@ RSpec.describe Gitlab::CryptoHelper do
expect(decrypted).to eq 'some-value'
end
-
- it 'does not save hashed token with iv value in database' do
- expect { described_class.aes256_gcm_decrypt(encrypted) }.not_to change { TokenWithIv.count }
- end
-
- context 'with feature flag switched on' do
- before do
- stub_feature_flags(dynamic_nonce_creation: true)
- end
-
- it 'correctly decrypts encrypted string' do
- decrypted = described_class.aes256_gcm_decrypt(encrypted)
-
- expect(decrypted).to eq 'some-value'
- end
- end
end
context 'when token was encrypted using random nonce' do
let(:value) { 'random-value' }
-
- # for compatibility with tokens encrypted using dynamic nonce
- let!(:encrypted) do
- iv = create_nonce
- encrypted_token = described_class.create_encrypted_token(value, iv)
- TokenWithIv.create!(hashed_token: Digest::SHA256.digest(encrypted_token), hashed_plaintext_token: Digest::SHA256.digest(encrypted_token), iv: iv)
- encrypted_token
- end
-
- before do
- stub_feature_flags(dynamic_nonce_creation: true)
- end
+ let(:iv) { create_nonce }
+ let(:encrypted) { described_class.aes256_gcm_encrypt(value, nonce: iv) }
it 'correctly decrypts encrypted string' do
- decrypted = described_class.aes256_gcm_decrypt(encrypted)
+ decrypted = described_class.aes256_gcm_decrypt(encrypted, nonce: iv)
expect(decrypted).to eq value
end
-
- it 'does not save hashed token with iv value in database' do
- expect { described_class.aes256_gcm_decrypt(encrypted) }.not_to change { TokenWithIv.count }
- end
end
end
def create_nonce
- cipher = OpenSSL::Cipher.new('aes-256-gcm')
- cipher.encrypt # Required before '#random_iv' can be called
- cipher.random_iv # Ensures that the IV is the correct length respective to the algorithm used.
+ ::Digest::SHA256.hexdigest('my-value').bytes.take(TokenAuthenticatableStrategies::EncryptionHelper::NONCE_SIZE).pack('c*')
end
end
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index ab1728414bb..932238f281e 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -19,6 +19,9 @@ RSpec.describe Gitlab::DataBuilder::Build do
it { expect(data[:tag]).to eq(build.tag) }
it { expect(data[:build_id]).to eq(build.id) }
it { expect(data[:build_status]).to eq(build.status) }
+ it { expect(data[:build_created_at]).to eq(build.created_at) }
+ it { expect(data[:build_started_at]).to eq(build.started_at) }
+ it { expect(data[:build_finished_at]).to eq(build.finished_at) }
it { expect(data[:build_allow_failure]).to eq(false) }
it { expect(data[:build_failure_reason]).to eq(build.failure_reason) }
it { expect(data[:project_id]).to eq(build.project.id) }
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index cf04f560ceb..bec1e612c02 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -59,7 +59,6 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
expect(runner_data[:id]).to eq(ci_runner.id)
expect(runner_data[:description]).to eq(ci_runner.description)
expect(runner_data[:active]).to eq(ci_runner.active)
- expect(runner_data[:is_shared]).to eq(ci_runner.instance_type?)
expect(runner_data[:tags]).to match_array(tag_names)
end
end
diff --git a/spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb b/spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb
new file mode 100644
index 00000000000..e96862fbc2d
--- /dev/null
+++ b/spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Database::BackgroundMigration::BatchMetrics do
+ let(:batch_metrics) { described_class.new }
+
+ describe '#time_operation' do
+ it 'tracks the duration of the operation using monotonic time' do
+ expect(batch_metrics.timings).to be_empty
+
+ expect(Gitlab::Metrics::System).to receive(:monotonic_time)
+ .exactly(6).times
+ .and_return(0.0, 111.0, 200.0, 290.0, 300.0, 410.0)
+
+ batch_metrics.time_operation(:my_label) do
+ # some operation
+ end
+
+ batch_metrics.time_operation(:my_other_label) do
+ # some operation
+ end
+
+ batch_metrics.time_operation(:my_label) do
+ # some operation
+ end
+
+ expect(batch_metrics.timings).to eq(my_label: [111.0, 110.0], my_other_label: [90.0])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
new file mode 100644
index 00000000000..7d0e10b62c6
--- /dev/null
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
@@ -0,0 +1,198 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
+ let(:migration_wrapper) { double('test wrapper') }
+ let(:runner) { described_class.new(migration_wrapper) }
+
+ describe '#run_migration_job' do
+ shared_examples_for 'it has completed the migration' do
+ it 'does not create and run a migration job' do
+ expect(migration_wrapper).not_to receive(:perform)
+
+ expect do
+ runner.run_migration_job(migration)
+ end.not_to change { Gitlab::Database::BackgroundMigration::BatchedJob.count }
+ end
+
+ it 'marks the migration as finished' do
+ relation = Gitlab::Database::BackgroundMigration::BatchedMigration.finished.where(id: migration.id)
+
+ expect { runner.run_migration_job(migration) }.to change { relation.count }.by(1)
+ end
+ end
+
+ context 'when the migration has no previous jobs' do
+ let(:migration) { create(:batched_background_migration, :active, batch_size: 2) }
+
+ let(:job_relation) do
+ Gitlab::Database::BackgroundMigration::BatchedJob.where(batched_background_migration_id: migration.id)
+ end
+
+ context 'when the migration has batches to process' do
+ let!(:event1) { create(:event) }
+ let!(:event2) { create(:event) }
+ let!(:event3) { create(:event) }
+
+ it 'runs the job for the first batch' do
+ migration.update!(min_value: event1.id, max_value: event2.id)
+
+ expect(migration_wrapper).to receive(:perform) do |job_record|
+ expect(job_record).to eq(job_relation.first)
+ end
+
+ expect { runner.run_migration_job(migration) }.to change { job_relation.count }.by(1)
+
+ expect(job_relation.first).to have_attributes(
+ min_value: event1.id,
+ max_value: event2.id,
+ batch_size: migration.batch_size,
+ sub_batch_size: migration.sub_batch_size)
+ end
+ end
+
+ context 'when the batch maximum exceeds the migration maximum' do
+ let!(:events) { create_list(:event, 3) }
+ let(:event1) { events[0] }
+ let(:event2) { events[1] }
+
+ it 'clamps the batch maximum to the migration maximum' do
+ migration.update!(min_value: event1.id, max_value: event2.id, batch_size: 5)
+
+ expect(migration_wrapper).to receive(:perform)
+
+ expect { runner.run_migration_job(migration) }.to change { job_relation.count }.by(1)
+
+ expect(job_relation.first).to have_attributes(
+ min_value: event1.id,
+ max_value: event2.id,
+ batch_size: migration.batch_size,
+ sub_batch_size: migration.sub_batch_size)
+ end
+ end
+
+ context 'when the migration has no batches to process' do
+ it_behaves_like 'it has completed the migration'
+ end
+ end
+
+ context 'when the migration has previous jobs' do
+ let!(:event1) { create(:event) }
+ let!(:event2) { create(:event) }
+ let!(:event3) { create(:event) }
+
+ let!(:migration) do
+ create(:batched_background_migration, :active, batch_size: 2, min_value: event1.id, max_value: event3.id)
+ end
+
+ let!(:previous_job) do
+ create(:batched_background_migration_job,
+ batched_migration: migration,
+ min_value: event1.id,
+ max_value: event2.id,
+ batch_size: 2,
+ sub_batch_size: 1)
+ end
+
+ let(:job_relation) do
+ Gitlab::Database::BackgroundMigration::BatchedJob.where(batched_background_migration_id: migration.id)
+ end
+
+ context 'when the migration has batches to process' do
+ it 'runs the migration job for the next batch' do
+ expect(migration_wrapper).to receive(:perform) do |job_record|
+ expect(job_record).to eq(job_relation.last)
+ end
+
+ expect { runner.run_migration_job(migration) }.to change { job_relation.count }.by(1)
+
+ expect(job_relation.last).to have_attributes(
+ min_value: event3.id,
+ max_value: event3.id,
+ batch_size: migration.batch_size,
+ sub_batch_size: migration.sub_batch_size)
+ end
+
+ context 'when the batch minimum exceeds the migration maximum' do
+ before do
+ migration.update!(batch_size: 5, max_value: event2.id)
+ end
+
+ it_behaves_like 'it has completed the migration'
+ end
+ end
+
+ context 'when the migration has no batches remaining' do
+ before do
+ create(:batched_background_migration_job,
+ batched_migration: migration,
+ min_value: event3.id,
+ max_value: event3.id,
+ batch_size: 2,
+ sub_batch_size: 1)
+ end
+
+ it_behaves_like 'it has completed the migration'
+ end
+ end
+ end
+
+ describe '#run_entire_migration' do
+ context 'when not in a development or test environment' do
+ it 'raises an error' do
+ environment = double('environment', development?: false, test?: false)
+ migration = build(:batched_background_migration, :finished)
+
+ allow(Rails).to receive(:env).and_return(environment)
+
+ expect do
+ runner.run_entire_migration(migration)
+ end.to raise_error('this method is not intended for use in real environments')
+ end
+ end
+
+ context 'when the given migration is not active' do
+ it 'does not create and run migration jobs' do
+ migration = build(:batched_background_migration, :finished)
+
+ expect(migration_wrapper).not_to receive(:perform)
+
+ expect do
+ runner.run_entire_migration(migration)
+ end.not_to change { Gitlab::Database::BackgroundMigration::BatchedJob.count }
+ end
+ end
+
+ context 'when the given migration is active' do
+ let!(:event1) { create(:event) }
+ let!(:event2) { create(:event) }
+ let!(:event3) { create(:event) }
+
+ let!(:migration) do
+ create(:batched_background_migration, :active, batch_size: 2, min_value: event1.id, max_value: event3.id)
+ end
+
+ let(:job_relation) do
+ Gitlab::Database::BackgroundMigration::BatchedJob.where(batched_background_migration_id: migration.id)
+ end
+
+ it 'runs all jobs inline until finishing the migration' do
+ expect(migration_wrapper).to receive(:perform) do |job_record|
+ expect(job_record).to eq(job_relation.first)
+ end
+
+ expect(migration_wrapper).to receive(:perform) do |job_record|
+ expect(job_record).to eq(job_relation.last)
+ end
+
+ expect { runner.run_entire_migration(migration) }.to change { job_relation.count }.by(2)
+
+ expect(job_relation.first).to have_attributes(min_value: event1.id, max_value: event2.id)
+ expect(job_relation.last).to have_attributes(min_value: event3.id, max_value: event3.id)
+
+ expect(migration.reload).to be_finished
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
index f4a939e7c1f..261e23d0745 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
@@ -29,6 +29,16 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
end
+ describe '.active_migration' do
+ let!(:migration1) { create(:batched_background_migration, :finished) }
+ let!(:migration2) { create(:batched_background_migration, :active) }
+ let!(:migration3) { create(:batched_background_migration, :active) }
+
+ it 'returns the first active migration according to queue order' do
+ expect(described_class.active_migration).to eq(migration2)
+ end
+ end
+
describe '#interval_elapsed?' do
context 'when the migration has no last_job' do
let(:batched_migration) { build(:batched_background_migration) }
@@ -77,6 +87,34 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
end
end
+
+ context 'when an interval variance is given' do
+ let(:variance) { 2.seconds }
+
+ context 'when the last job is less than an interval with variance old' do
+ it 'returns false' do
+ freeze_time do
+ create(:batched_background_migration_job,
+ batched_migration: batched_migration,
+ created_at: Time.current - 1.minute - 57.seconds)
+
+ expect(batched_migration.interval_elapsed?(variance: variance)).to eq(false)
+ end
+ end
+ end
+
+ context 'when the last job is more than an interval with variance old' do
+ it 'returns true' do
+ freeze_time do
+ create(:batched_background_migration_job,
+ batched_migration: batched_migration,
+ created_at: Time.current - 1.minute - 58.seconds)
+
+ expect(batched_migration.interval_elapsed?(variance: variance)).to eq(true)
+ end
+ end
+ end
+ end
end
end
@@ -157,4 +195,17 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
describe '#batch_class_name=' do
it_behaves_like 'an attr_writer that demodulizes assigned class names', :batch_class_name
end
+
+ describe '#prometheus_labels' do
+ let(:batched_migration) { create(:batched_background_migration, job_class_name: 'TestMigration', table_name: 'foo', column_name: 'bar') }
+
+ it 'returns a hash with labels for the migration' do
+ labels = {
+ migration_id: batched_migration.id,
+ migration_identifier: 'TestMigration/foo.bar'
+ }
+
+ expect(batched_migration.prometheus_labels).to eq(labels)
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb
index 17cceb35ff7..00d13f23d36 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb
@@ -3,43 +3,105 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '#perform' do
- let(:migration_wrapper) { described_class.new }
+ subject { described_class.new.perform(job_record) }
+
let(:job_class) { Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJob }
let_it_be(:active_migration) { create(:batched_background_migration, :active, job_arguments: [:id, :other_id]) }
let!(:job_record) { create(:batched_background_migration_job, batched_migration: active_migration) }
+ let(:job_instance) { double('job instance', batch_metrics: {}) }
+
+ before do
+ allow(job_class).to receive(:new).and_return(job_instance)
+ end
it 'runs the migration job' do
- expect_next_instance_of(job_class) do |job_instance|
- expect(job_instance).to receive(:perform).with(1, 10, 'events', 'id', 1, 'id', 'other_id')
- end
+ expect(job_instance).to receive(:perform).with(1, 10, 'events', 'id', 1, 'id', 'other_id')
- migration_wrapper.perform(job_record)
+ subject
end
- it 'updates the the tracking record in the database' do
+ it 'updates the tracking record in the database' do
+ test_metrics = { 'my_metris' => 'some value' }
+
+ expect(job_instance).to receive(:perform)
+ expect(job_instance).to receive(:batch_metrics).and_return(test_metrics)
+
expect(job_record).to receive(:update!).with(hash_including(attempts: 1, status: :running)).and_call_original
freeze_time do
- migration_wrapper.perform(job_record)
+ subject
reloaded_job_record = job_record.reload
expect(reloaded_job_record).not_to be_pending
expect(reloaded_job_record.attempts).to eq(1)
expect(reloaded_job_record.started_at).to eq(Time.current)
+ expect(reloaded_job_record.metrics).to eq(test_metrics)
+ end
+ end
+
+ context 'reporting prometheus metrics' do
+ let(:labels) { job_record.batched_migration.prometheus_labels }
+
+ before do
+ allow(job_instance).to receive(:perform)
+ end
+
+ it 'reports batch_size' do
+ expect(described_class.metrics[:gauge_batch_size]).to receive(:set).with(labels, job_record.batch_size)
+
+ subject
+ end
+
+ it 'reports sub_batch_size' do
+ expect(described_class.metrics[:gauge_sub_batch_size]).to receive(:set).with(labels, job_record.sub_batch_size)
+
+ subject
+ end
+
+ it 'reports updated tuples (currently based on batch_size)' do
+ expect(described_class.metrics[:counter_updated_tuples]).to receive(:increment).with(labels, job_record.batch_size)
+
+ subject
+ end
+
+ it 'reports summary of query timings' do
+ metrics = { 'timings' => { 'update_all' => [1, 2, 3, 4, 5] } }
+
+ expect(job_instance).to receive(:batch_metrics).and_return(metrics)
+
+ metrics['timings'].each do |key, timings|
+ summary_labels = labels.merge(operation: key)
+ timings.each do |timing|
+ expect(described_class.metrics[:histogram_timings]).to receive(:observe).with(summary_labels, timing)
+ end
+ end
+
+ subject
+ end
+
+ it 'reports time efficiency' do
+ freeze_time do
+ expect(Time).to receive(:current).and_return(Time.zone.now - 5.seconds).ordered
+ expect(Time).to receive(:current).and_return(Time.zone.now).ordered
+
+ ratio = 5 / job_record.batched_migration.interval.to_f
+
+ expect(described_class.metrics[:histogram_time_efficiency]).to receive(:observe).with(labels, ratio)
+
+ subject
+ end
end
end
context 'when the migration job does not raise an error' do
it 'marks the tracking record as succeeded' do
- expect_next_instance_of(job_class) do |job_instance|
- expect(job_instance).to receive(:perform).with(1, 10, 'events', 'id', 1, 'id', 'other_id')
- end
+ expect(job_instance).to receive(:perform).with(1, 10, 'events', 'id', 1, 'id', 'other_id')
freeze_time do
- migration_wrapper.perform(job_record)
+ subject
reloaded_job_record = job_record.reload
@@ -51,14 +113,12 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
context 'when the migration job raises an error' do
it 'marks the tracking record as failed before raising the error' do
- expect_next_instance_of(job_class) do |job_instance|
- expect(job_instance).to receive(:perform)
- .with(1, 10, 'events', 'id', 1, 'id', 'other_id')
- .and_raise(RuntimeError, 'Something broke!')
- end
+ expect(job_instance).to receive(:perform)
+ .with(1, 10, 'events', 'id', 1, 'id', 'other_id')
+ .and_raise(RuntimeError, 'Something broke!')
freeze_time do
- expect { migration_wrapper.perform(job_record) }.to raise_error(RuntimeError, 'Something broke!')
+ expect { subject }.to raise_error(RuntimeError, 'Something broke!')
reloaded_job_record = job_record.reload
diff --git a/spec/lib/gitlab/database/background_migration/scheduler_spec.rb b/spec/lib/gitlab/database/background_migration/scheduler_spec.rb
deleted file mode 100644
index ba745acdf8a..00000000000
--- a/spec/lib/gitlab/database/background_migration/scheduler_spec.rb
+++ /dev/null
@@ -1,182 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Database::BackgroundMigration::Scheduler, '#perform' do
- let(:scheduler) { described_class.new }
-
- shared_examples_for 'it has no jobs to run' do
- it 'does not create and run a migration job' do
- test_wrapper = double('test wrapper')
-
- expect(test_wrapper).not_to receive(:perform)
-
- expect do
- scheduler.perform(migration_wrapper: test_wrapper)
- end.not_to change { Gitlab::Database::BackgroundMigration::BatchedJob.count }
- end
- end
-
- context 'when there are no active migrations' do
- let!(:migration) { create(:batched_background_migration, :finished) }
-
- it_behaves_like 'it has no jobs to run'
- end
-
- shared_examples_for 'it has completed the migration' do
- it 'marks the migration as finished' do
- relation = Gitlab::Database::BackgroundMigration::BatchedMigration.finished.where(id: first_migration.id)
-
- expect { scheduler.perform }.to change { relation.count }.by(1)
- end
- end
-
- context 'when there are active migrations' do
- let!(:first_migration) { create(:batched_background_migration, :active, batch_size: 2) }
- let!(:last_migration) { create(:batched_background_migration, :active) }
-
- let(:job_relation) do
- Gitlab::Database::BackgroundMigration::BatchedJob.where(batched_background_migration_id: first_migration.id)
- end
-
- context 'when the migration interval has not elapsed' do
- before do
- expect_next_found_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigration) do |migration|
- expect(migration).to receive(:interval_elapsed?).and_return(false)
- end
- end
-
- it_behaves_like 'it has no jobs to run'
- end
-
- context 'when the interval has elapsed' do
- before do
- expect_next_found_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigration) do |migration|
- expect(migration).to receive(:interval_elapsed?).and_return(true)
- end
- end
-
- context 'when the first migration has no previous jobs' do
- context 'when the migration has batches to process' do
- let!(:event1) { create(:event) }
- let!(:event2) { create(:event) }
- let!(:event3) { create(:event) }
-
- it 'runs the job for the first batch' do
- first_migration.update!(min_value: event1.id, max_value: event3.id)
-
- expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper) do |wrapper|
- expect(wrapper).to receive(:perform).and_wrap_original do |_, job_record|
- expect(job_record).to eq(job_relation.first)
- end
- end
-
- expect { scheduler.perform }.to change { job_relation.count }.by(1)
-
- expect(job_relation.first).to have_attributes(
- min_value: event1.id,
- max_value: event2.id,
- batch_size: first_migration.batch_size,
- sub_batch_size: first_migration.sub_batch_size)
- end
- end
-
- context 'when the migration has no batches to process' do
- it_behaves_like 'it has no jobs to run'
- it_behaves_like 'it has completed the migration'
- end
- end
-
- context 'when the first migration has previous jobs' do
- let!(:event1) { create(:event) }
- let!(:event2) { create(:event) }
- let!(:event3) { create(:event) }
-
- let!(:previous_job) do
- create(:batched_background_migration_job,
- batched_migration: first_migration,
- min_value: event1.id,
- max_value: event2.id,
- batch_size: 2,
- sub_batch_size: 1)
- end
-
- context 'when the migration is ready to process another job' do
- it 'runs the migration job for the next batch' do
- first_migration.update!(min_value: event1.id, max_value: event3.id)
-
- expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper) do |wrapper|
- expect(wrapper).to receive(:perform).and_wrap_original do |_, job_record|
- expect(job_record).to eq(job_relation.last)
- end
- end
-
- expect { scheduler.perform }.to change { job_relation.count }.by(1)
-
- expect(job_relation.last).to have_attributes(
- min_value: event3.id,
- max_value: event3.id,
- batch_size: first_migration.batch_size,
- sub_batch_size: first_migration.sub_batch_size)
- end
- end
-
- context 'when the migration has no batches remaining' do
- let!(:final_job) do
- create(:batched_background_migration_job,
- batched_migration: first_migration,
- min_value: event3.id,
- max_value: event3.id,
- batch_size: 2,
- sub_batch_size: 1)
- end
-
- it_behaves_like 'it has no jobs to run'
- it_behaves_like 'it has completed the migration'
- end
- end
-
- context 'when the bounds of the next batch exceed the migration maximum value' do
- let!(:events) { create_list(:event, 3) }
- let(:event1) { events[0] }
- let(:event2) { events[1] }
-
- context 'when the batch maximum exceeds the migration maximum' do
- it 'clamps the batch maximum to the migration maximum' do
- first_migration.update!(batch_size: 5, min_value: event1.id, max_value: event2.id)
-
- expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper) do |wrapper|
- expect(wrapper).to receive(:perform)
- end
-
- expect { scheduler.perform }.to change { job_relation.count }.by(1)
-
- expect(job_relation.first).to have_attributes(
- min_value: event1.id,
- max_value: event2.id,
- batch_size: first_migration.batch_size,
- sub_batch_size: first_migration.sub_batch_size)
- end
- end
-
- context 'when the batch minimum exceeds the migration maximum' do
- let!(:previous_job) do
- create(:batched_background_migration_job,
- batched_migration: first_migration,
- min_value: event1.id,
- max_value: event2.id,
- batch_size: 5,
- sub_batch_size: 1)
- end
-
- before do
- first_migration.update!(batch_size: 5, min_value: 1, max_value: event2.id)
- end
-
- it_behaves_like 'it has no jobs to run'
- it_behaves_like 'it has completed the migration'
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/database/batch_count_spec.rb b/spec/lib/gitlab/database/batch_count_spec.rb
index 29688b18e94..da13bc425d1 100644
--- a/spec/lib/gitlab/database/batch_count_spec.rb
+++ b/spec/lib/gitlab/database/batch_count_spec.rb
@@ -270,6 +270,8 @@ RSpec.describe Gitlab::Database::BatchCount do
end
it "defaults the batch size to #{Gitlab::Database::BatchCounter::DEFAULT_DISTINCT_BATCH_SIZE}" do
+ stub_feature_flags(loose_index_scan_for_distinct_values: false)
+
min_id = model.minimum(:id)
relation = instance_double(ActiveRecord::Relation)
allow(model).to receive_message_chain(:select, public_send: relation)
@@ -315,13 +317,85 @@ RSpec.describe Gitlab::Database::BatchCount do
end
end
- it_behaves_like 'when batch fetch query is canceled' do
+ context 'when the loose_index_scan_for_distinct_values feature flag is off' do
+ it_behaves_like 'when batch fetch query is canceled' do
+ let(:mode) { :distinct }
+ let(:operation) { :count }
+ let(:operation_args) { nil }
+ let(:column) { nil }
+
+ subject { described_class.method(:batch_distinct_count) }
+
+ before do
+ stub_feature_flags(loose_index_scan_for_distinct_values: false)
+ end
+ end
+ end
+
+ context 'when the loose_index_scan_for_distinct_values feature flag is on' do
let(:mode) { :distinct }
let(:operation) { :count }
let(:operation_args) { nil }
let(:column) { nil }
+ let(:batch_size) { 10_000 }
+
subject { described_class.method(:batch_distinct_count) }
+
+ before do
+ stub_feature_flags(loose_index_scan_for_distinct_values: true)
+ end
+
+ it 'reduces batch size by half and retry fetch' do
+ too_big_batch_relation_mock = instance_double(ActiveRecord::Relation)
+
+ count_method = double(send: 1)
+
+ allow(too_big_batch_relation_mock).to receive(:send).and_raise(ActiveRecord::QueryCanceled)
+ allow(Gitlab::Database::LooseIndexScanDistinctCount).to receive_message_chain(:new, :build_query).with(from: 0, to: batch_size).and_return(too_big_batch_relation_mock)
+ allow(Gitlab::Database::LooseIndexScanDistinctCount).to receive_message_chain(:new, :build_query).with(from: 0, to: batch_size / 2).and_return(count_method)
+ allow(Gitlab::Database::LooseIndexScanDistinctCount).to receive_message_chain(:new, :build_query).with(from: batch_size / 2, to: batch_size).and_return(count_method)
+
+ subject.call(model, column, batch_size: batch_size, start: 0, finish: batch_size - 1)
+ end
+
+ context 'when all retries fail' do
+ let(:batch_count_query) { 'SELECT COUNT(id) FROM relation WHERE id BETWEEN 0 and 1' }
+
+ before do
+ relation = instance_double(ActiveRecord::Relation)
+ allow(Gitlab::Database::LooseIndexScanDistinctCount).to receive_message_chain(:new, :build_query).and_return(relation)
+ allow(relation).to receive(:send).and_raise(ActiveRecord::QueryCanceled.new('query timed out'))
+ allow(relation).to receive(:to_sql).and_return(batch_count_query)
+ end
+
+ it 'logs failing query' do
+ expect(Gitlab::AppJsonLogger).to receive(:error).with(
+ event: 'batch_count',
+ relation: model.table_name,
+ operation: operation,
+ operation_args: operation_args,
+ start: 0,
+ mode: mode,
+ query: batch_count_query,
+ message: 'Query has been canceled with message: query timed out'
+ )
+ expect(subject.call(model, column, batch_size: batch_size, start: 0)).to eq(-1)
+ end
+ end
+
+ context 'when LooseIndexScanDistinctCount raises error' do
+ let(:column) { :creator_id }
+ let(:error_class) { Gitlab::Database::LooseIndexScanDistinctCount::ColumnConfigurationError }
+
+ it 'rescues ColumnConfigurationError' do
+ allow(Gitlab::Database::LooseIndexScanDistinctCount).to receive(:new).and_raise(error_class.new('error message'))
+
+ expect(Gitlab::AppJsonLogger).to receive(:error).with(a_hash_including(message: 'LooseIndexScanDistinctCount column error: error message'))
+
+ expect(subject.call(Project, column, batch_size: 10_000, start: 0)).to eq(-1)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/database/loose_index_scan_distinct_count_spec.rb b/spec/lib/gitlab/database/loose_index_scan_distinct_count_spec.rb
new file mode 100644
index 00000000000..e0eac26e4d9
--- /dev/null
+++ b/spec/lib/gitlab/database/loose_index_scan_distinct_count_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::LooseIndexScanDistinctCount do
+ context 'counting distinct users' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:other_user) { create(:user) }
+
+ let(:column) { :creator_id }
+
+ before_all do
+ create_list(:project, 3, creator: user)
+ create_list(:project, 1, creator: other_user)
+ end
+
+ subject(:count) { described_class.new(Project, :creator_id).count(from: Project.minimum(:creator_id), to: Project.maximum(:creator_id) + 1) }
+
+ it { is_expected.to eq(2) }
+
+ context 'when STI model is queried' do
+ it 'does not raise error' do
+ expect { described_class.new(Group, :owner_id).count(from: 0, to: 1) }.not_to raise_error
+ end
+ end
+
+ context 'when model with default_scope is queried' do
+ it 'does not raise error' do
+ expect { described_class.new(GroupMember, :id).count(from: 0, to: 1) }.not_to raise_error
+ end
+ end
+
+ context 'when the fully qualified column is given' do
+ let(:column) { 'projects.creator_id' }
+
+ it { is_expected.to eq(2) }
+ end
+
+ context 'when AR attribute is given' do
+ let(:column) { Project.arel_table[:creator_id] }
+
+ it { is_expected.to eq(2) }
+ end
+
+ context 'when invalid value is given for the column' do
+ let(:column) { Class.new }
+
+ it { expect { described_class.new(Group, column) }.to raise_error(Gitlab::Database::LooseIndexScanDistinctCount::ColumnConfigurationError) }
+ end
+
+ context 'when null values are present' do
+ before do
+ create_list(:project, 2).each { |p| p.update_column(:creator_id, nil) }
+ end
+
+ it { is_expected.to eq(2) }
+ end
+ end
+
+ context 'counting STI models' do
+ let!(:groups) { create_list(:group, 3) }
+ let!(:namespaces) { create_list(:namespace, 2) }
+
+ let(:max_id) { Namespace.maximum(:id) + 1 }
+
+ it 'counts groups' do
+ count = described_class.new(Group, :id).count(from: 0, to: max_id)
+ expect(count).to eq(3)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 9178707a3d0..44293086e79 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -835,7 +835,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:install_rename_triggers_for_postgresql)
- .with(trigger_name, '"users"', '"old"', '"new"')
+ .with(:users, :old, :new)
expect(model).to receive(:add_column)
.with(:users, :new, :integer,
@@ -860,14 +860,18 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
context 'with existing records and type casting' do
let(:trigger_name) { model.rename_trigger_name(:users, :id, :new) }
let(:user) { create(:user) }
+ let(:copy_trigger) { double('copy trigger') }
+
+ before do
+ expect(Gitlab::Database::UnidirectionalCopyTrigger).to receive(:on_table)
+ .with(:users).and_return(copy_trigger)
+ end
it 'copies the value to the new column using the type_cast_function', :aggregate_failures do
expect(model).to receive(:copy_indexes).with(:users, :id, :new)
expect(model).to receive(:add_not_null_constraint).with(:users, :new)
expect(model).to receive(:execute).with("UPDATE \"users\" SET \"new\" = cast_to_jsonb_with_default(\"users\".\"id\") WHERE \"users\".\"id\" >= #{user.id}")
- expect(model).to receive(:execute).with("DROP TRIGGER IF EXISTS #{trigger_name}\nON \"users\"\n")
- expect(model).to receive(:execute).with("CREATE TRIGGER #{trigger_name}\nBEFORE INSERT OR UPDATE\nON \"users\"\nFOR EACH ROW\nEXECUTE FUNCTION #{trigger_name}()\n")
- expect(model).to receive(:execute).with("CREATE OR REPLACE FUNCTION #{trigger_name}()\nRETURNS trigger AS\n$BODY$\nBEGIN\n NEW.\"new\" := NEW.\"id\";\n RETURN NEW;\nEND;\n$BODY$\nLANGUAGE 'plpgsql'\nVOLATILE\n")
+ expect(copy_trigger).to receive(:create).with(:id, :new, trigger_name: nil)
model.rename_column_concurrently(:users, :id, :new, type_cast_function: 'cast_to_jsonb_with_default')
end
@@ -996,7 +1000,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:install_rename_triggers_for_postgresql)
- .with(trigger_name, '"users"', '"old"', '"new"')
+ .with(:users, :old, :new)
expect(model).to receive(:add_column)
.with(:users, :old, :integer,
@@ -1156,7 +1160,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
.with(:users, temp_undo_cleanup_column, :old)
expect(model).to receive(:install_rename_triggers_for_postgresql)
- .with(trigger_name, '"users"', '"old"', '"old_for_type_change"')
+ .with(:users, :old, 'old_for_type_change')
model.undo_cleanup_concurrent_column_type_change(:users, :old, :string)
end
@@ -1182,7 +1186,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
.with(:users, temp_undo_cleanup_column, :old)
expect(model).to receive(:install_rename_triggers_for_postgresql)
- .with(trigger_name, '"users"', '"old"', '"old_for_type_change"')
+ .with(:users, :old, 'old_for_type_change')
model.undo_cleanup_concurrent_column_type_change(
:users,
@@ -1204,28 +1208,25 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
describe '#install_rename_triggers_for_postgresql' do
it 'installs the triggers for PostgreSQL' do
- expect(model).to receive(:execute)
- .with(/CREATE OR REPLACE FUNCTION foo()/m)
+ copy_trigger = double('copy trigger')
- expect(model).to receive(:execute)
- .with(/DROP TRIGGER IF EXISTS foo/m)
+ expect(Gitlab::Database::UnidirectionalCopyTrigger).to receive(:on_table)
+ .with(:users).and_return(copy_trigger)
- expect(model).to receive(:execute)
- .with(/CREATE TRIGGER foo/m)
+ expect(copy_trigger).to receive(:create).with(:old, :new, trigger_name: 'foo')
- model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
- end
-
- it 'does not fail if trigger already exists' do
- model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
- model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
+ model.install_rename_triggers_for_postgresql(:users, :old, :new, trigger_name: 'foo')
end
end
describe '#remove_rename_triggers_for_postgresql' do
it 'removes the function and trigger' do
- expect(model).to receive(:execute).with('DROP TRIGGER IF EXISTS foo ON bar')
- expect(model).to receive(:execute).with('DROP FUNCTION IF EXISTS foo()')
+ copy_trigger = double('copy trigger')
+
+ expect(Gitlab::Database::UnidirectionalCopyTrigger).to receive(:on_table)
+ .with('bar').and_return(copy_trigger)
+
+ expect(copy_trigger).to receive(:drop).with('foo')
model.remove_rename_triggers_for_postgresql('bar', 'foo')
end
@@ -1702,65 +1703,171 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
describe '#initialize_conversion_of_integer_to_bigint' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:issue) { create(:issue, project: project) }
- let!(:event) do
- create(:event, :created, project: project, target: issue, author: user)
+ let(:table) { :test_table }
+ let(:column) { :id }
+ let(:tmp_column) { "#{column}_convert_to_bigint" }
+
+ before do
+ model.create_table table, id: false do |t|
+ t.integer :id, primary_key: true
+ t.integer :non_nullable_column, null: false
+ t.integer :nullable_column
+ t.timestamps
+ end
end
- context 'in a transaction' do
- it 'raises RuntimeError' do
- allow(model).to receive(:transaction_open?).and_return(true)
+ context 'when the target table does not exist' do
+ it 'raises an error' do
+ expect { model.initialize_conversion_of_integer_to_bigint(:this_table_is_not_real, column) }
+ .to raise_error('Table this_table_is_not_real does not exist')
+ end
+ end
- expect { model.initialize_conversion_of_integer_to_bigint(:events, :id) }
- .to raise_error(RuntimeError)
+ context 'when the primary key does not exist' do
+ it 'raises an error' do
+ expect { model.initialize_conversion_of_integer_to_bigint(table, column, primary_key: :foobar) }
+ .to raise_error("Column foobar does not exist on #{table}")
end
end
- context 'outside a transaction' do
- before do
- allow(model).to receive(:transaction_open?).and_return(false)
+ context 'when the column to convert does not exist' do
+ let(:column) { :foobar }
+
+ it 'raises an error' do
+ expect { model.initialize_conversion_of_integer_to_bigint(table, column) }
+ .to raise_error("Column #{column} does not exist on #{table}")
end
+ end
- it 'creates a bigint column and starts backfilling it' do
- expect(model)
- .to receive(:add_column)
- .with(
- :events,
- 'id_convert_to_bigint',
- :bigint,
- default: 0,
- null: false
- )
+ context 'when the column to convert is the primary key' do
+ it 'creates a not-null bigint column and installs triggers' do
+ expect(model).to receive(:add_column).with(table, tmp_column, :bigint, default: 0, null: false)
- expect(model)
- .to receive(:install_rename_triggers)
- .with(:events, :id, 'id_convert_to_bigint')
+ expect(model).to receive(:install_rename_triggers).with(table, column, tmp_column)
- expect(model).to receive(:queue_background_migration_jobs_by_range_at_intervals).and_call_original
+ model.initialize_conversion_of_integer_to_bigint(table, column)
+ end
+ end
- expect(BackgroundMigrationWorker)
- .to receive(:perform_in)
- .ordered
- .with(
- 2.minutes,
- 'CopyColumnUsingBackgroundMigrationJob',
- [event.id, event.id, :events, :id, 100, :id, 'id_convert_to_bigint']
- )
+ context 'when the column to convert is not the primary key, but non-nullable' do
+ let(:column) { :non_nullable_column }
+
+ it 'creates a not-null bigint column and installs triggers' do
+ expect(model).to receive(:add_column).with(table, tmp_column, :bigint, default: 0, null: false)
+
+ expect(model).to receive(:install_rename_triggers).with(table, column, tmp_column)
+
+ model.initialize_conversion_of_integer_to_bigint(table, column)
+ end
+ end
+
+ context 'when the column to convert is not the primary key, but nullable' do
+ let(:column) { :nullable_column }
+
+ it 'creates a nullable bigint column and installs triggers' do
+ expect(model).to receive(:add_column).with(table, tmp_column, :bigint, default: nil)
+
+ expect(model).to receive(:install_rename_triggers).with(table, column, tmp_column)
+
+ model.initialize_conversion_of_integer_to_bigint(table, column)
+ end
+ end
+ end
+
+ describe '#backfill_conversion_of_integer_to_bigint' do
+ let(:table) { :_test_backfill_table }
+ let(:column) { :id }
+ let(:tmp_column) { "#{column}_convert_to_bigint" }
+
+ before do
+ model.create_table table, id: false do |t|
+ t.integer :id, primary_key: true
+ t.text :message, null: false
+ t.timestamps
+ end
- expect(Gitlab::BackgroundMigration)
- .to receive(:steal)
- .ordered
- .with('CopyColumnUsingBackgroundMigrationJob')
+ allow(model).to receive(:perform_background_migration_inline?).and_return(false)
+ end
- model.initialize_conversion_of_integer_to_bigint(
- :events,
- :id,
- batch_size: 300,
- sub_batch_size: 100
+ context 'when the target table does not exist' do
+ it 'raises an error' do
+ expect { model.backfill_conversion_of_integer_to_bigint(:this_table_is_not_real, column) }
+ .to raise_error('Table this_table_is_not_real does not exist')
+ end
+ end
+
+ context 'when the primary key does not exist' do
+ it 'raises an error' do
+ expect { model.backfill_conversion_of_integer_to_bigint(table, column, primary_key: :foobar) }
+ .to raise_error("Column foobar does not exist on #{table}")
+ end
+ end
+
+ context 'when the column to convert does not exist' do
+ let(:column) { :foobar }
+
+ it 'raises an error' do
+ expect { model.backfill_conversion_of_integer_to_bigint(table, column) }
+ .to raise_error("Column #{column} does not exist on #{table}")
+ end
+ end
+
+ context 'when the temporary column does not exist' do
+ it 'raises an error' do
+ expect { model.backfill_conversion_of_integer_to_bigint(table, column) }
+ .to raise_error('The temporary column does not exist, initialize it with `initialize_conversion_of_integer_to_bigint`')
+ end
+ end
+
+ context 'when the conversion is properly initialized' do
+ let(:model_class) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = :_test_backfill_table
+ end
+ end
+
+ let(:migration_relation) { Gitlab::Database::BackgroundMigration::BatchedMigration.active }
+
+ before do
+ model.initialize_conversion_of_integer_to_bigint(table, column)
+
+ model_class.create!(message: 'hello')
+ model_class.create!(message: 'so long')
+ end
+
+ it 'creates the batched migration tracking record' do
+ last_record = model_class.create!(message: 'goodbye')
+
+ expect do
+ model.backfill_conversion_of_integer_to_bigint(table, column, batch_size: 2, sub_batch_size: 1)
+ end.to change { migration_relation.count }.by(1)
+
+ expect(migration_relation.last).to have_attributes(
+ job_class_name: 'CopyColumnUsingBackgroundMigrationJob',
+ table_name: table.to_s,
+ column_name: column.to_s,
+ min_value: 1,
+ max_value: last_record.id,
+ interval: 120,
+ batch_size: 2,
+ sub_batch_size: 1,
+ job_arguments: [column.to_s, "#{column}_convert_to_bigint"]
)
end
+
+ context 'when the migration should be performed inline' do
+ it 'calls the runner to run the entire migration' do
+ expect(model).to receive(:perform_background_migration_inline?).and_return(true)
+
+ expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |scheduler|
+ expect(scheduler).to receive(:run_entire_migration) do |batched_migration|
+ expect(batched_migration).to eq(migration_relation.last)
+ end
+ end
+
+ model.backfill_conversion_of_integer_to_bigint(table, column, batch_size: 2, sub_batch_size: 1)
+ end
+ end
end
end
@@ -1910,9 +2017,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
def setup
namespace = namespaces.create!(name: 'foo', path: 'foo')
- project = projects.create!(namespace_id: namespace.id)
-
- project
+ projects.create!(namespace_id: namespace.id)
end
it 'generates iids properly for models created after the migration' do
diff --git a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
index e25e4af2e86..c6d456964cf 100644
--- a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
@@ -263,7 +263,15 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
end
describe '#queue_batched_background_migration' do
+ let(:pgclass_info) { instance_double('Gitlab::Database::PgClass', cardinality_estimate: 42) }
+
+ before do
+ allow(Gitlab::Database::PgClass).to receive(:for_table).and_call_original
+ end
+
it 'creates the database record for the migration' do
+ expect(Gitlab::Database::PgClass).to receive(:for_table).with(:projects).and_return(pgclass_info)
+
expect do
model.queue_batched_background_migration(
'MyJobClass',
@@ -288,7 +296,8 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
batch_size: 100,
sub_batch_size: 10,
job_arguments: %w[],
- status: 'active')
+ status: 'active',
+ total_tuple_count: pgclass_info.cardinality_estimate)
end
context 'when the job interval is lower than the minimum' do
@@ -431,4 +440,21 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
end
end
+
+ describe '#delete_queued_jobs' do
+ let(:job1) { double }
+ let(:job2) { double }
+
+ it 'deletes all queued jobs for the given background migration' do
+ expect(Gitlab::BackgroundMigration).to receive(:steal).with('BackgroundMigrationClassName') do |&block|
+ expect(block.call(job1)).to be(false)
+ expect(block.call(job2)).to be(false)
+ end
+
+ expect(job1).to receive(:delete)
+ expect(job2).to receive(:delete)
+
+ model.delete_queued_jobs('BackgroundMigrationClassName')
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
index b5d741fc5e9..5b2a29d1d2d 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
@@ -704,6 +704,72 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
end
end
+ describe '#drop_nonpartitioned_archive_table' do
+ subject { migration.drop_nonpartitioned_archive_table source_table }
+
+ let(:archived_table) { "#{source_table}_archived" }
+
+ before do
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+ migration.replace_with_partitioned_table source_table
+ end
+
+ it 'drops the archive table' do
+ expect(table_type(archived_table)).to eq('normal')
+
+ subject
+
+ expect(table_type(archived_table)).to eq(nil)
+ end
+
+ it 'drops the trigger on the source table' do
+ expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update])
+
+ subject
+
+ expect_trigger_not_to_exist(source_table, trigger_name)
+ end
+
+ it 'drops the sync function' do
+ expect_function_to_exist(function_name)
+
+ subject
+
+ expect_function_not_to_exist(function_name)
+ end
+ end
+
+ describe '#create_trigger_to_sync_tables' do
+ subject { migration.create_trigger_to_sync_tables(source_table, target_table, :id) }
+
+ let(:target_table) { "#{source_table}_copy" }
+
+ before do
+ migration.create_table target_table do |t|
+ t.string :name, null: false
+ t.integer :age, null: false
+ t.datetime partition_column
+ t.datetime :updated_at
+ end
+ end
+
+ it 'creates the sync function' do
+ expect_function_not_to_exist(function_name)
+
+ subject
+
+ expect_function_to_exist(function_name)
+ end
+
+ it 'installs the trigger' do
+ expect_trigger_not_to_exist(source_table, trigger_name)
+
+ subject
+
+ expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update])
+ end
+ end
+
def filter_columns_by_name(columns, names)
columns.reject { |c| names.include?(c.name) }
end
diff --git a/spec/lib/gitlab/database/pg_class_spec.rb b/spec/lib/gitlab/database/pg_class_spec.rb
new file mode 100644
index 00000000000..83b50415a6c
--- /dev/null
+++ b/spec/lib/gitlab/database/pg_class_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::PgClass, type: :model do
+ describe '#cardinality_estimate' do
+ context 'when no information is available' do
+ subject { described_class.new(reltuples: 0.0).cardinality_estimate }
+
+ it 'returns nil for the estimate' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'with reltuples available' do
+ subject { described_class.new(reltuples: 42.0).cardinality_estimate }
+
+ it 'returns the reltuples for the estimate' do
+ expect(subject).to eq(42)
+ end
+ end
+ end
+
+ describe '.for_table' do
+ let(:relname) { :projects }
+
+ subject { described_class.for_table(relname) }
+
+ it 'returns PgClass for this table' do
+ expect(subject).to be_a(described_class)
+ end
+
+ it 'matches the relname' do
+ expect(subject.relname).to eq(relname.to_s)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index 757da2d9092..1edcd890370 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -246,7 +246,8 @@ RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :
subject.track_rename('namespace', 'path/to/namespace', 'path/to/renamed')
- old_path, new_path = [nil, nil]
+ old_path = nil
+ new_path = nil
Gitlab::Redis::SharedState.with do |redis|
rename_info = redis.lpop(key)
old_path, new_path = Gitlab::Json.parse(rename_info)
diff --git a/spec/lib/gitlab/database/unidirectional_copy_trigger_spec.rb b/spec/lib/gitlab/database/unidirectional_copy_trigger_spec.rb
new file mode 100644
index 00000000000..2955c208f16
--- /dev/null
+++ b/spec/lib/gitlab/database/unidirectional_copy_trigger_spec.rb
@@ -0,0 +1,191 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::UnidirectionalCopyTrigger do
+ include Database::TriggerHelpers
+
+ let(:table_name) { '_test_table' }
+ let(:connection) { ActiveRecord::Base.connection }
+ let(:copy_trigger) { described_class.on_table(table_name) }
+
+ describe '#name' do
+ context 'when a single column name is given' do
+ subject(:trigger_name) { copy_trigger.name('id', 'other_id') }
+
+ it 'returns the trigger name' do
+ expect(trigger_name).to eq('trigger_cfce7a56a9d6')
+ end
+ end
+
+ context 'when multiple column names are given' do
+ subject(:trigger_name) { copy_trigger.name(%w[id fk_id], %w[other_id other_fk_id]) }
+
+ it 'returns the trigger name' do
+ expect(trigger_name).to eq('trigger_166626e51481')
+ end
+ end
+
+ context 'when a different number of new and old column names are given' do
+ it 'raises an error' do
+ expect do
+ copy_trigger.name(%w[id fk_id], %w[other_id])
+ end.to raise_error(ArgumentError, 'number of source and destination columns must match')
+ end
+ end
+ end
+
+ describe '#create' do
+ let(:model) { Class.new(ActiveRecord::Base) }
+
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE #{table_name} (
+ id serial NOT NULL PRIMARY KEY,
+ other_id integer,
+ fk_id bigint,
+ other_fk_id bigint);
+ SQL
+
+ model.table_name = table_name
+ end
+
+ context 'when a single column name is given' do
+ let(:trigger_name) { 'trigger_cfce7a56a9d6' }
+
+ it 'creates the trigger and function' do
+ expect_function_not_to_exist(trigger_name)
+ expect_trigger_not_to_exist(table_name, trigger_name)
+
+ copy_trigger.create('id', 'other_id')
+
+ expect_function_to_exist(trigger_name)
+ expect_valid_function_trigger(table_name, trigger_name, trigger_name, before: %w[insert update])
+ end
+
+ it 'properly copies the column data using the trigger function' do
+ copy_trigger.create('id', 'other_id')
+
+ record = model.create!(id: 10)
+ expect(record.reload).to have_attributes(other_id: 10)
+
+ record.update!({ id: 20 })
+ expect(record.reload).to have_attributes(other_id: 20)
+ end
+ end
+
+ context 'when multiple column names are given' do
+ let(:trigger_name) { 'trigger_166626e51481' }
+
+ it 'creates the trigger and function to set all the columns' do
+ expect_function_not_to_exist(trigger_name)
+ expect_trigger_not_to_exist(table_name, trigger_name)
+
+ copy_trigger.create(%w[id fk_id], %w[other_id other_fk_id])
+
+ expect_function_to_exist(trigger_name)
+ expect_valid_function_trigger(table_name, trigger_name, trigger_name, before: %w[insert update])
+ end
+
+ it 'properly copies the columns using the trigger function' do
+ copy_trigger.create(%w[id fk_id], %w[other_id other_fk_id])
+
+ record = model.create!(id: 10, fk_id: 20)
+ expect(record.reload).to have_attributes(other_id: 10, other_fk_id: 20)
+
+ record.update!(id: 30, fk_id: 50)
+ expect(record.reload).to have_attributes(other_id: 30, other_fk_id: 50)
+ end
+ end
+
+ context 'when a custom trigger name is given' do
+ let(:trigger_name) { '_test_trigger' }
+
+ it 'creates the trigger and function with the custom name' do
+ expect_function_not_to_exist(trigger_name)
+ expect_trigger_not_to_exist(table_name, trigger_name)
+
+ copy_trigger.create('id', 'other_id', trigger_name: trigger_name)
+
+ expect_function_to_exist(trigger_name)
+ expect_valid_function_trigger(table_name, trigger_name, trigger_name, before: %w[insert update])
+ end
+ end
+
+ context 'when the trigger function already exists' do
+ let(:trigger_name) { 'trigger_cfce7a56a9d6' }
+
+ it 'does not raise an error' do
+ expect_function_not_to_exist(trigger_name)
+ expect_trigger_not_to_exist(table_name, trigger_name)
+
+ copy_trigger.create('id', 'other_id')
+
+ expect_function_to_exist(trigger_name)
+ expect_valid_function_trigger(table_name, trigger_name, trigger_name, before: %w[insert update])
+
+ copy_trigger.create('id', 'other_id')
+
+ expect_function_to_exist(trigger_name)
+ expect_valid_function_trigger(table_name, trigger_name, trigger_name, before: %w[insert update])
+ end
+ end
+
+ context 'when a different number of new and old column names are given' do
+ it 'raises an error' do
+ expect do
+ copy_trigger.create(%w[id fk_id], %w[other_id])
+ end.to raise_error(ArgumentError, 'number of source and destination columns must match')
+ end
+ end
+ end
+
+ describe '#drop' do
+ let(:trigger_name) { '_test_trigger' }
+
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE #{table_name} (
+ id serial NOT NULL PRIMARY KEY,
+ other_id integer NOT NULL);
+
+ CREATE FUNCTION #{trigger_name}()
+ RETURNS trigger
+ LANGUAGE plpgsql AS
+ $$
+ BEGIN
+ RAISE NOTICE 'hello';
+ RETURN NEW;
+ END
+ $$;
+
+ CREATE TRIGGER #{trigger_name}
+ BEFORE INSERT OR UPDATE
+ ON #{table_name}
+ FOR EACH ROW
+ EXECUTE FUNCTION #{trigger_name}();
+ SQL
+ end
+
+ it 'drops the trigger and function for the given arguments' do
+ expect_function_to_exist(trigger_name)
+ expect_valid_function_trigger(table_name, trigger_name, trigger_name, before: %w[insert update])
+
+ copy_trigger.drop(trigger_name)
+
+ expect_trigger_not_to_exist(table_name, trigger_name)
+ expect_function_not_to_exist(trigger_name)
+ end
+
+ context 'when the trigger does not exist' do
+ it 'does not raise an error' do
+ copy_trigger.drop(trigger_name)
+
+ expect_trigger_not_to_exist(table_name, trigger_name)
+ expect_function_not_to_exist(trigger_name)
+
+ copy_trigger.drop(trigger_name)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb b/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
index 39029322e25..e70b34d6557 100644
--- a/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
+++ b/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe Gitlab::DatabaseImporters::InstanceAdministrators::CreateGroup do
end
end
- context 'with application settings and admin users' do
+ context 'with application settings and admin users', :do_not_mock_admin_mode_setting do
let(:group) { result[:group] }
let(:application_setting) { Gitlab::CurrentSettings.current_application_settings }
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 1553a989dba..b735ac7940b 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -407,13 +407,13 @@ RSpec.describe Gitlab::Database do
expect(described_class.db_read_only?).to be_truthy
end
- it 'detects a read write database' do
+ it 'detects a read-write database' do
allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "f" }])
expect(described_class.db_read_only?).to be_falsey
end
- it 'detects a read write database' do
+ it 'detects a read-write database' do
allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => false }])
expect(described_class.db_read_only?).to be_falsey
diff --git a/spec/lib/gitlab/diff/char_diff_spec.rb b/spec/lib/gitlab/diff/char_diff_spec.rb
index e4e2a3ba050..d38008c16f2 100644
--- a/spec/lib/gitlab/diff/char_diff_spec.rb
+++ b/spec/lib/gitlab/diff/char_diff_spec.rb
@@ -49,15 +49,15 @@ RSpec.describe Gitlab::Diff::CharDiff do
old_diffs, new_diffs = subject
expect(old_diffs).to eq([])
- expect(new_diffs).to eq([0..12])
+ expect(new_diffs).to eq([Gitlab::MarkerRange.new(0, 12, mode: :addition)])
end
end
it 'returns ranges of changes' do
old_diffs, new_diffs = subject
- expect(old_diffs).to eq([11..11])
- expect(new_diffs).to eq([3..3])
+ expect(old_diffs).to eq([Gitlab::MarkerRange.new(11, 11, mode: :deletion)])
+ expect(new_diffs).to eq([Gitlab::MarkerRange.new(3, 3, mode: :addition)])
end
end
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
index d26bc5fc9a8..4c56911e665 100644
--- a/spec/lib/gitlab/diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -238,16 +238,36 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
subject { cache.key }
it 'returns cache key' do
- is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:true")
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:true:true:true")
end
- context 'when feature flag is disabled' do
+ context 'when the `introduce_marker_ranges` feature flag is disabled' do
before do
stub_feature_flags(introduce_marker_ranges: false)
end
it 'returns the original version of the cache' do
- is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:false")
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:false:true:true")
+ end
+ end
+
+ context 'when the `use_marker_ranges` feature flag is disabled' do
+ before do
+ stub_feature_flags(use_marker_ranges: false)
+ end
+
+ it 'returns the original version of the cache' do
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:true:false:true")
+ end
+ end
+
+ context 'when the `diff_line_syntax_highlighting` feature flag is disabled' do
+ before do
+ stub_feature_flags(diff_line_syntax_highlighting: false)
+ end
+
+ it 'returns the original version of the cache' do
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:true:true:false")
end
end
end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index e613674af3a..32ca6e4fde6 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -65,6 +65,14 @@ RSpec.describe Gitlab::Diff::Highlight do
expect(subject[5].rich_text).to eq(code)
end
+
+ context 'when use_marker_ranges feature flag is false too' do
+ it 'does not affect the result' do
+ code = %Q{+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class="idiff left">RuntimeError</span></span><span class="p"><span class="idiff">,</span></span><span class="idiff right"> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n}
+
+ expect(subject[5].rich_text).to eq(code)
+ end
+ end
end
context 'when no diff_refs' do
@@ -132,6 +140,18 @@ RSpec.describe Gitlab::Diff::Highlight do
end
end
+ context 'when `use_marker_ranges` feature flag is disabled' do
+ it 'returns the same result' do
+ with_feature_flag = described_class.new(diff_file, repository: project.repository).highlight
+
+ stub_feature_flags(use_marker_ranges: false)
+
+ without_feature_flag = described_class.new(diff_file, repository: project.repository).highlight
+
+ expect(with_feature_flag.map(&:rich_text)).to eq(without_feature_flag.map(&:rich_text))
+ end
+ end
+
context 'when no inline diffs' do
it_behaves_like 'without inline diffs'
end
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
index 714b5d813c4..d7b50eb73ee 100644
--- a/spec/lib/gitlab/diff/inline_diff_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -3,68 +3,30 @@
require 'spec_helper'
RSpec.describe Gitlab::Diff::InlineDiff do
- describe '.for_lines' do
- let(:diff) do
- <<-EOF.strip_heredoc
- class Test
- - def initialize(test = true)
- + def initialize(test = false)
- @test = test
- - if true
- - @foo = "bar"
- + unless false
- + @foo = "baz"
- end
- end
- end
- EOF
- end
-
- let(:subject) { described_class.for_lines(diff.lines) }
+ describe '#inline_diffs' do
+ subject { described_class.new(old_line, new_line, offset: offset).inline_diffs }
- it 'finds all inline diffs' do
- expect(subject[0]).to be_nil
- expect(subject[1]).to eq([25..27])
- expect(subject[2]).to eq([25..28])
- expect(subject[3]).to be_nil
- expect(subject[4]).to eq([5..10])
- expect(subject[5]).to eq([17..17])
- expect(subject[6]).to eq([5..15])
- expect(subject[7]).to eq([17..17])
- expect(subject[8]).to be_nil
- end
+ let(:old_line) { 'XXX def initialize(test = true)' }
+ let(:new_line) { 'YYY def initialize(test = false)' }
+ let(:offset) { 3 }
- it 'can handle unchanged empty lines' do
- expect { described_class.for_lines(['- bar', '+ baz', '']) }.not_to raise_error
+ it 'finds the inline diff', :aggregate_failures do
+ expect(subject[0]).to eq([Gitlab::MarkerRange.new(26, 28, mode: :deletion)])
+ expect(subject[1]).to eq([Gitlab::MarkerRange.new(26, 29, mode: :addition)])
end
context 'when lines have multiple changes' do
- let(:diff) do
- <<~EOF
- - Hello, how are you?
- + Hi, how are you doing?
- EOF
- end
-
- let(:subject) { described_class.for_lines(diff.lines) }
-
- it 'finds all inline diffs' do
- expect(subject[0]).to eq([3..6])
- expect(subject[1]).to eq([3..3, 17..22])
+ let(:old_line) { '- Hello, how are you?' }
+ let(:new_line) { '+ Hi, how are you doing?' }
+ let(:offset) { 1 }
+
+ it 'finds all inline diffs', :aggregate_failures do
+ expect(subject[0]).to eq([Gitlab::MarkerRange.new(3, 6, mode: :deletion)])
+ expect(subject[1]).to eq([
+ Gitlab::MarkerRange.new(3, 3, mode: :addition),
+ Gitlab::MarkerRange.new(17, 22, mode: :addition)
+ ])
end
end
end
-
- describe "#inline_diffs" do
- let(:old_line) { "XXX def initialize(test = true)" }
- let(:new_line) { "YYY def initialize(test = false)" }
- let(:subject) { described_class.new(old_line, new_line, offset: 3).inline_diffs }
-
- it "finds the inline diff" do
- old_diffs, new_diffs = subject
-
- expect(old_diffs).to eq([26..28])
- expect(new_diffs).to eq([26..29])
- end
- end
end
diff --git a/spec/lib/gitlab/diff/line_spec.rb b/spec/lib/gitlab/diff/line_spec.rb
index e10a50afde9..949def599ae 100644
--- a/spec/lib/gitlab/diff/line_spec.rb
+++ b/spec/lib/gitlab/diff/line_spec.rb
@@ -17,6 +17,8 @@ RSpec.describe Gitlab::Diff::Line do
rich_text: rich_text)
end
+ let(:rich_text) { nil }
+
describe '.init_from_hash' do
let(:rich_text) { '&lt;input&gt;' }
@@ -43,6 +45,29 @@ RSpec.describe Gitlab::Diff::Line do
end
end
+ describe '#text' do
+ let(:line) { described_class.new(raw_diff, 'new', 0, 0, 0) }
+ let(:raw_diff) { '+Hello' }
+
+ it 'returns raw diff text' do
+ expect(line.text).to eq('+Hello')
+ end
+
+ context 'when prefix is disabled' do
+ it 'returns raw diff text without prefix' do
+ expect(line.text(prefix: false)).to eq('Hello')
+ end
+
+ context 'when diff is empty' do
+ let(:raw_diff) { '' }
+
+ it 'returns an empty raw diff' do
+ expect(line.text(prefix: false)).to eq('')
+ end
+ end
+ end
+ end
+
context "when setting rich text" do
it 'escapes any HTML special characters in the diff chunk header' do
subject = described_class.new("<input>", "", 0, 0, 0)
@@ -51,4 +76,14 @@ RSpec.describe Gitlab::Diff::Line do
expect(line[:rich_text]).to eq("&lt;input&gt;")
end
end
+
+ describe '#set_marker_ranges' do
+ let(:marker_ranges) { [Gitlab::MarkerRange.new(1, 10, mode: :deletion)] }
+
+ it 'stores MarkerRanges in Diff::Line object' do
+ line.set_marker_ranges(marker_ranges)
+
+ expect(line.marker_ranges).to eq(marker_ranges)
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/lines_unfolder_spec.rb b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
index 4163c0eced5..8385cba3532 100644
--- a/spec/lib/gitlab/diff/lines_unfolder_spec.rb
+++ b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
@@ -302,7 +302,8 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_diff_lines = subject.unfolded_diff_lines
new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+ old_pos = expected_diff_lines[i][0]
+ new_pos = expected_diff_lines[i][1]
unless line.type == 'match'
expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
@@ -396,7 +397,8 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_diff_lines = subject.unfolded_diff_lines
new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+ old_pos = expected_diff_lines[i][0]
+ new_pos = expected_diff_lines[i][1]
unless line.type == 'match'
expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
@@ -490,7 +492,8 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_diff_lines = subject.unfolded_diff_lines
new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+ old_pos = expected_diff_lines[i][0]
+ new_pos = expected_diff_lines[i][1]
unless line.type == 'match'
expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
@@ -581,7 +584,8 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_diff_lines = subject.unfolded_diff_lines
new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+ old_pos = expected_diff_lines[i][0]
+ new_pos = expected_diff_lines[i][1]
unless line.type == 'match'
expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
@@ -691,7 +695,8 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_diff_lines = subject.unfolded_diff_lines
new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+ old_pos = expected_diff_lines[i][0]
+ new_pos = expected_diff_lines[i][1]
unless line.type == 'match'
expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
@@ -783,7 +788,8 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_diff_lines = subject.unfolded_diff_lines
new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+ old_pos = expected_diff_lines[i][0]
+ new_pos = expected_diff_lines[i][1]
unless line.type == 'match'
expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
diff --git a/spec/lib/gitlab/diff/suggestions_parser_spec.rb b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
index 5efce414dc8..a00c55d4fb2 100644
--- a/spec/lib/gitlab/diff/suggestions_parser_spec.rb
+++ b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
@@ -56,7 +56,8 @@ RSpec.describe Gitlab::Diff::SuggestionsParser do
end
it 'parsed suggestion has correct data' do
- from_line, to_line = position.new_line, position.new_line
+ from_line = position.new_line
+ to_line = position.new_line
expect(subject.first.to_hash).to include(from_content: blob_lines_data(from_line, to_line),
to_content: " foo\n bar\n",
diff --git a/spec/lib/gitlab/downtime_check/message_spec.rb b/spec/lib/gitlab/downtime_check/message_spec.rb
deleted file mode 100644
index 2d82836db33..00000000000
--- a/spec/lib/gitlab/downtime_check/message_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::DowntimeCheck::Message do
- describe '#to_s' do
- it 'returns an ANSI formatted String for an offline migration' do
- message = described_class.new('foo.rb', true, 'hello')
-
- expect(message.to_s).to eq("[\e[31moffline\e[0m]: foo.rb:\n\nhello\n\n")
- end
-
- it 'returns an ANSI formatted String for an online migration' do
- message = described_class.new('foo.rb')
-
- expect(message.to_s).to eq("[\e[32monline\e[0m]: foo.rb")
- end
- end
-
- describe '#reason?' do
- it 'returns false when no reason is specified' do
- message = described_class.new('foo.rb')
-
- expect(message.reason?).to eq(false)
- end
-
- it 'returns true when a reason is specified' do
- message = described_class.new('foo.rb', true, 'hello')
-
- expect(message.reason?).to eq(true)
- end
- end
-
- describe '#reason' do
- it 'strips excessive whitespace from the returned String' do
- message = described_class.new('foo.rb', true, " hello\n world\n\n foo")
-
- expect(message.reason).to eq("hello\nworld\n\nfoo")
- end
- end
-end
diff --git a/spec/lib/gitlab/downtime_check_spec.rb b/spec/lib/gitlab/downtime_check_spec.rb
deleted file mode 100644
index 761519425f6..00000000000
--- a/spec/lib/gitlab/downtime_check_spec.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::DowntimeCheck do
- subject { described_class.new }
-
- let(:path) { 'foo.rb' }
-
- describe '#check' do
- before do
- expect(subject).to receive(:require).with(path)
- end
-
- context 'when a migration does not specify if downtime is required' do
- it 'raises RuntimeError' do
- expect(subject).to receive(:class_for_migration_file)
- .with(path)
- .and_return(Class.new)
-
- expect { subject.check([path]) }
- .to raise_error(RuntimeError, /it requires downtime/)
- end
- end
-
- context 'when a migration requires downtime' do
- context 'when no reason is specified' do
- it 'raises RuntimeError' do
- stub_const('TestMigration::DOWNTIME', true)
-
- expect(subject).to receive(:class_for_migration_file)
- .with(path)
- .and_return(TestMigration)
-
- expect { subject.check([path]) }
- .to raise_error(RuntimeError, /no reason was given/)
- end
- end
-
- context 'when a reason is specified' do
- it 'returns an Array of messages' do
- stub_const('TestMigration::DOWNTIME', true)
- stub_const('TestMigration::DOWNTIME_REASON', 'foo')
-
- expect(subject).to receive(:class_for_migration_file)
- .with(path)
- .and_return(TestMigration)
-
- messages = subject.check([path])
-
- expect(messages).to be_an_instance_of(Array)
- expect(messages[0]).to be_an_instance_of(Gitlab::DowntimeCheck::Message)
-
- message = messages[0]
-
- expect(message.path).to eq(path)
- expect(message.offline).to eq(true)
- expect(message.reason).to eq('foo')
- end
- end
- end
- end
-
- describe '#check_and_print' do
- it 'checks the migrations and prints the results to STDOUT' do
- stub_const('TestMigration::DOWNTIME', true)
- stub_const('TestMigration::DOWNTIME_REASON', 'foo')
-
- expect(subject).to receive(:require).with(path)
-
- expect(subject).to receive(:class_for_migration_file)
- .with(path)
- .and_return(TestMigration)
-
- expect(subject).to receive(:puts).with(an_instance_of(String))
-
- subject.check_and_print([path])
- end
- end
-
- describe '#class_for_migration_file' do
- it 'returns the class for a migration file path' do
- expect(subject.class_for_migration_file('123_string.rb')).to eq(String)
- end
- end
-
- describe '#online?' do
- it 'returns true when a migration can be performed online' do
- stub_const('TestMigration::DOWNTIME', false)
-
- expect(subject.online?(TestMigration)).to eq(true)
- end
-
- it 'returns false when a migration can not be performed online' do
- stub_const('TestMigration::DOWNTIME', true)
-
- expect(subject.online?(TestMigration)).to eq(false)
- end
- end
-
- describe '#downtime_reason' do
- context 'when a reason is defined' do
- it 'returns the downtime reason' do
- stub_const('TestMigration::DOWNTIME_REASON', 'hello')
-
- expect(subject.downtime_reason(TestMigration)).to eq('hello')
- end
- end
-
- context 'when a reason is not defined' do
- it 'returns nil' do
- expect(subject.downtime_reason(Class.new)).to be_nil
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
index 8872800069a..e76a5d3fe32 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
end
shared_examples 'a reply to existing comment' do
- it 'creates a comment' do
+ it 'creates a discussion' do
expect { receiver.execute }.to change { noteable.notes.count }.by(1)
new_note = noteable.notes.last
@@ -68,11 +68,7 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
expect(new_note.note).to include('I could not disagree more.')
expect(new_note.in_reply_to?(note)).to be_truthy
- if note.part_of_discussion?
- expect(new_note.discussion_id).to eq(note.discussion_id)
- else
- expect(new_note.discussion_id).not_to eq(note.discussion_id)
- end
+ expect(new_note.discussion_id).to eq(note.discussion_id)
end
end
diff --git a/spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb
index 94f28d3399a..d3535fa9bd3 100644
--- a/spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteOnIssuableHandler do
it_behaves_like :note_handler_shared_examples, true do
let_it_be(:recipient) { user }
- let(:update_commands_only) { email_reply_fixture('emails/update_commands_only_reply.eml') }
+ let(:update_commands_only) { email_reply_fixture('emails/update_commands_only.eml') }
let(:no_content) { email_reply_fixture('emails/no_content_reply.eml') }
let(:commands_in_reply) { email_reply_fixture('emails/commands_in_reply.eml') }
let(:with_quick_actions) { email_reply_fixture('emails/valid_reply_with_quick_actions.eml') }
diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
index 13ad9ddd8ef..2c1badbd113 100644
--- a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
@@ -74,7 +74,7 @@ RSpec.describe Gitlab::Email::Handler::UnsubscribeHandler do
context 'when the noteable could not be found' do
before do
- noteable.destroy
+ noteable.destroy!
end
it 'raises a NoteableNotFoundError' do
diff --git a/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb
index 0db40eca989..24f5299d357 100644
--- a/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb
+++ b/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb
@@ -1,45 +1,66 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
RSpec.describe Gitlab::ErrorTracking::Processor::ContextPayloadProcessor do
- subject(:processor) { described_class.new }
-
- before do
- allow_next_instance_of(Gitlab::ErrorTracking::ContextPayloadGenerator) do |generator|
- allow(generator).to receive(:generate).and_return(
- user: { username: 'root' },
- tags: { locale: 'en', program: 'test', feature_category: 'feature_a', correlation_id: 'cid' },
- extra: { some_info: 'info' }
- )
+ shared_examples 'processing an exception' do
+ before do
+ allow_next_instance_of(Gitlab::ErrorTracking::ContextPayloadGenerator) do |generator|
+ allow(generator).to receive(:generate).and_return(
+ user: { username: 'root' },
+ tags: { locale: 'en', program: 'test', feature_category: 'feature_a', correlation_id: 'cid' },
+ extra: { some_info: 'info' }
+ )
+ end
end
- end
- it 'merges the context payload into event payload' do
- payload = {
- user: { ip_address: '127.0.0.1' },
- tags: { priority: 'high' },
- extra: { sidekiq: { class: 'SomeWorker', args: ['[FILTERED]', 1, 2] } }
- }
-
- processor.process(payload)
-
- expect(payload).to eql(
- user: {
- ip_address: '127.0.0.1',
- username: 'root'
- },
- tags: {
- priority: 'high',
- locale: 'en',
- program: 'test',
- feature_category: 'feature_a',
- correlation_id: 'cid'
- },
- extra: {
- some_info: 'info',
- sidekiq: { class: 'SomeWorker', args: ['[FILTERED]', 1, 2] }
+ let(:payload) do
+ {
+ user: { ip_address: '127.0.0.1' },
+ tags: { priority: 'high' },
+ extra: { sidekiq: { class: 'SomeWorker', args: ['[FILTERED]', 1, 2] } }
}
- )
+ end
+
+ it 'merges the context payload into event payload', :aggregate_failures do
+ expect(result_hash[:user]).to include(ip_address: '127.0.0.1', username: 'root')
+
+ expect(result_hash[:tags])
+ .to include(priority: 'high',
+ locale: 'en',
+ program: 'test',
+ feature_category: 'feature_a',
+ correlation_id: 'cid')
+
+ expect(result_hash[:extra])
+ .to include(some_info: 'info',
+ sidekiq: { class: 'SomeWorker', args: ['[FILTERED]', 1, 2] })
+ end
+ end
+
+ describe '.call' do
+ let(:event) { Raven::Event.new(payload) }
+ let(:result_hash) { described_class.call(event).to_hash }
+
+ it_behaves_like 'processing an exception'
+
+ context 'when followed by #process' do
+ let(:result_hash) { described_class.new.process(described_class.call(event).to_hash) }
+
+ it_behaves_like 'processing an exception'
+ end
+ end
+
+ describe '#process' do
+ let(:event) { Raven::Event.new(payload) }
+ let(:result_hash) { described_class.new.process(event.to_hash) }
+
+ context 'with sentry_processors_before_send disabled' do
+ before do
+ stub_feature_flags(sentry_processors_before_send: false)
+ end
+
+ it_behaves_like 'processing an exception'
+ end
end
end
diff --git a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
index 797707114a1..4808fdf2f06 100644
--- a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
+++ b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
@@ -3,73 +3,83 @@
require 'spec_helper'
RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
- describe '#process' do
- subject { described_class.new }
-
+ shared_examples 'processing an exception' do
context 'when there is no GRPC exception' do
+ let(:exception) { RuntimeError.new }
let(:data) { { fingerprint: ['ArgumentError', 'Missing arguments'] } }
it 'leaves data unchanged' do
- expect(subject.process(data)).to eq(data)
+ expect(result_hash).to include(data)
end
end
context 'when there is a GPRC exception with a debug string' do
+ let(:exception) { GRPC::DeadlineExceeded.new('Deadline Exceeded', {}, '{"hello":1}') }
+
let(:data) do
{
- exception: {
- values: [
- {
- type: "GRPC::DeadlineExceeded",
- value: "4:DeadlineExceeded. debug_error_string:{\"hello\":1}"
- }
- ]
- },
extra: {
caller: 'test'
},
fingerprint: [
- "GRPC::DeadlineExceeded",
- "4:Deadline Exceeded. debug_error_string:{\"created\":\"@1598938192.005782000\",\"description\":\"Error received from peer unix:/home/git/gitalypraefect.socket\",\"file\":\"src/core/lib/surface/call.cc\",\"file_line\":1055,\"grpc_message\":\"Deadline Exceeded\",\"grpc_status\":4}"
+ 'GRPC::DeadlineExceeded',
+ '4:Deadline Exceeded. debug_error_string:{"created":"@1598938192.005782000","description":"Error received from peer unix:/home/git/gitalypraefect.socket","file":"src/core/lib/surface/call.cc","file_line":1055,"grpc_message":"Deadline Exceeded","grpc_status":4}'
]
}
end
- let(:expected) do
- {
- fingerprint: [
- "GRPC::DeadlineExceeded",
- "4:Deadline Exceeded."
- ],
- exception: {
- values: [
- {
- type: "GRPC::DeadlineExceeded",
- value: "4:DeadlineExceeded."
- }
- ]
- },
- extra: {
- caller: 'test',
- grpc_debug_error_string: "{\"hello\":1}"
- }
- }
- end
-
it 'removes the debug error string and stores it as an extra field' do
- expect(subject.process(data)).to eq(expected)
+ expect(result_hash[:fingerprint])
+ .to eq(['GRPC::DeadlineExceeded', '4:Deadline Exceeded.'])
+
+ expect(result_hash[:exception][:values].first)
+ .to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
+
+ expect(result_hash[:extra])
+ .to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
end
context 'with no custom fingerprint' do
- before do
- data.delete(:fingerprint)
- expected.delete(:fingerprint)
+ let(:data) do
+ { extra: { caller: 'test' } }
end
it 'removes the debug error string and stores it as an extra field' do
- expect(subject.process(data)).to eq(expected)
+ expect(result_hash).not_to include(:fingerprint)
+
+ expect(result_hash[:exception][:values].first)
+ .to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
+
+ expect(result_hash[:extra])
+ .to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
end
end
end
end
+
+ describe '.call' do
+ let(:event) { Raven::Event.from_exception(exception, data) }
+ let(:result_hash) { described_class.call(event).to_hash }
+
+ it_behaves_like 'processing an exception'
+
+ context 'when followed by #process' do
+ let(:result_hash) { described_class.new.process(described_class.call(event).to_hash) }
+
+ it_behaves_like 'processing an exception'
+ end
+ end
+
+ describe '#process' do
+ let(:event) { Raven::Event.from_exception(exception, data) }
+ let(:result_hash) { described_class.new.process(event.to_hash) }
+
+ context 'with sentry_processors_before_send disabled' do
+ before do
+ stub_feature_flags(sentry_processors_before_send: false)
+ end
+
+ it_behaves_like 'processing an exception'
+ end
+ end
end
diff --git a/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
index da7205c7f4f..20fd5d085a9 100644
--- a/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
+++ b/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
@@ -94,28 +94,37 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
end
end
- describe '#process' do
+ shared_examples 'processing an exception' do
context 'when there is Sidekiq data' do
+ let(:wrapped_value) { { extra: { sidekiq: value } } }
+
shared_examples 'Sidekiq arguments' do |args_in_job_hash: true|
let(:path) { [:extra, :sidekiq, args_in_job_hash ? :job : nil, 'args'].compact }
let(:args) { [1, 'string', { a: 1 }, [1, 2]] }
- it 'only allows numeric arguments for an unknown worker' do
- value = { 'args' => args, 'class' => 'UnknownWorker' }
+ context 'for an unknown worker' do
+ let(:value) do
+ hash = { 'args' => args, 'class' => 'UnknownWorker' }
- value = { job: value } if args_in_job_hash
+ args_in_job_hash ? { job: hash } : hash
+ end
- expect(subject.process(extra_sidekiq(value)).dig(*path))
- .to eq([1, described_class::FILTERED_STRING, described_class::FILTERED_STRING, described_class::FILTERED_STRING])
+ it 'only allows numeric arguments for an unknown worker' do
+ expect(result_hash.dig(*path))
+ .to eq([1, described_class::FILTERED_STRING, described_class::FILTERED_STRING, described_class::FILTERED_STRING])
+ end
end
- it 'allows all argument types for a permitted worker' do
- value = { 'args' => args, 'class' => 'PostReceive' }
+ context 'for a permitted worker' do
+ let(:value) do
+ hash = { 'args' => args, 'class' => 'PostReceive' }
- value = { job: value } if args_in_job_hash
+ args_in_job_hash ? { job: hash } : hash
+ end
- expect(subject.process(extra_sidekiq(value)).dig(*path))
- .to eq(args)
+ it 'allows all argument types for a permitted worker' do
+ expect(result_hash.dig(*path)).to eq(args)
+ end
end
end
@@ -127,39 +136,62 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
include_examples 'Sidekiq arguments', args_in_job_hash: false
end
- it 'removes a jobstr field if present' do
- value = {
- job: { 'args' => [1] },
- jobstr: { 'args' => [1] }.to_json
- }
+ context 'when a jobstr field is present' do
+ let(:value) do
+ {
+ job: { 'args' => [1] },
+ jobstr: { 'args' => [1] }.to_json
+ }
+ end
- expect(subject.process(extra_sidekiq(value)))
- .to eq(extra_sidekiq(value.except(:jobstr)))
+ it 'removes the jobstr' do
+ expect(result_hash.dig(:extra, :sidekiq)).to eq(value.except(:jobstr))
+ end
end
- it 'does nothing with no jobstr' do
- value = { job: { 'args' => [1] } }
+ context 'when no jobstr value is present' do
+ let(:value) { { job: { 'args' => [1] } } }
- expect(subject.process(extra_sidekiq(value)))
- .to eq(extra_sidekiq(value))
+ it 'does nothing' do
+ expect(result_hash.dig(:extra, :sidekiq)).to eq(value)
+ end
end
end
context 'when there is no Sidekiq data' do
- it 'does nothing' do
- value = {
- request: {
- method: 'POST',
- data: { 'key' => 'value' }
- }
- }
+ let(:value) { { tags: { foo: 'bar', baz: 'quux' } } }
+ let(:wrapped_value) { value }
- expect(subject.process(value)).to eq(value)
+ it 'does nothing' do
+ expect(result_hash).to include(value)
+ expect(result_hash.dig(:extra, :sidekiq)).to be_nil
end
end
+ end
+
+ describe '.call' do
+ let(:event) { Raven::Event.new(wrapped_value) }
+ let(:result_hash) { described_class.call(event).to_hash }
+
+ it_behaves_like 'processing an exception'
+
+ context 'when followed by #process' do
+ let(:result_hash) { described_class.new.process(described_class.call(event).to_hash) }
+
+ it_behaves_like 'processing an exception'
+ end
+ end
+
+ describe '#process' do
+ let(:event) { Raven::Event.new(wrapped_value) }
+ let(:result_hash) { described_class.new.process(event.to_hash) }
+
+ context 'with sentry_processors_before_send disabled' do
+ before do
+ stub_feature_flags(sentry_processors_before_send: false)
+ end
- def extra_sidekiq(hash)
- { extra: { sidekiq: hash } }
+ it_behaves_like 'processing an exception'
end
end
end
diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb
index a905b9f8d40..2e67a9f0874 100644
--- a/spec/lib/gitlab/error_tracking_spec.rb
+++ b/spec/lib/gitlab/error_tracking_spec.rb
@@ -7,6 +7,7 @@ require 'raven/transports/dummy'
RSpec.describe Gitlab::ErrorTracking do
let(:exception) { RuntimeError.new('boom') }
let(:issue_url) { 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' }
+ let(:extra) { { issue_url: issue_url, some_other_info: 'info' } }
let(:user) { create(:user) }
@@ -42,6 +43,8 @@ RSpec.describe Gitlab::ErrorTracking do
}
end
+ let(:sentry_event) { Gitlab::Json.parse(Raven.client.transport.events.last[1]) }
+
before do
stub_sentry_settings
@@ -133,8 +136,6 @@ RSpec.describe Gitlab::ErrorTracking do
end
describe '.track_exception' do
- let(:extra) { { issue_url: issue_url, some_other_info: 'info' } }
-
subject(:track_exception) { described_class.track_exception(exception, extra) }
before do
@@ -195,6 +196,55 @@ RSpec.describe Gitlab::ErrorTracking do
end
end
+ context 'when the error is kind of an `ActiveRecord::StatementInvalid`' do
+ let(:exception) { ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1') }
+
+ it 'injects the normalized sql query into extra' do
+ track_exception
+
+ expect(sentry_event.dig('extra', 'sql')).to eq('SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
+ end
+ end
+
+ context 'when the `ActiveRecord::StatementInvalid` is wrapped in another exception' do
+ it 'injects the normalized sql query into extra' do
+ allow(exception).to receive(:cause).and_return(ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1'))
+
+ track_exception
+
+ expect(sentry_event.dig('extra', 'sql')).to eq('SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
+ end
+ end
+ end
+
+ shared_examples 'event processors' do
+ subject(:track_exception) { described_class.track_exception(exception, extra) }
+
+ before do
+ allow(Raven).to receive(:capture_exception).and_call_original
+ allow(Gitlab::ErrorTracking::Logger).to receive(:error)
+ end
+
+ context 'custom GitLab context when using Raven.capture_exception directly' do
+ subject(:raven_capture_exception) { Raven.capture_exception(exception) }
+
+ it 'merges a default set of tags into the existing tags' do
+ allow(Raven.context).to receive(:tags).and_return(foo: 'bar')
+
+ raven_capture_exception
+
+ expect(sentry_event['tags']).to include('correlation_id', 'feature_category', 'foo', 'locale', 'program')
+ end
+
+ it 'merges the current user information into the existing user information' do
+ Raven.user_context(id: -1)
+
+ raven_capture_exception
+
+ expect(sentry_event['user']).to eq('id' => -1, 'username' => user.username)
+ end
+ end
+
context 'with sidekiq args' do
context 'when the args does not have anything sensitive' do
let(:extra) { { sidekiq: { 'class' => 'PostReceive', 'args' => [1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value'] } } }
@@ -211,16 +261,20 @@ RSpec.describe Gitlab::ErrorTracking do
)
)
end
+
+ it 'does not filter parameters when sending to Sentry' do
+ track_exception
+
+ expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq([1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value'])
+ end
end
context 'when the args has sensitive information' do
let(:extra) { { sidekiq: { 'class' => 'UnknownWorker', 'args' => ['sensitive string', 1, 2] } } }
- it 'filters sensitive arguments before sending' do
+ it 'filters sensitive arguments before sending and logging' do
track_exception
- sentry_event = Gitlab::Json.parse(Raven.client.transport.events.last[1])
-
expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq(['[FILTERED]', 1, 2])
expect(Gitlab::ErrorTracking::Logger).to have_received(:error).with(
hash_including(
@@ -234,28 +288,44 @@ RSpec.describe Gitlab::ErrorTracking do
end
end
- context 'when the error is kind of an `ActiveRecord::StatementInvalid`' do
- let(:exception) { ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1') }
+ context 'when the error is a GRPC error' do
+ context 'when the GRPC error contains a debug_error_string value' do
+ let(:exception) { GRPC::DeadlineExceeded.new('unknown cause', {}, '{"hello":1}') }
- it 'injects the normalized sql query into extra' do
- allow(Raven.client.transport).to receive(:send_event) do |event|
- expect(event.extra).to include(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
+ it 'sets the GRPC debug error string in the Sentry event and adds a custom fingerprint' do
+ track_exception
+
+ expect(sentry_event.dig('extra', 'grpc_debug_error_string')).to eq('{"hello":1}')
+ expect(sentry_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause.'])
end
+ end
- track_exception
+ context 'when the GRPC error does not contain a debug_error_string value' do
+ let(:exception) { GRPC::DeadlineExceeded.new }
+
+ it 'does not do any processing on the event' do
+ track_exception
+
+ expect(sentry_event['extra']).not_to include('grpc_debug_error_string')
+ expect(sentry_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause'])
+ end
end
end
+ end
- context 'when the `ActiveRecord::StatementInvalid` is wrapped in another exception' do
- let(:exception) { RuntimeError.new(cause: ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1')) }
+ context 'with sentry_processors_before_send enabled' do
+ before do
+ stub_feature_flags(sentry_processors_before_send: true)
+ end
- it 'injects the normalized sql query into extra' do
- allow(Raven.client.transport).to receive(:send_event) do |event|
- expect(event.extra).to include(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
- end
+ include_examples 'event processors'
+ end
- track_exception
- end
+ context 'with sentry_processors_before_send disabled' do
+ before do
+ stub_feature_flags(sentry_processors_before_send: false)
end
+
+ include_examples 'event processors'
end
end
diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb
index 83c6b556fc6..5fef14bd2a0 100644
--- a/spec/lib/gitlab/experimentation_spec.rb
+++ b/spec/lib/gitlab/experimentation_spec.rb
@@ -7,7 +7,6 @@ require 'spec_helper'
RSpec.describe Gitlab::Experimentation::EXPERIMENTS do
it 'temporarily ensures we know what experiments exist for backwards compatibility' do
expected_experiment_keys = [
- :upgrade_link_in_user_menu_a,
:invite_members_version_b,
:invite_members_empty_group_version_a,
:contact_sales_btn_in_app
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index 1a3c332a21b..114b3d01952 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -31,6 +31,19 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
end
end
+ let(:overflow_max_bytes) { false }
+ let(:overflow_max_files) { false }
+ let(:overflow_max_lines) { false }
+
+ shared_examples 'overflow stuff' do
+ it 'returns the expected overflow values' do
+ subject.overflow?
+ expect(subject.overflow_max_bytes?).to eq(overflow_max_bytes)
+ expect(subject.overflow_max_files?).to eq(overflow_max_files)
+ expect(subject.overflow_max_lines?).to eq(overflow_max_lines)
+ end
+ end
+
subject do
Gitlab::Git::DiffCollection.new(
iterator,
@@ -76,12 +89,19 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
end
context 'overflow handling' do
+ subject { super() }
+
+ let(:collapsed_safe_files) { false }
+ let(:collapsed_safe_lines) { false }
+
context 'adding few enough files' do
let(:file_count) { 3 }
context 'and few enough lines' do
let(:line_count) { 10 }
+ it_behaves_like 'overflow stuff'
+
describe '#overflow?' do
subject { super().overflow? }
@@ -117,6 +137,11 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'when limiting is disabled' do
let(:limits) { false }
+ let(:overflow_max_bytes) { false }
+ let(:overflow_max_files) { false }
+ let(:overflow_max_lines) { false }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -155,6 +180,9 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'and too many lines' do
let(:line_count) { 1000 }
+ let(:overflow_max_lines) { true }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -184,6 +212,11 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'when limiting is disabled' do
let(:limits) { false }
+ let(:overflow_max_bytes) { false }
+ let(:overflow_max_files) { false }
+ let(:overflow_max_lines) { false }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -216,10 +249,13 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'adding too many files' do
let(:file_count) { 11 }
+ let(:overflow_max_files) { true }
context 'and few enough lines' do
let(:line_count) { 1 }
+ it_behaves_like 'overflow stuff'
+
describe '#overflow?' do
subject { super().overflow? }
@@ -248,6 +284,11 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'when limiting is disabled' do
let(:limits) { false }
+ let(:overflow_max_bytes) { false }
+ let(:overflow_max_files) { false }
+ let(:overflow_max_lines) { false }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -279,6 +320,10 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'and too many lines' do
let(:line_count) { 30 }
+ let(:overflow_max_lines) { true }
+ let(:overflow_max_files) { false }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -308,6 +353,11 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'when limiting is disabled' do
let(:limits) { false }
+ let(:overflow_max_bytes) { false }
+ let(:overflow_max_files) { false }
+ let(:overflow_max_lines) { false }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -344,6 +394,8 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'and few enough lines' do
let(:line_count) { 1 }
+ it_behaves_like 'overflow stuff'
+
describe '#overflow?' do
subject { super().overflow? }
@@ -375,6 +427,9 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'adding too many bytes' do
let(:file_count) { 10 }
let(:line_length) { 5200 }
+ let(:overflow_max_bytes) { true }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -404,6 +459,11 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'when limiting is disabled' do
let(:limits) { false }
+ let(:overflow_max_bytes) { false }
+ let(:overflow_max_files) { false }
+ let(:overflow_max_lines) { false }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -437,6 +497,8 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
describe 'empty collection' do
subject { Gitlab::Git::DiffCollection.new([]) }
+ it_behaves_like 'overflow stuff'
+
describe '#overflow?' do
subject { super().overflow? }
@@ -555,7 +617,7 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
.and_return({ max_files: 2, max_lines: max_lines })
end
- it 'prunes diffs by default even little ones' do
+ it 'prunes diffs by default even little ones and sets collapsed_safe_files true' do
subject.each_with_index do |d, i|
if i < 2
expect(d.diff).not_to eq('')
@@ -563,6 +625,8 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
expect(d.diff).to eq('')
end
end
+
+ expect(subject.collapsed_safe_files?).to eq(true)
end
end
@@ -582,7 +646,7 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
.and_return({ max_files: max_files, max_lines: 80 })
end
- it 'prunes diffs by default even little ones' do
+ it 'prunes diffs by default even little ones and sets collapsed_safe_lines true' do
subject.each_with_index do |d, i|
if i < 2
expect(d.diff).not_to eq('')
@@ -590,26 +654,30 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
expect(d.diff).to eq('')
end
end
+
+ expect(subject.collapsed_safe_lines?).to eq(true)
end
end
context 'when go over safe limits on bytes' do
let(:iterator) do
[
- fake_diff(1, 45),
- fake_diff(1, 45),
- fake_diff(1, 20480),
- fake_diff(1, 1)
+ fake_diff(5, 10),
+ fake_diff(5000, 10),
+ fake_diff(5, 10),
+ fake_diff(5, 10)
]
end
before do
+ allow(Gitlab::CurrentSettings).to receive(:diff_max_patch_bytes).and_return(1.megabyte)
+
allow(Gitlab::Git::DiffCollection)
.to receive(:default_limits)
- .and_return({ max_files: max_files, max_lines: 80 })
+ .and_return({ max_files: 4, max_lines: 3000 })
end
- it 'prunes diffs by default even little ones' do
+ it 'prunes diffs by default even little ones and sets collapsed_safe_bytes true' do
subject.each_with_index do |d, i|
if i < 2
expect(d.diff).not_to eq('')
@@ -617,6 +685,8 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
expect(d.diff).to eq('')
end
end
+
+ expect(subject.collapsed_safe_bytes?).to eq(true)
end
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index cc1b1ceadcf..1e259c9c153 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -564,6 +564,41 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#search_files_by_regexp' do
+ let(:ref) { 'master' }
+
+ subject(:result) { mutable_repository.search_files_by_regexp(filter, ref) }
+
+ context 'when sending a valid regexp' do
+ let(:filter) { 'files\/.*\/.*\.rb' }
+
+ it 'returns matched files' do
+ expect(result).to contain_exactly('files/links/regex.rb',
+ 'files/ruby/popen.rb',
+ 'files/ruby/regex.rb',
+ 'files/ruby/version_info.rb')
+ end
+ end
+
+ context 'when sending an ivalid regexp' do
+ let(:filter) { '*.rb' }
+
+ it 'raises error' do
+ expect { result }.to raise_error(GRPC::InvalidArgument,
+ /missing argument to repetition operator: `*`/)
+ end
+ end
+
+ context "when the ref doesn't exist" do
+ let(:filter) { 'files\/.*\/.*\.rb' }
+ let(:ref) { 'non-existing-branch' }
+
+ it 'returns an empty array' do
+ expect(result).to eq([])
+ end
+ end
+ end
+
describe '#find_remote_root_ref' do
it 'gets the remote root ref from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
@@ -1711,14 +1746,15 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
let(:right_branch) { 'test-master' }
let(:first_parent_ref) { 'refs/heads/test-master' }
let(:target_ref) { 'refs/merge-requests/999/merge' }
- let(:allow_conflicts) { false }
before do
repository.create_branch(right_branch, branch_head) unless repository.ref_exists?(first_parent_ref)
end
def merge_to_ref
- repository.merge_to_ref(user, left_sha, right_branch, target_ref, 'Merge message', first_parent_ref, allow_conflicts)
+ repository.merge_to_ref(user,
+ source_sha: left_sha, branch: right_branch, target_ref: target_ref,
+ message: 'Merge message', first_parent_ref: first_parent_ref)
end
it 'generates a commit in the target_ref' do
diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb
index f83ccc6cae0..b6ff76c5e1c 100644
--- a/spec/lib/gitlab/git/tag_spec.rb
+++ b/spec/lib/gitlab/git/tag_spec.rb
@@ -101,4 +101,17 @@ RSpec.describe Gitlab::Git::Tag, :seed_helper do
end
end
end
+
+ describe "#cache_key" do
+ subject { repository.tags.first }
+
+ it "returns a cache key that changes based on changeable values" do
+ expect(subject).to receive(:name).and_return("v1.0.0")
+ expect(subject).to receive(:message).and_return("Initial release")
+
+ digest = Digest::SHA1.hexdigest(["v1.0.0", "Initial release", subject.target, subject.target_commit.sha].join)
+
+ expect(subject.cache_key).to eq("tag:#{digest}")
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
index 037734f1b13..f0ec58f3c2d 100644
--- a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
@@ -14,14 +14,14 @@ RSpec.describe Gitlab::GitalyClient::BlobService do
let(:limit) { 5 }
let(:not_in) { %w[branch-a branch-b] }
let(:expected_params) do
- { revision: revision, limit: limit, not_in_refs: not_in, not_in_all: false }
+ { revisions: ["master", "--not", "branch-a", "branch-b"], limit: limit }
end
subject { client.get_new_lfs_pointers(revision, limit, not_in) }
it 'sends a get_new_lfs_pointers message' do
expect_any_instance_of(Gitaly::BlobService::Stub)
- .to receive(:get_new_lfs_pointers)
+ .to receive(:list_lfs_pointers)
.with(gitaly_request_with_params(expected_params), kind_of(Hash))
.and_return([])
@@ -31,12 +31,39 @@ RSpec.describe Gitlab::GitalyClient::BlobService do
context 'with not_in = :all' do
let(:not_in) { :all }
let(:expected_params) do
- { revision: revision, limit: limit, not_in_refs: [], not_in_all: true }
+ { revisions: ["master", "--not", "--all"], limit: limit }
end
it 'sends the correct message' do
expect_any_instance_of(Gitaly::BlobService::Stub)
- .to receive(:get_new_lfs_pointers)
+ .to receive(:list_lfs_pointers)
+ .with(gitaly_request_with_params(expected_params), kind_of(Hash))
+ .and_return([])
+
+ subject
+ end
+ end
+
+ context 'with hook environment' do
+ let(:git_env) do
+ {
+ 'GIT_OBJECT_DIRECTORY_RELATIVE' => '.git/objects',
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['/dir/one', '/dir/two']
+ }
+ end
+
+ let(:expected_params) do
+ expected_repository = repository.gitaly_repository
+ expected_repository.git_alternate_object_directories = Google::Protobuf::RepeatedField.new(:string)
+
+ { limit: limit, repository: expected_repository }
+ end
+
+ it 'sends a list_all_lfs_pointers message' do
+ allow(Gitlab::Git::HookEnv).to receive(:all).with(repository.gl_repository).and_return(git_env)
+
+ expect_any_instance_of(Gitaly::BlobService::Stub)
+ .to receive(:list_all_lfs_pointers)
.with(gitaly_request_with_params(expected_params), kind_of(Hash))
.and_return([])
@@ -46,12 +73,16 @@ RSpec.describe Gitlab::GitalyClient::BlobService do
end
describe '#get_all_lfs_pointers' do
+ let(:expected_params) do
+ { revisions: ["--all"], limit: 0 }
+ end
+
subject { client.get_all_lfs_pointers }
it 'sends a get_all_lfs_pointers message' do
expect_any_instance_of(Gitaly::BlobService::Stub)
- .to receive(:get_all_lfs_pointers)
- .with(gitaly_request_with_params({}), kind_of(Hash))
+ .to receive(:list_lfs_pointers)
+ .with(gitaly_request_with_params(expected_params), kind_of(Hash))
.and_return([])
subject
diff --git a/spec/lib/gitlab/gitaly_client/call_spec.rb b/spec/lib/gitlab/gitaly_client/call_spec.rb
index 5c33ac40460..099307fc4e1 100644
--- a/spec/lib/gitlab/gitaly_client/call_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/call_spec.rb
@@ -24,11 +24,14 @@ RSpec.describe Gitlab::GitalyClient::Call do
def expect_call_details_to_match(duration_higher_than: 0)
expect(client.list_call_details.size).to eq(1)
expect(client.list_call_details.first)
- .to match a_hash_including(feature: "#{service}##{rpc}",
- duration: a_value > duration_higher_than,
- request: an_instance_of(Hash),
- rpc: rpc,
- backtrace: an_instance_of(Array))
+ .to match a_hash_including(
+ start: a_value > 0,
+ feature: "#{service}##{rpc}",
+ duration: a_value > duration_higher_than,
+ request: an_instance_of(Hash),
+ rpc: rpc,
+ backtrace: an_instance_of(Array)
+ )
end
context 'when the response is not an enumerator' 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 15eebf62a39..9c3bc935acc 100644
--- a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Gitlab::GitalyClient::ObjectPoolService do
subject { described_class.new(object_pool) }
before do
- subject.create(raw_repository)
+ subject.create(raw_repository) # rubocop:disable Rails/SaveBang
end
describe '#create' do
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::GitalyClient::ObjectPoolService do
context 'when the pool already exists' do
it 'returns an error' do
expect do
- subject.create(raw_repository)
+ subject.create(raw_repository) # rubocop:disable Rails/SaveBang
end.to raise_error(GRPC::FailedPrecondition)
end
end
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 22707c9a36b..9a17140a1e0 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -88,17 +88,29 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
let(:ref) { 'refs/merge-requests/x/merge' }
let(:message) { 'validación' }
- let(:allow_conflicts) { false }
let(:response) { Gitaly::UserMergeToRefResponse.new(commit_id: 'new-commit-id') }
- subject { client.user_merge_to_ref(user, source_sha, nil, ref, message, first_parent_ref, allow_conflicts) }
+ let(:payload) do
+ { source_sha: source_sha, branch: 'branch', target_ref: ref,
+ message: message, first_parent_ref: first_parent_ref, allow_conflicts: true }
+ end
it 'sends a user_merge_to_ref message' do
- expect_any_instance_of(Gitaly::OperationService::Stub)
- .to receive(:user_merge_to_ref).with(kind_of(Gitaly::UserMergeToRefRequest), kind_of(Hash))
- .and_return(response)
-
- subject
+ freeze_time do
+ expect_any_instance_of(Gitaly::OperationService::Stub).to receive(:user_merge_to_ref) do |_, request, options|
+ expect(options).to be_kind_of(Hash)
+ expect(request.to_h).to eq(
+ payload.merge({
+ 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,
+ timestamp: { nanos: 0, seconds: Time.current.to_i }
+ })
+ )
+ end.and_return(response)
+
+ client.user_merge_to_ref(user, **payload)
+ end
end
end
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index 7a382df1248..26ec194a2e7 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -246,6 +246,21 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
end
end
+ describe '#search_files_by_regexp' do
+ subject(:result) { client.search_files_by_regexp('master', '.*') }
+
+ before do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:search_files_by_name)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return([double(files: ['file1.txt']), double(files: ['file2.txt'])])
+ end
+
+ it 'sends a search_files_by_name message and returns a flatten array' do
+ expect(result).to contain_exactly('file1.txt', 'file2.txt')
+ end
+ end
+
describe '#disconnect_alternates' do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
@@ -255,7 +270,7 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
let(:object_pool_service) { Gitlab::GitalyClient::ObjectPoolService.new(object_pool) }
before do
- object_pool_service.create(repository)
+ object_pool_service.create(repository) # rubocop:disable Rails/SaveBang
object_pool_service.link_repository(repository)
end
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb
index e42b6d89c30..01d9edf0ba1 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::PullRequestMergedByImporter, :clean_gitlab_redis_cache do
let_it_be(:merge_request) { create(:merged_merge_request) }
+
let(:project) { merge_request.project }
let(:merged_at) { Time.new(2017, 1, 1, 12, 00).utc }
let(:client_double) { double(user: double(id: 999, login: 'merger', email: 'merger@email.com')) }
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb
index 290f3f51202..5002e0384f3 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestReviewImporter, :clean
using RSpec::Parameterized::TableSyntax
let_it_be(:merge_request) { create(:merge_request) }
+
let(:project) { merge_request.project }
let(:client_double) { double(user: double(id: 999, login: 'author', email: 'author@email.com')) }
let(:submitted_at) { Time.new(2017, 1, 1, 12, 00).utc }
diff --git a/spec/lib/gitlab/github_import/milestone_finder_spec.rb b/spec/lib/gitlab/github_import/milestone_finder_spec.rb
index 5da45b1897f..fe8652eb5a2 100644
--- a/spec/lib/gitlab/github_import/milestone_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/milestone_finder_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let_it_be(:milestone) { create(:milestone, project: project) }
+
let(:finder) { described_class.new(project) }
describe '#id_for' do
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
deleted file mode 100644
index c88506899cd..00000000000
--- a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
+++ /dev/null
@@ -1,253 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-# Also see spec/graphql/features/authorization_spec.rb for
-# integration tests of AuthorizeFieldService
-RSpec.describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
- def type(type_authorizations = [])
- Class.new(Types::BaseObject) do
- graphql_name 'TestType'
-
- authorize type_authorizations
- end
- end
-
- def type_with_field(field_type, field_authorizations = [], resolved_value = 'Resolved value', **options)
- Class.new(Types::BaseObject) do
- graphql_name 'TestTypeWithField'
- options.reverse_merge!(null: true)
- field :test_field, field_type,
- authorize: field_authorizations,
- **options
-
- define_method :test_field do
- resolved_value
- end
- end
- end
-
- def resolve
- service.authorized_resolve[type_instance, {}, context]
- end
-
- subject(:service) { described_class.new(field) }
-
- describe '#authorized_resolve' do
- let_it_be(:current_user) { build(:user) }
- let_it_be(:presented_object) { 'presented object' }
- let_it_be(:query_type) { GraphQL::ObjectType.new }
- let_it_be(:schema) { GitlabSchema }
- let_it_be(:query) { GraphQL::Query.new(schema, document: nil, context: {}, variables: {}) }
- let_it_be(:context) { GraphQL::Query::Context.new(query: query, values: { current_user: current_user }, object: nil) }
-
- let(:type_class) { type_with_field(custom_type, :read_field, presented_object) }
- let(:type_instance) { type_class.authorized_new(presented_object, context) }
- let(:field) { type_class.fields['testField'].to_graphql }
-
- subject(:resolved) { ::Gitlab::Graphql::Lazy.force(resolve) }
-
- context 'reading the field of a lazy value' do
- let(:ability) { :read_field }
- let(:presented_object) { lazy_upcase('a') }
- let(:type_class) { type_with_field(GraphQL::STRING_TYPE, ability) }
-
- let(:upcaser) do
- Module.new do
- def self.upcase(strs)
- strs.map(&:upcase)
- end
- end
- end
-
- def lazy_upcase(str)
- ::BatchLoader::GraphQL.for(str).batch do |strs, found|
- strs.zip(upcaser.upcase(strs)).each { |s, us| found[s, us] }
- end
- end
-
- it 'does not run authorizations until we force the resolved value' do
- expect(Ability).not_to receive(:allowed?)
-
- expect(resolve).to respond_to(:force)
- end
-
- it 'runs authorizations when we force the resolved value' do
- spy_ability_check_for(ability, 'A')
-
- expect(resolved).to eq('Resolved value')
- end
-
- it 'redacts values that fail the permissions check' do
- spy_ability_check_for(ability, 'A', passed: false)
-
- expect(resolved).to be_nil
- end
-
- context 'we batch two calls' do
- def resolve(value)
- instance = type_class.authorized_new(lazy_upcase(value), context)
- service.authorized_resolve[instance, {}, context]
- end
-
- it 'batches resolution, but authorizes each object separately' do
- expect(upcaser).to receive(:upcase).once.and_call_original
- spy_ability_check_for(:read_field, 'A', passed: true)
- spy_ability_check_for(:read_field, 'B', passed: false)
- spy_ability_check_for(:read_field, 'C', passed: true)
-
- a = resolve('a')
- b = resolve('b')
- c = resolve('c')
-
- expect(a.force).to be_present
- expect(b.force).to be_nil
- expect(c.force).to be_present
- end
- end
- end
-
- shared_examples 'authorizing fields' do
- context 'scalar types' do
- shared_examples 'checking permissions on the presented object' do
- it 'checks the abilities on the object being presented and returns the value' do
- expected_permissions.each do |permission|
- spy_ability_check_for(permission, presented_object, passed: true)
- end
-
- expect(resolved).to eq('Resolved value')
- end
-
- it 'returns nil if the value was not authorized' do
- allow(Ability).to receive(:allowed?).and_return false
-
- expect(resolved).to be_nil
- end
- end
-
- context 'when the field is a built-in scalar type' do
- let(:type_class) { type_with_field(GraphQL::STRING_TYPE, :read_field) }
- let(:expected_permissions) { [:read_field] }
-
- it_behaves_like 'checking permissions on the presented object'
- end
-
- context 'when the field is a list of scalar types' do
- let(:type_class) { type_with_field([GraphQL::STRING_TYPE], :read_field) }
- let(:expected_permissions) { [:read_field] }
-
- it_behaves_like 'checking permissions on the presented object'
- end
-
- context 'when the field is sub-classed scalar type' do
- let(:type_class) { type_with_field(Types::TimeType, :read_field) }
- let(:expected_permissions) { [:read_field] }
-
- it_behaves_like 'checking permissions on the presented object'
- end
-
- context 'when the field is a list of sub-classed scalar types' do
- let(:type_class) { type_with_field([Types::TimeType], :read_field) }
- let(:expected_permissions) { [:read_field] }
-
- it_behaves_like 'checking permissions on the presented object'
- end
- end
-
- context 'when the field is a connection' do
- context 'when it resolves to nil' do
- let(:type_class) { type_with_field(Types::QueryType.connection_type, :read_field, nil) }
-
- it 'does not fail when authorizing' do
- expect(resolved).to be_nil
- end
- end
-
- context 'when it returns values' do
- let(:objects) { [1, 2, 3] }
- let(:field_type) { type([:read_object]).connection_type }
- let(:type_class) { type_with_field(field_type, [], objects) }
-
- it 'filters out unauthorized values' do
- spy_ability_check_for(:read_object, 1, passed: true)
- spy_ability_check_for(:read_object, 2, passed: false)
- spy_ability_check_for(:read_object, 3, passed: true)
-
- expect(resolved.nodes).to eq [1, 3]
- end
- end
- end
-
- context 'when the field is a specific type' do
- let(:custom_type) { type(:read_type) }
- let(:object_in_field) { double('presented in field') }
-
- let(:type_class) { type_with_field(custom_type, :read_field, object_in_field) }
- let(:type_instance) { type_class.authorized_new(object_in_field, context) }
-
- it 'checks both field & type permissions' do
- spy_ability_check_for(:read_field, object_in_field, passed: true)
- spy_ability_check_for(:read_type, object_in_field, passed: true)
-
- expect(resolved).to eq(object_in_field)
- end
-
- it 'returns nil if viewing was not allowed' do
- spy_ability_check_for(:read_field, object_in_field, passed: false)
- spy_ability_check_for(:read_type, object_in_field, passed: true)
-
- expect(resolved).to be_nil
- end
-
- context 'when the field is not nullable' do
- let(:type_class) { type_with_field(custom_type, :read_field, object_in_field, null: false) }
-
- it 'returns nil when viewing is not allowed' do
- spy_ability_check_for(:read_type, object_in_field, passed: false)
-
- expect(resolved).to be_nil
- end
- end
-
- context 'when the field is a list' do
- let(:object_1) { double('presented in field 1') }
- let(:object_2) { double('presented in field 2') }
- let(:presented_types) { [double(object: object_1), double(object: object_2)] }
-
- let(:type_class) { type_with_field([custom_type], :read_field, presented_types) }
- let(:type_instance) { type_class.authorized_new(presented_types, context) }
-
- it 'checks all permissions' do
- allow(Ability).to receive(:allowed?) { true }
-
- spy_ability_check_for(:read_field, object_1, passed: true)
- spy_ability_check_for(:read_type, object_1, passed: true)
- spy_ability_check_for(:read_field, object_2, passed: true)
- spy_ability_check_for(:read_type, object_2, passed: true)
-
- expect(resolved).to eq(presented_types)
- end
-
- it 'filters out objects that the user cannot see' do
- allow(Ability).to receive(:allowed?) { true }
-
- spy_ability_check_for(:read_type, object_1, passed: false)
-
- expect(resolved).to contain_exactly(have_attributes(object: object_2))
- end
- end
- end
- end
-
- it_behaves_like 'authorizing fields'
- end
-
- private
-
- def spy_ability_check_for(ability, object, passed: true)
- expect(Ability)
- .to receive(:allowed?)
- .with(current_user, ability, object)
- .and_return(passed)
- end
-end
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
index c5d7665c3b2..0c548e1ce32 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
@@ -12,7 +12,8 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
authorize :read_the_thing
def initialize(user, found_object)
- @user, @found_object = user, found_object
+ @user = user
+ @found_object = found_object
end
def find_object
@@ -22,6 +23,14 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
def current_user
user
end
+
+ def context
+ { current_user: user }
+ end
+
+ def self.authorization
+ @authorization ||= ::Gitlab::Graphql::Authorize::ObjectAuthorization.new(required_permissions)
+ end
end
end
@@ -30,11 +39,14 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
subject(:loading_resource) { fake_class.new(user, project) }
+ before do
+ # don't allow anything by default
+ allow(Ability).to receive(:allowed?).and_return(false)
+ end
+
context 'when the user is allowed to perform the action' do
before do
- allow(Ability).to receive(:allowed?).with(user, :read_the_thing, project, scope: :user) do
- true
- end
+ allow(Ability).to receive(:allowed?).with(user, :read_the_thing, project).and_return(true)
end
describe '#authorized_find!' do
@@ -48,24 +60,12 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
expect { loading_resource.authorize!(project) }.not_to raise_error
end
end
-
- describe '#authorized_resource?' do
- it 'is true' do
- expect(loading_resource.authorized_resource?(project)).to be(true)
- end
- end
end
context 'when the user is not allowed to perform the action' do
- before do
- allow(Ability).to receive(:allowed?).with(user, :read_the_thing, project, scope: :user) do
- false
- end
- end
-
describe '#authorized_find!' do
it 'raises an error' do
- expect { loading_resource.authorize!(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ expect { loading_resource.authorized_find! }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
@@ -74,12 +74,6 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
expect { loading_resource.authorize!(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
-
- describe '#authorized_resource?' do
- it 'is false' do
- expect(loading_resource.authorized_resource?(project)).to be(false)
- end
- end
end
context 'when the class does not define #find_object' do
@@ -92,46 +86,6 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
end
end
- context 'when the class does not define authorize' do
- let(:fake_class) do
- Class.new do
- include Gitlab::Graphql::Authorize::AuthorizeResource
-
- attr_reader :user, :found_object
-
- def initialize(user, found_object)
- @user, @found_object = user, found_object
- end
-
- def find_object(*_args)
- found_object
- end
-
- def current_user
- user
- end
-
- def self.name
- 'TestClass'
- end
- end
- end
-
- let(:error) { /#{fake_class.name} has no authorizations/ }
-
- describe '#authorized_find!' do
- it 'raises a comprehensive error message' do
- expect { loading_resource.authorized_find! }.to raise_error(error)
- end
- end
-
- describe '#authorized_resource?' do
- it 'raises a comprehensive error message' do
- expect { loading_resource.authorized_resource?(project) }.to raise_error(error)
- end
- end
- end
-
describe '#authorize' do
it 'adds permissions from subclasses to those of superclasses when used on classes' do
base_class = Class.new do
diff --git a/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb b/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb
new file mode 100644
index 00000000000..73e25f23848
--- /dev/null
+++ b/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe ::Gitlab::Graphql::Authorize::ObjectAuthorization do
+ describe '#ok?' do
+ subject { described_class.new(%i[go_fast go_slow]) }
+
+ let(:user) { double(:User, id: 10001) }
+
+ let(:policy) do
+ Class.new(::DeclarativePolicy::Base) do
+ condition(:fast, scope: :subject) { @subject.x >= 10 }
+ condition(:slow, scope: :subject) { @subject.y >= 10 }
+
+ rule { fast }.policy do
+ enable :go_fast
+ end
+
+ rule { slow }.policy do
+ enable :go_slow
+ end
+ end
+ end
+
+ before do
+ stub_const('Foo', Struct.new(:x, :y))
+ stub_const('FooPolicy', policy)
+ end
+
+ context 'when there are no abilities' do
+ subject { described_class.new([]) }
+
+ it { is_expected.to be_ok(double, double) }
+ end
+
+ context 'when no ability should be allowed' do
+ let(:object) { Foo.new(0, 0) }
+
+ it { is_expected.not_to be_ok(object, user) }
+ end
+
+ context 'when go_fast should be allowed' do
+ let(:object) { Foo.new(100, 0) }
+
+ it { is_expected.not_to be_ok(object, user) }
+ end
+
+ context 'when go_fast and go_slow should be allowed' do
+ let(:object) { Foo.new(100, 100) }
+
+ it { is_expected.to be_ok(object, user) }
+ end
+
+ context 'when the object delegates to another subject' do
+ def proxy(foo)
+ double(:Proxy, declarative_policy_subject: foo)
+ end
+
+ it { is_expected.to be_ok(proxy(Foo.new(100, 100)), user) }
+ it { is_expected.not_to be_ok(proxy(Foo.new(0, 100)), user) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/batch_key_spec.rb b/spec/lib/gitlab/graphql/batch_key_spec.rb
index 881fba5c1be..7b73b27f24b 100644
--- a/spec/lib/gitlab/graphql/batch_key_spec.rb
+++ b/spec/lib/gitlab/graphql/batch_key_spec.rb
@@ -6,6 +6,7 @@ require 'test_prof/recipes/rspec/let_it_be'
RSpec.describe ::Gitlab::Graphql::BatchKey do
let_it_be(:rect) { Struct.new(:len, :width) }
let_it_be(:circle) { Struct.new(:radius) }
+
let(:lookahead) { nil }
let(:object) { rect.new(2, 3) }
diff --git a/spec/lib/gitlab/graphql/deprecation_spec.rb b/spec/lib/gitlab/graphql/deprecation_spec.rb
new file mode 100644
index 00000000000..8b41145b855
--- /dev/null
+++ b/spec/lib/gitlab/graphql/deprecation_spec.rb
@@ -0,0 +1,213 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'active_model'
+
+RSpec.describe ::Gitlab::Graphql::Deprecation do
+ let(:options) { {} }
+
+ subject(:deprecation) { described_class.parse(options) }
+
+ describe '.parse' do
+ context 'with nil' do
+ let(:options) { nil }
+
+ it 'parses to nil' do
+ expect(deprecation).to be_nil
+ end
+ end
+
+ context 'with empty options' do
+ let(:options) { {} }
+
+ it 'parses to an empty deprecation' do
+ expect(deprecation).to eq(described_class.new)
+ end
+ end
+
+ context 'with defined options' do
+ let(:options) { { reason: :renamed, milestone: '10.10' } }
+
+ it 'assigns the properties' do
+ expect(deprecation).to eq(described_class.new(reason: 'This was renamed', milestone: '10.10'))
+ end
+ end
+ end
+
+ describe 'validations' do
+ let(:options) { { reason: :renamed, milestone: '10.10' } }
+
+ it { is_expected.to be_valid }
+
+ context 'when the milestone is absent' do
+ before do
+ options.delete(:milestone)
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when the milestone is not milestone-ish' do
+ before do
+ options[:milestone] = 'next year'
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when the milestone is not a string' do
+ before do
+ options[:milestone] = 10.01
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when the reason is absent' do
+ before do
+ options.delete(:reason)
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when the reason is not a known reason' do
+ before do
+ options[:reason] = :not_stylish_enough
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when the reason is a string' do
+ before do
+ options[:reason] = 'not stylish enough'
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when the reason is a string ending with a period' do
+ before do
+ options[:reason] = 'not stylish enough.'
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ describe '#deprecation_reason' do
+ context 'when there is a replacement' do
+ let(:options) { { reason: :renamed, milestone: '10.10', replacement: 'X.y' } }
+
+ it 'renders as reason-replacement-milestone' do
+ expect(deprecation.deprecation_reason).to eq('This was renamed. Please use `X.y`. Deprecated in 10.10.')
+ end
+ end
+
+ context 'when there is no replacement' do
+ let(:options) { { reason: :renamed, milestone: '10.10' } }
+
+ it 'renders as reason-milestone' do
+ expect(deprecation.deprecation_reason).to eq('This was renamed. Deprecated in 10.10.')
+ end
+ end
+
+ describe 'processing of reason' do
+ described_class::REASONS.each_key do |known_reason|
+ context "when the reason is a known reason such as #{known_reason.inspect}" do
+ let(:options) { { reason: known_reason } }
+
+ it 'renders the reason_text correctly' do
+ expect(deprecation.deprecation_reason).to start_with(described_class::REASONS[known_reason])
+ end
+ end
+ end
+
+ context 'when the reason is any other string' do
+ let(:options) { { reason: 'unhelpful' } }
+
+ it 'appends a period' do
+ expect(deprecation.deprecation_reason).to start_with('unhelpful.')
+ end
+ end
+ end
+ end
+
+ describe '#edit_description' do
+ let(:options) { { reason: :renamed, milestone: '10.10' } }
+
+ it 'appends milestone:reason with a leading space if there is a description' do
+ desc = deprecation.edit_description('Some description.')
+
+ expect(desc).to eq('Some description. Deprecated in 10.10: This was renamed.')
+ end
+
+ it 'returns nil if there is no description' do
+ desc = deprecation.edit_description(nil)
+
+ expect(desc).to be_nil
+ end
+ end
+
+ describe '#original_description' do
+ it 'records the description passed to it' do
+ deprecation.edit_description('Some description.')
+
+ expect(deprecation.original_description).to eq('Some description.')
+ end
+ end
+
+ describe '#markdown' do
+ context 'when there is a replacement' do
+ let(:options) { { reason: :renamed, milestone: '10.10', replacement: 'X.y' } }
+
+ context 'when the context is :inline' do
+ it 'renders on one line' do
+ expectation = '**Deprecated** in 10.10. This was renamed. Use: `X.y`.'
+
+ expect(deprecation.markdown).to eq(expectation)
+ expect(deprecation.markdown(context: :inline)).to eq(expectation)
+ end
+ end
+
+ context 'when the context is :block' do
+ it 'renders a warning note' do
+ expectation = <<~MD.chomp
+ WARNING:
+ **Deprecated** in 10.10.
+ This was renamed.
+ Use: `X.y`.
+ MD
+
+ expect(deprecation.markdown(context: :block)).to eq(expectation)
+ end
+ end
+ end
+
+ context 'when there is no replacement' do
+ let(:options) { { reason: 'Removed', milestone: '10.10' } }
+
+ context 'when the context is :inline' do
+ it 'renders on one line' do
+ expectation = '**Deprecated** in 10.10. Removed.'
+
+ expect(deprecation.markdown).to eq(expectation)
+ expect(deprecation.markdown(context: :inline)).to eq(expectation)
+ end
+ end
+
+ context 'when the context is :block' do
+ it 'renders a warning note' do
+ expectation = <<~MD.chomp
+ WARNING:
+ **Deprecated** in 10.10.
+ Removed.
+ MD
+
+ expect(deprecation.markdown(context: :block)).to eq(expectation)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/docs/renderer_spec.rb b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
index 5afed8c3390..8c0f7aac081 100644
--- a/spec/lib/gitlab/graphql/docs/renderer_spec.rb
+++ b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
@@ -1,32 +1,35 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
RSpec.describe Gitlab::Graphql::Docs::Renderer do
describe '#contents' do
- # Returns a Schema that uses the given `type`
- def mock_schema(type, field_description)
- query_type = Class.new(Types::BaseObject) do
- graphql_name 'Query'
+ let(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
- field :foo, type, null: true do
- description field_description
+ let(:query_type) do
+ Class.new(Types::BaseObject) { graphql_name 'Query' }.tap do |t|
+ # this keeps type and field_description in scope.
+ t.field :foo, type, null: true, description: field_description do
argument :id, GraphQL::ID_TYPE, required: false, description: 'ID of the object.'
end
end
+ end
- GraphQL::Schema.define(
- query: query_type,
- resolve_type: ->(obj, ctx) { raise 'Not a real schema' }
- )
+ let(:mock_schema) do
+ Class.new(GraphQL::Schema) do
+ def resolve_type(obj, ctx)
+ raise 'Not a real schema'
+ end
+ end
end
- let_it_be(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
let(:field_description) { 'List of objects.' }
subject(:contents) do
+ mock_schema.query(query_type)
+
described_class.new(
- mock_schema(type, field_description).graphql_definition,
+ mock_schema,
output_dir: nil,
template: template
).contents
@@ -136,6 +139,22 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
null: false,
deprecated: { reason: 'This is deprecated', milestone: '1.10' },
description: 'A description.'
+ field :foo_with_args,
+ type: GraphQL::STRING_TYPE,
+ null: false,
+ deprecated: { reason: 'Do not use', milestone: '1.10' },
+ description: 'A description.' do
+ argument :fooity, ::GraphQL::INT_TYPE, required: false, description: 'X'
+ end
+ field :bar,
+ type: GraphQL::STRING_TYPE,
+ null: false,
+ description: 'A description.',
+ deprecated: {
+ reason: :renamed,
+ milestone: '1.10',
+ replacement: 'Query.boom'
+ }
end
end
@@ -145,7 +164,40 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Field | Type | Description |
| ----- | ---- | ----------- |
- | `foo` **{warning-solid}** | [`String!`](#string) | **Deprecated:** This is deprecated. Deprecated in 1.10. |
+ | `bar` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. This was renamed. Use: `Query.boom`. |
+ | `foo` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. This is deprecated. |
+ | `fooWithArgs` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. Do not use. |
+ DOC
+
+ is_expected.to include(expectation)
+ end
+ end
+
+ context 'when a Query.field is deprecated' do
+ let(:type) { ::GraphQL::INT_TYPE }
+
+ before do
+ query_type.field(
+ name: :bar,
+ type: type,
+ null: true,
+ description: 'A bar',
+ deprecated: { reason: :renamed, milestone: '10.11', replacement: 'Query.foo' }
+ )
+ end
+
+ it 'includes the deprecation' do
+ expectation = <<~DOC
+ ### `bar`
+
+ A bar.
+
+ WARNING:
+ **Deprecated** in 10.11.
+ This was renamed.
+ Use: `Query.foo`.
+
+ Returns [`Int`](#int).
DOC
is_expected.to include(expectation)
diff --git a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
index ae5d9686c54..35750a87fb5 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::Graphql::Loaders::BatchLfsOidLoader do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
+
let(:repository) { project.repository }
let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'files/lfs/lfs_object.iso'), repository) }
let(:otherblob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'README'), repository) }
diff --git a/spec/lib/gitlab/graphql/markdown_field_spec.rb b/spec/lib/gitlab/graphql/markdown_field_spec.rb
index 0e36ea14ac3..44ca23f547c 100644
--- a/spec/lib/gitlab/graphql/markdown_field_spec.rb
+++ b/spec/lib/gitlab/graphql/markdown_field_spec.rb
@@ -57,6 +57,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do
describe 'basic verification that references work' do
let_it_be(:project) { create(:project, :public) }
+
let(:issue) { create(:issue, project: project) }
let(:note) { build(:note, note: "Referencing #{issue.to_reference(full: true)}") }
diff --git a/spec/lib/gitlab/graphql/negatable_arguments_spec.rb b/spec/lib/gitlab/graphql/negatable_arguments_spec.rb
new file mode 100644
index 00000000000..bc6e25eb018
--- /dev/null
+++ b/spec/lib/gitlab/graphql/negatable_arguments_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Graphql::NegatableArguments do
+ let(:test_resolver) do
+ Class.new(Resolvers::BaseResolver).tap do |klass|
+ klass.extend described_class
+ allow(klass).to receive(:name).and_return('Resolvers::TestResolver')
+ end
+ end
+
+ describe '#negated' do
+ it 'defines :not argument' do
+ test_resolver.negated {}
+
+ expect(test_resolver.arguments['not'].type.name).to eq "Types::TestResolverNegatedParamsType"
+ end
+
+ it 'defines any arguments passed as block' do
+ test_resolver.negated do
+ argument :foo, GraphQL::STRING_TYPE, required: false
+ end
+
+ expect(test_resolver.arguments['not'].type.arguments.keys).to match_array(['foo'])
+ end
+
+ it 'defines all arguments passed as block even if called multiple times' do
+ test_resolver.negated do
+ argument :foo, GraphQL::STRING_TYPE, required: false
+ end
+ test_resolver.negated do
+ argument :bar, GraphQL::STRING_TYPE, required: false
+ end
+
+ expect(test_resolver.arguments['not'].type.arguments.keys).to match_array(%w[foo bar])
+ end
+
+ it 'allows to specify custom argument name' do
+ test_resolver.negated(param_key: :negative) {}
+
+ expect(test_resolver.arguments).to include('negative')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
index 02e67488d3f..839ad9110cc 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
@@ -337,6 +337,7 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
describe '#nodes' do
let_it_be(:all_nodes) { create_list(:project, 5) }
+
let(:paged_nodes) { subject.nodes }
it_behaves_like 'connection with paged nodes' do
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb
index ec2ec4bf50d..792cb03e8c7 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Graphql::Pagination::Keyset::LastItems do
let_it_be(:merge_request) { create(:merge_request) }
+
let(:scope) { MergeRequest.order_merged_at_asc }
subject { described_class.take_items(*args) }
diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
index 8450396284a..fc723138d88 100644
--- a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
+++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
@@ -3,43 +3,46 @@
require 'spec_helper'
RSpec.describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
- subject { described_class.new }
-
- describe '#initial_value' do
- it 'filters out sensitive variables' do
- doc = GraphQL.parse <<-GRAPHQL
- mutation createNote($body: String!) {
- createNote(input: {noteableId: "1", body: $body}) {
- note {
- id
- }
+ let(:initial_value) { analyzer.initial_value(query) }
+ let(:analyzer) { described_class.new }
+ let(:query) { GraphQL::Query.new(GitlabSchema, document: document, context: {}, variables: { body: "some note" }) }
+ let(:document) do
+ GraphQL.parse <<-GRAPHQL
+ mutation createNote($body: String!) {
+ createNote(input: {noteableId: "1", body: $body}) {
+ note {
+ id
}
}
- GRAPHQL
+ }
+ GRAPHQL
+ end
- query = GraphQL::Query.new(GitlabSchema, document: doc, context: {}, variables: { body: "some note" })
+ describe 'variables' do
+ subject { initial_value.fetch(:variables) }
- expect(subject.initial_value(query)[:variables]).to eq('{:body=>"[FILTERED]"}')
- end
+ it { is_expected.to eq('{:body=>"[FILTERED]"}') }
end
describe '#final_value' do
let(:monotonic_time_before) { 42 }
let(:monotonic_time_after) { 500 }
let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before }
+ let(:memo) { initial_value }
+
+ subject(:final_value) { analyzer.final_value(memo) }
+
+ before do
+ RequestStore.store[:graphql_logs] = nil
- it 'returns a duration in seconds' do
allow(GraphQL::Analysis).to receive(:analyze_query).and_return([4, 2, [[], []]])
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after)
allow(Gitlab::GraphqlLogger).to receive(:info)
+ end
- expected_duration = monotonic_time_duration
- memo = subject.initial_value(spy('query'))
-
- subject.final_value(memo)
-
- expect(memo).to have_key(:duration_s)
- expect(memo[:duration_s]).to eq(expected_duration)
+ it 'inserts duration in seconds to memo and sets request store' do
+ expect { final_value }.to change { memo[:duration_s] }.to(monotonic_time_duration)
+ .and change { RequestStore.store[:graphql_logs] }.to([memo])
end
end
end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index 9271b868e36..1a929373716 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -79,6 +79,21 @@ RSpec.describe Gitlab::Highlight do
expect(result).to eq(expected)
end
+
+ context 'when start line number is set' do
+ let(:expected) do
+ %q(<span id="LC10" class="line" lang="diff"><span class="gi">+aaa</span></span>
+<span id="LC11" class="line" lang="diff"><span class="gi">+bbb</span></span>
+<span id="LC12" class="line" lang="diff"><span class="gd">- ccc</span></span>
+<span id="LC13" class="line" lang="diff"> ddd</span>)
+ end
+
+ it 'highlights each line properly' do
+ result = described_class.new(file_name, content).highlight(content, context: { line_number: 10 })
+
+ expect(result).to eq(expected)
+ end
+ end
end
describe 'with CRLF' do
diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
index 8a2395d70b2..8f898d898de 100644
--- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
@@ -5,6 +5,7 @@ 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) }
+
let(:builder) { described_class.new(issue) }
describe '#build' 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 fede7f273f1..0339faa9fcf 100644
--- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::HookData::MergeRequestBuilder do
let_it_be(:merge_request) { create(:merge_request) }
+
let(:builder) { described_class.new(merge_request) }
describe '#build' do
diff --git a/spec/lib/gitlab/hook_data/release_builder_spec.rb b/spec/lib/gitlab/hook_data/release_builder_spec.rb
index b630780b162..449965f5df1 100644
--- a/spec/lib/gitlab/hook_data/release_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/release_builder_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::HookData::ReleaseBuilder do
let_it_be(:project) { create(:project, :public, :repository) }
+
let(:release) { create(:release, project: project) }
let(:builder) { described_class.new(release) }
diff --git a/spec/lib/gitlab/hook_data/user_builder_spec.rb b/spec/lib/gitlab/hook_data/user_builder_spec.rb
new file mode 100644
index 00000000000..f971089850b
--- /dev/null
+++ b/spec/lib/gitlab/hook_data/user_builder_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::HookData::UserBuilder do
+ let_it_be(:user) { create(:user, name: 'John Doe', username: 'johndoe', email: 'john@example.com') }
+
+ describe '#build' do
+ let(:data) { described_class.new(user).build(event) }
+ let(:event_name) { data[:event_name] }
+ let(:attributes) do
+ [
+ :event_name, :created_at, :updated_at, :name, :email, :user_id, :username
+ ]
+ end
+
+ context 'data' do
+ shared_examples_for 'includes the required attributes' do
+ it 'includes the required attributes' do
+ expect(data).to include(*attributes)
+
+ expect(data[:name]).to eq('John Doe')
+ expect(data[:email]).to eq('john@example.com')
+ expect(data[:user_id]).to eq(user.id)
+ expect(data[:username]).to eq('johndoe')
+ expect(data[:created_at]).to eq(user.created_at.xmlschema)
+ expect(data[:updated_at]).to eq(user.updated_at.xmlschema)
+ end
+ end
+
+ shared_examples_for 'does not include old username attributes' do
+ it 'does not include old username attributes' do
+ expect(data).not_to include(:old_username)
+ end
+ end
+
+ shared_examples_for 'does not include state attributes' do
+ it 'does not include state attributes' do
+ expect(data).not_to include(:state)
+ end
+ end
+
+ context 'on create' do
+ let(:event) { :create }
+
+ it { expect(event_name).to eq('user_create') }
+ it_behaves_like 'includes the required attributes'
+ it_behaves_like 'does not include old username attributes'
+ it_behaves_like 'does not include state attributes'
+ end
+
+ context 'on destroy' do
+ let(:event) { :destroy }
+
+ it { expect(event_name).to eq('user_destroy') }
+ it_behaves_like 'includes the required attributes'
+ it_behaves_like 'does not include old username attributes'
+ it_behaves_like 'does not include state attributes'
+ end
+
+ context 'on rename' do
+ let(:event) { :rename }
+
+ it { expect(event_name).to eq('user_rename') }
+ it_behaves_like 'includes the required attributes'
+ it_behaves_like 'does not include state attributes'
+
+ it 'includes old username details' do
+ allow(user).to receive(:username_before_last_save).and_return('old-username')
+
+ expect(data[:old_username]).to eq(user.username_before_last_save)
+ end
+ end
+
+ context 'on failed_login' do
+ let(:event) { :failed_login }
+
+ it { expect(event_name).to eq('user_failed_login') }
+ it_behaves_like 'includes the required attributes'
+ it_behaves_like 'does not include old username attributes'
+
+ it 'includes state details' do
+ user.ldap_block!
+
+ expect(data[:state]).to eq('ldap_blocked')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb
index 96e6e485841..7c57d162e9b 100644
--- a/spec/lib/gitlab/http_connection_adapter_spec.rb
+++ b/spec/lib/gitlab/http_connection_adapter_spec.rb
@@ -124,130 +124,5 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do
expect(connection.port).to eq(443)
end
end
-
- context 'when proxy settings are configured' do
- let(:options) do
- {
- http_proxyaddr: 'https://proxy.org',
- http_proxyport: 1557,
- http_proxyuser: 'user',
- http_proxypass: 'pass'
- }
- end
-
- before do
- stub_all_dns('https://proxy.org', ip_address: '166.84.12.54')
- end
-
- it 'sets up the proxy settings' do
- expect(connection.proxy_address).to eq('https://166.84.12.54')
- expect(connection.proxy_port).to eq(1557)
- expect(connection.proxy_user).to eq('user')
- expect(connection.proxy_pass).to eq('pass')
- end
-
- context 'when the address has path' do
- before do
- options[:http_proxyaddr] = 'https://proxy.org/path'
- end
-
- it 'sets up the proxy settings' do
- expect(connection.proxy_address).to eq('https://166.84.12.54/path')
- expect(connection.proxy_port).to eq(1557)
- end
- end
-
- context 'when the port is in the address and port' do
- before do
- options[:http_proxyaddr] = 'https://proxy.org:1422'
- end
-
- it 'sets up the proxy settings' do
- expect(connection.proxy_address).to eq('https://166.84.12.54')
- expect(connection.proxy_port).to eq(1557)
- end
-
- context 'when the port is only in the address' do
- before do
- options[:http_proxyport] = nil
- end
-
- it 'sets up the proxy settings' do
- expect(connection.proxy_address).to eq('https://166.84.12.54')
- expect(connection.proxy_port).to eq(1422)
- end
- end
- end
-
- context 'when it is a request to local network' do
- before do
- options[:http_proxyaddr] = 'http://172.16.0.0/12'
- end
-
- it 'raises error' do
- expect { subject }.to raise_error(
- Gitlab::HTTP::BlockedUrlError,
- "URL 'http://172.16.0.0:1557/12' is blocked: Requests to the local network are not allowed"
- )
- end
-
- context 'when local request allowed' do
- before do
- options[:allow_local_requests] = true
- end
-
- it 'sets up the connection' do
- expect(connection.proxy_address).to eq('http://172.16.0.0/12')
- expect(connection.proxy_port).to eq(1557)
- end
- end
- end
-
- context 'when it is a request to local address' do
- before do
- options[:http_proxyaddr] = 'http://127.0.0.1'
- end
-
- it 'raises error' do
- expect { subject }.to raise_error(
- Gitlab::HTTP::BlockedUrlError,
- "URL 'http://127.0.0.1:1557' is blocked: Requests to localhost are not allowed"
- )
- end
-
- context 'when local request allowed' do
- before do
- options[:allow_local_requests] = true
- end
-
- it 'sets up the connection' do
- expect(connection.proxy_address).to eq('http://127.0.0.1')
- expect(connection.proxy_port).to eq(1557)
- end
- end
- end
-
- context 'when http(s) environment variable is set' do
- before do
- stub_env('https_proxy' => 'https://my.proxy')
- end
-
- it 'sets up the connection' do
- expect(connection.proxy_address).to eq('https://proxy.org')
- expect(connection.proxy_port).to eq(1557)
- end
- end
-
- context 'when DNS rebinding protection is disabled' do
- before do
- stub_application_setting(dns_rebinding_protection_enabled: false)
- end
-
- it 'sets up the connection' do
- expect(connection.proxy_address).to eq('https://proxy.org')
- expect(connection.proxy_port).to eq(1557)
- end
- end
- end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 37b43066a62..5d1e3c79474 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -256,6 +256,8 @@ ci_pipelines:
- messages
- pipeline_artifacts
- latest_statuses
+- dast_profile
+- dast_profiles_pipeline
ci_refs:
- project
- ci_pipelines
@@ -269,6 +271,7 @@ stages:
- builds
- bridges
- latest_statuses
+- retried_statuses
statuses:
- project
- pipeline
@@ -740,3 +743,5 @@ status_page_published_incident:
- issue
issuable_sla:
- issue
+push_rule:
+ - group
diff --git a/spec/lib/gitlab/import_export/design_repo_saver_spec.rb b/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
index 5501e3dee5a..fd3539ab99c 100644
--- a/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::ImportExport::DesignRepoSaver do
describe 'bundle a design Git repo' do
let_it_be(:user) { create(:user) }
let_it_be(:design) { create(:design, :with_file, versions_count: 1) }
+
let!(:project) { create(:project, :design_repo) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
index d084b9d7f7e..29b192de809 100644
--- a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe Gitlab::ImportExport::FastHashSerializer do
let_it_be(:user) { create(:user) }
let_it_be(:project) { setup_project }
+
let(:shared) { project.import_export_shared }
let(:reader) { Gitlab::ImportExport::Reader.new(shared: shared) }
let(:tree) { reader.project_tree }
diff --git a/spec/lib/gitlab/import_export/project/export_task_spec.rb b/spec/lib/gitlab/import_export/project/export_task_spec.rb
index 1048379a5d6..7fcd2187a90 100644
--- a/spec/lib/gitlab/import_export/project/export_task_spec.rb
+++ b/spec/lib/gitlab/import_export/project/export_task_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::ImportExport::Project::ExportTask do
let_it_be(:username) { 'root' }
let(:namespace_path) { username }
let_it_be(:user) { create(:user, username: username) }
+
let(:measurement_enabled) { false }
let(:file_path) { 'spec/fixtures/gitlab/import_export/test_project_export.tar.gz' }
let(:project) { create(:project, creator: user, namespace: user.namespace) }
diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
index e2bf87bf29f..bc5e6ea7bb3 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -684,7 +684,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
it 'overrides project feature access levels' do
access_level_keys = ProjectFeature.available_features.map { |feature| ProjectFeature.access_level_attribute(feature) }
- disabled_access_levels = Hash[access_level_keys.collect { |item| [item, 'disabled'] }]
+ disabled_access_levels = access_level_keys.to_h { |item| [item, 'disabled'] }
project.create_import_data(data: { override_params: disabled_access_levels })
diff --git a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
index 50494433c5d..fd6c66a10a7 100644
--- a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
@@ -267,6 +267,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do
describe '#saves project tree' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
+
let(:project) { setup_project }
let(:full_path) do
if ndjson_enabled
diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb
index 52001e778d6..73e0e0a08b9 100644
--- a/spec/lib/gitlab/import_export/repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::ImportExport::RepoSaver do
describe 'bundle a project Git repo' do
let_it_be(:user) { create(:user) }
+
let!(:project) { create(:project, :repository) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
index 323ed9a746e..9f3e8d2fa86 100644
--- a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Gitlab::ImportExport::SnippetRepoSaver do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) }
+
let(:shared) { project.import_export_shared }
let(:bundler) { described_class.new(project: project, shared: shared, repository: snippet.repository) }
let(:bundle_path) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) }
diff --git a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
index 8507c46ec83..aa284c60e73 100644
--- a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do
describe 'bundle a project Git repo' do
let_it_be(:user) { create(:user) }
+
let!(:project) { create(:project) }
let(:shared) { project.import_export_shared }
let(:bundler) { described_class.new(current_user: user, project: project, shared: shared) }
diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
index 540f90e7804..c936d2bc27d 100644
--- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::ImportExport::WikiRepoSaver do
describe 'bundle a wiki Git repo' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :wiki_repo) }
+
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
let(:wiki_bundler) { described_class.new(exportable: project, shared: shared) }
diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb
index a5c9cde4c37..488324ccddc 100644
--- a/spec/lib/gitlab/instrumentation_helper_spec.rb
+++ b/spec/lib/gitlab/instrumentation_helper_spec.rb
@@ -6,53 +6,6 @@ require 'rspec-parameterized'
RSpec.describe Gitlab::InstrumentationHelper do
using RSpec::Parameterized::TableSyntax
- describe '.keys' do
- it 'returns all available payload keys' do
- expected_keys = [
- :cpu_s,
- :gitaly_calls,
- :gitaly_duration_s,
- :rugged_calls,
- :rugged_duration_s,
- :elasticsearch_calls,
- :elasticsearch_duration_s,
- :elasticsearch_timed_out_count,
- :mem_objects,
- :mem_bytes,
- :mem_mallocs,
- :redis_calls,
- :redis_duration_s,
- :redis_read_bytes,
- :redis_write_bytes,
- :redis_action_cable_calls,
- :redis_action_cable_duration_s,
- :redis_action_cable_read_bytes,
- :redis_action_cable_write_bytes,
- :redis_cache_calls,
- :redis_cache_duration_s,
- :redis_cache_read_bytes,
- :redis_cache_write_bytes,
- :redis_queues_calls,
- :redis_queues_duration_s,
- :redis_queues_read_bytes,
- :redis_queues_write_bytes,
- :redis_shared_state_calls,
- :redis_shared_state_duration_s,
- :redis_shared_state_read_bytes,
- :redis_shared_state_write_bytes,
- :db_count,
- :db_write_count,
- :db_cached_count,
- :external_http_count,
- :external_http_duration_s,
- :rack_attack_redis_count,
- :rack_attack_redis_duration_s
- ]
-
- expect(described_class.keys).to eq(expected_keys)
- end
- end
-
describe '.add_instrumentation_data', :request_store do
let(:payload) { {} }
diff --git a/spec/lib/gitlab/jira_import/base_importer_spec.rb b/spec/lib/gitlab/jira_import/base_importer_spec.rb
index 1470bad2c4c..9d8143775f9 100644
--- a/spec/lib/gitlab/jira_import/base_importer_spec.rb
+++ b/spec/lib/gitlab/jira_import/base_importer_spec.rb
@@ -27,6 +27,7 @@ RSpec.describe Gitlab::JiraImport::BaseImporter do
context 'when import data exists' do
let_it_be(:project) { create(:project) }
let_it_be(:jira_import) { create(:jira_import_state, project: project) }
+
let(:subject) { described_class.new(project) }
context 'when #imported_items_cache_key is not implemented' do
diff --git a/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb b/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb
index 4e2c5afb077..b8c0dc64581 100644
--- a/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb
+++ b/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe Gitlab::JiraImport::HandleLabelsService do
let_it_be(:project_label) { create(:label, project: project, title: 'bug') }
let_it_be(:other_project_label) { create(:label, title: 'feature') }
let_it_be(:group_label) { create(:group_label, group: group, title: 'dev') }
+
let(:jira_labels) { %w(bug feature dev group::new) }
subject { described_class.new(project, jira_labels).execute }
diff --git a/spec/lib/gitlab/jira_import_spec.rb b/spec/lib/gitlab/jira_import_spec.rb
index 2b602c80640..94fdff984d5 100644
--- a/spec/lib/gitlab/jira_import_spec.rb
+++ b/spec/lib/gitlab/jira_import_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe Gitlab::JiraImport do
include JiraServiceHelper
let_it_be(:project, reload: true) { create(:project) }
+
let(:additional_params) { {} }
subject { described_class.validate_project_settings!(project, **additional_params) }
diff --git a/spec/lib/gitlab/json_spec.rb b/spec/lib/gitlab/json_spec.rb
index 59ec94f2855..42c4b315edf 100644
--- a/spec/lib/gitlab/json_spec.rb
+++ b/spec/lib/gitlab/json_spec.rb
@@ -348,6 +348,66 @@ RSpec.describe Gitlab::Json do
subject
end
end
+
+ context "precompiled JSON" do
+ let(:obj) { Gitlab::Json::PrecompiledJson.new(result) }
+
+ it "renders the string directly" do
+ expect(subject).to eq(result)
+ end
+
+ it "calls #to_s on the object" do
+ expect(obj).to receive(:to_s).once
+
+ subject
+ end
+
+ it "doesn't run the JSON formatter" do
+ expect(Gitlab::Json).not_to receive(:dump)
+
+ subject
+ end
+ end
+ end
+
+ describe Gitlab::Json::PrecompiledJson do
+ subject(:precompiled) { described_class.new(obj) }
+
+ describe "#to_s" do
+ subject { precompiled.to_s }
+
+ context "obj is a string" do
+ let(:obj) { "{}" }
+
+ it "returns a string" do
+ expect(subject).to eq("{}")
+ end
+ end
+
+ context "obj is an array" do
+ let(:obj) { ["{\"foo\": \"bar\"}", "{}"] }
+
+ it "returns a string" do
+ expect(subject).to eq("[{\"foo\": \"bar\"},{}]")
+ end
+ end
+
+ context "obj is an array of un-stringables" do
+ let(:obj) { [BasicObject.new] }
+
+ it "raises an error" do
+ expect { subject }.to raise_error(NoMethodError)
+ end
+ end
+
+ context "obj is something else" do
+ let(:obj) { {} }
+
+ it "raises an error" do
+ expect { subject }.to raise_error(described_class::UnsupportedFormatError)
+ end
+ end
+ end
end
describe Gitlab::Json::LimitedEncoder do
diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
index 56074147854..9a4d7bd996e 100644
--- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
@@ -290,7 +290,7 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer do
subject { described_class.new(project) }
before do
- project.update(import_type: 'gitea', import_url: "#{repo_root}/foo/group/project.git")
+ project.update!(import_type: 'gitea', import_url: "#{repo_root}/foo/group/project.git")
end
it_behaves_like 'Gitlab::LegacyGithubImport::Importer#execute' do
diff --git a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
index 4b1e0d2c144..454bab8846c 100644
--- a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
@@ -152,7 +152,7 @@ RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter do
context 'when importing a Gitea project' do
before do
- project.update(import_type: 'gitea')
+ project.update!(import_type: 'gitea')
end
it_behaves_like 'Gitlab::LegacyGithubImport::IssueFormatter#attributes'
diff --git a/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb
index 148b59dedab..64fcc46d304 100644
--- a/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe Gitlab::LegacyGithubImport::MilestoneFormatter do
let(:iid_attr) { :id }
before do
- project.update(import_type: 'gitea')
+ project.update!(import_type: 'gitea')
end
it_behaves_like 'Gitlab::LegacyGithubImport::MilestoneFormatter#attributes'
diff --git a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
index 3e6b9340d0b..7d8875e36c3 100644
--- a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
@@ -260,7 +260,7 @@ RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter do
context 'when importing a Gitea project' do
before do
- project.update(import_type: 'gitea')
+ project.update!(import_type: 'gitea')
end
it_behaves_like 'Gitlab::LegacyGithubImport::PullRequestFormatter#attributes'
diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
index be562d916d3..23dbd4a5bb3 100644
--- a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
end
let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 }
- let(:thing) { klass.create(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+ let(:thing) { klass.create!(title: markdown, title_html: html, cached_markdown_version: cache_version) }
let(:markdown) { '`Foo`' }
let(:html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Foo</code></p>' }
@@ -28,7 +28,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
before do
thing.title = thing.title
- thing.save
+ thing.save!
end
it { expect(thing.title).to eq(markdown) }
@@ -38,11 +38,11 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
end
context 'a changed markdown field' do
- let(:thing) { klass.create(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+ let(:thing) { klass.create!(title: markdown, title_html: html, cached_markdown_version: cache_version) }
before do
thing.title = updated_markdown
- thing.save
+ thing.save!
end
it { expect(thing.title_html).to eq(updated_html) }
@@ -53,9 +53,9 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
it do
expect(thing).to receive(:refresh_markdown_cache).once
thing.title = ''
- thing.save
+ thing.save!
thing.title = ''
- thing.save
+ thing.save!
end
end
@@ -63,9 +63,9 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
it do
expect(thing).to receive(:refresh_markdown_cache).once
thing.title = '[//]: # (This is also a comment.)'
- thing.save
+ thing.save!
thing.title = '[//]: # (This is also a comment.)'
- thing.save
+ thing.save!
end
end
@@ -74,7 +74,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
before do
thing.state_id = 2
- thing.save
+ thing.save!
end
it { expect(thing.state_id).to eq(2) }
@@ -87,7 +87,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
let(:thing) { klass.new(title: updated_markdown, title_html: html, cached_markdown_version: nil) }
before do
- thing.save
+ thing.save!
end
it { expect(thing.title_html).to eq(updated_html) }
@@ -99,7 +99,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
thing.project = :new_project
allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
- thing.save
+ thing.save!
expect(thing.title_html).to eq(updated_html)
expect(thing.description_html).to eq(updated_html)
@@ -110,7 +110,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
thing.author = :new_author
allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
- thing.save
+ thing.save!
expect(thing.title_html).to eq(updated_html)
expect(thing.description_html).to eq(updated_html)
@@ -125,7 +125,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
end
describe '#cached_html_up_to_date?' do
- let(:thing) { klass.create(title: updated_markdown, title_html: html, cached_markdown_version: nil) }
+ let(:thing) { klass.create!(title: updated_markdown, title_html: html, cached_markdown_version: nil) }
subject { thing.cached_html_up_to_date?(:title) }
diff --git a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
index 3dcb9f160ba..b5d458f15fc 100644
--- a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
@@ -7,7 +7,8 @@ RSpec.describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cach
include CacheMarkdownField
def initialize(title: nil, description: nil)
- @title, @description = title, description
+ @title = title
+ @description = description
end
attr_reader :title, :description
diff --git a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
index bf40af8e62e..07a87b245c2 100644
--- a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
describe '.bulk_read' do
before do
- store.save(field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1)
+ store.save(field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1) # rubocop:disable Rails/SaveBang
end
it 'returns a hash of values from store' do
@@ -59,7 +59,7 @@ RSpec.describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
it 'stores updates to html fields and version' do
values_to_store = { field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1 }
- store.save(values_to_store)
+ store.save(values_to_store) # rubocop:disable Rails/SaveBang
expect(read_values)
.to eq(field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1")
diff --git a/spec/lib/gitlab/marker_range_spec.rb b/spec/lib/gitlab/marker_range_spec.rb
index 5f73d2a5048..c4670ec58a8 100644
--- a/spec/lib/gitlab/marker_range_spec.rb
+++ b/spec/lib/gitlab/marker_range_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Gitlab::MarkerRange do
let(:last) { 10 }
let(:mode) { nil }
- it { is_expected.to eq(first..last) }
+ it { expect(marker_range.to_range).to eq(first..last) }
it 'behaves like a Range' do
is_expected.to be_kind_of(Range)
@@ -51,14 +51,14 @@ RSpec.describe Gitlab::MarkerRange do
end
it 'keeps correct range' do
- is_expected.to eq(range)
+ is_expected.to eq(described_class.new(1, 3))
end
context 'when range excludes end' do
let(:range) { 1...3 }
it 'keeps correct range' do
- is_expected.to eq(range)
+ is_expected.to eq(described_class.new(1, 3, exclude_end: true))
end
end
@@ -68,4 +68,31 @@ RSpec.describe Gitlab::MarkerRange do
it { is_expected.to be(marker_range) }
end
end
+
+ describe '#==' do
+ subject { default_marker_range == another_marker_range }
+
+ let(:default_marker_range) { described_class.new(0, 1, mode: :addition) }
+ let(:another_marker_range) { default_marker_range }
+
+ it { is_expected.to be_truthy }
+
+ context 'when marker ranges have different modes' do
+ let(:another_marker_range) { described_class.new(0, 1, mode: :deletion) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when marker ranges have different ranges' do
+ let(:another_marker_range) { described_class.new(0, 2, mode: :addition) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when marker ranges is a simple range' do
+ let(:another_marker_range) { (0..1) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
end
diff --git a/spec/lib/gitlab/metrics/background_transaction_spec.rb b/spec/lib/gitlab/metrics/background_transaction_spec.rb
index b31a2f7549a..d36ee24fc50 100644
--- a/spec/lib/gitlab/metrics/background_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/background_transaction_spec.rb
@@ -29,19 +29,62 @@ RSpec.describe Gitlab::Metrics::BackgroundTransaction do
end
describe '#labels' do
- it 'provides labels with endpoint_id and feature_category' do
- Labkit::Context.with_context(feature_category: 'projects', caller_id: 'TestWorker') do
- expect(transaction.labels).to eq({ endpoint_id: 'TestWorker', feature_category: 'projects' })
+ context 'when the worker queue is accessible' do
+ before do
+ test_worker_class = Class.new do
+ def self.queue
+ 'test_worker'
+ end
+ end
+ stub_const('TestWorker', test_worker_class)
+ end
+
+ it 'provides labels with endpoint_id, feature_category and queue' do
+ Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: 'TestWorker') do
+ expect(transaction.labels).to eq({ endpoint_id: 'TestWorker', feature_category: 'projects', queue: 'test_worker' })
+ end
+ end
+ end
+
+ context 'when the worker name does not exist' do
+ it 'provides labels with endpoint_id and feature_category' do
+ # 123TestWorker is an invalid constant
+ Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: '123TestWorker') do
+ expect(transaction.labels).to eq({ endpoint_id: '123TestWorker', feature_category: 'projects', queue: nil })
+ end
+ end
+ end
+
+ context 'when the worker queue is not accessible' do
+ before do
+ stub_const('TestWorker', Class.new)
+ end
+
+ it 'provides labels with endpoint_id and feature_category' do
+ Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: 'TestWorker') do
+ expect(transaction.labels).to eq({ endpoint_id: 'TestWorker', feature_category: 'projects', queue: nil })
+ end
end
end
end
RSpec.shared_examples 'metric with labels' do |metric_method|
+ before do
+ test_worker_class = Class.new do
+ def self.queue
+ 'test_worker'
+ end
+ end
+ stub_const('TestWorker', test_worker_class)
+ end
+
it 'measures with correct labels and value' do
value = 1
- expect(prometheus_metric).to receive(metric_method).with({ endpoint_id: 'TestWorker', feature_category: 'projects' }, value)
+ expect(prometheus_metric).to receive(metric_method).with({
+ endpoint_id: 'TestWorker', feature_category: 'projects', queue: 'test_worker'
+ }, value)
- Labkit::Context.with_context(feature_category: 'projects', caller_id: 'TestWorker') do
+ Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: 'TestWorker') do
transaction.send(metric_method, :test_metric, value)
end
end
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index dffd37eeb9d..6bfcfa21289 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -8,65 +8,146 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
let(:env) { {} }
let(:subscriber) { described_class.new }
let(:connection) { double(:connection) }
- let(:payload) { { sql: 'SELECT * FROM users WHERE id = 10', connection: connection } }
-
- let(:event) do
- double(
- :event,
- name: 'sql.active_record',
- duration: 2,
- payload: payload
- )
- end
- # Emulate Marginalia pre-pending comments
- def sql(query, comments: true)
- if comments && !%w[BEGIN COMMIT].include?(query)
- "/*application:web,controller:badges,action:pipeline,correlation_id:01EYN39K9VMJC56Z7808N7RSRH*/ #{query}"
- else
- query
+ describe '#transaction' do
+ let(:web_transaction) { double('Gitlab::Metrics::WebTransaction') }
+ let(:background_transaction) { double('Gitlab::Metrics::WebTransaction') }
+
+ let(:event) do
+ double(
+ :event,
+ name: 'transaction.active_record',
+ duration: 230,
+ payload: { connection: connection }
+ )
end
- end
- shared_examples 'track generic sql events' do
- where(:name, :sql_query, :record_query, :record_write_query, :record_cached_query) do
- 'SQL' | 'SELECT * FROM users WHERE id = 10' | true | false | false
- 'SQL' | 'WITH active_milestones AS (SELECT COUNT(*), state FROM milestones GROUP BY state) SELECT * FROM active_milestones' | true | false | false
- 'SQL' | 'SELECT * FROM users WHERE id = 10 FOR UPDATE' | true | true | false
- 'SQL' | 'WITH archived_rows AS (SELECT * FROM users WHERE archived = true) INSERT INTO products_log SELECT * FROM archived_rows' | true | true | false
- 'SQL' | 'DELETE FROM users where id = 10' | true | true | false
- 'SQL' | 'INSERT INTO project_ci_cd_settings (project_id) SELECT id FROM projects' | true | true | false
- 'SQL' | 'UPDATE users SET admin = true WHERE id = 10' | true | true | false
- 'CACHE' | 'SELECT * FROM users WHERE id = 10' | true | false | true
- 'SCHEMA' | "SELECT attr.attname FROM pg_attribute attr INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) WHERE cons.contype = 'p' AND cons.conrelid = '\"projects\"'::regclass" | false | false | false
- nil | 'BEGIN' | false | false | false
- nil | 'COMMIT' | false | false | false
+ before do
+ allow(background_transaction).to receive(:observe)
+ allow(web_transaction).to receive(:observe)
end
- with_them do
- let(:payload) { { name: name, sql: sql(sql_query, comments: comments), connection: connection } }
+ context 'when both web and background transaction are available' do
+ before do
+ allow(::Gitlab::Metrics::WebTransaction).to receive(:current)
+ .and_return(web_transaction)
+ allow(::Gitlab::Metrics::BackgroundTransaction).to receive(:current)
+ .and_return(background_transaction)
+ end
+
+ it 'captures the metrics for web only' do
+ expect(web_transaction).to receive(:observe).with(:gitlab_database_transaction_seconds, 0.23)
- it 'marks the current thread as using the database' do
- # since it would already have been toggled by other specs
- Thread.current[:uses_db_connection] = nil
+ expect(background_transaction).not_to receive(:observe)
+ expect(background_transaction).not_to receive(:increment)
- expect { subscriber.sql(event) }.to change { Thread.current[:uses_db_connection] }.from(nil).to(true)
+ subscriber.transaction(event)
end
+ end
+
+ context 'when web transaction is available' do
+ let(:web_transaction) { double('Gitlab::Metrics::WebTransaction') }
+
+ before do
+ allow(::Gitlab::Metrics::WebTransaction).to receive(:current)
+ .and_return(web_transaction)
+ allow(::Gitlab::Metrics::BackgroundTransaction).to receive(:current)
+ .and_return(nil)
+ end
+
+ it 'captures the metrics for web only' do
+ expect(web_transaction).to receive(:observe).with(:gitlab_database_transaction_seconds, 0.23)
- it_behaves_like 'record ActiveRecord metrics'
- it_behaves_like 'store ActiveRecord info in RequestStore'
+ expect(background_transaction).not_to receive(:observe)
+ expect(background_transaction).not_to receive(:increment)
+
+ subscriber.transaction(event)
+ end
end
- end
- context 'without Marginalia comments' do
- let(:comments) { false }
+ context 'when background transaction is available' do
+ let(:background_transaction) { double('Gitlab::Metrics::BackgroundTransaction') }
+
+ before do
+ allow(::Gitlab::Metrics::WebTransaction).to receive(:current)
+ .and_return(nil)
+ allow(::Gitlab::Metrics::BackgroundTransaction).to receive(:current)
+ .and_return(background_transaction)
+ end
- it_behaves_like 'track generic sql events'
+ it 'captures the metrics for web only' do
+ expect(background_transaction).to receive(:observe).with(:gitlab_database_transaction_seconds, 0.23)
+
+ expect(web_transaction).not_to receive(:observe)
+ expect(web_transaction).not_to receive(:increment)
+
+ subscriber.transaction(event)
+ end
+ end
end
- context 'with Marginalia comments' do
- let(:comments) { true }
+ describe '#sql' do
+ let(:payload) { { sql: 'SELECT * FROM users WHERE id = 10', connection: connection } }
- it_behaves_like 'track generic sql events'
+ let(:event) do
+ double(
+ :event,
+ name: 'sql.active_record',
+ duration: 2,
+ payload: payload
+ )
+ end
+
+ # Emulate Marginalia pre-pending comments
+ def sql(query, comments: true)
+ if comments && !%w[BEGIN COMMIT].include?(query)
+ "/*application:web,controller:badges,action:pipeline,correlation_id:01EYN39K9VMJC56Z7808N7RSRH*/ #{query}"
+ else
+ query
+ end
+ end
+
+ shared_examples 'track generic sql events' do
+ where(:name, :sql_query, :record_query, :record_write_query, :record_cached_query) do
+ 'SQL' | 'SELECT * FROM users WHERE id = 10' | true | false | false
+ 'SQL' | 'WITH active_milestones AS (SELECT COUNT(*), state FROM milestones GROUP BY state) SELECT * FROM active_milestones' | true | false | false
+ 'SQL' | 'SELECT * FROM users WHERE id = 10 FOR UPDATE' | true | true | false
+ 'SQL' | 'WITH archived_rows AS (SELECT * FROM users WHERE archived = true) INSERT INTO products_log SELECT * FROM archived_rows' | true | true | false
+ 'SQL' | 'DELETE FROM users where id = 10' | true | true | false
+ 'SQL' | 'INSERT INTO project_ci_cd_settings (project_id) SELECT id FROM projects' | true | true | false
+ 'SQL' | 'UPDATE users SET admin = true WHERE id = 10' | true | true | false
+ 'CACHE' | 'SELECT * FROM users WHERE id = 10' | true | false | true
+ 'SCHEMA' | "SELECT attr.attname FROM pg_attribute attr INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) WHERE cons.contype = 'p' AND cons.conrelid = '\"projects\"'::regclass" | false | false | false
+ nil | 'BEGIN' | false | false | false
+ nil | 'COMMIT' | false | false | false
+ end
+
+ with_them do
+ let(:payload) { { name: name, sql: sql(sql_query, comments: comments), connection: connection } }
+ let(:record_wal_query) { false }
+
+ it 'marks the current thread as using the database' do
+ # since it would already have been toggled by other specs
+ Thread.current[:uses_db_connection] = nil
+
+ expect { subscriber.sql(event) }.to change { Thread.current[:uses_db_connection] }.from(nil).to(true)
+ end
+
+ it_behaves_like 'record ActiveRecord metrics'
+ it_behaves_like 'store ActiveRecord info in RequestStore'
+ end
+ end
+
+ context 'without Marginalia comments' do
+ let(:comments) { false }
+
+ it_behaves_like 'track generic sql events'
+ end
+
+ context 'with Marginalia comments' do
+ let(:comments) { true }
+
+ it_behaves_like 'track generic sql events'
+ end
end
end
diff --git a/spec/lib/gitlab/metrics/subscribers/external_http_spec.rb b/spec/lib/gitlab/metrics/subscribers/external_http_spec.rb
index 5bcaf8fbc47..adbc05cb711 100644
--- a/spec/lib/gitlab/metrics/subscribers/external_http_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/external_http_spec.rb
@@ -6,29 +6,45 @@ RSpec.describe Gitlab::Metrics::Subscribers::ExternalHttp, :request_store do
let(:transaction) { Gitlab::Metrics::Transaction.new }
let(:subscriber) { described_class.new }
+ around do |example|
+ freeze_time { example.run }
+ end
+
let(:event_1) do
- double(:event, payload: {
- method: 'POST', code: "200", duration: 0.321,
- scheme: 'https', host: 'gitlab.com', port: 80, path: '/api/v4/projects',
- query: 'current=true'
- })
+ double(
+ :event,
+ payload: {
+ method: 'POST', code: "200", duration: 0.321,
+ scheme: 'https', host: 'gitlab.com', port: 80, path: '/api/v4/projects',
+ query: 'current=true'
+ },
+ time: Time.current
+ )
end
let(:event_2) do
- double(:event, payload: {
- method: 'GET', code: "301", duration: 0.12,
- scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2',
- query: 'current=true'
- })
+ double(
+ :event,
+ payload: {
+ method: 'GET', code: "301", duration: 0.12,
+ scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2',
+ query: 'current=true'
+ },
+ time: Time.current
+ )
end
let(:event_3) do
- double(:event, payload: {
- method: 'POST', duration: 5.3,
- scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2/issues',
- query: 'current=true',
- exception_object: Net::ReadTimeout.new
- })
+ double(
+ :event,
+ payload: {
+ method: 'POST', duration: 5.3,
+ scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2/issues',
+ query: 'current=true',
+ exception_object: Net::ReadTimeout.new
+ },
+ time: Time.current
+ )
end
describe '.detail_store' do
@@ -134,19 +150,22 @@ RSpec.describe Gitlab::Metrics::Subscribers::ExternalHttp, :request_store do
subscriber.request(event_3)
expect(Gitlab::SafeRequestStore[:external_http_detail_store].length).to eq(3)
- expect(Gitlab::SafeRequestStore[:external_http_detail_store][0]).to include(
+ expect(Gitlab::SafeRequestStore[:external_http_detail_store][0]).to match a_hash_including(
+ start: be_like_time(Time.current),
method: 'POST', code: "200", duration: 0.321,
scheme: 'https', host: 'gitlab.com', port: 80, path: '/api/v4/projects',
query: 'current=true', exception_object: nil,
backtrace: be_a(Array)
)
- expect(Gitlab::SafeRequestStore[:external_http_detail_store][1]).to include(
+ expect(Gitlab::SafeRequestStore[:external_http_detail_store][1]).to match a_hash_including(
+ start: be_like_time(Time.current),
method: 'GET', code: "301", duration: 0.12,
scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2',
query: 'current=true', exception_object: nil,
backtrace: be_a(Array)
)
- expect(Gitlab::SafeRequestStore[:external_http_detail_store][2]).to include(
+ expect(Gitlab::SafeRequestStore[:external_http_detail_store][2]).to match a_hash_including(
+ start: be_like_time(Time.current),
method: 'POST', duration: 5.3,
scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2/issues',
query: 'current=true',
diff --git a/spec/lib/gitlab/middleware/rack_multipart_tempfile_factory_spec.rb b/spec/lib/gitlab/middleware/rack_multipart_tempfile_factory_spec.rb
new file mode 100644
index 00000000000..b9d00b556c5
--- /dev/null
+++ b/spec/lib/gitlab/middleware/rack_multipart_tempfile_factory_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rack'
+
+RSpec.describe Gitlab::Middleware::RackMultipartTempfileFactory do
+ let(:app) do
+ lambda do |env|
+ params = Rack::Request.new(env).params
+
+ if params['file']
+ [200, { 'Content-Type' => params['file'][:type] }, [params['file'][:tempfile].read]]
+ else
+ [204, {}, []]
+ end
+ end
+ end
+
+ let(:file_contents) { '/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg' }
+
+ let(:multipart_fixture) do
+ boundary = 'AaB03x'
+ data = <<~DATA
+ --#{boundary}\r
+ Content-Disposition: form-data; name="file"; filename="dj.jpg"\r
+ Content-Type: image/jpeg\r
+ Content-Transfer-Encoding: base64\r
+ \r
+ #{file_contents}\r
+ --#{boundary}--\r
+ DATA
+
+ {
+ 'CONTENT_TYPE' => "multipart/form-data; boundary=#{boundary}",
+ 'CONTENT_LENGTH' => data.bytesize.to_s,
+ input: StringIO.new(data)
+ }
+ end
+
+ subject { described_class.new(app) }
+
+ context 'for a multipart request' do
+ let(:env) { Rack::MockRequest.env_for('/', multipart_fixture) }
+
+ context 'when the environment variable is enabled' do
+ before do
+ stub_env('GITLAB_TEMPFILE_IMMEDIATE_UNLINK', '1')
+ end
+
+ it 'immediately unlinks the temporary file' do
+ tempfile = Tempfile.new('foo')
+
+ expect(tempfile.path).not_to be(nil)
+ expect(Rack::Multipart::Parser::TEMPFILE_FACTORY).to receive(:call).and_return(tempfile)
+ expect(tempfile).to receive(:unlink).and_call_original
+
+ subject.call(env)
+
+ expect(tempfile.path).to be(nil)
+ end
+
+ it 'processes the request as normal' do
+ expect(subject.call(env)).to eq([200, { 'Content-Type' => 'image/jpeg' }, [file_contents]])
+ end
+ end
+
+ context 'when the environment variable is disabled' do
+ it 'does not immediately unlink the temporary file' do
+ tempfile = Tempfile.new('foo')
+
+ expect(tempfile.path).not_to be(nil)
+ expect(Rack::Multipart::Parser::TEMPFILE_FACTORY).to receive(:call).and_return(tempfile)
+ expect(tempfile).not_to receive(:unlink).and_call_original
+
+ subject.call(env)
+
+ expect(tempfile.path).not_to be(nil)
+ end
+
+ it 'processes the request as normal' do
+ expect(subject.call(env)).to eq([200, { 'Content-Type' => 'image/jpeg' }, [file_contents]])
+ end
+ end
+ end
+
+ context 'for a regular request' do
+ let(:env) { Rack::MockRequest.env_for('/', params: { 'foo' => 'bar' }) }
+
+ it 'does nothing' do
+ expect(Rack::Multipart::Parser::TEMPFILE_FACTORY).not_to receive(:call)
+ expect(subject.call(env)).to eq([204, {}, []])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/object_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb
index 08e1a5ee0a3..eebd67695e0 100644
--- a/spec/lib/gitlab/object_hierarchy_spec.rb
+++ b/spec/lib/gitlab/object_hierarchy_spec.rb
@@ -3,14 +3,16 @@
require 'spec_helper'
RSpec.describe Gitlab::ObjectHierarchy do
- let!(:parent) { create(:group) }
- let!(:child1) { create(:group, parent: parent) }
- let!(:child2) { create(:group, parent: child1) }
+ let_it_be(:parent) { create(:group) }
+ let_it_be(:child1) { create(:group, parent: parent) }
+ let_it_be(:child2) { create(:group, parent: child1) }
+
+ let(:options) { {} }
shared_context 'Gitlab::ObjectHierarchy test cases' do
describe '#base_and_ancestors' do
let(:relation) do
- described_class.new(Group.where(id: child2.id)).base_and_ancestors
+ described_class.new(Group.where(id: child2.id), options: options).base_and_ancestors
end
it 'includes the base rows' do
@@ -22,13 +24,13 @@ RSpec.describe Gitlab::ObjectHierarchy do
end
it 'can find ancestors upto a certain level' do
- relation = described_class.new(Group.where(id: child2)).base_and_ancestors(upto: child1)
+ relation = described_class.new(Group.where(id: child2), options: options).base_and_ancestors(upto: child1)
expect(relation).to contain_exactly(child2)
end
it 'uses ancestors_base #initialize argument' do
- relation = described_class.new(Group.where(id: child2.id), Group.none).base_and_ancestors
+ relation = described_class.new(Group.where(id: child2.id), Group.none, options: options).base_and_ancestors
expect(relation).to include(parent, child1, child2)
end
@@ -40,7 +42,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe 'hierarchy_order option' do
let(:relation) do
- described_class.new(Group.where(id: child2.id)).base_and_ancestors(hierarchy_order: hierarchy_order)
+ described_class.new(Group.where(id: child2.id), options: options).base_and_ancestors(hierarchy_order: hierarchy_order)
end
context ':asc' do
@@ -63,7 +65,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe '#base_and_descendants' do
let(:relation) do
- described_class.new(Group.where(id: parent.id)).base_and_descendants
+ described_class.new(Group.where(id: parent.id), options: options).base_and_descendants
end
it 'includes the base rows' do
@@ -75,7 +77,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
end
it 'uses descendants_base #initialize argument' do
- relation = described_class.new(Group.none, Group.where(id: parent.id)).base_and_descendants
+ relation = described_class.new(Group.none, Group.where(id: parent.id), options: options).base_and_descendants
expect(relation).to include(parent, child1, child2)
end
@@ -87,7 +89,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
context 'when with_depth is true' do
let(:relation) do
- described_class.new(Group.where(id: parent.id)).base_and_descendants(with_depth: true)
+ described_class.new(Group.where(id: parent.id), options: options).base_and_descendants(with_depth: true)
end
it 'includes depth in the results' do
@@ -106,14 +108,14 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe '#descendants' do
it 'includes only the descendants' do
- relation = described_class.new(Group.where(id: parent)).descendants
+ relation = described_class.new(Group.where(id: parent), options: options).descendants
expect(relation).to contain_exactly(child1, child2)
end
end
describe '#max_descendants_depth' do
- subject { described_class.new(base_relation).max_descendants_depth }
+ subject { described_class.new(base_relation, options: options).max_descendants_depth }
context 'when base relation is empty' do
let(:base_relation) { Group.where(id: nil) }
@@ -136,13 +138,13 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe '#ancestors' do
it 'includes only the ancestors' do
- relation = described_class.new(Group.where(id: child2)).ancestors
+ relation = described_class.new(Group.where(id: child2), options: options).ancestors
expect(relation).to contain_exactly(child1, parent)
end
it 'can find ancestors upto a certain level' do
- relation = described_class.new(Group.where(id: child2)).ancestors(upto: child1)
+ relation = described_class.new(Group.where(id: child2), options: options).ancestors(upto: child1)
expect(relation).to be_empty
end
@@ -150,7 +152,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe '#all_objects' do
let(:relation) do
- described_class.new(Group.where(id: child1.id)).all_objects
+ described_class.new(Group.where(id: child1.id), options: options).all_objects
end
it 'includes the base rows' do
@@ -166,13 +168,13 @@ RSpec.describe Gitlab::ObjectHierarchy do
end
it 'uses ancestors_base #initialize argument for ancestors' do
- relation = described_class.new(Group.where(id: child1.id), Group.where(id: non_existing_record_id)).all_objects
+ relation = described_class.new(Group.where(id: child1.id), Group.where(id: non_existing_record_id), options: options).all_objects
expect(relation).to include(parent)
end
it 'uses descendants_base #initialize argument for descendants' do
- relation = described_class.new(Group.where(id: non_existing_record_id), Group.where(id: child1.id)).all_objects
+ relation = described_class.new(Group.where(id: non_existing_record_id), Group.where(id: child1.id), options: options).all_objects
expect(relation).to include(child2)
end
@@ -187,19 +189,78 @@ RSpec.describe Gitlab::ObjectHierarchy do
context 'when the use_distinct_in_object_hierarchy feature flag is enabled' do
before do
stub_feature_flags(use_distinct_in_object_hierarchy: true)
+ stub_feature_flags(use_distinct_for_all_object_hierarchy: false)
+ end
+
+ it_behaves_like 'Gitlab::ObjectHierarchy test cases'
+
+ it 'calls DISTINCT' do
+ expect(child2.self_and_ancestors.to_sql).to include("DISTINCT")
+ end
+
+ context 'when use_traversal_ids feature flag is enabled' do
+ it 'does not call DISTINCT' do
+ expect(parent.self_and_descendants.to_sql).not_to include("DISTINCT")
+ end
+ end
+
+ context 'when use_traversal_ids feature flag is disabled' do
+ before do
+ stub_feature_flags(use_traversal_ids: false)
+ end
+
+ it 'calls DISTINCT' do
+ expect(parent.self_and_descendants.to_sql).to include("DISTINCT")
+ end
+ end
+ end
+
+ context 'when the use_distinct_for_all_object_hierarchy feature flag is enabled' do
+ before do
+ stub_feature_flags(use_distinct_in_object_hierarchy: false)
+ stub_feature_flags(use_distinct_for_all_object_hierarchy: true)
end
it_behaves_like 'Gitlab::ObjectHierarchy test cases'
it 'calls DISTINCT' do
- expect(parent.self_and_descendants.to_sql).to include("DISTINCT")
expect(child2.self_and_ancestors.to_sql).to include("DISTINCT")
end
+
+ context 'when use_traversal_ids feature flag is enabled' do
+ it 'does not call DISTINCT' do
+ expect(parent.self_and_descendants.to_sql).not_to include("DISTINCT")
+ end
+ end
+
+ context 'when use_traversal_ids feature flag is disabled' do
+ before do
+ stub_feature_flags(use_traversal_ids: false)
+ end
+
+ it 'calls DISTINCT' do
+ expect(parent.self_and_descendants.to_sql).to include("DISTINCT")
+ end
+
+ context 'when the skip_ordering option is set' do
+ let(:options) { { skip_ordering: true } }
+
+ it_behaves_like 'Gitlab::ObjectHierarchy test cases'
+
+ it 'does not include ROW_NUMBER()' do
+ query = described_class.new(Group.where(id: parent.id), options: options).base_and_descendants.to_sql
+
+ expect(query).to include("DISTINCT")
+ expect(query).not_to include("ROW_NUMBER()")
+ end
+ end
+ end
end
context 'when the use_distinct_in_object_hierarchy feature flag is disabled' do
before do
stub_feature_flags(use_distinct_in_object_hierarchy: false)
+ stub_feature_flags(use_distinct_for_all_object_hierarchy: false)
end
it_behaves_like 'Gitlab::ObjectHierarchy test cases'
diff --git a/spec/lib/gitlab/pages/settings_spec.rb b/spec/lib/gitlab/pages/settings_spec.rb
index f5424a98153..c89bf9ff206 100644
--- a/spec/lib/gitlab/pages/settings_spec.rb
+++ b/spec/lib/gitlab/pages/settings_spec.rb
@@ -3,11 +3,11 @@
require 'spec_helper'
RSpec.describe Gitlab::Pages::Settings do
+ let(:settings) { double(path: 'the path', local_store: 'local store') }
+
describe '#path' do
subject { described_class.new(settings).path }
- let(:settings) { double(path: 'the path') }
-
it { is_expected.to eq('the path') }
context 'when running under a web server outside of test mode' do
@@ -16,9 +16,43 @@ RSpec.describe Gitlab::Pages::Settings do
allow(::Gitlab::Runtime).to receive(:web_server?).and_return(true)
end
- it 'raises a DiskAccessDenied exception' do
- expect { subject }.to raise_error(described_class::DiskAccessDenied)
+ it 'logs a DiskAccessDenied error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ instance_of(described_class::DiskAccessDenied)
+ )
+
+ subject
+ end
+ end
+
+ context 'when local_store settings does not exist yet' do
+ before do
+ allow(Settings.pages).to receive(:local_store).and_return(nil)
end
+
+ it { is_expected.to eq('the path') }
+ end
+
+ context 'when local store exists but legacy storage is disabled' do
+ before do
+ allow(Settings.pages.local_store).to receive(:enabled).and_return(false)
+ end
+
+ it 'logs a DiskAccessDenied error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ instance_of(described_class::DiskAccessDenied)
+ )
+
+ subject
+ end
+ end
+ end
+
+ describe '#local_store' do
+ subject(:local_store) { described_class.new(settings).local_store }
+
+ it 'is an instance of Gitlab::Pages::Stores::LocalStore' do
+ expect(local_store).to be_a(Gitlab::Pages::Stores::LocalStore)
end
end
end
diff --git a/spec/lib/gitlab/pages/stores/local_store_spec.rb b/spec/lib/gitlab/pages/stores/local_store_spec.rb
new file mode 100644
index 00000000000..adab81b2589
--- /dev/null
+++ b/spec/lib/gitlab/pages/stores/local_store_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Pages::Stores::LocalStore do
+ describe '#enabled' do
+ let(:local_store) { double(enabled: true) }
+
+ subject(:local_store_enabled) { described_class.new(local_store).enabled }
+
+ context 'when the pages_update_legacy_storage FF is disabled' do
+ before do
+ stub_feature_flags(pages_update_legacy_storage: false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when the pages_update_legacy_storage FF is enabled' do
+ it 'is equal to the original value' do
+ expect(local_store_enabled).to eq(local_store.enabled)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/pages_transfer_spec.rb b/spec/lib/gitlab/pages_transfer_spec.rb
index 552a2e0701c..021d9cb7318 100644
--- a/spec/lib/gitlab/pages_transfer_spec.rb
+++ b/spec/lib/gitlab/pages_transfer_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe Gitlab::PagesTransfer do
end
it 'does nothing if legacy storage is disabled' do
- stub_feature_flags(pages_update_legacy_storage: false)
+ allow(Settings.pages.local_store).to receive(:enabled).and_return(false)
described_class::METHODS.each do |meth|
expect(PagesTransferWorker)
@@ -72,7 +72,7 @@ RSpec.describe Gitlab::PagesTransfer do
end
it 'does nothing if legacy storage is disabled' do
- stub_feature_flags(pages_update_legacy_storage: false)
+ allow(Settings.pages.local_store).to receive(:enabled).and_return(false)
subject.public_send(meth, *args)
diff --git a/spec/lib/gitlab/pagination/keyset/order_spec.rb b/spec/lib/gitlab/pagination/keyset/order_spec.rb
index 665f790ee47..06a8aee1048 100644
--- a/spec/lib/gitlab/pagination/keyset/order_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/order_spec.rb
@@ -417,4 +417,59 @@ RSpec.describe Gitlab::Pagination::Keyset::Order do
end
end
end
+
+ context 'extract and apply cursor attributes' do
+ let(:model) { Project.new(id: 100) }
+ let(:scope) { Project.all }
+
+ shared_examples 'cursor attribute examples' do
+ describe '#cursor_attributes_for_node' do
+ it { expect(order.cursor_attributes_for_node(model)).to eq({ id: '100' }.with_indifferent_access) }
+ end
+
+ describe '#apply_cursor_conditions' do
+ context 'when params with string keys are passed' do
+ subject(:sql) { order.apply_cursor_conditions(scope, { 'id' => '100' }).to_sql }
+
+ it { is_expected.to include('"projects"."id" < 100)') }
+ end
+
+ context 'when params with symbol keys are passed' do
+ subject(:sql) { order.apply_cursor_conditions(scope, { id: '100' }).to_sql }
+
+ it { is_expected.to include('"projects"."id" < 100)') }
+ end
+ end
+ end
+
+ context 'when string attribute name is given' do
+ let(:order) do
+ Gitlab::Pagination::Keyset::Order.build([
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'id',
+ order_expression: Project.arel_table['id'].desc,
+ nullable: :not_nullable,
+ distinct: true
+ )
+ ])
+ end
+
+ it_behaves_like 'cursor attribute examples'
+ end
+
+ context 'when symbol attribute name is given' do
+ let(:order) do
+ Gitlab::Pagination::Keyset::Order.build([
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: :id,
+ order_expression: Project.arel_table['id'].desc,
+ nullable: :not_nullable,
+ distinct: true
+ )
+ ])
+ end
+
+ it_behaves_like 'cursor attribute examples'
+ end
+ end
end
diff --git a/spec/lib/gitlab/pagination/offset_header_builder_with_controller_spec.rb b/spec/lib/gitlab/pagination/offset_header_builder_with_controller_spec.rb
new file mode 100644
index 00000000000..85e4b621e83
--- /dev/null
+++ b/spec/lib/gitlab/pagination/offset_header_builder_with_controller_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Pagination::OffsetHeaderBuilder, type: :controller do
+ controller(ActionController::Base) do
+ def index
+ relation = Project.where(archived: params[:archived]).page(params[:page]).order(:id).per(1)
+
+ params_for_pagination = { archived: params[:archived], page: params[:page] }
+
+ Gitlab::Pagination::OffsetHeaderBuilder.new(
+ request_context: self,
+ per_page: relation.limit_value,
+ page: relation.current_page,
+ next_page: relation.next_page,
+ prev_page: relation.prev_page,
+ params: params_for_pagination
+ ).execute(exclude_total_headers: true, data_without_counts: true)
+
+ render json: relation.map(&:id)
+ end
+ end
+
+ let_it_be(:projects) { create_list(:project, 2, archived: true).sort_by(&:id) }
+
+ describe 'pagination' do
+ it 'returns correct result for the first page' do
+ get :index, params: { page: 1, archived: true }
+
+ expect(json_response).to eq([projects.first.id])
+ end
+
+ it 'returns correct result for the second page' do
+ get :index, params: { page: 2, archived: true }
+
+ expect(json_response).to eq([projects.last.id])
+ end
+ end
+
+ describe 'pagination heders' do
+ it 'adds next page header' do
+ get :index, params: { page: 1, archived: true }
+
+ expect(response.headers['X-Next-Page']).to eq('2')
+ end
+
+ it 'adds only the specified params to the lnk' do
+ get :index, params: { page: 1, archived: true, some_param: '1' }
+
+ expect(response.headers['Link']).not_to include('some_param')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
index 08ac85c2625..157b3ca56c9 100644
--- a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
+
let(:redis) { Gitlab::Redis::Cache }
subject(:map) { described_class.new(project) }
diff --git a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
index 3cb15f08627..0539bacba44 100644
--- a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
RSpec.describe Gitlab::PhabricatorImport::Issues::TaskImporter do
let_it_be(:project) { create(:project) }
+
let(:task) do
Gitlab::PhabricatorImport::Representation::Task.new(
{
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 89917e515d0..48e2a2e9794 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -78,13 +78,8 @@ RSpec.describe Gitlab::Profiler do
end
it 'strips out the private token' do
- expect(custom_logger).to receive(:add) do |severity, _progname, message|
- next if message.include?('spec/')
-
- expect(severity).to eq(Logger::DEBUG)
- expect(message).to include('public').and include(described_class::FILTERED_STRING)
- expect(message).not_to include(private_token)
- end.at_least(1) # This spec could be wrapped in more blocks in the future
+ allow(custom_logger).to receive(:add).and_call_original
+ expect(custom_logger).to receive(:add).with(Logger::DEBUG, anything, 'public [FILTERED]').at_least(1)
custom_logger.debug("public #{private_token}")
end
diff --git a/spec/lib/gitlab/prometheus/adapter_spec.rb b/spec/lib/gitlab/prometheus/adapter_spec.rb
index 4762e4ad108..9d4806ea73b 100644
--- a/spec/lib/gitlab/prometheus/adapter_spec.rb
+++ b/spec/lib/gitlab/prometheus/adapter_spec.rb
@@ -32,6 +32,14 @@ RSpec.describe Gitlab::Prometheus::Adapter do
context "prometheus service can't execute queries" do
let(:prometheus_service) { double(:prometheus_service, can_query?: false) }
+ context 'with cluster with prometheus integration' do
+ let!(:prometheus_integration) { create(:clusters_integrations_prometheus, cluster: cluster) }
+
+ it 'returns the integration' do
+ expect(subject.prometheus_adapter).to eq(prometheus_integration)
+ end
+ end
+
context 'with cluster with prometheus not available' do
let!(:prometheus) { create(:clusters_applications_prometheus, :installable, cluster: cluster) }
@@ -46,6 +54,14 @@ RSpec.describe Gitlab::Prometheus::Adapter do
it 'returns application handling all environments' do
expect(subject.prometheus_adapter).to eq(prometheus)
end
+
+ context 'with cluster with prometheus integration' do
+ let!(:prometheus_integration) { create(:clusters_integrations_prometheus, cluster: cluster) }
+
+ it 'returns the integration instead' do
+ expect(subject.prometheus_adapter).to eq(prometheus_integration)
+ end
+ end
end
context 'with cluster without prometheus installed' do
diff --git a/spec/lib/gitlab/query_limiting/transaction_spec.rb b/spec/lib/gitlab/query_limiting/transaction_spec.rb
index 40804736b86..76bb2b4c4cc 100644
--- a/spec/lib/gitlab/query_limiting/transaction_spec.rb
+++ b/spec/lib/gitlab/query_limiting/transaction_spec.rb
@@ -68,11 +68,15 @@ RSpec.describe Gitlab::QueryLimiting::Transaction do
it 'increments the number of executed queries' do
transaction = described_class.new
- expect(transaction.count).to be_zero
+ expect { transaction.increment }.to change { transaction.count }.by(1)
+ end
+
+ it 'does not increment the number of executed queries when query limiting is disabled' do
+ transaction = described_class.new
- transaction.increment
+ allow(transaction).to receive(:enabled?).and_return(false)
- expect(transaction.count).to eq(1)
+ expect { transaction.increment }.not_to change { transaction.count }
end
end
diff --git a/spec/lib/gitlab/query_limiting_spec.rb b/spec/lib/gitlab/query_limiting_spec.rb
index 4f70c65adca..fbb12629056 100644
--- a/spec/lib/gitlab/query_limiting_spec.rb
+++ b/spec/lib/gitlab/query_limiting_spec.rb
@@ -2,81 +2,85 @@
require 'spec_helper'
-RSpec.describe Gitlab::QueryLimiting do
- describe '.enable?' do
+RSpec.describe Gitlab::QueryLimiting, :request_store do
+ describe '.enabled_for_env?' do
it 'returns true in a test environment' do
- expect(described_class.enable?).to eq(true)
+ expect(described_class.enabled_for_env?).to eq(true)
end
it 'returns true in a development environment' do
stub_rails_env('development')
stub_rails_env('development')
- expect(described_class.enable?).to eq(true)
+ expect(described_class.enabled_for_env?).to eq(true)
end
it 'returns false on GitLab.com' do
stub_rails_env('production')
allow(Gitlab).to receive(:com?).and_return(true)
- expect(described_class.enable?).to eq(false)
+ expect(described_class.enabled_for_env?).to eq(false)
end
it 'returns false in a non GitLab.com' do
allow(Gitlab).to receive(:com?).and_return(false)
stub_rails_env('production')
- expect(described_class.enable?).to eq(false)
+ expect(described_class.enabled_for_env?).to eq(false)
end
end
- describe '.whitelist' do
- it 'raises ArgumentError when an invalid issue URL is given' do
- expect { described_class.whitelist('foo') }
- .to raise_error(ArgumentError)
+ shared_context 'disable and enable' do |result|
+ let(:transaction) { Gitlab::QueryLimiting::Transaction.new }
+ let(:code) do
+ proc do
+ 2.times { User.count }
+ end
end
- context 'without a transaction' do
- it 'does nothing' do
- expect { described_class.whitelist('https://example.com') }
- .not_to raise_error
- end
+ before do
+ allow(Gitlab::QueryLimiting::Transaction)
+ .to receive(:current)
+ .and_return(transaction)
end
+ end
- context 'with a transaction' do
- let(:transaction) { Gitlab::QueryLimiting::Transaction.new }
+ describe '.disable!' do
+ include_context 'disable and enable'
- before do
- allow(Gitlab::QueryLimiting::Transaction)
- .to receive(:current)
- .and_return(transaction)
- end
+ it 'raises an ArgumentError when an invalid issue URL is given' do
+ expect { described_class.disable!('foo') }
+ .to raise_error(ArgumentError)
+ end
- it 'does not increment the number of SQL queries executed in the block' do
- before = transaction.count
+ it 'stops the number of SQL queries from being incremented' do
+ described_class.disable!('https://example.com')
- described_class.whitelist('https://example.com')
+ expect { code.call }.not_to change { transaction.count }
+ end
+ end
- 2.times do
- User.count
- end
+ describe '.enable!' do
+ include_context 'disable and enable'
- expect(transaction.count).to eq(before)
- end
+ it 'allows the number of SQL queries to be incremented' do
+ described_class.enable!
- it 'whitelists when enabled' do
- described_class.whitelist('https://example.com')
+ expect { code.call }.to change { transaction.count }.by(2)
+ end
+ end
- expect(transaction.whitelisted).to eq(true)
- end
+ describe '#enabled?' do
+ it 'returns true when enabled' do
+ Gitlab::SafeRequestStore[:query_limiting_disabled] = nil
- it 'does not whitelist when disabled' do
- allow(described_class).to receive(:enable?).and_return(false)
+ expect(described_class).to be_enabled
+ end
- described_class.whitelist('https://example.com')
+ it 'returns false when disabled' do
+ Gitlab::SafeRequestStore[:query_limiting_disabled] = true
- expect(transaction.whitelisted).to eq(false)
- end
+ expect(described_class).not_to be_enabled
end
end
end
diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
index d63c21954f2..73629ce3da2 100644
--- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
@@ -127,10 +127,10 @@ RSpec.describe Gitlab::QuickActions::CommandDefinition do
subject.condition_block = proc { false }
end
- it "doesn't execute the command" do
+ it "counts the command as executed" do
subject.execute(context, nil)
- expect(context.commands_executed_count).to be_nil
+ expect(context.commands_executed_count).to eq(1)
expect(context.run).to be false
end
end
@@ -238,8 +238,8 @@ RSpec.describe Gitlab::QuickActions::CommandDefinition do
subject.condition_block = proc { false }
end
- it 'returns nil' do
- expect(subject.execute_message({}, nil)).to be_nil
+ it 'returns an error message' do
+ expect(subject.execute_message({}, nil)).to eq('Could not apply command command.')
end
end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 1aca3dae41b..f62a3c74005 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -667,7 +667,14 @@ RSpec.describe Gitlab::Regex do
it { is_expected.to match('1.2.3') }
it { is_expected.to match('1.3.350') }
- it { is_expected.not_to match('1.3.350-20201230123456') }
+ it { is_expected.to match('1.3.350-20201230123456') }
+ it { is_expected.to match('1.2.3-rc1') }
+ it { is_expected.to match('1.2.3g') }
+ it { is_expected.to match('1.2') }
+ it { is_expected.to match('1.2.bananas') }
+ it { is_expected.to match('v1.2.4-build') }
+ it { is_expected.to match('d50d836eb3de6177ce6c7a5482f27f9c2c84b672') }
+ it { is_expected.to match('this_is_a_string_only') }
it { is_expected.not_to match('..1.2.3') }
it { is_expected.not_to match(' 1.2.3') }
it { is_expected.not_to match("1.2.3 \r\t") }
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index 625dcf11546..d14c3f44c6f 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -29,10 +29,19 @@ RSpec.describe Gitlab::RepositoryCacheAdapter do
def project
end
+
+ def cached_methods
+ [:letters]
+ end
+
+ def exists?
+ true
+ end
end
end
let(:fake_repository) { klass.new }
+ let(:redis_set_cache) { fake_repository.redis_set_cache }
context 'with an existing repository' do
it 'caches the output, sorting the results' do
@@ -42,47 +51,43 @@ RSpec.describe Gitlab::RepositoryCacheAdapter do
expect(fake_repository.letters).to eq(%w(a b c))
end
- expect(fake_repository.redis_set_cache.exist?(:letters)).to eq(true)
+ expect(redis_set_cache.exist?(:letters)).to eq(true)
expect(fake_repository.instance_variable_get(:@letters)).to eq(%w(a b c))
end
context 'membership checks' do
context 'when the cache key does not exist' do
it 'calls the original method and populates the cache' do
- expect(fake_repository.redis_set_cache.exist?(:letters)).to eq(false)
+ expect(redis_set_cache.exist?(:letters)).to eq(false)
expect(fake_repository).to receive(:_uncached_letters).once.and_call_original
# This populates the cache and memoizes the full result
expect(fake_repository.letters_include?('a')).to eq(true)
expect(fake_repository.letters_include?('d')).to eq(false)
- expect(fake_repository.redis_set_cache.exist?(:letters)).to eq(true)
+ expect(redis_set_cache.exist?(:letters)).to eq(true)
end
end
context 'when the cache key exists' do
before do
- fake_repository.redis_set_cache.write(:letters, %w(b a c))
+ redis_set_cache.write(:letters, %w(b a c))
end
- it 'calls #include? on the set cache' do
- expect(fake_repository.redis_set_cache)
- .to receive(:include?).with(:letters, 'a').and_call_original
- expect(fake_repository.redis_set_cache)
- .to receive(:include?).with(:letters, 'd').and_call_original
+ it 'calls #try_include? on the set cache' do
+ expect(redis_set_cache).to receive(:try_include?).with(:letters, 'a').and_call_original
+ expect(redis_set_cache).to receive(:try_include?).with(:letters, 'd').and_call_original
expect(fake_repository.letters_include?('a')).to eq(true)
expect(fake_repository.letters_include?('d')).to eq(false)
end
it 'memoizes the result' do
- expect(fake_repository.redis_set_cache)
- .to receive(:include?).once.and_call_original
+ expect(redis_set_cache).to receive(:try_include?).once.and_call_original
expect(fake_repository.letters_include?('a')).to eq(true)
expect(fake_repository.letters_include?('a')).to eq(true)
- expect(fake_repository.redis_set_cache)
- .to receive(:include?).once.and_call_original
+ expect(redis_set_cache).to receive(:try_include?).once.and_call_original
expect(fake_repository.letters_include?('d')).to eq(false)
expect(fake_repository.letters_include?('d')).to eq(false)
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
index 07f4d7c462d..eaecbb0233d 100644
--- a/spec/lib/gitlab/repository_set_cache_spec.rb
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -124,6 +124,18 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
end
end
+ describe '#search' do
+ subject do
+ cache.search(:foo, 'val*') do
+ %w[value helloworld notvalmatch]
+ end
+ end
+
+ it 'returns search pattern matches from the key' do
+ is_expected.to contain_exactly('value')
+ end
+ end
+
describe '#include?' do
it 'checks inclusion in the Redis set' do
cache.write(:foo, ['value'])
@@ -132,4 +144,15 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
expect(cache.include?(:foo, 'bar')).to be(false)
end
end
+
+ describe '#try_include?' do
+ it 'checks existence of the redis set and inclusion' do
+ expect(cache.try_include?(:foo, 'value')).to eq([false, false])
+
+ cache.write(:foo, ['value'])
+
+ expect(cache.try_include?(:foo, 'value')).to eq([true, true])
+ expect(cache.try_include?(:foo, 'bar')).to eq([false, true])
+ end
+ end
end
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
index 63b2f3fc693..fbda9e6d0be 100644
--- a/spec/lib/gitlab/sanitizers/exif_spec.rb
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -113,7 +113,7 @@ RSpec.describe Gitlab::Sanitizers::Exif do
it 'cleans only jpg/tiff images with the correct mime types' do
expect(sanitizer).not_to receive(:extra_tags)
- expect { subject }.to raise_error(RuntimeError, /File type text\/plain not supported/)
+ expect { subject }.to raise_error(RuntimeError, %r{File type text/plain not supported})
end
end
end
diff --git a/spec/lib/gitlab/search_context/builder_spec.rb b/spec/lib/gitlab/search_context/builder_spec.rb
index 5b4190fc67e..079477115bb 100644
--- a/spec/lib/gitlab/search_context/builder_spec.rb
+++ b/spec/lib/gitlab/search_context/builder_spec.rb
@@ -127,6 +127,35 @@ RSpec.describe Gitlab::SearchContext::Builder, type: :controller do
it { is_expected.to be_for_group }
it { is_expected.to be_search_context(group: group) }
+
+ context 'with group scope' do
+ let(:action_name) { '' }
+
+ before do
+ allow(controller).to receive(:controller_name).and_return('groups')
+ allow(controller).to receive(:action_name).and_return(action_name)
+ end
+
+ it 'returns nil without groups controller action' do
+ expect(subject.scope).to be(nil)
+ end
+
+ context 'when on issues scope' do
+ let(:action_name) { 'issues' }
+
+ it 'search context returns issues scope' do
+ expect(subject.scope).to be('issues')
+ end
+ end
+
+ context 'when on merge requests scope' do
+ let(:action_name) { 'merge_requests' }
+
+ it 'search context returns issues scope' do
+ expect(subject.scope).to be('merge_requests')
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb b/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
index 74834fb9014..43cbe71dd6b 100644
--- a/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
+++ b/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
@@ -214,7 +214,7 @@ RSpec.describe Gitlab::SidekiqCluster::CLI do
expect(Gitlab::SidekiqCluster).not_to receive(:start)
expect { cli.run(%W(#{flag} unknown_field=chatops)) }
- .to raise_error(Gitlab::SidekiqConfig::CliMethods::QueryError)
+ .to raise_error(Gitlab::SidekiqConfig::WorkerMatcher::QueryError)
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
index 01e7c06249a..bc63289a344 100644
--- a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require 'fast_spec_helper'
-require 'rspec-parameterized'
RSpec.describe Gitlab::SidekiqConfig::CliMethods do
let(:dummy_root) { '/tmp/' }
@@ -122,10 +121,8 @@ RSpec.describe Gitlab::SidekiqConfig::CliMethods do
end
end
- describe '.query_workers' do
- using RSpec::Parameterized::TableSyntax
-
- let(:queues) do
+ describe '.query_queues' do
+ let(:worker_metadatas) do
[
{
name: 'a',
@@ -162,79 +159,16 @@ RSpec.describe Gitlab::SidekiqConfig::CliMethods do
]
end
- context 'with valid input' do
- where(:query, :selected_queues) do
- # feature_category
- 'feature_category=category_a' | %w(a a:2)
- 'feature_category=category_a,category_c' | %w(a a:2 c)
- 'feature_category=category_a|feature_category=category_c' | %w(a a:2 c)
- 'feature_category!=category_a' | %w(b c)
-
- # has_external_dependencies
- 'has_external_dependencies=true' | %w(b)
- 'has_external_dependencies=false' | %w(a a:2 c)
- 'has_external_dependencies=true,false' | %w(a a:2 b c)
- 'has_external_dependencies=true|has_external_dependencies=false' | %w(a a:2 b c)
- 'has_external_dependencies!=true' | %w(a a:2 c)
-
- # urgency
- 'urgency=high' | %w(a:2 b)
- 'urgency=low' | %w(a)
- 'urgency=high,low,throttled' | %w(a a:2 b c)
- 'urgency=low|urgency=throttled' | %w(a c)
- 'urgency!=high' | %w(a c)
-
- # name
- 'name=a' | %w(a)
- 'name=a,b' | %w(a b)
- 'name=a,a:2|name=b' | %w(a a:2 b)
- 'name!=a,a:2' | %w(b c)
-
- # resource_boundary
- 'resource_boundary=memory' | %w(b c)
- 'resource_boundary=memory,cpu' | %w(a b c)
- 'resource_boundary=memory|resource_boundary=cpu' | %w(a b c)
- 'resource_boundary!=memory,cpu' | %w(a:2)
-
- # tags
- 'tags=no_disk_io' | %w(a b)
- 'tags=no_disk_io,git_access' | %w(a a:2 b)
- 'tags=no_disk_io|tags=git_access' | %w(a a:2 b)
- 'tags=no_disk_io&tags=git_access' | %w(a)
- 'tags!=no_disk_io' | %w(a:2 c)
- 'tags!=no_disk_io,git_access' | %w(c)
- 'tags=unknown_tag' | []
- 'tags!=no_disk_io' | %w(a:2 c)
- 'tags!=no_disk_io,git_access' | %w(c)
- 'tags!=unknown_tag' | %w(a a:2 b c)
-
- # combinations
- 'feature_category=category_a&urgency=high' | %w(a:2)
- 'feature_category=category_a&urgency=high|feature_category=category_c' | %w(a:2 c)
- end
+ let(:worker_matcher) { double(:WorkerMatcher) }
+ let(:query) { 'feature_category=category_a,category_c' }
- with_them do
- it do
- expect(described_class.query_workers(query, queues))
- .to match_array(selected_queues)
- end
- end
+ before do
+ allow(::Gitlab::SidekiqConfig::WorkerMatcher).to receive(:new).with(query).and_return(worker_matcher)
+ allow(worker_matcher).to receive(:match?).and_return(true, true, false, true)
end
- context 'with invalid input' do
- where(:query, :error) do
- 'feature_category="category_a"' | described_class::InvalidTerm
- 'feature_category=' | described_class::InvalidTerm
- 'feature_category~category_a' | described_class::InvalidTerm
- 'worker_name=a' | described_class::UnknownPredicate
- end
-
- with_them do
- it do
- expect { described_class.query_workers(query, queues) }
- .to raise_error(error)
- end
- end
+ it 'returns the queue names of matched workers' do
+ expect(described_class.query_queues(query, worker_metadatas)).to match(%w(a a:2 c))
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb b/spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb
new file mode 100644
index 00000000000..75e9c8c100b
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+RSpec.describe Gitlab::SidekiqConfig::WorkerMatcher do
+ describe '#match?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:worker_metadatas) do
+ [
+ {
+ name: 'a',
+ feature_category: :category_a,
+ has_external_dependencies: false,
+ urgency: :low,
+ resource_boundary: :cpu,
+ tags: [:no_disk_io, :git_access]
+ },
+ {
+ name: 'a:2',
+ feature_category: :category_a,
+ has_external_dependencies: false,
+ urgency: :high,
+ resource_boundary: :none,
+ tags: [:git_access]
+ },
+ {
+ name: 'b',
+ feature_category: :category_b,
+ has_external_dependencies: true,
+ urgency: :high,
+ resource_boundary: :memory,
+ tags: [:no_disk_io]
+ },
+ {
+ name: 'c',
+ feature_category: :category_c,
+ has_external_dependencies: false,
+ urgency: :throttled,
+ resource_boundary: :memory,
+ tags: []
+ }
+ ]
+ end
+
+ context 'with valid input' do
+ where(:query, :expected_metadatas) do
+ # feature_category
+ 'feature_category=category_a' | %w(a a:2)
+ 'feature_category=category_a,category_c' | %w(a a:2 c)
+ 'feature_category=category_a|feature_category=category_c' | %w(a a:2 c)
+ 'feature_category!=category_a' | %w(b c)
+
+ # has_external_dependencies
+ 'has_external_dependencies=true' | %w(b)
+ 'has_external_dependencies=false' | %w(a a:2 c)
+ 'has_external_dependencies=true,false' | %w(a a:2 b c)
+ 'has_external_dependencies=true|has_external_dependencies=false' | %w(a a:2 b c)
+ 'has_external_dependencies!=true' | %w(a a:2 c)
+
+ # urgency
+ 'urgency=high' | %w(a:2 b)
+ 'urgency=low' | %w(a)
+ 'urgency=high,low,throttled' | %w(a a:2 b c)
+ 'urgency=low|urgency=throttled' | %w(a c)
+ 'urgency!=high' | %w(a c)
+
+ # name
+ 'name=a' | %w(a)
+ 'name=a,b' | %w(a b)
+ 'name=a,a:2|name=b' | %w(a a:2 b)
+ 'name!=a,a:2' | %w(b c)
+
+ # resource_boundary
+ 'resource_boundary=memory' | %w(b c)
+ 'resource_boundary=memory,cpu' | %w(a b c)
+ 'resource_boundary=memory|resource_boundary=cpu' | %w(a b c)
+ 'resource_boundary!=memory,cpu' | %w(a:2)
+
+ # tags
+ 'tags=no_disk_io' | %w(a b)
+ 'tags=no_disk_io,git_access' | %w(a a:2 b)
+ 'tags=no_disk_io|tags=git_access' | %w(a a:2 b)
+ 'tags=no_disk_io&tags=git_access' | %w(a)
+ 'tags!=no_disk_io' | %w(a:2 c)
+ 'tags!=no_disk_io,git_access' | %w(c)
+ 'tags=unknown_tag' | []
+ 'tags!=no_disk_io' | %w(a:2 c)
+ 'tags!=no_disk_io,git_access' | %w(c)
+ 'tags!=unknown_tag' | %w(a a:2 b c)
+
+ # combinations
+ 'feature_category=category_a&urgency=high' | %w(a:2)
+ 'feature_category=category_a&urgency=high|feature_category=category_c' | %w(a:2 c)
+
+ # Match all
+ '*' | %w(a a:2 b c)
+ end
+
+ with_them do
+ it do
+ matched_metadatas = worker_metadatas.select do |metadata|
+ described_class.new(query).match?(metadata)
+ end
+ expect(matched_metadatas.map { |m| m[:name] }).to match_array(expected_metadatas)
+ end
+ end
+ end
+
+ context 'with invalid input' do
+ where(:query, :error) do
+ 'feature_category="category_a"' | described_class::InvalidTerm
+ 'feature_category=' | described_class::InvalidTerm
+ 'feature_category~category_a' | described_class::InvalidTerm
+ 'worker_name=a' | described_class::UnknownPredicate
+ end
+
+ with_them do
+ it do
+ worker_metadatas.each do |metadata|
+ expect { described_class.new(query).match?(metadata) }
+ .to raise_error(error)
+ end
+ end
+ end
+ end
+ 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 3e8e117ec71..537844df72f 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -10,80 +10,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
end
describe '#call', :request_store do
- let(:timestamp) { Time.iso8601('2018-01-01T12:00:00.000Z') }
- let(:created_at) { timestamp - 1.second }
- let(:scheduling_latency_s) { 1.0 }
-
- let(:job) do
- {
- "class" => "TestWorker",
- "args" => [1234, 'hello', { 'key' => 'value' }],
- "retry" => false,
- "queue" => "cronjob:test_queue",
- "queue_namespace" => "cronjob",
- "jid" => "da883554ee4fe414012f5f42",
- "created_at" => created_at.to_f,
- "enqueued_at" => created_at.to_f,
- "correlation_id" => 'cid',
- "error_message" => "wrong number of arguments (2 for 3)",
- "error_class" => "ArgumentError",
- "error_backtrace" => []
- }
- end
-
- let(:logger) { double }
- let(:clock_realtime_start) { 0.222222299 }
- let(:clock_realtime_end) { 1.333333799 }
- let(:clock_thread_cputime_start) { 0.222222299 }
- let(:clock_thread_cputime_end) { 1.333333799 }
- let(:start_payload) do
- job.except('error_backtrace', 'error_class', 'error_message').merge(
- 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: start',
- 'job_status' => 'start',
- 'pid' => Process.pid,
- 'created_at' => created_at.to_f,
- 'enqueued_at' => created_at.to_f,
- 'scheduling_latency_s' => scheduling_latency_s,
- 'job_size_bytes' => be > 0
- )
- end
-
- let(:end_payload) do
- start_payload.merge(
- 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: done: 0.0 sec',
- 'job_status' => 'done',
- 'duration_s' => 0.0,
- 'completed_at' => timestamp.to_f,
- 'cpu_s' => 1.111112,
- 'db_duration_s' => 0.0,
- 'db_cached_count' => 0,
- 'db_count' => 0,
- 'db_write_count' => 0
- )
- end
-
- let(:exception_payload) do
- end_payload.merge(
- 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec',
- 'job_status' => 'fail',
- 'error_class' => 'ArgumentError',
- 'error_message' => 'Something went wrong',
- 'error_backtrace' => be_a(Array).and(be_present)
- )
- end
-
- before do
- allow(Sidekiq).to receive(:logger).and_return(logger)
-
- allow(subject).to receive(:current_time).and_return(timestamp.to_f)
-
- allow(Process).to receive(:clock_gettime).with(Process::CLOCK_REALTIME, :float_second)
- .and_return(clock_realtime_start, clock_realtime_end)
- allow(Process).to receive(:clock_gettime).with(Process::CLOCK_THREAD_CPUTIME_ID, :float_second)
- .and_return(clock_thread_cputime_start, clock_thread_cputime_end)
- end
-
- subject { described_class.new }
+ include_context 'structured_logger'
context 'with SIDEKIQ_LOG_ARGUMENTS enabled' do
before do
@@ -283,14 +210,19 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
end_payload.merge(timing_data.stringify_keys)
end
- it 'logs with Gitaly and Rugged timing data' do
+ before do
+ allow(::Gitlab::InstrumentationHelper).to receive(:add_instrumentation_data).and_wrap_original do |method, values|
+ method.call(values)
+ values.merge!(timing_data)
+ end
+ end
+
+ it 'logs with Gitaly and Rugged timing data', :aggregate_failures do
Timecop.freeze(timestamp) do
expect(logger).to receive(:info).with(start_payload).ordered
expect(logger).to receive(:info).with(expected_end_payload).ordered
- call_subject(job, 'test_queue') do
- job.merge!(timing_data)
- end
+ call_subject(job, 'test_queue') { }
end
end
end
@@ -361,15 +293,6 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
end
end
end
-
- def call_subject(job, queue)
- # This structured logger strongly depends on execution of `InstrumentationLogger`
- subject.call(job, queue) do
- ::Gitlab::SidekiqMiddleware::InstrumentationLogger.new.call('worker', job, queue) do
- yield
- end
- end
- end
end
describe '#add_time_keys!' do
diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
index 3ba08455d01..9d5d5f28eab 100644
--- a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
@@ -74,9 +74,9 @@ RSpec.describe Gitlab::SidekiqMiddleware::AdminMode::Client, :request_store do
end
end
- context 'admin mode feature disabled' do
+ context 'admin mode setting disabled' do
before do
- stub_feature_flags(user_mode_in_session: false)
+ stub_application_setting(admin_mode: false)
end
it 'yields block' do
diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
index e8322b11875..3ab1a9cd2f4 100644
--- a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
@@ -52,9 +52,9 @@ RSpec.describe Gitlab::SidekiqMiddleware::AdminMode::Server, :request_store do
end
end
- context 'admin mode feature disabled' do
+ context 'admin mode setting disabled' do
before do
- stub_feature_flags(user_mode_in_session: false)
+ stub_application_setting(admin_mode: false)
end
it 'yields block' do
diff --git a/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
index e2b36125b4e..82ca84f0697 100644
--- a/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
@@ -3,156 +3,33 @@
require 'spec_helper'
RSpec.describe Gitlab::SidekiqMiddleware::ClientMetrics do
- context "with worker attribution" do
- subject { described_class.new }
+ shared_examples "a metrics middleware" do
+ context "with mocked prometheus" do
+ let(:enqueued_jobs_metric) { double('enqueued jobs metric', increment: true) }
- let(:queue) { :test }
- let(:worker_class) { worker.class }
- let(:job) { {} }
- let(:default_labels) do
- { queue: queue.to_s,
- worker: worker_class.to_s,
- boundary: "",
- external_dependencies: "no",
- feature_category: "",
- urgency: "low" }
- end
-
- shared_examples "a metrics client middleware" do
- context "with mocked prometheus" do
- let(:enqueued_jobs_metric) { double('enqueued jobs metric', increment: true) }
-
- before do
- allow(Gitlab::Metrics).to receive(:counter).with(described_class::ENQUEUED, anything).and_return(enqueued_jobs_metric)
- end
-
- describe '#call' do
- it 'yields block' do
- expect { |b| subject.call(worker_class, job, :test, double, &b) }.to yield_control.once
- end
-
- it 'increments enqueued jobs metric with correct labels when worker is a string of the class' do
- expect(enqueued_jobs_metric).to receive(:increment).with(labels, 1)
-
- subject.call(worker_class.to_s, job, :test, double) { nil }
- end
-
- it 'increments enqueued jobs metric with correct labels' do
- expect(enqueued_jobs_metric).to receive(:increment).with(labels, 1)
-
- subject.call(worker_class, job, :test, double) { nil }
- end
- end
- end
- end
-
- context "when workers are not attributed" do
before do
- stub_const('TestNonAttributedWorker', Class.new)
- TestNonAttributedWorker.class_eval do
- include Sidekiq::Worker
- end
- end
-
- it_behaves_like "a metrics client middleware" do
- let(:worker) { TestNonAttributedWorker.new }
- let(:labels) { default_labels.merge(urgency: "") }
- end
- end
-
- context "when a worker is wrapped into ActiveJob" do
- before do
- stub_const('TestWrappedWorker', Class.new)
- TestWrappedWorker.class_eval do
- include Sidekiq::Worker
- end
- end
-
- it_behaves_like "a metrics client middleware" do
- let(:job) do
- {
- "class" => ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper,
- "wrapped" => TestWrappedWorker
- }
- end
-
- let(:worker) { TestWrappedWorker.new }
- let(:labels) { default_labels.merge(urgency: "") }
- end
- end
-
- context "when workers are attributed" do
- def create_attributed_worker_class(urgency, external_dependencies, resource_boundary, category)
- klass = Class.new do
- include Sidekiq::Worker
- include WorkerAttributes
-
- urgency urgency if urgency
- worker_has_external_dependencies! if external_dependencies
- worker_resource_boundary resource_boundary unless resource_boundary == :unknown
- feature_category category unless category.nil?
- end
- stub_const("TestAttributedWorker", klass)
- end
-
- let(:urgency) { nil }
- let(:external_dependencies) { false }
- let(:resource_boundary) { :unknown }
- let(:feature_category) { nil }
- let(:worker_class) { create_attributed_worker_class(urgency, external_dependencies, resource_boundary, feature_category) }
- let(:worker) { worker_class.new }
-
- context "high urgency" do
- it_behaves_like "a metrics client middleware" do
- let(:urgency) { :high }
- let(:labels) { default_labels.merge(urgency: "high") }
- end
+ allow(Gitlab::Metrics).to receive(:counter).with(described_class::ENQUEUED, anything).and_return(enqueued_jobs_metric)
end
- context "no urgency" do
- it_behaves_like "a metrics client middleware" do
- let(:urgency) { :throttled }
- let(:labels) { default_labels.merge(urgency: "throttled") }
+ describe '#call' do
+ it 'yields block' do
+ expect { |b| subject.call(worker_class, job, :test, double, &b) }.to yield_control.once
end
- end
- context "external dependencies" do
- it_behaves_like "a metrics client middleware" do
- let(:external_dependencies) { true }
- let(:labels) { default_labels.merge(external_dependencies: "yes") }
- end
- end
+ it 'increments enqueued jobs metric with correct labels when worker is a string of the class' do
+ expect(enqueued_jobs_metric).to receive(:increment).with(labels, 1)
- context "cpu boundary" do
- it_behaves_like "a metrics client middleware" do
- let(:resource_boundary) { :cpu }
- let(:labels) { default_labels.merge(boundary: "cpu") }
+ subject.call(worker_class.to_s, job, :test, double) { nil }
end
- end
- context "memory boundary" do
- it_behaves_like "a metrics client middleware" do
- let(:resource_boundary) { :memory }
- let(:labels) { default_labels.merge(boundary: "memory") }
- end
- end
+ it 'increments enqueued jobs metric with correct labels' do
+ expect(enqueued_jobs_metric).to receive(:increment).with(labels, 1)
- context "feature category" do
- it_behaves_like "a metrics client middleware" do
- let(:feature_category) { :authentication }
- let(:labels) { default_labels.merge(feature_category: "authentication") }
- end
- end
-
- context "combined" do
- it_behaves_like "a metrics client middleware" do
- let(:urgency) { :high }
- let(:external_dependencies) { true }
- let(:resource_boundary) { :cpu }
- let(:feature_category) { :authentication }
- let(:labels) { default_labels.merge(urgency: "high", external_dependencies: "yes", boundary: "cpu", feature_category: "authentication") }
+ subject.call(worker_class, job, :test, double) { nil }
end
end
end
end
+
+ it_behaves_like 'metrics middleware with worker attribution'
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb b/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb
new file mode 100644
index 00000000000..eb9ba50cdcd
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::InstrumentationLogger do
+ let(:job) { { 'jid' => 123 } }
+ let(:queue) { 'test_queue' }
+ let(:worker) do
+ Class.new do
+ def self.name
+ 'TestDWorker'
+ end
+
+ include ApplicationWorker
+
+ def perform(*args)
+ end
+ end
+ end
+
+ subject { described_class.new }
+
+ before do
+ stub_const('TestWorker', worker)
+ end
+
+ describe '.keys' do
+ it 'returns all available payload keys' do
+ expected_keys = [
+ :cpu_s,
+ :gitaly_calls,
+ :gitaly_duration_s,
+ :rugged_calls,
+ :rugged_duration_s,
+ :elasticsearch_calls,
+ :elasticsearch_duration_s,
+ :elasticsearch_timed_out_count,
+ :mem_objects,
+ :mem_bytes,
+ :mem_mallocs,
+ :redis_calls,
+ :redis_duration_s,
+ :redis_read_bytes,
+ :redis_write_bytes,
+ :redis_action_cable_calls,
+ :redis_action_cable_duration_s,
+ :redis_action_cable_read_bytes,
+ :redis_action_cable_write_bytes,
+ :redis_cache_calls,
+ :redis_cache_duration_s,
+ :redis_cache_read_bytes,
+ :redis_cache_write_bytes,
+ :redis_queues_calls,
+ :redis_queues_duration_s,
+ :redis_queues_read_bytes,
+ :redis_queues_write_bytes,
+ :redis_shared_state_calls,
+ :redis_shared_state_duration_s,
+ :redis_shared_state_read_bytes,
+ :redis_shared_state_write_bytes,
+ :db_count,
+ :db_write_count,
+ :db_cached_count,
+ :external_http_count,
+ :external_http_duration_s,
+ :rack_attack_redis_count,
+ :rack_attack_redis_duration_s
+ ]
+
+ expect(described_class.keys).to include(*expected_keys)
+ end
+ end
+
+ describe '#call', :request_store do
+ let(:instrumentation_values) do
+ {
+ cpu_s: 10,
+ unknown_attribute: 123,
+ db_count: 0,
+ db_cached_count: 0,
+ db_write_count: 0,
+ gitaly_calls: 0,
+ redis_calls: 0
+ }
+ end
+
+ before do
+ allow(::Gitlab::InstrumentationHelper).to receive(:add_instrumentation_data) do |values|
+ values.merge!(instrumentation_values)
+ end
+ end
+
+ it 'merges correct instrumentation data in the job' do
+ expect { |b| subject.call(worker, job, queue, &b) }.to yield_control
+
+ expected_values = instrumentation_values.except(:unknown_attribute)
+
+ expect(job[:instrumentation]).to eq(expected_values)
+ 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 71f4f2a3b64..95be76ce351 100644
--- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
@@ -4,296 +4,108 @@ require 'spec_helper'
# rubocop: disable RSpec/MultipleMemoizedHelpers
RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
- context "with worker attribution" do
- subject { described_class.new }
+ shared_examples "a metrics middleware" do
+ context "with mocked prometheus" do
+ include_context 'server metrics with mocked prometheus'
- let(:queue) { :test }
- let(:worker_class) { worker.class }
- let(:job) { {} }
- let(:job_status) { :done }
- let(:labels_with_job_status) { labels.merge(job_status: job_status.to_s) }
- let(:default_labels) do
- { queue: queue.to_s,
- worker: worker_class.to_s,
- boundary: "",
- external_dependencies: "no",
- feature_category: "",
- urgency: "low" }
- end
-
- shared_examples "a metrics middleware" do
- context "with mocked prometheus" do
- let(:concurrency_metric) { double('concurrency metric') }
-
- let(:queue_duration_seconds) { double('queue duration seconds metric') }
- let(:completion_seconds_metric) { double('completion seconds metric') }
- let(:user_execution_seconds_metric) { double('user execution seconds metric') }
- let(:db_seconds_metric) { double('db seconds metric') }
- let(:gitaly_seconds_metric) { double('gitaly seconds metric') }
- let(:failed_total_metric) { double('failed total metric') }
- let(:retried_total_metric) { double('retried total metric') }
- let(:redis_requests_total) { double('redis calls total metric') }
- let(:running_jobs_metric) { double('running jobs metric') }
- let(:redis_seconds_metric) { double('redis seconds metric') }
- let(:elasticsearch_seconds_metric) { double('elasticsearch seconds metric') }
- let(:elasticsearch_requests_total) { double('elasticsearch calls total metric') }
+ describe '#initialize' do
+ it 'sets concurrency metrics' do
+ expect(concurrency_metric).to receive(:set).with({}, Sidekiq.options[:concurrency].to_i)
- before do
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_queue_duration_seconds, anything, anything, anything).and_return(queue_duration_seconds)
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything, anything, anything).and_return(completion_seconds_metric)
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_cpu_seconds, anything, anything, anything).and_return(user_execution_seconds_metric)
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_db_seconds, anything, anything, anything).and_return(db_seconds_metric)
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_gitaly_seconds, anything, anything, anything).and_return(gitaly_seconds_metric)
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_redis_requests_duration_seconds, anything, anything, anything).and_return(redis_seconds_metric)
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_elasticsearch_requests_duration_seconds, anything, anything, anything).and_return(elasticsearch_seconds_metric)
- allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_failed_total, anything).and_return(failed_total_metric)
- allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_retried_total, anything).and_return(retried_total_metric)
- allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_redis_requests_total, anything).and_return(redis_requests_total)
- allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_elasticsearch_requests_total, anything).and_return(elasticsearch_requests_total)
- allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :all).and_return(running_jobs_metric)
- allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_concurrency, anything, {}, :all).and_return(concurrency_metric)
-
- allow(concurrency_metric).to receive(:set)
+ subject
end
+ end
- describe '#initialize' do
- it 'sets concurrency metrics' do
- expect(concurrency_metric).to receive(:set).with({}, Sidekiq.options[:concurrency].to_i)
+ describe '#call' do
+ include_context 'server metrics call'
- subject
- end
+ it 'yields block' do
+ expect { |b| subject.call(worker, job, :test, &b) }.to yield_control.once
end
- describe '#call' do
- let(:thread_cputime_before) { 1 }
- let(:thread_cputime_after) { 2 }
- let(:thread_cputime_duration) { thread_cputime_after - thread_cputime_before }
-
- let(:monotonic_time_before) { 11 }
- let(:monotonic_time_after) { 20 }
- let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before }
-
- let(:queue_duration_for_job) { 0.01 }
-
- let(:db_duration) { 3 }
- let(:gitaly_duration) { 4 }
-
- let(:redis_calls) { 2 }
- let(:redis_duration) { 0.01 }
-
- let(:elasticsearch_calls) { 8 }
- let(:elasticsearch_duration) { 0.54 }
-
- before do
- allow(subject).to receive(:get_thread_cputime).and_return(thread_cputime_before, thread_cputime_after)
- allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after)
- allow(Gitlab::InstrumentationHelper).to receive(:queue_duration_for_job).with(job).and_return(queue_duration_for_job)
- allow(ActiveRecord::LogSubscriber).to receive(:runtime).and_return(db_duration * 1000)
-
- job[:gitaly_duration_s] = gitaly_duration
- job[:redis_calls] = redis_calls
- job[:redis_duration_s] = redis_duration
-
- job[:elasticsearch_calls] = elasticsearch_calls
- job[:elasticsearch_duration_s] = elasticsearch_duration
-
- allow(running_jobs_metric).to receive(:increment)
- allow(redis_requests_total).to receive(:increment)
- allow(elasticsearch_requests_total).to receive(:increment)
- allow(queue_duration_seconds).to receive(:observe)
- allow(user_execution_seconds_metric).to receive(:observe)
- allow(db_seconds_metric).to receive(:observe)
- allow(gitaly_seconds_metric).to receive(:observe)
- allow(completion_seconds_metric).to receive(:observe)
- allow(redis_seconds_metric).to receive(:observe)
- allow(elasticsearch_seconds_metric).to receive(:observe)
+ it 'calls BackgroundTransaction' do
+ expect_next_instance_of(Gitlab::Metrics::BackgroundTransaction) do |instance|
+ expect(instance).to receive(:run)
end
- it 'yields block' do
- expect { |b| subject.call(worker, job, :test, &b) }.to yield_control.once
- end
+ subject.call(worker, job, :test) {}
+ end
- it 'calls BackgroundTransaction' do
- expect_next_instance_of(Gitlab::Metrics::BackgroundTransaction) do |instance|
- expect(instance).to receive(:run)
- end
+ it 'sets queue specific metrics' do
+ expect(running_jobs_metric).to receive(:increment).with(labels, -1)
+ expect(running_jobs_metric).to receive(:increment).with(labels, 1)
+ expect(queue_duration_seconds).to receive(:observe).with(labels, queue_duration_for_job) if queue_duration_for_job
+ expect(user_execution_seconds_metric).to receive(:observe).with(labels_with_job_status, thread_cputime_duration)
+ expect(db_seconds_metric).to receive(:observe).with(labels_with_job_status, db_duration)
+ expect(gitaly_seconds_metric).to receive(:observe).with(labels_with_job_status, gitaly_duration)
+ expect(completion_seconds_metric).to receive(:observe).with(labels_with_job_status, monotonic_time_duration)
+ expect(redis_seconds_metric).to receive(:observe).with(labels_with_job_status, redis_duration)
+ expect(elasticsearch_seconds_metric).to receive(:observe).with(labels_with_job_status, elasticsearch_duration)
+ 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)
+
+ subject.call(worker, job, :test) { nil }
+ end
- subject.call(worker, job, :test) {}
- end
+ it 'sets the thread name if it was nil' do
+ allow(Thread.current).to receive(:name).and_return(nil)
+ expect(Thread.current).to receive(:name=).with(Gitlab::Metrics::Samplers::ThreadsSampler::SIDEKIQ_WORKER_THREAD_NAME)
- it 'sets queue specific metrics' do
- expect(running_jobs_metric).to receive(:increment).with(labels, -1)
- expect(running_jobs_metric).to receive(:increment).with(labels, 1)
- expect(queue_duration_seconds).to receive(:observe).with(labels, queue_duration_for_job) if queue_duration_for_job
- expect(user_execution_seconds_metric).to receive(:observe).with(labels_with_job_status, thread_cputime_duration)
- expect(db_seconds_metric).to receive(:observe).with(labels_with_job_status, db_duration)
- expect(gitaly_seconds_metric).to receive(:observe).with(labels_with_job_status, gitaly_duration)
- expect(completion_seconds_metric).to receive(:observe).with(labels_with_job_status, monotonic_time_duration)
- expect(redis_seconds_metric).to receive(:observe).with(labels_with_job_status, redis_duration)
- expect(elasticsearch_seconds_metric).to receive(:observe).with(labels_with_job_status, elasticsearch_duration)
- 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)
+ subject.call(worker, job, :test) { nil }
+ end
- subject.call(worker, job, :test) { nil }
- end
+ context 'when job_duration is not available' do
+ let(:queue_duration_for_job) { nil }
- it 'sets the thread name if it was nil' do
- allow(Thread.current).to receive(:name).and_return(nil)
- expect(Thread.current).to receive(:name=).with(Gitlab::Metrics::Samplers::ThreadsSampler::SIDEKIQ_WORKER_THREAD_NAME)
+ it 'does not set the queue_duration_seconds histogram' do
+ expect(queue_duration_seconds).not_to receive(:observe)
subject.call(worker, job, :test) { nil }
end
+ end
- context 'when job_duration is not available' do
- let(:queue_duration_for_job) { nil }
-
- it 'does not set the queue_duration_seconds histogram' do
- expect(queue_duration_seconds).not_to receive(:observe)
-
- subject.call(worker, job, :test) { nil }
- end
- end
-
- context 'when error is raised' do
- let(:job_status) { :fail }
-
- it 'sets sidekiq_jobs_failed_total and reraises' do
- expect(failed_total_metric).to receive(:increment).with(labels, 1)
-
- expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed")
- end
- end
-
- context 'when job is retried' do
- let(:job) { { 'retry_count' => 1 } }
+ context 'when error is raised' do
+ let(:job_status) { :fail }
- it 'sets sidekiq_jobs_retried_total metric' do
- expect(retried_total_metric).to receive(:increment)
+ it 'sets sidekiq_jobs_failed_total and reraises' do
+ expect(failed_total_metric).to receive(:increment).with(labels, 1)
- subject.call(worker, job, :test) { nil }
- end
+ expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed")
end
end
- end
- context "with prometheus integrated" do
- describe '#call' do
- it 'yields block' do
- expect { |b| subject.call(worker, job, :test, &b) }.to yield_control.once
- end
+ context 'when job is retried' do
+ let(:job) { { 'retry_count' => 1 } }
- context 'when error is raised' do
- let(:job_status) { :fail }
+ it 'sets sidekiq_jobs_retried_total metric' do
+ expect(retried_total_metric).to receive(:increment)
- it 'sets sidekiq_jobs_failed_total and reraises' do
- expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed")
- end
+ subject.call(worker, job, :test) { nil }
end
end
end
end
- context "when workers are not attributed" do
- before do
- stub_const('TestNonAttributedWorker', Class.new)
- TestNonAttributedWorker.class_eval do
- include Sidekiq::Worker
+ context "with prometheus integrated" do
+ describe '#call' do
+ it 'yields block' do
+ expect { |b| subject.call(worker, job, :test, &b) }.to yield_control.once
end
- end
- let(:worker) { TestNonAttributedWorker.new }
- let(:labels) { default_labels.merge(urgency: "") }
+ context 'when error is raised' do
+ let(:job_status) { :fail }
- it_behaves_like "a metrics middleware"
- end
-
- context "when a worker is wrapped into ActiveJob" do
- before do
- stub_const('TestWrappedWorker', Class.new)
- TestWrappedWorker.class_eval do
- include Sidekiq::Worker
+ it 'sets sidekiq_jobs_failed_total and reraises' do
+ expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed")
+ end
end
end
-
- let(:job) do
- {
- "class" => ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper,
- "wrapped" => TestWrappedWorker
- }
- end
-
- let(:worker) { TestWrappedWorker.new }
- let(:worker_class) { TestWrappedWorker }
- let(:labels) { default_labels.merge(urgency: "") }
-
- it_behaves_like "a metrics middleware"
end
+ end
- context "when workers are attributed" do
- def create_attributed_worker_class(urgency, external_dependencies, resource_boundary, category)
- Class.new do
- include Sidekiq::Worker
- include WorkerAttributes
-
- urgency urgency if urgency
- worker_has_external_dependencies! if external_dependencies
- worker_resource_boundary resource_boundary unless resource_boundary == :unknown
- feature_category category unless category.nil?
- end
- end
-
- let(:urgency) { nil }
- let(:external_dependencies) { false }
- let(:resource_boundary) { :unknown }
- let(:feature_category) { nil }
- let(:worker_class) { create_attributed_worker_class(urgency, external_dependencies, resource_boundary, feature_category) }
- let(:worker) { worker_class.new }
-
- context "high urgency" do
- let(:urgency) { :high }
- let(:labels) { default_labels.merge(urgency: "high") }
-
- it_behaves_like "a metrics middleware"
- end
-
- context "external dependencies" do
- let(:external_dependencies) { true }
- let(:labels) { default_labels.merge(external_dependencies: "yes") }
-
- it_behaves_like "a metrics middleware"
- end
-
- context "cpu boundary" do
- let(:resource_boundary) { :cpu }
- let(:labels) { default_labels.merge(boundary: "cpu") }
-
- it_behaves_like "a metrics middleware"
- end
-
- context "memory boundary" do
- let(:resource_boundary) { :memory }
- let(:labels) { default_labels.merge(boundary: "memory") }
-
- it_behaves_like "a metrics middleware"
- end
-
- context "feature category" do
- let(:feature_category) { :authentication }
- let(:labels) { default_labels.merge(feature_category: "authentication") }
-
- it_behaves_like "a metrics middleware"
- end
-
- context "combined" do
- let(:urgency) { :throttled }
- let(:external_dependencies) { true }
- let(:resource_boundary) { :cpu }
- let(:feature_category) { :authentication }
- let(:labels) { default_labels.merge(urgency: "throttled", external_dependencies: "yes", boundary: "cpu", feature_category: "authentication") }
-
- it_behaves_like "a metrics middleware"
- end
- end
+ it_behaves_like 'metrics middleware with worker attribution' do
+ let(:job_status) { :done }
+ let(:labels_with_job_status) { labels.merge(job_status: job_status.to_s) }
end
end
# rubocop: enable RSpec/MultipleMemoizedHelpers
diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
index ca473462d2e..f736a7db774 100644
--- a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Server do
worker_context user: nil
def perform(identifier, *args)
- self.class.contexts.merge!(identifier => Labkit::Context.current.to_h)
+ self.class.contexts.merge!(identifier => Gitlab::ApplicationContext.current)
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb
index 755f6004e52..0efdef0c999 100644
--- a/spec/lib/gitlab/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb
@@ -69,11 +69,13 @@ RSpec.describe Gitlab::SidekiqMiddleware do
shared_examples "a server middleware chain" do
it "passes through the right server middlewares" do
enabled_sidekiq_middlewares.each do |middleware|
- expect_any_instance_of(middleware).to receive(:call).with(*middleware_expected_args).once.and_call_original
+ expect_next_instance_of(middleware) do |middleware_instance|
+ expect(middleware_instance).to receive(:call).with(*middleware_expected_args).once.and_call_original
+ end
end
disabled_sidekiq_middlewares.each do |middleware|
- expect_any_instance_of(middleware).not_to receive(:call)
+ expect(middleware).not_to receive(:new)
end
worker_class.perform_async(*job_args)
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
index 109b4b8fee1..690ffb15a5d 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::SlashCommands::Presenters::IssueComment do
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:note) { create(:note, project: project, noteable: issue) }
+
let(:author) { note.author }
describe '#present' do
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
index a4d8e3957cf..7b3440b40a7 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Gitlab::SlashCommands::Presenters::IssueMove do
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:other_project) { create(:project) }
let_it_be(:old_issue, reload: true) { create(:issue, project: project) }
+
let(:new_issue) { Issues::MoveService.new(project, user).execute(old_issue, other_project) }
let(:attachment) { subject[:attachments].first }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
index 03a94ea5e29..21a983090fb 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
@@ -1,19 +1,22 @@
# frozen_string_literal: true
-
require 'spec_helper'
RSpec.describe Gitlab::SlashCommands::Presenters::IssueNew do
+ include Gitlab::Routing
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
- let(:attachment) { subject[:attachments].first }
subject { described_class.new(issue).present }
it { is_expected.to be_a(Hash) }
it 'shows the issue' do
- expect(subject[:response_type]).to be(:in_channel)
- expect(subject).to have_key(:attachments)
- expect(attachment[:title]).to start_with(issue.title)
+ expected_text = "I created an issue on <#{url_for(issue.author)}|#{issue.author.to_reference}>'s behalf: *<#{project_issue_url(issue.project, issue)}|#{issue.to_reference}>* in <#{project.web_url}|#{project.full_name}>"
+
+ expect(subject).to eq(
+ response_type: :in_channel,
+ status: 200,
+ text: expected_text
+ )
end
end
diff --git a/spec/lib/gitlab/slash_commands/run_spec.rb b/spec/lib/gitlab/slash_commands/run_spec.rb
index c9ff580d586..9d204228d21 100644
--- a/spec/lib/gitlab/slash_commands/run_spec.rb
+++ b/spec/lib/gitlab/slash_commands/run_spec.rb
@@ -3,6 +3,26 @@
require 'spec_helper'
RSpec.describe Gitlab::SlashCommands::Run do
+ describe '.match' do
+ it 'returns true for a run command' do
+ expect(described_class.match('run foo')).to be_an_instance_of(MatchData)
+ end
+
+ it 'returns true for a run command with arguments' do
+ expect(described_class.match('run foo bar baz'))
+ .to be_an_instance_of(MatchData)
+ end
+
+ it 'returns true for a command containing newlines' do
+ expect(described_class.match("run foo\nbar\nbaz"))
+ .to be_an_instance_of(MatchData)
+ end
+
+ it 'returns false for an unrelated command' do
+ expect(described_class.match('foo bar')).to be_nil
+ end
+ end
+
describe '.available?' do
it 'returns true when builds are enabled for the project' do
project = double(:project, builds_enabled?: true)
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
index 2177b2be6d6..fc342b7e9b1 100644
--- a/spec/lib/gitlab/snippet_search_results_spec.rb
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::SnippetSearchResults do
include SearchHelpers
let_it_be(:snippet) { create(:snippet, content: 'foo', file_name: 'foo') }
+
let(:results) { described_class.new(snippet.author, 'foo') }
describe '#snippet_titles_count' do
diff --git a/spec/lib/gitlab/sourcegraph_spec.rb b/spec/lib/gitlab/sourcegraph_spec.rb
index ad947475f06..6bebd1ca3e6 100644
--- a/spec/lib/gitlab/sourcegraph_spec.rb
+++ b/spec/lib/gitlab/sourcegraph_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Sourcegraph do
let_it_be(:user) { create(:user) }
+
let(:feature_scope) { true }
before do
diff --git a/spec/lib/gitlab/sql/cte_spec.rb b/spec/lib/gitlab/sql/cte_spec.rb
index fdc150cd4b9..4cf94f4dcab 100644
--- a/spec/lib/gitlab/sql/cte_spec.rb
+++ b/spec/lib/gitlab/sql/cte_spec.rb
@@ -14,7 +14,14 @@ RSpec.describe Gitlab::SQL::CTE do
relation.except(:order).to_sql
end
- expect(sql).to eq("#{name} AS (#{sql1})")
+ expected = [
+ "#{name} AS ",
+ Gitlab::Database::AsWithMaterialized.materialized_if_supported,
+ (' ' unless Gitlab::Database::AsWithMaterialized.materialized_if_supported.blank?),
+ "(#{sql1})"
+ ].join
+
+ expect(sql).to eq(expected)
end
end
@@ -41,4 +48,15 @@ RSpec.describe Gitlab::SQL::CTE do
expect(relation.to_a).to eq(User.where(id: user.id).to_a)
end
end
+
+ it_behaves_like 'CTE with MATERIALIZED keyword examples' do
+ let(:expected_query_block_with_materialized) { 'WITH "some_cte" AS MATERIALIZED (' }
+ let(:expected_query_block_without_materialized) { 'WITH "some_cte" AS (' }
+
+ let(:query) do
+ cte = described_class.new(:some_cte, User.active, **options)
+
+ User.with(cte.to_arel).to_sql
+ end
+ end
end
diff --git a/spec/lib/gitlab/sql/recursive_cte_spec.rb b/spec/lib/gitlab/sql/recursive_cte_spec.rb
index 02611620989..edcacd404c2 100644
--- a/spec/lib/gitlab/sql/recursive_cte_spec.rb
+++ b/spec/lib/gitlab/sql/recursive_cte_spec.rb
@@ -57,4 +57,17 @@ RSpec.describe Gitlab::SQL::RecursiveCTE do
expect(relation.to_a).to eq(User.where(id: user.id).to_a)
end
end
+
+ it_behaves_like 'CTE with MATERIALIZED keyword examples' do
+ # MATERIALIZED keyword is not needed for recursive queries
+ let(:expected_query_block_with_materialized) { 'WITH RECURSIVE "some_cte" AS (' }
+ let(:expected_query_block_without_materialized) { 'WITH RECURSIVE "some_cte" AS (' }
+
+ let(:query) do
+ recursive_cte = described_class.new(:some_cte)
+ recursive_cte << User.active
+
+ User.with.recursive(recursive_cte.to_arel).to_sql
+ end
+ end
end
diff --git a/spec/lib/gitlab/subscription_portal_spec.rb b/spec/lib/gitlab/subscription_portal_spec.rb
index 351af3c07d2..ad1affdac0b 100644
--- a/spec/lib/gitlab/subscription_portal_spec.rb
+++ b/spec/lib/gitlab/subscription_portal_spec.rb
@@ -3,39 +3,41 @@
require 'spec_helper'
RSpec.describe ::Gitlab::SubscriptionPortal do
- describe '.default_subscriptions_url' do
- subject { described_class.default_subscriptions_url }
-
- context 'on non test and non dev environments' do
- before do
- allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
- allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
+ unless Gitlab.jh?
+ describe '.default_subscriptions_url' do
+ subject { described_class.default_subscriptions_url }
+
+ context 'on non test and non dev environments' do
+ before do
+ allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
+ allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
+ end
+
+ it 'returns production subscriptions app URL' do
+ is_expected.to eq('https://customers.gitlab.com')
+ end
end
- it 'returns production subscriptions app URL' do
- is_expected.to eq('https://customers.gitlab.com')
- end
- end
+ context 'on dev environment' do
+ before do
+ allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
+ allow(Rails).to receive_message_chain(:env, :development?).and_return(true)
+ end
- context 'on dev environment' do
- before do
- allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
- allow(Rails).to receive_message_chain(:env, :development?).and_return(true)
+ it 'returns staging subscriptions app url' do
+ is_expected.to eq('https://customers.stg.gitlab.com')
+ end
end
- it 'returns staging subscriptions app url' do
- is_expected.to eq('https://customers.stg.gitlab.com')
- end
- end
-
- context 'on test environment' do
- before do
- allow(Rails).to receive_message_chain(:env, :test?).and_return(true)
- allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
- end
+ context 'on test environment' do
+ before do
+ allow(Rails).to receive_message_chain(:env, :test?).and_return(true)
+ allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
+ end
- it 'returns staging subscriptions app url' do
- is_expected.to eq('https://customers.stg.gitlab.com')
+ it 'returns staging subscriptions app url' do
+ is_expected.to eq('https://customers.stg.gitlab.com')
+ end
end
end
end
diff --git a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
index 05f351be702..793ad1c1959 100644
--- a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
+++ b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Template::Finders::RepoTemplateFinder do
let_it_be(:project) { create(:project, :repository) }
+
let(:categories) { { 'HTML' => 'html' } }
subject(:finder) { described_class.new(project, 'files/', '.html', categories) }
diff --git a/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb b/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb
index 0e8647ad78a..65597e6568d 100644
--- a/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb
+++ b/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb
@@ -41,21 +41,6 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow do
.with('category', 'action', 'label', 'property', 1.5, nil, (Time.now.to_f * 1000).to_i)
end
end
-
- describe '#self_describing_event' do
- it 'sends event to tracker' do
- allow(tracker).to receive(:track_self_describing_event).and_call_original
-
- subject.self_describing_event('iglu:com.gitlab/foo/jsonschema/1-0-0', data: { foo: 'bar' })
-
- expect(tracker).to have_received(:track_self_describing_event) do |event, context, timestamp|
- expect(event.to_json[:schema]).to eq('iglu:com.gitlab/foo/jsonschema/1-0-0')
- expect(event.to_json[:data]).to eq(foo: 'bar')
- expect(context).to eq(nil)
- expect(timestamp).to eq((Time.now.to_f * 1000).to_i)
- end
- end
- end
end
context 'when snowplow is not enabled' do
@@ -66,13 +51,5 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow do
subject.event('category', 'action', label: 'label', property: 'property', value: 1.5)
end
end
-
- describe '#self_describing_event' do
- it 'does not send event to tracker' do
- expect_any_instance_of(SnowplowTracker::Tracker).not_to receive(:track_self_describing_event)
-
- subject.self_describing_event('iglu:com.gitlab/foo/jsonschema/1-0-0', data: { foo: 'bar' })
- end
- end
end
end
diff --git a/spec/lib/gitlab/tracking/standard_context_spec.rb b/spec/lib/gitlab/tracking/standard_context_spec.rb
index 561edbd38f8..dacd08cf12b 100644
--- a/spec/lib/gitlab/tracking/standard_context_spec.rb
+++ b/spec/lib/gitlab/tracking/standard_context_spec.rb
@@ -58,10 +58,16 @@ RSpec.describe Gitlab::Tracking::StandardContext do
end
context 'with extra data' do
- subject { described_class.new(foo: 'bar') }
+ subject { described_class.new(extra_key_1: 'extra value 1', extra_key_2: 'extra value 2') }
- it 'creates a Snowplow context with the given data' do
- expect(snowplow_context.to_json.dig(:data, :foo)).to eq('bar')
+ it 'includes extra data in `extra` hash' do
+ expect(snowplow_context.to_json.dig(:data, :extra)).to eq(extra_key_1: 'extra value 1', extra_key_2: 'extra value 2')
+ end
+ end
+
+ context 'without extra data' do
+ it 'contains an empty `extra` hash' do
+ expect(snowplow_context.to_json.dig(:data, :extra)).to be_empty
end
end
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index ac052bd7a80..4d856205609 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -36,12 +36,12 @@ RSpec.describe Gitlab::Tracking do
end
describe '.event' do
- before do
- allow_any_instance_of(Gitlab::Tracking::Destinations::Snowplow).to receive(:event)
- allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event)
- end
-
shared_examples 'delegates to destination' do |klass|
+ before do
+ allow_any_instance_of(Gitlab::Tracking::Destinations::Snowplow).to receive(:event)
+ allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event)
+ end
+
it "delegates to #{klass} destination" do
other_context = double(:context)
@@ -51,7 +51,7 @@ RSpec.describe Gitlab::Tracking do
expect(Gitlab::Tracking::StandardContext)
.to receive(:new)
- .with(project: project, user: user, namespace: namespace)
+ .with(project: project, user: user, namespace: namespace, extra_key_1: 'extra value 1', extra_key_2: 'extra value 2')
.and_call_original
expect_any_instance_of(klass).to receive(:event) do |_, category, action, args|
@@ -66,21 +66,21 @@ RSpec.describe Gitlab::Tracking do
end
described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5,
- context: [other_context], project: project, user: user, namespace: namespace)
+ context: [other_context], project: project, user: user, namespace: namespace,
+ extra_key_1: 'extra value 1', extra_key_2: 'extra value 2')
end
end
- include_examples 'delegates to destination', Gitlab::Tracking::Destinations::Snowplow
- include_examples 'delegates to destination', Gitlab::Tracking::Destinations::ProductAnalytics
- end
+ it_behaves_like 'delegates to destination', Gitlab::Tracking::Destinations::Snowplow
+ it_behaves_like 'delegates to destination', Gitlab::Tracking::Destinations::ProductAnalytics
- describe '.self_describing_event' do
- it 'delegates to snowplow destination' do
- expect_any_instance_of(Gitlab::Tracking::Destinations::Snowplow)
- .to receive(:self_describing_event)
- .with('iglu:com.gitlab/foo/jsonschema/1-0-0', data: { foo: 'bar' }, context: nil)
+ it 'tracks errors' do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(
+ an_instance_of(ContractError),
+ snowplow_category: nil, snowplow_action: 'some_action'
+ )
- described_class.self_describing_event('iglu:com.gitlab/foo/jsonschema/1-0-0', data: { foo: 'bar' })
+ described_class.event(nil, 'some_action')
end
end
end
diff --git a/spec/lib/gitlab/tree_summary_spec.rb b/spec/lib/gitlab/tree_summary_spec.rb
index 661ef507a82..a86afa9cba5 100644
--- a/spec/lib/gitlab/tree_summary_spec.rb
+++ b/spec/lib/gitlab/tree_summary_spec.rb
@@ -226,6 +226,7 @@ RSpec.describe Gitlab::TreeSummary do
describe 'References in commit messages' do
let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:issue) { create(:issue, project: project) }
+
let(:entries) { summary.summarize.first }
let(:entry) { entries.find { |entry| entry[:file_name] == 'issue.txt' } }
diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb
index aac3d5e27f5..270c4beec97 100644
--- a/spec/lib/gitlab/untrusted_regexp_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp_spec.rb
@@ -136,4 +136,22 @@ RSpec.describe Gitlab::UntrustedRegexp do
end
end
end
+
+ describe '#match' do
+ context 'when there are matches' do
+ it 'returns a match object' do
+ result = described_class.new('(?P<number>\d+)').match('hello 10')
+
+ expect(result[:number]).to eq('10')
+ end
+ end
+
+ context 'when there are no matches' do
+ it 'returns nil' do
+ result = described_class.new('(?P<number>\d+)').match('hello')
+
+ expect(result).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 6d055fe3643..b359eb422d7 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -92,6 +92,7 @@ RSpec.describe Gitlab::UrlBuilder do
context 'when passing a Snippet' do
let_it_be(:personal_snippet) { create(:personal_snippet, :repository) }
let_it_be(:project_snippet) { create(:project_snippet, :repository) }
+
let(:blob) { snippet.blobs.first }
let(:ref) { blob.repository.root_ref }
diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb
index 8b592838f5d..e99d720058a 100644
--- a/spec/lib/gitlab/usage/metric_definition_spec.rb
+++ b/spec/lib/gitlab/usage/metric_definition_spec.rb
@@ -16,7 +16,8 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
time_frame: 'none',
data_source: 'database',
distribution: %w(ee ce),
- tier: %w(free starter premium ultimate bronze silver gold)
+ tier: %w(free starter premium ultimate bronze silver gold),
+ name: 'count_boards'
}
end
@@ -24,6 +25,13 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
let(:definition) { described_class.new(path, attributes) }
let(:yaml_content) { attributes.deep_stringify_keys.to_yaml }
+ def write_metric(metric, path, content)
+ path = File.join(metric, path)
+ dir = File.dirname(path)
+ FileUtils.mkdir_p(dir)
+ File.write(path, content)
+ end
+
it 'has all definitons valid' do
expect { described_class.definitions }.not_to raise_error(Gitlab::Usage::Metric::InvalidMetricError)
end
@@ -53,6 +61,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
:distribution | nil
:distribution | 'test'
:tier | %w(test ee)
+ :name | 'count_<adjective_describing>_boards'
end
with_them do
@@ -82,6 +91,28 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
end
end
+ describe 'statuses' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:status, :skip_validation?) do
+ 'deprecated' | true
+ 'removed' | true
+ 'data_available' | false
+ 'implemented' | false
+ 'not_used' | false
+ end
+
+ with_them do
+ subject(:validation) do
+ described_class.new(path, attributes.merge( { status: status } )).send(:skip_validation?)
+ end
+
+ it 'returns true/false for skip_validation' do
+ expect(validation).to eq(skip_validation?)
+ end
+ end
+ end
+
describe '.load_all!' do
let(:metric1) { Dir.mktmpdir('metric1') }
let(:metric2) { Dir.mktmpdir('metric2') }
@@ -121,12 +152,54 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
FileUtils.rm_rf(metric1)
FileUtils.rm_rf(metric2)
end
+ end
+
+ describe 'dump_metrics_yaml' do
+ let(:other_attributes) do
+ {
+ description: 'Test metric definition',
+ value_type: 'string',
+ product_category: 'collection',
+ product_stage: 'growth',
+ status: 'data_available',
+ default_generation: 'generation_1',
+ key_path: 'counter.category.event',
+ product_group: 'group::product analytics',
+ time_frame: 'none',
+ data_source: 'database',
+ distribution: %w(ee ce),
+ tier: %w(free starter premium ultimate bronze silver gold)
+ }
+ end
+
+ let(:other_yaml_content) { other_attributes.deep_stringify_keys.to_yaml }
+ let(:other_path) { File.join('metrics', 'test_metric.yml') }
+ let(:metric1) { Dir.mktmpdir('metric1') }
+ let(:metric2) { Dir.mktmpdir('metric2') }
+
+ before do
+ allow(described_class).to receive(:paths).and_return(
+ [
+ File.join(metric1, '**', '*.yml'),
+ File.join(metric2, '**', '*.yml')
+ ]
+ )
+ # Reset memoized `definitions` result
+ described_class.instance_variable_set(:@definitions, nil)
+ end
+
+ after do
+ FileUtils.rm_rf(metric1)
+ FileUtils.rm_rf(metric2)
+ end
+
+ subject { described_class.dump_metrics_yaml }
+
+ it 'returns a YAML with both metrics in a sequence' do
+ write_metric(metric1, path, yaml_content)
+ write_metric(metric2, other_path, other_yaml_content)
- def write_metric(metric, path, content)
- path = File.join(metric, path)
- dir = File.dirname(path)
- FileUtils.mkdir_p(dir)
- File.write(path, content)
+ is_expected.to eq([attributes, other_attributes].map(&:deep_stringify_keys).to_yaml)
end
end
end
diff --git a/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb b/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb
index a2a40f17269..db878828cd6 100644
--- a/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Gitlab::Usage::Metrics::Aggregates::Sources::PostgresHll, :clean_
let_it_be(:end_date) { Date.current }
let_it_be(:recorded_at) { Time.current }
let_it_be(:time_period) { { created_at: (start_date..end_date) } }
+
let(:metric_1) { 'metric_1' }
let(:metric_2) { 'metric_2' }
let(:metric_names) { [metric_1, metric_2] }
diff --git a/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb b/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
index cd0413feab4..34b073b4729 100644
--- a/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
describe '#generate' do
shared_examples 'name suggestion' do
it 'return correct name' do
- expect(described_class.generate(key_path)).to eq name_suggestion
+ expect(described_class.generate(key_path)).to match name_suggestion
end
end
@@ -20,7 +20,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with count(Board)
let(:key_path) { 'counts.boards' }
- let(:name_suggestion) { 'count_boards' }
+ let(:name_suggestion) { /count_boards/ }
end
end
@@ -28,7 +28,44 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator 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' }
+ let(:name_suggestion) { /count_distinct_issue_id_from_zoom_meetings/ }
+ end
+ end
+
+ context 'joined relations' do
+ context 'counted attribute comes from joined relation' do
+ it_behaves_like 'name suggestion' do
+ # corresponding metric is collected with:
+ # distinct_count(
+ # ::Clusters::Applications::Ingress.modsecurity_enabled.logging
+ # .joins(cluster: :deployments)
+ # .merge(::Clusters::Cluster.enabled)
+ # .merge(Deployment.success),
+ # ::Deployment.arel_table[:environment_id]
+ # )
+ let(:key_path) { 'counts.ingress_modsecurity_logging' }
+ let(:name_suggestion) do
+ constrains = /'\(clusters_applications_ingress\.modsecurity_enabled = TRUE AND clusters_applications_ingress\.modsecurity_mode = \d+ AND clusters.enabled = TRUE AND deployments.status = \d+\)'/
+ /count_distinct_environment_id_from_<adjective describing\: #{constrains}>_deployments_<with>_<adjective describing\: #{constrains}>_clusters_<having>_<adjective describing\: #{constrains}>_clusters_applications_ingress/
+ end
+ end
+ end
+
+ 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(:key_path) { 'counts.issues_created_manually_from_alerts' }
+ 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(: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
@@ -36,7 +73,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator 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 = 4)'>_jira_imports" }
+ let(:name_suggestion) { /sum_imported_issues_count_from_<adjective describing\: '\(jira_imports\.status = \d+\)'>_jira_imports/ }
end
end
@@ -44,7 +81,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
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" }
+ let(:name_suggestion) { /add_count_<adjective describing\: '\(snippets\.type = 'PersonalSnippet'\)'>_snippets_and_count_<adjective describing\: '\(snippets\.type = 'ProjectSnippet'\)'>_snippets/ }
end
end
@@ -52,7 +89,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics) }
let(:key_path) { 'analytics_unique_visits.analytics_unique_visits_for_any_target' }
- let(:name_suggestion) { '<please fill metric name>' }
+ 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
@@ -60,7 +97,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with alt_usage_data(fallback: nil) { operating_system }
let(:key_path) { 'settings.operating_system' }
- let(:name_suggestion) { '<please fill metric name>' }
+ let(:name_suggestion) { /<please fill metric name>/ }
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
new file mode 100644
index 00000000000..fb3bd564e34
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::RelationParsers::Joins do
+ describe '#accept' do
+ let(:collector) { Arel::Collectors::SubstituteBinds.new(ActiveRecord::Base.connection, Arel::Collectors::SQLString.new) }
+
+ 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_data_counters/aggregated_metrics_spec.rb b/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb
deleted file mode 100644
index 9aba86cdaf2..00000000000
--- a/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'aggregated metrics' do
- RSpec::Matchers.define :be_known_event do
- match do |event|
- Gitlab::UsageDataCounters::HLLRedisCounter.known_event?(event)
- end
-
- failure_message do |event|
- "Event with name: `#{event}` can not be found within `#{Gitlab::UsageDataCounters::HLLRedisCounter::KNOWN_EVENTS_PATH}`"
- end
- end
-
- RSpec::Matchers.define :has_known_source do
- match do |aggregate|
- Gitlab::Usage::Metrics::Aggregates::SOURCES.include?(aggregate[:source])
- end
-
- failure_message do |aggregate|
- "Aggregate with name: `#{aggregate[:name]}` uses not allowed source `#{aggregate[:source]}`"
- end
- end
-
- RSpec::Matchers.define :have_known_time_frame do
- allowed_time_frames = [
- Gitlab::Utils::UsageData::ALL_TIME_TIME_FRAME_NAME,
- Gitlab::Utils::UsageData::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME,
- Gitlab::Utils::UsageData::SEVEN_DAYS_TIME_FRAME_NAME
- ]
-
- match do |aggregate|
- (aggregate[:time_frame] - allowed_time_frames).empty?
- end
-
- failure_message do |aggregate|
- "Aggregate with name: `#{aggregate[:name]}` uses not allowed time_frame`#{aggregate[:time_frame] - allowed_time_frames}`"
- end
- end
-
- let_it_be(:known_events) do
- Gitlab::UsageDataCounters::HLLRedisCounter.known_events
- end
-
- Gitlab::Usage::Metrics::Aggregates::Aggregate.new(Time.current).send(:aggregated_metrics).tap do |aggregated_metrics|
- it 'all events has unique name' do
- event_names = aggregated_metrics&.map { |event| event[:name] }
-
- expect(event_names).to eq(event_names&.uniq)
- end
-
- it 'all aggregated metrics has known source' do
- expect(aggregated_metrics).to all has_known_source
- end
-
- it 'all aggregated metrics has known source' do
- expect(aggregated_metrics).to all have_known_time_frame
- end
-
- aggregated_metrics&.select { |agg| agg[:source] == Gitlab::Usage::Metrics::Aggregates::REDIS_SOURCE }&.each do |aggregate|
- context "for #{aggregate[:name]} aggregate of #{aggregate[:events].join(' ')}" do
- let_it_be(:events_records) { known_events.select { |event| aggregate[:events].include?(event[:name]) } }
-
- it "does not include 'all' time frame for Redis sourced aggregate" do
- expect(aggregate[:time_frame]).not_to include(Gitlab::Utils::UsageData::ALL_TIME_TIME_FRAME_NAME)
- end
-
- it "only refers to known events" do
- expect(aggregate[:events]).to all be_known_event
- end
-
- it "has expected structure" do
- expect(aggregate.keys).to include(*%w[name operator events])
- end
-
- it "uses allowed aggregation operators" do
- expect(Gitlab::Usage::Metrics::Aggregates::ALLOWED_METRICS_AGGREGATIONS).to include aggregate[:operator]
- end
-
- it "uses events from the same Redis slot" do
- event_slots = events_records.map { |event| event[:redis_slot] }.uniq
-
- expect(event_slots).to contain_exactly(be_present)
- end
-
- it "uses events with the same aggregation period" do
- event_slots = events_records.map { |event| event[:aggregation] }.uniq
-
- expect(event_slots).to contain_exactly(be_present)
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb b/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb
index 664e7938a7e..a1dee442131 100644
--- a/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
# If this spec fails, we need to add the new code review event to the correct aggregated metric
RSpec.describe 'Code review events' do
it 'the aggregated metrics contain all the code review metrics' do
- path = Rails.root.join('lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml')
+ path = Rails.root.join('config/metrics/aggregates/code_review.yml')
aggregated_events = YAML.safe_load(File.read(path), aliases: true)&.map(&:with_indifferent_access)
code_review_aggregated_events = aggregated_events
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 d12dcdae955..9fc28f6c4ec 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
@@ -34,6 +34,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'source_code',
'incident_management',
'incident_management_alerts',
+ 'incident_management_oncall',
'testing',
'issues_edit',
'ci_secrets_management',
@@ -43,7 +44,8 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'ci_templates',
'quickactions',
'pipeline_authoring',
- 'epics_usage'
+ 'epics_usage',
+ 'secure'
)
end
end
@@ -93,7 +95,25 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
describe '.track_event' do
- context 'with feature flag set' do
+ context 'with redis_hll_tracking' do
+ it 'tracks the event when feature enabled' do
+ stub_feature_flags(redis_hll_tracking: true)
+
+ expect(Gitlab::Redis::HLL).to receive(:add)
+
+ described_class.track_event(weekly_event, values: 1)
+ end
+
+ it 'does not track the event with feature flag disabled' do
+ stub_feature_flags(redis_hll_tracking: false)
+
+ expect(Gitlab::Redis::HLL).not_to receive(:add)
+
+ described_class.track_event(weekly_event, values: 1)
+ end
+ end
+
+ context 'with event feature flag set' do
it 'tracks the event when feature enabled' do
stub_feature_flags(feature => true)
@@ -111,7 +131,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
end
- context 'with no feature flag set' do
+ context 'with no event feature flag set' do
it 'tracks the event' do
expect(Gitlab::Redis::HLL).to receive(:add)
@@ -289,6 +309,11 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
described_class.track_event(daily_event, values: entity4, time: 29.days.ago)
end
+ it 'returns 0 if there are no keys for the given events' do
+ expect(Gitlab::Redis::HLL).not_to receive(:count)
+ expect(described_class.unique_events(event_names: [weekly_event], start_date: Date.current, end_date: 4.weeks.ago)).to eq(-1)
+ end
+
it 'raise error if metrics are not in the same slot' do
expect do
described_class.unique_events(event_names: [compliance_slot_event, analytics_slot_event], start_date: 4.weeks.ago, end_date: Date.current)
@@ -508,6 +533,11 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
expect { described_class.calculate_events_union(**time_range.merge(event_names: %w[event1_slot event4])) }.to raise_error described_class::SlotMismatch
expect { described_class.calculate_events_union(**time_range.merge(event_names: %w[event5_slot event3_slot])) }.to raise_error described_class::AggregationMismatch
end
+
+ it 'returns 0 if there are no keys for given events' do
+ expect(Gitlab::Redis::HLL).not_to receive(:count)
+ expect(described_class.calculate_events_union(event_names: %w[event1_slot event2_slot event3_slot], start_date: Date.current, end_date: 4.weeks.ago)).to eq(-1)
+ end
end
describe '.weekly_time_range' do
diff --git a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
index f8f6494b92e..1b73e5269d7 100644
--- a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
@@ -3,9 +3,10 @@
require 'spec_helper'
RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_gitlab_redis_shared_state do
- let(:user1) { build(:user, id: 1) }
- let(:user2) { build(:user, id: 2) }
- let(:user3) { build(:user, id: 3) }
+ let_it_be(:user1) { build(:user, id: 1) }
+ let_it_be(:user2) { build(:user, id: 2) }
+ let_it_be(:user3) { build(:user, id: 3) }
+
let(:time) { Time.zone.now }
context 'for Issue title edit actions' do
@@ -272,10 +273,13 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
described_class.track_issue_title_changed_action(author: user1)
described_class.track_issue_description_changed_action(author: user1)
described_class.track_issue_assignee_changed_action(author: user1)
- described_class.track_issue_title_changed_action(author: user2, time: time - 2.days)
- described_class.track_issue_title_changed_action(author: user3, time: time - 3.days)
- described_class.track_issue_description_changed_action(author: user3, time: time - 3.days)
- described_class.track_issue_assignee_changed_action(author: user3, time: time - 3.days)
+
+ travel_to(2.days.ago) do
+ described_class.track_issue_title_changed_action(author: user2)
+ described_class.track_issue_title_changed_action(author: user3)
+ described_class.track_issue_description_changed_action(author: user3)
+ described_class.track_issue_assignee_changed_action(author: user3)
+ end
events = Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category(described_class::ISSUE_CATEGORY)
today_count = Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: events, start_date: time, end_date: time)
diff --git a/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb
index 2df0f331f73..1940442d2ad 100644
--- a/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb
@@ -115,6 +115,26 @@ RSpec.describe Gitlab::UsageDataCounters::QuickActionActivityUniqueCounter, :cle
end
end
+ context 'tracking spent' do
+ let(:quickaction_name) { 'spent' }
+
+ context 'adding time' do
+ let(:args) { '1d' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_spend_add' }
+ end
+ end
+
+ context 'removing time' do
+ let(:args) { '-1d' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_spend_subtract' }
+ end
+ end
+ end
+
context 'tracking unassign' do
let(:quickaction_name) { 'unassign' }
diff --git a/spec/lib/gitlab/usage_data_non_sql_metrics_spec.rb b/spec/lib/gitlab/usage_data_non_sql_metrics_spec.rb
new file mode 100644
index 00000000000..32d1288c59c
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_non_sql_metrics_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataNonSqlMetrics do
+ let(:default_count) { Gitlab::UsageDataNonSqlMetrics::SQL_METRIC_DEFAULT }
+
+ describe '.count' do
+ it 'returns default value for count' do
+ expect(described_class.count(User)).to eq(default_count)
+ end
+ end
+
+ describe '.distinct_count' do
+ it 'returns default value for distinct count' do
+ expect(described_class.distinct_count(User)).to eq(default_count)
+ end
+ end
+
+ describe '.estimate_batch_distinct_count' do
+ it 'returns default value for estimate_batch_distinct_count' do
+ expect(described_class.estimate_batch_distinct_count(User)).to eq(default_count)
+ end
+ end
+
+ describe '.sum' do
+ it 'returns default value for sum' do
+ expect(described_class.sum(JiraImportState.finished, :imported_issues_count)).to eq(default_count)
+ end
+ end
+
+ describe '.histogram' do
+ it 'returns default value for histogram' do
+ expect(described_class.histogram(JiraImportState.finished, :imported_issues_count, buckets: [], bucket_size: 0)).to eq(default_count)
+ end
+ end
+
+ describe 'min/max methods' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:model, :result) do
+ User | nil
+ Issue | nil
+ Deployment | nil
+ Project | nil
+ end
+
+ with_them do
+ it 'returns nil' do
+ expect(described_class.minimum_id(model)).to eq(result)
+ expect(described_class.maximum_id(model)).to eq(result)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_queries_spec.rb b/spec/lib/gitlab/usage_data_queries_spec.rb
index 12eac643383..718ab3b2d95 100644
--- a/spec/lib/gitlab/usage_data_queries_spec.rb
+++ b/spec/lib/gitlab/usage_data_queries_spec.rb
@@ -11,12 +11,24 @@ RSpec.describe Gitlab::UsageDataQueries do
it 'returns the raw SQL' do
expect(described_class.count(User)).to start_with('SELECT COUNT("users"."id") FROM "users"')
end
+
+ it 'does not mix a nil column with keyword arguments' do
+ expect(described_class).to receive(:raw_sql).with(User, nil)
+
+ described_class.count(User, start: 1, finish: 2)
+ end
end
describe '.distinct_count' do
it 'returns the raw SQL' do
expect(described_class.distinct_count(Issue, :author_id)).to eq('SELECT COUNT(DISTINCT "issues"."author_id") FROM "issues"')
end
+
+ it 'does not mix a nil column with keyword arguments' do
+ expect(described_class).to receive(:raw_sql).with(Issue, nil, :distinct)
+
+ described_class.distinct_count(Issue, nil, start: 1, finish: 2)
+ end
end
describe '.redis_usage_data' do
@@ -46,4 +58,24 @@ RSpec.describe Gitlab::UsageDataQueries do
.to eq('SELECT (SELECT COUNT("users"."id") FROM "users") + (SELECT COUNT("issues"."id") FROM "issues")')
end
end
+
+ describe 'min/max methods' do
+ it 'returns nil' do
+ # user min/max
+ expect(described_class.minimum_id(User)).to eq(nil)
+ expect(described_class.maximum_id(User)).to eq(nil)
+
+ # issue min/max
+ expect(described_class.minimum_id(Issue)).to eq(nil)
+ expect(described_class.maximum_id(Issue)).to eq(nil)
+
+ # deployment min/max
+ expect(described_class.minimum_id(Deployment)).to eq(nil)
+ expect(described_class.maximum_id(Deployment)).to eq(nil)
+
+ # project min/max
+ expect(described_class.minimum_id(Project)).to eq(nil)
+ expect(described_class.maximum_id(Project)).to eq(nil)
+ end
+ end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index b1581bf02a6..01701f7aebd 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -167,7 +167,10 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
create(:key, user: user)
create(:project, creator: user, disable_overriding_approvers_per_merge_request: true)
create(:project, creator: user, disable_overriding_approvers_per_merge_request: false)
- create(:remote_mirror, project: project)
+ create(:remote_mirror, project: project, enabled: true)
+ another_user = create(:user)
+ another_project = create(:project, :repository, creator: another_user)
+ create(:remote_mirror, project: another_project, enabled: false)
create(:snippet, author: user)
end
@@ -176,7 +179,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
keys: 2,
merge_requests: 2,
projects_with_disable_overriding_approvers_per_merge_request: 2,
- projects_without_disable_overriding_approvers_per_merge_request: 4,
+ projects_without_disable_overriding_approvers_per_merge_request: 6,
remote_mirrors: 2,
snippets: 2
)
@@ -185,7 +188,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
keys: 1,
merge_requests: 1,
projects_with_disable_overriding_approvers_per_merge_request: 1,
- projects_without_disable_overriding_approvers_per_merge_request: 2,
+ projects_without_disable_overriding_approvers_per_merge_request: 3,
remote_mirrors: 1,
snippets: 1
)
@@ -1288,6 +1291,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
'p_analytics_repo' => 123,
'i_analytics_cohorts' => 123,
'i_analytics_dev_ops_score' => 123,
+ 'i_analytics_dev_ops_adoption' => 123,
'i_analytics_instance_statistics' => 123,
'p_analytics_merge_request' => 123,
'g_analytics_merge_request' => 123,
@@ -1358,24 +1362,36 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories }
let(:ineligible_total_categories) do
- %w[source_code ci_secrets_management incident_management_alerts snippets terraform epics_usage]
+ %w[source_code ci_secrets_management incident_management_alerts snippets terraform incident_management_oncall secure]
end
- it 'has all known_events' do
- expect(subject).to have_key(:redis_hll_counters)
+ context 'with redis_hll_tracking feature enabled' do
+ it 'has all known_events' do
+ stub_feature_flags(redis_hll_tracking: true)
- expect(subject[:redis_hll_counters].keys).to match_array(categories)
+ expect(subject).to have_key(:redis_hll_counters)
- categories.each do |category|
- keys = ::Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category(category)
+ expect(subject[:redis_hll_counters].keys).to match_array(categories)
- metrics = keys.map { |key| "#{key}_weekly" } + keys.map { |key| "#{key}_monthly" }
+ categories.each do |category|
+ keys = ::Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category(category)
- if ineligible_total_categories.exclude?(category)
- metrics.append("#{category}_total_unique_counts_weekly", "#{category}_total_unique_counts_monthly")
+ metrics = keys.map { |key| "#{key}_weekly" } + keys.map { |key| "#{key}_monthly" }
+
+ if ineligible_total_categories.exclude?(category)
+ metrics.append("#{category}_total_unique_counts_weekly", "#{category}_total_unique_counts_monthly")
+ end
+
+ expect(subject[:redis_hll_counters][category].keys).to match_array(metrics)
end
+ end
+ end
+
+ context 'with redis_hll_tracking disabled' do
+ it 'does not have redis_hll_tracking key' do
+ stub_feature_flags(redis_hll_tracking: false)
- expect(subject[:redis_hll_counters][category].keys).to match_array(metrics)
+ expect(subject).not_to have_key(:redis_hll_counters)
end
end
end
diff --git a/spec/lib/gitlab/utils/lazy_attributes_spec.rb b/spec/lib/gitlab/utils/lazy_attributes_spec.rb
index dfffe70defb..1ebc9b0d711 100644
--- a/spec/lib/gitlab/utils/lazy_attributes_spec.rb
+++ b/spec/lib/gitlab/utils/lazy_attributes_spec.rb
@@ -13,8 +13,10 @@ RSpec.describe Gitlab::Utils::LazyAttributes do
def initialize
@number = -> { 1 }
- @reader_1, @reader_2 = 'reader_1', -> { 'reader_2' }
- @incorrect_type, @accessor_2 = -> { :incorrect_type }, -> { 'accessor_2' }
+ @reader_1 = 'reader_1'
+ @reader_2 = -> { 'reader_2' }
+ @incorrect_type = -> { :incorrect_type }
+ @accessor_2 = -> { 'accessor_2' }
end
end
end
diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb
index 6e1904c43e1..11b2a12f228 100644
--- a/spec/lib/gitlab/utils/usage_data_spec.rb
+++ b/spec/lib/gitlab/utils/usage_data_spec.rb
@@ -187,6 +187,7 @@ RSpec.describe Gitlab::Utils::UsageData do
describe '#histogram' do
let_it_be(:projects) { create_list(:project, 3) }
+
let(:project1) { projects.first }
let(:project2) { projects.second }
let(:project3) { projects.third }
@@ -478,4 +479,22 @@ RSpec.describe Gitlab::Utils::UsageData do
expect { described_class.track_usage_event(unknown_event, value) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent)
end
end
+
+ describe 'min/max' do
+ let(:model) { double(:relation) }
+
+ it 'returns min from the model' do
+ allow(model).to receive(:minimum).and_return(2)
+ allow(model).to receive(:name).and_return('sample_min_model')
+
+ expect(described_class.minimum_id(model)).to eq(2)
+ end
+
+ it 'returns max from the model' do
+ allow(model).to receive(:maximum).and_return(100)
+ allow(model).to receive(:name).and_return('sample_max_model')
+
+ expect(described_class.maximum_id(model)).to eq(100)
+ end
+ end
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 665eebdfd9e..11dba610faf 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -162,7 +162,7 @@ RSpec.describe Gitlab::Utils do
describe '.nlbr' do
it 'replaces new lines with <br>' do
- expect(described_class.nlbr("<b>hello</b>\n<i>world</i>".freeze)).to eq("hello<br>world")
+ expect(described_class.nlbr("<b>hello</b>\n<i>world</i>")).to eq("hello<br>world")
end
end
@@ -192,6 +192,7 @@ RSpec.describe Gitlab::Utils do
expect(to_boolean('YeS')).to be(true)
expect(to_boolean('t')).to be(true)
expect(to_boolean('1')).to be(true)
+ expect(to_boolean(1)).to be(true)
expect(to_boolean('ON')).to be(true)
expect(to_boolean('FaLse')).to be(false)
@@ -199,6 +200,7 @@ RSpec.describe Gitlab::Utils do
expect(to_boolean('NO')).to be(false)
expect(to_boolean('n')).to be(false)
expect(to_boolean('0')).to be(false)
+ expect(to_boolean(0)).to be(false)
expect(to_boolean('oFF')).to be(false)
end
@@ -388,8 +390,8 @@ RSpec.describe Gitlab::Utils do
describe ".safe_downcase!" do
where(:str, :result) do
- "test".freeze | "test"
- "Test".freeze | "test"
+ "test" | "test"
+ "Test" | "test"
"test" | "test"
"Test" | "test"
end
diff --git a/spec/lib/gitlab/web_ide/config/entry/global_spec.rb b/spec/lib/gitlab/web_ide/config/entry/global_spec.rb
index 3e29bf89785..8dbe64af1c7 100644
--- a/spec/lib/gitlab/web_ide/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/web_ide/config/entry/global_spec.rb
@@ -83,6 +83,7 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Global do
expect(global.terminal_value).to eq({
tag_list: [],
yaml_variables: [],
+ job_variables: [],
options: {
before_script: ['ls'],
script: ['sleep 10s'],
diff --git a/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb b/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb
index 0df0f56f440..d6d0fc4224d 100644
--- a/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb
+++ b/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb
@@ -132,7 +132,7 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Terminal do
{ before_script: %w[ls pwd],
script: 'sleep 100',
tags: ['webide'],
- image: 'ruby:2.5',
+ image: 'ruby:3.0',
services: ['mysql'],
variables: { KEY: 'value' } }
end
@@ -142,8 +142,9 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Terminal do
.to eq(
tag_list: ['webide'],
yaml_variables: [{ key: 'KEY', value: 'value', public: true }],
+ job_variables: [{ key: 'KEY', value: 'value', public: true }],
options: {
- image: { name: "ruby:2.5" },
+ image: { name: "ruby:3.0" },
services: [{ name: "mysql" }],
before_script: %w[ls pwd],
script: ['sleep 100']
diff --git a/spec/lib/gitlab/word_diff/chunk_collection_spec.rb b/spec/lib/gitlab/word_diff/chunk_collection_spec.rb
index aa837f760c1..73e9ff3974a 100644
--- a/spec/lib/gitlab/word_diff/chunk_collection_spec.rb
+++ b/spec/lib/gitlab/word_diff/chunk_collection_spec.rb
@@ -41,4 +41,27 @@ RSpec.describe Gitlab::WordDiff::ChunkCollection do
expect(collection.content).to eq('')
end
end
+
+ describe '#marker_ranges' do
+ let(:chunks) do
+ [
+ Gitlab::WordDiff::Segments::Chunk.new(' Hello '),
+ Gitlab::WordDiff::Segments::Chunk.new('-World'),
+ Gitlab::WordDiff::Segments::Chunk.new('+GitLab'),
+ Gitlab::WordDiff::Segments::Chunk.new('+!!!')
+ ]
+ end
+
+ it 'returns marker ranges for every chunk with changes' do
+ chunks.each { |chunk| collection.add(chunk) }
+
+ expect(collection.marker_ranges).to eq(
+ [
+ Gitlab::MarkerRange.new(6, 10, mode: :deletion),
+ Gitlab::MarkerRange.new(11, 16, mode: :addition),
+ Gitlab::MarkerRange.new(17, 19, mode: :addition)
+ ]
+ )
+ end
+ end
end
diff --git a/spec/lib/gitlab/word_diff/parser_spec.rb b/spec/lib/gitlab/word_diff/parser_spec.rb
index 3aeefb57a02..e793e44fd45 100644
--- a/spec/lib/gitlab/word_diff/parser_spec.rb
+++ b/spec/lib/gitlab/word_diff/parser_spec.rb
@@ -36,15 +36,26 @@ RSpec.describe Gitlab::WordDiff::Parser do
aggregate_failures do
expect(diff_lines.count).to eq(7)
- expect(diff_lines.map(&:to_hash)).to match_array(
+ expect(diff_lines.map { |line| diff_line_attributes(line) }).to eq(
[
- a_hash_including(index: 0, old_pos: 1, new_pos: 1, text: '', type: nil),
- a_hash_including(index: 1, old_pos: 2, new_pos: 2, text: 'Unchanged line', type: nil),
- a_hash_including(index: 2, old_pos: 3, new_pos: 3, text: '', type: nil),
- a_hash_including(index: 3, old_pos: 4, new_pos: 4, text: 'Old changeNew addition unchanged content', type: nil),
- a_hash_including(index: 4, old_pos: 50, new_pos: 50, text: '@@ -50,14 +50,13 @@', type: 'match'),
- a_hash_including(index: 5, old_pos: 50, new_pos: 50, text: 'First change same same same_removed_added_end of the line', type: nil),
- a_hash_including(index: 6, old_pos: 51, new_pos: 51, text: '', type: nil)
+ { index: 0, old_pos: 1, new_pos: 1, text: '', type: nil, marker_ranges: [] },
+ { index: 1, old_pos: 2, new_pos: 2, text: 'Unchanged line', type: nil, marker_ranges: [] },
+ { index: 2, old_pos: 3, new_pos: 3, text: '', type: nil, marker_ranges: [] },
+ { index: 3, old_pos: 4, new_pos: 4, text: 'Old changeNew addition unchanged content', type: nil,
+ marker_ranges: [
+ Gitlab::MarkerRange.new(0, 9, mode: :deletion),
+ Gitlab::MarkerRange.new(10, 21, mode: :addition)
+ ] },
+
+ { index: 4, old_pos: 50, new_pos: 50, text: '@@ -50,14 +50,13 @@', type: 'match', marker_ranges: [] },
+ { index: 5, old_pos: 50, new_pos: 50, text: 'First change same same same_removed_added_end of the line', type: nil,
+ marker_ranges: [
+ Gitlab::MarkerRange.new(0, 11, mode: :addition),
+ Gitlab::MarkerRange.new(28, 35, mode: :deletion),
+ Gitlab::MarkerRange.new(36, 41, mode: :addition)
+ ] },
+
+ { index: 6, old_pos: 51, new_pos: 51, text: '', type: nil, marker_ranges: [] }
]
)
end
@@ -64,4 +75,17 @@ RSpec.describe Gitlab::WordDiff::Parser do
it { is_expected.to eq([]) }
end
end
+
+ private
+
+ def diff_line_attributes(diff_line)
+ {
+ index: diff_line.index,
+ old_pos: diff_line.old_pos,
+ new_pos: diff_line.new_pos,
+ text: diff_line.text,
+ type: diff_line.type,
+ marker_ranges: diff_line.marker_ranges
+ }
+ end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index c22df5dd063..d40ecc7e04e 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Workhorse do
let_it_be(:project) { create(:project, :repository) }
+
let(:repository) { project.repository }
def decode_workhorse_header(array)